iPhoneOS: Available Memory

An iPhone has very limited memory, and even simple applications can easily trigger a low memory warning. If you've implemented caching for performance reasons, you'll often find yourself balancing memory consumption against user experience.

Measuring the current available RAM allows one to make pre-emptive decisions about memory utilization before a low memory warning is triggered, possibly avoiding overly broad cache evictions when a memory warning is triggered.

As far as I know, the only way to find out how much memory is left on the device is to ask the Mach VM statistics. The following code is commonly referenced to:

#import <mach/mach.h>
#import <mach/mach_host.h>

static void print_free_memory () {
mach_port_t host_port;
mach_msg_type_number_t host_size;
vm_size_t pagesize;

host_port = mach_host_self();
host_size = sizeof(vm_statistics_data_t) / sizeof(integer_t);
host_page_size(host_port, &pagesize);

vm_statistics_data_t vm_stat;

if (host_statistics(host_port, HOST_VM_INFO, (host_info_t)&vm_stat, &host_size) != KERN_SUCCESS)
NSLog(@"Failed to fetch vm statistics");

/* Stats in bytes */
natural_t mem_used = (vm_stat.active_count +
vm_stat.inactive_count +
vm_stat.wire_count) * pagesize;
natural_t mem_free = vm_stat.free_count * pagesize;
natural_t mem_total = mem_used + mem_free;
NSLog(@"used: %u free: %u total: %u", mem_used, mem_free, mem_total);
}

Also interesting is this snipped to free up some memory on the device:

