Syntax Highlighting

Sunday, February 15, 2015

The iOS Deallocation Nightmare Heap

So, well into my failed foray iPhone app development with Rubymotion, I said the hell with having one code base (Ruby) between iPhone and Android and started developing my iPhone app with Swift. It required a lot of typing translating my Java (now Ruby) code into Swift. Uggg.

Unfortunately, the iPhone system, the Objective-C base is hopelessly stuck in the early 80s and doesn't have a garbage collector. It has something called Automatic Reference Counting(ARC). The ARC came about a couple of years ago and was hailed as a major break through. Really? We really are in the 80s.

So, what is ARC? Before ARC you used to have to do RC, i.e. reference counting, which was a tool to handle deallocations in the heap. If you allocated an object you could deallocate it when you didn't need it anymore, by calling .release() on it. Conversely, if you needed it held onto in multiple places you could call .retain() on it. These calls would lower and increase the reference count on that object respectively. When the count would hit zero, then magically, the space the object was taking up would be given back to the heap. In order to deallocate space that the object may have references to, you would have to override a function called dealloc() and call .release() on some references, or you used some other strategy. Some strange strategies did emerge, but that is not the subject here.

Apple discovered, or worked hard at, noticing that the approach developers were doing was pretty boilerplate for deallocating objects. At least that what I surmise, since that is the way garbage collectors have been working for decades. Seriously, we tackled the issue of garbage collection a long time ago in the 70s with LISP and probably before, seriously enhancing garbage collection in the 80s and 90s to be really efficient and effective, yielding smarter, and also less stressed programming.

Enter ARC, that is "Automatic Reference Counting". Voila, It's much like not having to manually shift gears in your car, which is something we developed in the 40s, but I digress. You get my point. The 90s are over.

So, now with the ARC, Apple will take care of this reference counting for you. Apparently, the compiler would be able to notice when ".retain()" or ".release()" needed to be called, based on syntax. Also, at deallocation, much like my 'dealloc()" override strategy mentioned above, one would assume that it would "automatically" reduce all the reference counts of space which was being held by the object being deallocated. So, one would think. Think again Einstein.

One other supreme <sarcasm> benefit </sarcasm> with ARC is that it is NOT a garbage collector. "It's much more efficient than a garbage collector," they say. So, unlike a garbage collector, you cannot just ditch the top pointer of an object graph and have the whole object graph go away. No, uhh unnh. no nada, no god damn way. If you have any reference cycles in the object graph, you are hosed because a cycle chain will never reduce its reference counts to zero, and therefore will never be freed. I'll leave that thesis to the reader as an exercise.

So, Apple developed, at least in Swift, two different types of references, "weak" and "unowned". Pointers to objects declared with these keywords are said not to increment or decrement reference counts on the object for which they are references. For example, the following declarations:

class A {
   var myChild : B
}

class B {
   weak var myParent : A?
}

var a : A? = A()
var b : B? = B()

b.myParent = a!
a.myChild = b!

Now, if you got rid of the reference "a" by the following:

a = nil

Then, both objects would automatically get deallocated. Magic, eh? My god, what an accomplishment!!! If myParent were not a "weak" reference to A, then none of it would get deallocated. Okay, you say. That seems logical.

So, what is the problem with this approach?

YOU HAVE NO FUCKING CLUE whether any 3rd party software actually declared myParent a weak or unowned reference. 

In fact, the above example is quite simple for illustration, but if the object graph is complicated you have to do a whole bunch of this stuff, just to MANAGE the GARBAGE! You have to MANAGE the GARBAGE.

Let me say that one more fucking time.
YOU have to MANAGE the FUCKING GARBAGE!!!!!
So, back to my point. My problem arose from doing this:

class MapViewController : UIViewController, MKMapViewDelegate {
    init() {
       mapView = MKMapView()
       mapView.delegate = self
       .....
       mapView.addOverlay(MYOverlay())
   }

