Friday, September 14, 2012

Creating Bitmap Context for Retina and regular iOS devices

When it is a Retina display iOS device, the resolution is 4 times as a regular display.  How is a bitmap context created in this case that can make use of the higher resolution, while for a regular display, a regular bitmap context is used so that memory isn't wasted?

The answer is using the [[UIScreen mainScreen] scale], and create the bitmap context accordingly.  But will any drawing routine also take special care to draw on this bitmap context because now the pixels on x and y-axis have both doubled?

The solution is that we can just do a transform, and everything will be taken care of.  By doing this, any drawing routine will not need to tailor to any particular size.  Moving to a point at (300, 300) will be actually moving to pixel (600, 600), but the drawing can just use (300, 300) for both a regular and Retina device.  The solution is:

float scaleFactor = [[UIScreen mainScreen] scale];
CGSize size = CGSizeMake(768, 768);
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef context = CGBitmapContextCreate(NULL, 
                           size.width * scaleFactor, size.height * scaleFactor, 
                           8, size.width * scaleFactor * 4, colorSpace, 
CGContextScaleCTM(context, scaleFactor, scaleFactor);

note that the last line, the CGContextScaleCTM is important.  It does the work of making (300, 300) to be the actually pixel (600, 600) on a Retina device.  The line that does the CGSizeMake(768, 768) is how big you'd like the bitmap context to be.  It works on a regular display and is automatically scaled up for a Retina display in the code above.

Thursday, September 13, 2012

Objective-C Manual Retain Release

The Manual Retain Release in Objective-C is not really that hard, but with the following precise rules:

Our motivation is:
  • We would like to hold onto an object, when at least one reference is pointing to it
  • We would like to free an object's memory space, when there is 0 reference pointing to it.

The mechanism is:
  • We use retain count to make this work
  • When an object is alloc'ed by [Foo alloc], the retain count is 1
  • When an object is created by [Foo new], it is the same as [[Foo alloc] init], and the retain count is also 1
  • When an object is copied, the retain count of the new object is 1
  • When the retain count is 1 or greater, that means the object should stay around.
  • When we send the retain message to an object, the retain count is incremented by 1.  This is how we send the retain message to obj: [obj retain];
  • What about decrementing the count?  It is by sending the release message to the object: [obj release];
  • When this release message is sent to the object, first, the retain count is decremented by 1, and when this retain count reaches 0, the system will send the dealloc message to the object.  That is, the system will do [obj dealloc];  for you.  So in the object's dealloc method that you define, make sure to clean up any other objects you keep around for the current object.  Then, call [super dealloc], so that the superclass will clean up objects at each higher level of the class hierarchy.  Then when it reaches the last one: [NSObject dealloc]; the actually memory (RAM or think of it as virtual memory) is released (freed up), and becomes available for other apps or your app to use again (RAM / virtual memory).

That's it.  So match the alloc, new, copy, retain, release, so that there is a balance.  Don't retain too much, and don't release too much (or too early).

Somethings to note:
  • Never call dealloc yourself, except to call super class's dealloc: [super dealloc].  The system will call dealloc for you when performing [obj release] and found the retain count to be 0 after that release.
  • You can check the retain count by [obj retainCount], although you should never use this number to do memory management.  It is only for understanding the mechanism and for experimenting and checking to see how the retain count increased or decreased.

For factory methods:
  • factory methods, such as stringWithFormat, needs to alloc a string, and return it.  So this method cannot do a [str release] because the object cannot be freed up yet.  But alloc has to be matched with a release, so how can this be solved?  This is the way:
  • There is an autorelease pool at each iteration of the app's main loop.  When we do [[[str alloc] initWithFormat: ...] autorelease] the retain count of the str object remain as 1, but the object is added to the autorelease pool.  When the caller gets back the string, it will perform the retain to hold onto the object, so that the object is not freed up.  At this point, the retain count is 2.  And when all application events are handled, the system will drain the autorelease pool, and make the retain count of str become 1, and now everything is in good order, and the system later will start an autorelease pool again, and handle all app related events, and at the end of this iteration, drain the autorelease pool again.
  • Note that every time you send the autorelease to that object, the autorelease pool will keep a number as to how many times to send the release message to the object when the pool drains.  So for example, if [[obj retain] autorelease]; is done 10 times, the retain count of the object will increase by 10, and when the autorelease pool drains, the object will be sent the release message 10 times.  So autorelease doesn't decrease the retain count immediately.  It decreases the retain count later.  A good way to think of autorelease is to think of it the same as a release, but deferred.

For @property:

  • If the property attribute is retain, copy, (or strong, but that is part of ARC), then the instance variable will automatically hold onto this object (the object will be retained once).  The previous object pointed to by this property will be released once.
  • If the property attribute is assign, unsafe_unretained, (or weak, which is ARC), then the instance variable will not cause the object's retain count to increase, as this property is not trying to hold onto the object (no ownership claimed).

Retain count with Cocoa and Cocoa-Touch (iOS) frameworks:

  • The frameworks work naturally with the retain count, so that when an object is added to a collection, such as an NSMutableArray, the retain count is increased by 1, to let the array hold onto the object.
  • In UIKit, such as when a UIView object is added as a subview, its retain count is increased by 1.  When this subview is removed from the superview, the retain count of the subview is decreased by 1.
  • When a certain array element is replaced by another object reference, the old object is released once, while at the same time, the new object is retained once.  This works the same way as a retain property.