Skip to content

Instantly share code, notes, and snippets.

@daveanderson
Created March 14, 2012 20:51
Show Gist options
  • Save daveanderson/2039419 to your computer and use it in GitHub Desktop.
Save daveanderson/2039419 to your computer and use it in GitHub Desktop.
Lesson 99: Beekeeper or Bee-Haver?

Lesson 99 – Beekeeper or Bee-haver

A BEE-HAVER is someone who can say they “HAVE” bees but they do not want beekeeping to consume their time or interest, so they spend little to no time keeping bees, they simply have bees. That’s certainly one approach.

Then there are those who want to evolve from just having bees to truly doing all they can to make sure their bees are as healthy as possible.

Beekeeper

Objective-C and Cocoa Conventions

AKA "Stop making your keyboard cry!"

Cocoa

Going to assume some familiarity with Obj-C & Cocoa and look at some basic tips for improving your code (Obj-C is the language, Cocoa is the application frameworks.)

Why do I care about this? Because when you inherit one of my projects I want

  • the code to be self documenting (not a bunch obsolete comments)
  • reusable – copy bits into other projects
  • flexible - readily adaptable to meet changing requirements

You should be able to skim my code and quickly see what happens in each class.

  1. Better method names
    • spacing - recommend following 's convention. That way you match Xcode snippets, sample code, etc. (Apple isn't perfect in following their own convention, but there's less friction if you just follow their lead.)

    • method signature is the name of a message sent to an object (sent via obj_msg_send())

    • method name "selects" a method implementation, so often referred to as selector

    • methods with multiple parameters should have the method name interleaved with the parameters. This lets the method name describe the parameters. Selectro name includes all parts of the name, including the colons.

      Method Signature:

      - (void)insertHive:(id)beehive atIndex:(NSUInteger)index
      

      Selector

      insertHive:atIndex:
      

      Usage

      [aFarmersField insertHive:whiteBeehive atIndex:5];
      

      Syntactically permissble but you're making your keyboard cry

      - (void)insert:(id)something :(NSUInteger)index
      
      insert::
      
      [anObject insert:newObject :5];
      

      This fundamental difference in method naming in ObjC may be difficult to grok at the beginning but improves code readability and should give you hints as to what parameters are required. The behaviour should definitely be clear.

    • accessor methods – getters should be the name of the ivar/property, and shouldn't include the term "get".

      Bravo!

      NSArray *hives = [NSArray array];
      
      [aFarmersField setHives:hives];
      aFarmersField.hives = hives; // property assignment calls setter setHives:
      
      hives = [aFarmersField hives];
      hives = aFarmersField.hives; // use of property calls getter
      

      Keyboard Rain

      [aFarmersField getHives];
      
    • we read more than we write – be descriptive. Objective-C and Cocoa are designed to read well.

      Obj-C Pro

      destinationSelection
      setBackgroundColor:
      rowIndex
      name
      RPUpdateLocationInformationOperationDidCompleteNotification
      

      Tears make the keys slippery

      destSel
      setBkgdColor:
      i
      szName
      downloadNotification
      
    • methodNamesUseCamelCase:additionalParameter:

    • RPSmokeTheBees() && RPBeeHiveWidth

    • beware of methods starting with new, copy, create – they imply specific memory management and may confuse ARC (and other developers!)

