📜 ⬆️ ⬇️

RubyMotion: native iOS Ruby applications (translation)


In 2007, Laurent Sansonetti, a developer at Apple, founded the open source project MacRuby. His goal was to create a Ruby interpreter on top of the Objective-C runtime that would provide transparent interaction between Ruby and the OS X Cocoa ecosystem - and he succeeded. Now Sonsonetti hopes to do something similar under iOS.


Recently, Sonsonetti resigned from Apple, where he worked for the past seven years to establish his startup called HipByte. He announced his first product today [ May 3 - approx. trans. ] - a development tool called RubyMotion, which will open up opportunities for writing native iOS applications in the Ruby programming language.

I tested RubyMotion from Martha when Sonsonetti gave me access to an early closed beta of the product. In this article, I will present an exclusive review of RubyMotion first-hand and describe how it can be used to write software for iOS. The article includes all the source code of a simple iOS demo that I created, displaying a list of top posts on reddit.
')
Rubymotion

RubyMotion is built on top of the very Objective-C Ruby implementation behind MacRuby, but uses the new LLVM compiler to convert Ruby code into optimized native code. The RubyMotion compiler yields efficient native applications that do not have the speed limitations inherent in regular Ruby code.



Mobile applications created using RubyMotion work as fast as equivalents on Objective-C and use a comparable amount of hardware resources. Moreover, RubyMotion applications fully satisfy Apple’s App Store requirements. According to Sansonetti, several RubyMotion applications have already been accepted in the App Store, having passed all the validation steps.

All standard iOS APIs are available in RubyMotion, which means that all features of Objective-C developers are also available to Ruby developers. In addition, RubyMotion applications can match the appearance of the rest of the platform, since use a standard set of widgets from UIKit.

Software Development with RubyMotion

Instead of trying to embed RubyMotion into Apple's IDE, Sonsonetti decided to go more traditional for Ruby by using the command line tools. The motion console command generates a new project from a template that contains a blank of the application with folders for code, graphics and other resources. Any .rb file that resides in the app directory will be automatically compiled into the final assembly. In RubyMotion, there is no need to use the require keyword at all.

The build process in RubyMotion relies on the Rake tool, familiar to most Ruby programmers. Rake build options are used to specify dependencies and other application settings. When you run rake from the command line, the application will be compiled and run on the iOS simulator.

In addition to running the application in the simulator, a REPL becomes available in the terminal, allowing you to interact interactively with a running application using expressions in Ruby. The ability on the fly to make changes to the properties of widgets and internal data structures of an application is extremely useful when testing and detecting errors.



You can also use goals in Rake to run an application on a device or create a .ipa package followed by distribution in the App Store. Due to restrictions on digitally signing a code, you need to have a subscription to the Apple Developer Program to test your applications on devices.

Compiling and running reddit demos on my RubyMotion simulator took about five seconds on MacBook Air 2011. The compilation time is not too long, but it looks unusual for Ruby developers who are used to seeing the result immediately after making the changes. The RubyMotion compiler comes with its own ruby command, which allows you to run Ruby scripts in the RubyMotion environment.

The development process on RubyMotion is quite comfortable. I write code in Vim and keep the terminal open, running Rake to test the program. One key disadvantage compared to Xcode is the inability to use visual interface design tools. This can cause problems for novice developers, because they will have to delve into the UIKit API.

Talk to Sunsonetti

I asked him about the incompatibility with the Xcode visual tools, he replied that the integration with Xcode will be implemented, but not in the near future.

Instead, it creates a set of Ruby libraries for programmatically creating iOS interfaces. These libraries will be high-level wrappers over the UIKit API, which are much easier to use. This approach is the same as the HotCocoa for MacRuby library, but with a higher level of abstraction.

“We are focusing on creating a high-level Ruby gem set for RubyMotion. One of them is a lightweight wrap over UIKit, ”Sansonetti told me. “We decided to implement an interface markup system using a domain-specific language, a bit like CSS, so Ruby developers will feel at home. Our idea is similar to the Cocoa automatic layout. [ Cocoa auto layout - approx. trans. ], which is built in an ASCII-like language, but we can do it in Ruby even better. We believe that this is a better way to create a UI, rather than Interface Builder, because You can visualize all this in code. ”

Instead of embedding these libraries in RubyMotion, Sonsonetti is going to post them on GitHub under a permissive license. He believes that RubyMotion devotees will create their own versions of markup libraries with different styles. And application developers will have a big choice - use native wrappers or third-party ones.