   func mapView(mapView: MapView, rendererForOverlay: MKOverlay) -> MKOverlayRenderer {
     return MyOverlayView(rendererForOverlay as MyOverlay)
   }
}

MyOverlay would get a MyOverLayView and go on with its business. So, one would think that when MapViewController got deallocated, it would also deallocate the MyOverlayView, which it does. One would also think that the mapView would in turn deallocate the overlay, or at least reduce its reference count. Apparently, after painstaking investigation, that is NOT the logical case. WTF?

It took me more than a fucking week with debug statements and the almost usless Xcode debugger to figure out what I now call an undocumented requirement. It's not documented anywhere probably because, why would you want to deallocate a mapView? Fuck me.

So, as I labored for weeks, I find this solution. You have to keep a reference to the overlay you added, and also remove it from the mapView when the ViewController is getting deallocated, Otherwise, I can only surmise that it is holding onto a lot of closures, or something that isn't even referenced in the overlay object itself, but to things in the MyOverlayView.

This was the hard part. The matching MyOverlayView was getting deallocated, however, a lot of stuff that was actually referenced in the MyOverlayView was not. The MyOverlay was NOT getting deallocated, and it actually contained references to anything that was still being held. But that stuff in question did have some indirect references in the MyOverlayView. But let me say this again. the MYOverlayView object was deallocated. So, that lead me to look for something else, possibly stupid, that *I* was doing. Think again Einstein.

After trying everything else that was related to the objects/memory that should have been getting released and was not,  this approach magically seemed to do the trick:

class MapViewController : UIController, MKMapViewDelegate {
    var myOverlay : MyOverlay?
    init() {
       mapView = MKMapView()
       mapView.delegate = self
       .....
       myOverlay = MyOverlay()
       mapView.addOverlay(myOverlay)
   }
   deinit {
      mapView.removeOverlay(myOverlay)
   }
}

Then, BINGO, WTF? Really? My memory leak from releasing and creating difference instances of the MapViewController disappeared, well, as far as I can tell.

One would think, that if the MapViewController only carries a reference to the MapView that when it gets deallocated, it should also deallocate the MapView object, and therefore AUTOMATICALLY remove the overlays. Obviously,  that is not the case, and that is not documented anywhere.

I am kind of thinking that the MKOverlay class actually holds an undocumented non-weak reference to the MKMapView object, and the MKMapView object obviously holds a reference to the MKOverlay in a list somewhere. So, neither the mapView nor overlay object get deallocated due to their reference cycle. I'm still at a loss for why all the rest of the stuff was being held onto as they never had references to the mapview except within executed function through the MyOverlayView, which had been deallocated. Who the fuck knows?

This situation does not seem to be documented probably because nobody would probably think about deallocating a ViewController containing a mapView. In my app, this situation may occur as much as the user wants it, so I've gotten burned.

Finally, though, I now know how to solve the memory leak with this mapView, and hopefully this blog post helps the next poor schmuck who has to do MapKit shit in iPhone development.

Now maybe I can finish this fucking iPhone App that took a significantly less time (an order of magnitude less time) to implement in Java and Android.

Thanks Apple.

Sunday, February 8, 2015

The Rubymotion Failure

A while ago I wrote a post about my foray into using Rubymotion (RM) for iPhone development. RM is a development tool for programming in the Ruby language for the iPhone. The basic reason for this approach was that I had an Android App with a lot of non-ui code and Hipbyte's promise that Rubymotion will support Android. Therefore, I would have a single code base to maintain between the two platforms. The other reason was merely cosmetic in nature. I think Obj-C is one of the more horrendous languages around. This effort was a resounding failure.

From that post, fast forward a few months, and as of Jan 1st, the start of the new year, I have scrapped the entire Rubymotion project. A total of 5-6 months of development has come to a screeching halt. Rubymotion has been a disaster.