Method Declaration Syntax

  1. Actions – Those things that happen when you tap a button. The method name is the response, not the state.

    • when you connect a button between a xib and a method its essentially the same as calling these two methods

        [myButton setAction:@selector(processHoney:)];
        [myButton setTarget:anObject];
      
    • when the button is tapped, this method is getting called

        [anObject performSelector:@selector(processHoney:) withObject:myButton];
      

      This message passing is decided at run time and is totally dynamic, so there's no need to create static associations between a button and a method. An IBActionisn't an button or event handler! (IBAction is actually #define IBAction void)

    • the identifier IBAction indicates the action that will happen not the action that did happen.

      Über-clear

        - (IBAction)showSettingsPopover:(id)sender;
        - (IBAction)checkUserLoginCredentials:(id)sender;
        - (IBAction)smokeTheHive;
        - (IBAction)hideLicenseAgreement;
      

      The home row weeps

        - (IBAction)blueButtonPressed:(id)sender;
        - (IBAction)loginTapped:(id)sender;
        - (IBAction)tabButtonTouchUpInside:(id)sender;
        - (IBAction)agree;
      
    • Why is this a big deal?

      Designs change. Buttons change. Actions change.

      By defining the state in which you expect a method to be used, you're immediately and artificially limiting any other use. You're also giving no cues as to the behaviour of the code in the method.

      What happens when the blue button is pressed? Do you care that the button, 3 revisions ago, used to be blue?

      Will the sender always be blue? Login? a TouchUpInside?

      If you name it per proper convention, then its no big deal when 3 different buttons are all connected to smokeTheHive or when the button that triggers showSettingsPopover: gets changed 36 times over the course of the project - their actions that will happen are always clear.

  2. Class Prefixes

      RPLoginViewController
      RPHive
      RPDroneBee
    
      LoginViewController
      Hive
      DroneBee
    
    • Recommended to avoid collisions
    • Redundant characters and seem less readable
    • Great for refactoring and find/replace because the origin is totally clear
  3. Constants

    • Use enumerations for groups of related constants that have integer values

         typedef enum {
             RPMapListSegmentedControlMapIndex = 0,
             RPMapListSegmentedControlListIndex,
         } RPMapListSegmentedControl;
      
    • Use const to create constants for floating point values

         // Common.h
         extern const float RPImageOverlayViewRadiusMeters;
         
         // RPImageOverlayView.m
         
         const float RPImageOverlayViewRadiusMeters = 100.0f;
      
    • In general don't use the #define preprocessor command to create constants. (e.g They won't be available in the debugger.)

    • Define constants for strings used for notification names and dictionary keys. By using string constants the compiler can perform verification.

        // Common.h
        extern NSString * const RPLocationsDatabaseName;
        extern NSString * const RPHelpVersionKey;
      
        // RPSecretLocations.m
        NSString * const RPLocationsDatabaseName = @"secretLocations.sqlite";
        NSString * const RPHelpVersionKey = @"42";
      
  4. Extensions vs Categories

    • Categories - add methods to an existing class or break up an implementation into multiple files. (Can't add ivars or properties)

        // NSString+RPAdditions.h
        
        @interface NSString (RPAdditions)
        
        + (NSString *)RP_stringFromBase64Data:(NSData *)data;
        
        // this allows [NSString RP_stringFromBase64Data:base64Data];
      
        @end
      
    • Extensions - private implementation details

        @interface RPHive ()
        
        // private methods
        - (NSNumber *)hiveWeight;
        - (NSNumber *)smokePenetrationFactor;
        
        // private properties
        @property (strong, nonatomic) NSNumber *numberOfDrones;
        @property (nonatomic) NSNumber *numberOfWorkers;
        @property (nonatomic) NSString *hiveIdentifier;
        
        @end
        
        @implementation RPHive {
            // private ivars
            BOOL _queenPresent;
            NSNumber *width;
            NSNumber *height;
            NSNumber *depth;
        }
        
        @synthesize numberOfDrones = _numberOfDrones;
        @synthesize numberOfWorkers = _numberOfWorkers;
        @synthesize hiveIdentifier = _hiveIdentifier;
      
        // implementation
        
        @end
      
  5. Literals for NSDictionary, NSArray and NSNumber (available in Xcode 4.4)

    • @"moof"; is a literal for [NSString stringWithCString:"moof" encoding:NSUTF8StringEncoding];

    Soon we'll have

    • @42; instead of [NSNumber numberWithInt:42];
    • @3.141592653; instead of [NSNumber numberWithDouble:3.141592653];
    • @'L'; instead of [NSNumber numberWithChar:'L'];
    • @YES; instead of [NSNumber numberWithBool:YES];
    • @2.718f; instead of [NSNumber numberWithFloat:2.719f];
    • @256u; instead of [NSNumber numberWithUnsignedInt:256u];

    Then in combination with the new NSArray literal

    • NSArray *easyArray = @[@"foo", @'Q', @100.0f, @YES];

    Similary for NSDictionary

    • NSDictionary *simpleDictionary = @{ @"redKey": @"red", @42: @NO, @"blueKey": @22.0f };

    And a non-literal improvement coming in Xcode4.4 is that @synthesize becomes optional!

  6. ARC (instead of MRC/MRR - Manual Reference Counting / Manual Retain Release)

    • Use it! Converting most projects is a 30 minute process (or less)
    • It's less code (less is always better - less room for mistakes)
    • It's more efficient (leaking is harder, memory footprint is smaller)
    • Its less work – my knowledge of effective retain/release patterns are like old war medals – well earned and give great perspective, but not something I want to use all the time.

References:

Intro to Blocks and GCD

Blocks

  1. Blocks - what is a block

    Blocks encapsulate a unit of work (a segment of code) that can be executed at any time. In other languages these they might be called "closures" or "lambdas".

    If you're familiar with function pointers, just swap the * for a ^.

Function Pointer

float (*Multiply)(float, float) = NULL; // defining a function pointer
float result = (*Multiply)(3.14, 2.0); // call function pointer

Block

int (^Multiply)(int, int) = ^(int num1, int num2) {
    return num1 * num2;
}; // defining a block

int result = Multiply(7, 4); // using a block

Block Definition

As an argument to a method, a block acts as a callback or a way to customize a method or function. Better than function pointers, blocks are defined where the method is invoked. You don't have to add a method or function and the implementation is defined right where you're using it.

- (void)viewDidLoad {
   [super viewDidLoad];
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(keyboardWillShow:)
                                                 name:UIKeyboardWillShowNotification 
                                               object:nil];
}