I asked Sunsonetti about his interaction with the project MacRuby, which he founded as an open source project from Apple, and how the project will now develop after he focused on developing RubyMotion. He explained that the desire to continue working on MacRuby was the main reason for his departure from Apple and the creation of his company.

“As the main developer of the Core OS team at Apple, I created and developed MacRuby, but MacRuby was only one part of all my responsibilities,” he said. “After MacRuby became quite stable, I realized that I would have to leave its development. I did not want to leave the project and its amazing community, so I thought about it for a while and realized that creating a RubyMotion startup would be the best thing I could do - I will continue to work on MacRuby and make a living at the same time. ”

Although he was forced to temporarily stop taking part in the development of MacRuby during the six-month transition period after leaving Apple, he now has returned and is not going to stop work. At the moment, he is the only employee of his new company, but plans to assemble a team, hiring "several participants from the MacRuby community towards the end of the year."

Creating RubyMotion alone was a daunting technical task. We asked him to describe some of the points that he had to overcome during development.

“The biggest challenge was implementing a completely new static compiler and memory model for RubyMotion,” he said. “The implementation of ARM ABI and low-level protocols was also difficult. This took considerable time to fine-tune the speed of execution on devices. ”

Sunsonetti also shared his vision of how expressive and flexible Ruby can improve development speed.

“One of the most important fitch is the interactive environment that rubists take for granted, which is absent on many other platforms. Having the ability to monitor changes in real time is very useful when debugging and testing an application, ”he said. “Ruby is a short and expressive language. An iOS application written in Ruby will contain significantly fewer lines of code than a similar Objective-C application. Less code means a shorter development cycle, fewer bugs, easier to maintain, which means more time to play Skyrim. ”

Build bridges

Before we create a working application, let's take a quick look at how RubyMotion bridges between Ruby and the Objective-C runtime.

Objective-C is a C extension that adds features such as optional dynamic typing and object-oriented programming. NeXT licensed this programming language from Stepstone, which was at the origin of the language, and used it to create frameworks for NeXTstep OS, which later became Mac OS X. Since then, Apple has continued to refine and improve Objective-C.

Objective-C has several common features with Ruby that facilitate interaction between languages. Ruby and Objective-C have a similar method call mechanism, taken from Smalltalk - sending messages. Their object models are similar in that they observe single inheritance and have great introspection capabilities. Recently, Objective-C has received a new syntactic mechanism for creating lightweight anonymous functions similar to Ruby blocks.

Despite the fact that these features allow you to feel familiar when designing RubyMotion, there is also a fly in the ointment. The biggest obstacle is the presentation of method signatures in Objective-C and their calls, which is radically different from what developers see in other common programming languages.

Methods in Objective-C are designed so that parameters are part of their name. At first glance, this may seem just a naming convention, but in practice everything is different (the description of the syntax of Objective-C methods is beyond the scope of this article, however there are several resources with a detailed description).

The main conclusion for users of RubyMotion is that Objective-C APIs are presented in a slightly unusual format when used in Ruby. To illustrate the problem, I'm going to show you an example from UIKit that will be used in the demo reddit application later in the article. In this application, I needed to deselect a row in a table. In Objective-C, it looks like this:

 [tableView deselectRowAtIndexPath:indexPath animated:YES] 


The actual method name is deselectRowAtIndexPath:animated . Parameter values ​​that are of type NSIndexPath * and BOOL are embedded in the function name at the place they are followed by the call. Ruby does not have this syntax. You still follow the convention of named parameters, but in a more traditional style with parentheses:

 tableView.deselectRowAtIndexPath(indexPath, animated:true) 


MacRuby and RubyMotion have a hybrid system that allows them to be on par with Objective-C. In this hybrid system, all standard Ruby types are implemented on top of the standard Objective-C types. For example, all Ruby objects inherit from NSObject, and any string in Ruby is an NSMutableString. You can get an idea of ​​what this means by calling the String.ancestors command in the MacRuby application console:

 => [String, NSMutableString, NSString, Comparable, NSObject, Kernel] 


This kind of type equivalence is very effective within the framework of the execution environment. There is no need to make heavy calculations on converting complex data types between Ruby and the Objective-C API.

Another advantage is that all the commonly used and useful methods that come with these base classes have become universally available. This gives a great advantage when working with containers, for example, when using the chained methods of NSMutableArray map , group_by and sort_by with blocks.

Demo application