/* Allocate the remaining amount of free memory, minus 2 megs * /
size_t size = freemem - (2*1024*1024);
void *allocation = malloc(size);
bzero(allocation, size);
free(allocation);

TrailRunner BonjourTrailSyncService

TrailRunner BonjourTrailSyncService

TrailRunner can communicate with an iPhone app via Wifi. The main idea is that you can send recorded tracks from the iPhone to TrailRunner and also send tracks from TrailRunner to the iPhone.

Introduction
To make this happen, the code within this project must be available and running in both apps. In TrailRunner and your iPhone app.
The rest is just delegate methods being called and individual handling on both sides.

The following two use cases give a good overview on what is going on:

Send Route from TrailRunner to iPhone:
  • In TrailRunner user clicks on iPhone toolbar Icon > Send and selects a route
  • User clicks on the send button.
  • What happens now is that TrailRunner makes a Bonjour lookup to find an iPhone running your app that is waiting for a connection
  • When the connection was established, the selected route is being encoded as a GPX string and then being transferred to your app.
  • A delegate method gets called within you app, you receive the GPX string, decode it into CLLocations (No parser provided by me) and you're done.

Send recorded track from iPhone to TrailRunner
  • In TrailRunner user clicks on iPhone toolbar Icon > Receive. TrailRunner is now waiting for a connection.
  • User starts your app on the iPhone and selects a recoded track.
  • What happens now is that the bonjour lookup again connects your app to TrailRunner that is now waiting for data.
  • User taps on a send button within your app.
  • You now encode the track as a GPX String and send it over to TrailRunner.
  • TrailRunner receives the track and displays it.

Some additional words:
  • Both use case assume that in TrailRunner > Preferences > Synchronization the user has selected your app as the iPhone Helper app (requires an individual app identifier we must agree upon, for testing use com.trailrunnerx.racebunny as the helper application identifier)
  • Sending and receiving data can be handled within your application delegate. You start the Bonjour controller and then whatever happens, delegate methods -- you must implement against a required protocol -- will be called.

Sample Project
The TrailSocket sample code within this project contains the following sources:

/BEKit this contains some basic categories and extensions I use in all of my projects.
/Shared This contains the Bonjour communication "framework". You need to add this code along with the Apple CFNetwork.framework to your project
sample.gpx A sample GPX file used for testing
/Classes A sample implementation against the BonjourTrailSyncServiceDelegate protocol.

More details about the protocol below:

As with a web-browser talking to a web-server, two communication partners must agree upon who is server and who is client. Within the context of BonjourTrailSyncService assume the iPhone is always set up as a server and TrailRunner as a client -- Even though TrailRunner may send data --

To make both apps find each other, the server announces his availability via Bonjour in the local wifi network. The name of the service must be known by the client. This is typically the application bundle identifier of the server. (see mention of com.trailrunnerx.racebunny above)

When TrailRunner detects the server via Bonjour it opens an input and output stream. At the same time the server gets connected to these streams. Both are ready to communicate. As soon as a transfer is being initiated from either side, the receiver starts collecting data packets asynchronously in the run loop. When all data packets are collected, a receiver delegate method is being called containing the full data string from the source.

BonjourTrailSyncServiceDelegate Protocol

The sample project contains a protocol implementation within the TrackExchangeViewController class. The ViewController approach enforces that you can only send and receive data while that view controller is running. You may also shift the code over to your application delegate and have your app always be ready for communication.
As the communication overhead gets disposed once a transfer is done, it should be very lightweight while in idle mode.

within the viewDidLoad or applicationDidFinishLaunching: setup you local server:
NSString *applicationIdentifier = @"";
self.streamManager = [[BonjourTrailSyncService alloc] initWithController:self serviceIdentifier:applicationIdentifier isServer:true];
make sure that the streamManager is a retained reference.

Send track from TrailRunner to iPhone (sendDataFile:):
[streamManager sendDataString:dataString];
and then for notification that sending is done, implement
- (void)syncServiceDidFinishSending:(BonjourTrailSyncService *)service

To receive a track from TrailRunner, implement:
- (void)syncService:(BonjourTrailSyncService *)service didReceiveDataString:(NSString *)dataString

Within either sending or receiving, two events may occur. First, to realize that TrailRunner and your app find or loose each other implement:
- (void)updateForNetServiceDidDisconnect
- (void)updateForNetServiceDidConnect
You can use these methods to enable and disable a send button in your interface. Only between those two events TrarilRunner is really listening to you.

and during the course of data transfer which may last several seconds, you get notified within these methods:
- (void)syncServiceDidStartTransfer:(BonjourTrailSyncService *)service
- (void)syncService:(BonjourTrailSyncService *)service didFailWithError:(NSError *)error
whereas the events on success are already mentioned above.

to successfully get rid of the BonjourTrailSyncService instance, do the following in your closing method:
self.streamManager = nil;

And that´s it!

> Download Sample Code

NSArrayController awakeFromInsert

This week I got into some trouble with Systems running MacOS X 10.5 and my custom implementation of NSArrayController

Problem: I have an overwritten version of NSArrayController that has an implementation for awakeFromNib to initialize some internal states.
First, under 10.5 the method awakeFromNib is not being declared in NSObject (in contrast to 10.6) so there is no [super awakeFromNib] for NSArrayController in 10.5
Second, when you have an implementation for awakeFromNib in your NSArrayController/NSObjectController the whole CoreData usesLazyFetching mechanism for Managed Objects of a certain entity is not being called by Mac OS X 10.5
In my implementation of awakeFromNib I was observing the selection and the arrangedObjects.

Solution: Do not overwrite awakeFromNib if you want to deploy for 10.5. Im my special case: as usesLazyFetching fires delayed, there is no reason not to put the observation code into initWithCoder or respectively into your designated initializer method.

Macro Goodness

If you read books on programming they all tell you to write documented code. But even better is to write compact code. Code that is so compact, it can even document itself. Objective-C is already pretty self documenting but some common conventions require you to write the same kind of code ever and ever again, just to follow these conventions. Macros have been ever very helpful in these cases. Following some examples. You can imagine how many macros like this I use in my code.

Objective C 2.0 will make the whole retain release cycle go away but I am still sticked to Tiger.


+ (void)setSomeReference:(NSObject *)object
{
    
BEReferenceWithRetain(_myStrongReferenceobject);
}


Uses this macro. I have others for BEReferenceWithCopy, BERelease and more.


#define BEReferenceWithRetain(ab) \
{ \
    
id bb = b; \
    
if (a != bb) \
    { \
        
if (bb != nil) \
            [
bb retain]; \
        [
a release]; \
        
a = bb; \
    } \
}


Another common task is adding elements to a result set or array. If nothing should be added the method might return nil, but otherwise the resultArray. So this code is a very common task:


NSMutableArray *resultArray = nil;

while (someLoop)
{
    
if (shouldNotAdd)
        
continue;

    
BEAddObjectToMutableArray(resultArraynewItem);
}


It uses this macro


#define BEAddObjectToMutableArray(arrayitem) \
{ \
    
if (item) { \
        
if (array == NULL) {\
            
array = [NSMutableArray arrayWithObject:item]; \
        } 
else {\
            [
array addObject:item]; \
        }\
    }\
}

[NSURLConnection sendSynchronousRequest:returningResponse:error:]

I had a lot of trouble with this beast in Tiger and it came up in Leopard again. As I did not find any solutions elsewhere, here's how I fixed it for me.

Problem: NSURLConnection sendSynchronousRequest:returningResponse:error starts a download and blocks the calling thread until all bytes are received from the stream. In most cases this just works. But for some reason, if you call this on the main thread, it sometimes does not unlock the thread resulting in an application hang.
Lesson Learned: Fully synchronous url requests are impossible. Always load networks resources from a background thread.

Next problem: Code Data loads an XML data store using this method. So it's no wonder that sometimes, when the arraycontroller gets filled, your application just hangs. Or, under Tiger, crashes because the NSData the store loads the XML into gets deallocated before the store is even finished loading.

Solution: To fix this, I wrote a category for NSXMLDocument that loads file resources using standard file methods. Otherwise it does what I expect it to do in the default implementation.


- (id)initWithContentsOfURL:(NSURL *)url options:(unsigned int)mask error:(NSError **)error
{
    
id result = nil;
    
NSData *data = nil;
    
if ([url isFileURL])
    {
        
data = [[NSData allocinitWithContentsOfFile:[url path]];
    }
    
else
    {
        
NSHTTPURLResponse *response = NULL;
        
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:10];
        
data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:error];
    }
    
result = [self initWithData:data options:mask error:error];
    
return result;
}