// at some other location in the same file separated by multiple methods
- (void)keyboardWillShow:(NSNotification *)notification {
    // Notification-handling code goes here.
}

vs

- (void)viewDidLoad {
    [super viewDidLoad];
    [[NSNotificationCenter defaultCenter] addObserverForName:UIKeyboardWillShowNotification
         object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) {
             // Notification-handling code goes here - right where you're adding the observer
    }];
}

Blocks allow you to write code at the point of invocation that is executed later in the context of the method implementation.

Blocks allow access to local variables. Rather than using callbacks requiring a data structure that embodies all the contextual information you need to perform an operation, you simply access local variables directly.

  1. Block basics

    • blocks can have parameters and a return type (just like functions)

    • but they can also have access to local variables (better than functions)

        int multiplier = 7; // local variable
        
        int (^myBlock)(int) = ^(int num) {
            return num * multiplier;
        };
        
        // if the block is defined as a variable, then you can used it just like a function
        printf("%d", myBlock(3));
      
  2. Favourite block API - UIView animateWithBlock:

     [UIView animateWithDuration:0.5f
     				 animations:^{
     				     // what animatable properties do I want to change
     					 self.loginView.alpha = 0.0f;
     				 } completion:^(BOOL finished) {
     				     // what should happen when this animation is done
     					 [self showWelcomeMessage]
     				 }
      ];	
    
  3. Getting data out of a block

    Using FMDB as a front end to an sqlite database. The latest version of FMDB uses a queue to manage access to a database and lets you provide a block to specify the block you need to happen.

     - (int)numberOfDatabaseEntries {
         __block int count = 0;
         [self.queue inDatabase:^(FMDatabase *db) {
             FMResultSet *rs = [db executeQuery:@"SELECT COUNT(*) AS 'Count' from location_attributes"];
             while ([rs next]) {
                 count = [rs intForColumn:@"Count"];
             }
         }];
         
         return count;
     }
    

    I want to query the database to get find out how many items are in my database. FMDB lets me provide a block for what I want to happen with the database (db) whose access is managed by the queue (self.queue). My count variable is in scope for the block to read, however I want to modify the value of count and return it outside the block. In order for changes to count to be available, I have to mark it with the __block storage type modifier.

  4. Avoid retain issues

    I love to use property within a class so that I know that I'm using accessors and not touching the ivar directly, so my code is littered with self.thisProperty and self.thatProperty. However, in order for self.thisProperty accessor to be triggered from within the block, self would have to be retained by the block, causing an unbalanced retain which will turn into a memory leak! Solution? Use a local variable.

     - (NSArray *)locationsForCityOrderByName:(NSString *)city {
     
         __block NSMutableArray *locations = [[NSMutableArray alloc] init];
         
         NSString *searchString = self.searchString;
     
         [self.queue inDatabase:^(FMDatabase *db) {
             FMResultSet *rs = nil;
             rs = [db executeQuery:@"SELECT location_attributes.* FROM location_attributes, location_fts \
                   WHERE location_attributes.city=? \
                   AND location_fts.id=location_attributes.id \
                   AND location_fts.name_type_address LIKE ? \
                   ORDER BY location_attributes.locationName", 
                   city, 
                   [NSString stringWithFormat:@"%%%@%%%", searchString]];
                   
             while ([rs next]) {
                 
                 RPLocation *location = [RPLocation locationFromDictionary:[rs resultDict]];
                 if (!location) {
                     continue;
                 }
                 [locations addObject:location];
             }
         }];
         return locations;    
     }
    
  5. Summary

    A block is an anonymous inline collection of code that:

    • Has a typed argument list just like a function
    • Has an inferred or declared return type
    • Can capture state from the lexical scope within which it is defined
    • Can optionally modify the state of the lexical scope (via __block)
    • Can share the potential for modification with other blocks defined within the same lexical scope
    • Can continue to share and modify state defined within the lexical scope (the stack frame) after the lexical scope (the stack frame) has been destroyed

    Because blocks are portable and anonymous objects encapsulating a unit of work that can (often) be performed asynchronously, they are a central feature of Grand Central Dispatch