Over the past month, I've created several applications on RubyMotion, and also used it to port simple MacRuby applications to iOS. In this article I want to show you a simple demo application that I wrote while I dealt with some of the UIKit APIs.

This application is a simple client for the Reddit website. It downloads a list of current top posts through the Reddit JSON API, then parses the data and displays them in a tabular form on the screen. When a user clicks on an item in the list, the corresponding link opens in a mobile Safari.

 class RedditPost attr_accessor :title, :url, :author attr_accessor :comments, :score attr_accessor :subreddit def initialize(data) @url = data["url"] @title = data["title"] @author = data["author"] @comments = data["num_comments"] @link = data["permalink"] @score = data["score"] @subreddit = data["subreddit"] end end class RedditController < UITableViewController def viewDidLoad @posts = [] view.dataSource = view.delegate = self refresh "top.json" end def tableView(tv, numberOfRowsInSection:section) @posts.size end def tableView(tv, cellForRowAtIndexPath:indexPath) cid = "PostCell" cell = tv.dequeueReusableCellWithIdentifier(cid) || UITableViewCell.alloc.initWithStyle( UITableViewCellStyleSubtitle, reuseIdentifier:cid) p = @posts[indexPath.row] cell.textLabel.text = p.title cell.detailTextLabel.text = "Posted by #{p.author} in #{p.subreddit}" cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator cell end def tableView(tv, didSelectRowAtIndexPath:indexPath) url = NSURL.URLWithString @posts[indexPath.row].url UIApplication.sharedApplication.openURL url tv.deselectRowAtIndexPath(indexPath, animated:true) end def get(address) err = Pointer.new_with_type "@" url = NSURL.URLWithString address raise "Loading Error: #{err[0].description}" unless data = NSData.alloc.initWithContentsOfURL( url, options:0, error:err) raise "Parsing Error: #{err[0].description}" unless json = NSJSONSerialization.JSONObjectWithData( data, options:0, error:err) json end def refresh(endpoint) Dispatch::Queue.concurrent.async do begin response = get "http://reddit.com/#{endpoint}" data = response["data"]["children"].map {|i| RedditPost.new i["data"] } Dispatch::Queue.main.sync { @posts = data; view.reloadData } rescue Exception => msg puts "Loading Failed: #{msg}" end end end end class AppDelegate def application(app, didFinishLaunchingWithOptions:launchOptions) @win = UIWindow.alloc.initWithFrame( UIScreen.mainScreen.applicationFrame) @win.rootViewController = RedditController.alloc.initWithStyle( UITableViewStylePlain) @win.rootViewController.wantsFullScreenLayout = true @win.makeKeyAndVisible return true end end 


The entire application is designed as one .rb file with less than 100 lines of code. The application method in the AppDelegate class AppDelegate called when the application starts. We use this method to create and display an instance of the view that we want to show to the user at startup.

Almost all application logic is implemented in the RedditController class, which is a subclass of UITableViewController . The viewDidLoad method, which is called when the class is loaded into the user interface, sets the instance variables containing the posts, and then calls the refresh method, which loads the data.

The refresh method calls get , which uses the NSData class to download data in JSON format and uses the NSJSONSerialization to convert it to an easily used data structure. You may have noticed that the refresh method uses Grand Central Dispatch (GCD) to load and convert data in the background stream.

Several tableView methods in the RedditController class handle various aspects of a view's behavior. The tableView:cellForRowAtIndexPath used to create row objects and populate them with data. The tableView:didSelectRowAtIndexPath is called when a user taps an item in a table. The code in this method determines which object was selected and opens the URL of this object in the browser, and then removes the selection from the object.

Conclusion

RubyMotion offers iOS application developers the power and expressiveness of Ruby without compromise. It’s hard to imagine a more attractive way to develop native iOS applications. The implementation of RubyMotion is really impressive, and the maturity of the underlying technologies is felt.

After some time spent developing applications using RubyMotion, the only drawback was the lack of integration with Xcode to create the user interface. For developers with no UIKit experience, writing code manually can be long and complicated.

Although this defect does not deny the productivity of working with Ruby, new developers will have to go through the initial training phase before they can fully benefit from RubyMotion. Fortunately, high-level libraries that are about to be released should fix this situation.

The RubyMotion license, which includes annual support for updates, costs $ 199.99. Now it can be purchased at a discount of $ 149.99. More information can be found on the official website of the product. There you can see the introductory screencast from Pragmatic studio.

This article is a translation. The original is available by reference .

Source: https://habr.com/ru/post/143332/


All Articles