First off, from a old cogger developer that actually likes statically typed languages, Ruby is an atrocity. But with some discipline it could be worked. However, the mere fact that it is Ruby, makes it problematic for smallish platforms because of its memory usage. I found that Rubymotion had bizarre memory problems. Weak Reference exceptions would popup when trying to do NSCoder serializations, where I wasn't using any weak references in the object graph. That lead me to write my own text based externalization library, because the Sugarcube NSCoder strategy kept failing. Sugarcube is a Ruby Gem that makes using Rubymotion a bit more like Ruby and less than Obj-C.

Other memory problems kept creeping up occasionally. One notable one is where somehow a reference to a list of one type of object became a list of another object. Now, Ruby doesn't have any type safety, but if you are disciplined enough, you can be 100% sure that if a variable to be of a certain type, it should be just that.

I had something of the sort:
class A  
  attr_accessor :bList
  def initialize
     self.bList = []
  end
  def add(args)
     self.bList << B.new(args)
  end
  def process
     bList.each do |b|
        b.doit
     end
  end
end 
class B
   def initialize(args)
     ...   
   end
   def doit
     ....
   end
end
 In A#process, the app would crash because bList didn't contain a list of B. That was evident because the error message said that the target object didn't have a method named "doit".  A few puts statements, revealed that it was an object of a different type. WTF? The list of the other object did exist, but should have been in some other object.

To top all that, it wasn't consistent, which probably was a result of the particular memory recovery strategy they were using. Apparently, I got from the community support list that they don't use iPhone Automated Reference Counting and use something else. That is probably due to the dynamic nature of Ruby and what they have to plan on with the crazy bullshit you can do in Ruby.  This error was the most egregious one and with the group not being quite forthcoming with the problems leads to undermine my faith in the product. If you can't trust the fundamentals then what the hell are we doing. Gödel, I digress.

Other memory problems seem to be with the memory collection. I won't call it garbage collection, because the Rubymotion people seemed to vehemently avoid that term. It would seem that at the end of segment of a thread, the app would freeze up, maybe for as much as 10 seconds. One community member confirmed this problem, and could replicated using autorelease_pool.  That seem to fall on deaf ears, and the RM community guy couldn't understand why I was having all these memory problems and doubted my use of mutexes , locks, and semaphores. I'm quite well versed at concurrent programming, thank you, and instead of doing any real investigation, passed it off as a problem that I'm doing something too complicated. Really?

I actually found a problem, and did my best to reduce the problem to a minimal app to demonstrate the problem, put it on Github. The AppDelegate class would illustrate the problem. Even that was too much. Here is a response from the HipByte support site:
Hello Dr. Polar Humenn,     
A good test project usually consist of one file with the minimum amount of code that exposes the error or wrong behaviour. It would really help us if you could create a further reduced test project.     We can take a look at the unreduced test project. But please be advised that won't happen until we have gone through our backlog of support tickets.     
Thank you, 
Mark. 
At this point, I was sorry to  tell them that wasn't going to happen, and I was giving up on Rubymotion. That was Jan 1st. Time to start a new year.

So, now what? I'm using Xcode and Swift, and unfortunately, I will have to maintain two completely different code bases.  And I'm on a fast and furious programming to get it up and running using Swift. Swift is not without its own problems, but the type safety definitely helps, and it's not Obj-C. The problem is that Xcode is not Eclipse, and the syntax evaluator keeps crashing. At least it is resilient that it restarts itself, but the crashing all the time is quite annoying. The code completion, when the syntax evaluator isn't crashing, it works, albeit a bit slow. Documentation is crap. And apparently, as I wrote here, Xcode cannot refactor Swift code, only Obj-C.

I'm a registered  Beta user for RoboVM which is a Java product for the iPhone out of Sweden, but I haven't had the courage to fire it up. Right now due to this failure, time has become critical.

Cheers,
Dr. Polar Humenn