References:

GCD

GCD - is essentially a pool of threads available to perform work units, governed by a first-in, first-out queue (dispatch queue). Dispatch

  1. Protecting access (using a queue)

Use a single queue and dispatch_sync() instead of a lock or other mechanism to protect a critical section.

FMDB has its own serial queue and uses dispatch_sync() to make sure that multiple threads can safely use the same database, and the queue ensures that only one thread's operation happens at a time.

// from FMDatabaseQueue.m
- (void)inDatabase:(void (^)(FMDatabase *db))block {
    FMDBRetain(self);
    
    dispatch_sync(_queue, ^() {
        
        FMDatabase *db = [self database];
        block(db);
        
        if ([db hasOpenResultSets]) {
            NSLog(@"Warning: there is at least one open result set around after performing [FMDatabaseQueue inDatabase:]");
        }
    });
    
    FMDBRelease(self);
}

I have a project that downloads a new JSON dataset, converts it into a database and then when the database is up to date, I want to use it without relaunching the app. How do I do this safely? I leveraged the queue!

// added by DWA to safely swap database used by queue at runtime
- (NSError *)switchToDatabaseWithPath:(NSString *)aPath {
    // use the queue to ensure that we're not accessing the db as we switch
    __block NSError *error = 0x00;
    
    dispatch_sync(_queue, ^() { 
        
        [_db close]; // close the old database
        FMDBRelease(_db); // release it if not ARC
        _db = 0x00; // set it to nil (FMDB convention)
        
        // delete old database and copy the new one so it has the same name
        if([[NSFileManager defaultManager] removeItemAtPath:_path error:&error] &&
           [[NSFileManager defaultManager] copyItemAtPath:aPath
                                                   toPath:_path
                                                    error:&error]){
               NSLog(@"%@ successfully copied to %@", aPath, _path);
           } else {
               NSLog(@"Error description-%@ \n", [error localizedDescription]);
               NSLog(@"Error reason-%@", [error localizedFailureReason]);
           }
        
        _db = [FMDatabase databaseWithPath:_path]; // open my new database
        FMDBRetain(_db); // retain it if not ARC
        
        if (![_db open]) {
            NSLog(@"Could not create database queue for path %@", aPath);
            FMDBRelease(self);
            NSLog(@"Warning: failed to switch databases [FMDatabaseQueue switchToDatabaseWithPath:]");
            error = [NSError errorWithDomain:@"FMDatabaseQueue" code:-1L userInfo:nil];
        }
    }); // end critical section!  All further queries using this queue will use the new database.
    
    return error;
}
  1. Fire and forget

Use dispatch_async() to fire off an operation to complete in the background.

