-
29 Dec
New in RubyMotion: Device Crash Reports, Pretty Vendor Build Output, Proc#weak!, Performance
Our last release of the year, featuring a few major changes. Let’s dive in!
Device Crash Reports
It is an unfortunate reality that applications crash. This is even more so the case during development cycles.
Until today, the best way to deal with device crash reports during development was to use the Xcode organizer. As a lot of you already know, the user experience was far from ideal and symbolication did not always work as expected.
As of RubyMotion 2.18 we are happy to introduce a long-awaited Rake task to deal with device crash reports: rake crashlog:device.
The task will:
- Connect to your development device
- Retrieve all crash reports generated by your app
- Symbolicate them using the .dSYM bundle created by our build system
- Save the final reports on your file system
- Open the last generated report in Console.app
Let us show you an example. We create a new RubyMotion project and slightly change the application delegate method to trigger one of those UIKit-level exceptions which will crash the process hard.
class AppDelegate def application(application, didFinishLaunchingWithOptions:launchOptions) NSArray.arrayWithArray(42) end end
As expected, running the app on the device causes a crash.
$ rake device [...] Deploy ./build/iPhoneOS-7.0-Development/foo.ipa *** Application is running on the device, use ^C to quit. Dec 29 21:01:49 foo[936]
: -[__NSCFNumber count]: unrecognized selector sent to instance 0x17da1190 Dec 29 21:01:49 foo[936] : *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSCFNumber count]: unrecognized selector sent to instance 0x17da1190' Since this is a lower-level exception happening on the device the terminal output does not contain its full backtrace. We could easily get it by connecting a debugger, but let’s use another way! Since the application crashes a report was generated on the device. We can use the new rake crashlog:device to retrieve that report and see what happened.
$ rake crashlog:device New crash report: /Users/lrz/Library/Logs/RubyMotion Device/foo_2013-12-29-210200_lrzs-iPhone.crash
The command saved a new crash report file and opened it in Console.app. This is what we can see (some of the lines have been trimmed out for better readability):
Last Exception Backtrace: 0 CoreFoundation 0x2f013e7e __exceptionPreprocess + 126 1 libobjc.A.dylib 0x393706c2 objc_exception_throw + 34 2 CoreFoundation 0x2f0177b2 -[NSObject(NSObject) doesNotRecognizeSelector:] + 198 3 CoreFoundation 0x2f0160aa ___forwarding___ + 702 4 CoreFoundation 0x2ef64dc4 __forwarding_prep_0___ + 20 5 CoreFoundation 0x2ef4d70c +[NSArray arrayWithArray:] + 40 [...] 9 foo 0x00097008 rb_scope__application:didFinishLaunchingWithOptions:__ (app_delegate.rb:3) [...]
As we can see, the last frame from our application (highlighted here) in this exception backtrace was in the app_delegate.rb file, line 3, which coincides perfectly with our wrong arrayWithArray: call.
Just by typing one command in the terminal we were able to determine what went wrong on the device. Pretty cool, isn’t it?
Pretty Vendor Build Output
The RubyMotion build system invokes the xcodebuild tool in order to build Xcode-based projects. If your project uses CocoaPods or manually vendors Xcode-based libraries you already know that the build terminal output can be messy sometimes.
As of RubyMotion 2.18 the build output of Xcode-based projects will be consistent with the rest of the build. Here is an example of the new output of a project that uses CocoaPods.
Build ./vendor/Pods/Pods.xcodeproj [SORelativeDateTransformer - Release] Compile ./vendor/Pods/Pods-SORelativeDateTransformer-dummy.m Compile ./vendor/Pods/SORelativeDateTransformer/SORelativeDateTransformer/ SORelativeDateTransformer.m Link ./vendor/Pods/.build/libPods-SORelativeDateTransformer.a Build ./vendor/Pods/Pods.xcodeproj [Pods - Release] Compile ./vendor/Pods/Pods-dummy.m Link ./vendor/Pods/.build/libPods.a
This feature would not have been possible without integrating the awesome XCPretty project, created by Marin Usalj and Delisa Mason.
Proc#weak!
Since RubyMotion relies entirely on the underlying iOS’ retain count memory model, creating cyclic references is unfortunately possible.
In order to help developers avoid the creation of retain cycles, in the past we introduced the WeakRef class which can be used to create weak-references in Ruby and also a cycle detector that can resolve basic cyclic references at runtime.
Sadly it was still possible to easily create cyclic references using blocks. Let us explain why.
A block in RubyMotion keep a strong reference to the object it was created from, bound as self, which prevents said object from being destroyed before the block can send messages to it.
class Test def foo lambda { bar } end def bar p :here end end o = Test.new b = o.foo o = nil b.call
In this case, the Test object will remain alive until the Proc object dies.
This behavior has the side effect of easily creating cyclic references.
class MyController < UIViewController def viewDidLoad @b = lambda {} end end
In the snippet above, the Proc object keeps a reference to the MyController object, and vice-versa, resulting in a cyclic reference. Because the cyclic detector is at this point not able to detect cycles outside autorelease pool contexts, both objects will leak.
This is where Proc#weak! can come handy. This method makes sure the given Proc object does not keep a strong reference to its self binding anymore.
The method always returns a reference to the receiver and can be safely called multiple times. The snippet above can be rewritten to prevent a cycle:
class MyController < UIViewController def viewDidLoad @b = lambda {}.weak! end end
Now, MyController will still keep a strong reference to our Proc object, but the later will not. MyController can then be safely destroyed when it is no longer needed, along with the Proc object.
We believe that the addition of Proc#weak! was the last remaining piece to help RubyMotion developers prevent cycles in their apps. Some popular libraries (such as BubbleWrap) and high-profile apps (such as Bandcamp) are already successfully using it.
Performance
As you might have already seen from the previous build release notes, we have been working on performance improvements for the past few weeks.
We have been internally building an intensive performance suite. Our objective is to run it periodically against previous versions of RubyMotion in order to catch performance regressions. We are also running the suite against CRuby 2.0 with the goal of providing better performance results in every possible scenario.
This is a long process and we are not done yet, but we will publish the suite and the data online as soon as we have something we are pleased with.
In the meantime, if your app or library is experiencing performance issues, please let us know via a support ticket, attach some code and we will add it to the suite!