- (void)loadDatabaseWithJSONFile:(NSString *)JSONFilePath {

    dispatch_queue_t q = dispatch_queue_create("com.robotsandpencils.locationApp.loadDatabaseQueue", NULL);

    FMDatabaseQueue *localQueue = self.queue; // use FMDB queue in the block but avoid using self

    dispatch_async(q, ^{
        // All this will happen on a background thread
		
        NSError *jsonError = nil;
        NSData *jsonData = [NSData dataWithContentsOfFile:JSONFilePath 
                                                  options:NSDataReadingMappedAlways 
                                                    error:&jsonError];
        id json = [NSJSONSerialization JSONObjectWithData:jsonData 
                                                  options:NSJSONReadingMutableContainers 
                                                    error:&jsonError];
        	        
		// error and version checking have been removed

        NSString *documentsDirectory = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
        NSString *databaseFilePath = [documentsDirectory stringByAppendingPathComponent:@"temp.db"];
        
        // create a new database and setup the tables and columns
        FMDatabase *db = [RPDatabaseHelper databaseWithPath:databaseFilePath]; 
	
	    // the new location information
        NSArray *locations = [json valueForKey:kDataLocationsKey]; 

        int pk = 0;
        if ([locations count] > 0) { // have locations to add to the database
	
            [db beginTransaction];
            NSLog(@"Begin loading database");
            for (NSArray *location in locations) {

                [RPDatabaseHelper insertLocation:location forIndex:pk intoDatabase:db];
                pk += 1;
            }

            [db commit];
            NSLog(@"End loading database");
            
            // can now call [localQueue switchToDatabaseWithPath:databaseFilePath]; to start using the new database

        }
        
    });
    dispatch_release(q); // dispatch_queues aren't memory managed by ARC
}
  1. Asynchronous operations

use nested dispatch_async() to fire off an operation and provide a block that will get called back on the main queue (main thread) to update the interface, for example.

- (IBAction)showRenderedImage:(id)sender {
	dispatch_queue_t q = dispatch_queue_create("com.robotsandpencils.locationApp.renderImageQueue", NULL);
    
    NSString *imageKey = self.imageKey; // avoid using self in the block
    UIImageView *imageView = self.imageView;
    
	// do my rendering on a different thread
    dispatch_async(q, ^{
        NSImageRep *image = [RPImageRenderer renderImageForKey:imageKey]; // computationally intensive
        // have image, need to display it using the main thread
        dispatch_async(dispatch_get_main_queue()), ^{
            
            // do something with the image on the main thread
            [imageView setImage:image];
            
        });
    });
    dispatch_release(q);
}

That's great, but I really want to reuse this asynchronous renderer for more than one image.

- (void)imageForKey:(NSString *)imageKey completion:(void (^)(UIImage *image))completion {

	dispatch_queue_t q = dispatch_queue_create("com.robotsandpencils.locationApp.renderImageQueue", NULL);
    
	// do my rendering on a different thread
    dispatch_async(q, ^{
        NSImageRep *image = [RPImageRenderer renderImageForKey:imageKey]; // computationally intensive
        // have image, need to display it using the main thread
        dispatch_async(dispatch_get_main_queue()), ^{
            completion(image);
        });
    });
    dispatch_release(q);
}

- (IBAction)showRenderedBeehiveImage:(id)sender {
    UIImageView *imageView = self.beehiveImageView;
    [self imageForKey:@"beehive" completion:^(NSImageRep *image) {
        [imageView setImage:image];        
    }];
}

- (IBAction)showRenderedHoneyImage:(id)sender {
    UIImageView *imageView = self.honeyImageView;
    [self imageForKey:@"honey" completion:^(NSImageRep *image) {
        [imageView setImage:image];        
    }];
}
  1. Avoiding the watchdog in - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions

iOS has a watchdog timer that will kill apps that take to long to complete an operation. Its handy to put lots of calls in didFinishLaunchingWithOptions: for things you need to do when the app launches. But if you take too long, not only will you annoy your users by wasting their time, you risk having the watchdog terminate your app!

The app that inspired the snippets above has a bunch of content, images, and info that needs to be updated. If all the downloading, database creating and other oprations happened sequentially, I'd surely trip the watchdog and get the app killed.

Minimize what happens in the main thread in this method by initiating operations and use GCD so that they complete on a background thread and didFinishLaunchingWithOptions: can end quickly.

This can include

  • configuring and setting up analytics
  • downloading updates and content
  • loading user data

Your app will launch faster and your users will appreciate it.

sample case/app in practice if I have time - which I don't

References:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment