How To Use Blocks in iOS 5 Tutorial – Part 2

This is a blog post by iOS Tutorial Team member Adam Burkepile, a full-time Software Consultant and independent iOS developer. Check out his latest app Pocket No Agenda, or follow him on Twitter. Welcome back to our tutorial series on using blocks in iOS 5 – with some Storyboard/Interface Builder practice along the way! In […] By .

Leave a rating/review
Save for later
Share
You are currently viewing page 2 of 6 of this article. Click here to view the first page.

Back to the iOS Diner: Setting Up Model Classes

You'll be picking up right where you left off in Part One. If you didn't do Part One or just need a fresh start, you can download the project in its current state here.

Open the project in Xcode and switch to the Project Navigator. Right-click on the iOSDiner folder and select New Group. Name it “Models.”

Right-click on the new Models folder and click New File. Select Objective-C Class. Name the class “IODItem” and make it a subclass of NSObject.

For the file location, select the iOSDiner folder, and select New Folder to create a matching Models folder in the file system. Make sure the newly created Models folder is selected and then hit Create. This will create .h and .m files for the IODItem class.

Repeat this file creation process for a class called IODOrder. Right-click on the Models folder and click New File. Select Objective-C Class. Name the class “IODOrder” and make it a subclass of NSObject.

Make sure the Models folder is selected and then click Create.

Now you have all the classes and files you need set up. Time to get coding!

Setting Up Basic Properties in IODItem

Select IODItem.h. The first thing you're going to do is add the NSCopying protocol to the class.

Protocols are a way of setting up a contract of the methods the class will implement. Basically, if a class implements a certain protocol, then the class would need to implement certain methods that are required or optional, as defined for that particular protocol. To implement the NSCopying protocol, change IODItem.h to look like this:

@interface IODItem : NSObject <NSCopying>

Next, add the properties for the item's attributes. An item will have a name/title, price, and an image file. Add the following properties underneath the previous line. The complete .h file should look like this now:

#import <Foundation/Foundation.h>

@interface IODItem : NSObject <NSCopying>

@property (nonatomic,strong) NSString* name;
@property (nonatomic,assign) float price;
@property (nonatomic,strong) NSString* pictureFile;

@end

Now switch over to IODItem.m and add the synthesizers for the properties by adding the following below the @implementation IODItem line:

@synthesize name;
@synthesize price;
@synthesize pictureFile;

If you build the code right now and look at it, you'll see a warning.

This warning is a reference to the NSCopying protocol that you added above. Remember how I said the protocol might define required methods? Well, the NSCopying protocol requires that -(id)copyWithZone:(NSZone *)zone be implemented. Since you haven't done that, the class is incomplete – hence the warning!

Add the following method to the end of IODItem.m (before the @end):

-(id)copyWithZone:(NSZone *)zone {
    IODItem* newItem = [IODItem new];
    [newItem setName:[self name]];
    [newItem setPrice:[self price]];
    [newItem setPictureFile:[self pictureFile]];

    return newItem;
}

Poof! No more warning!

All the code does is to create a new IODItem, set the properties to be exactly the same as the existing object, and return the new object instance.

You're also going to set up an initializer method. This is just a quick way to set up the default properties when you initialize an instance. Add the following method to the end of IODItem.m:

- (id)initWithName:(NSString*)inName andPrice:(float)inPrice andPictureFile:(NSString*)inPictureFile {
    if (self = [self init]) {
        [self setName:inName];
        [self setPrice:inPrice];
        [self setPictureFile:inPictureFile];
    }

    return self;
}

Switch back to IODItem.h and add the prototype for the above method to the end of the file (but above the @end):

- (id)initWithName:(NSString*)inName andPrice:(float)inPrice andPictureFile:(NSString*)inPictureFile;

Setting Up Basic Properties in IODOrder

Next, we are going to work on the other class, IODOrder. This class will represent the order and the operations that go along with an order: adding an item, removing an item, calculating the total for the order, and printing out an overview of the order.

Switch to IODOrder.h and before the @interface section, add the following to let the IODOrder class know there is such a class as IODItem.

@class IODItem;

Inside the @interface section, add the following property:

@property (nonatomic,strong) NSMutableDictionary* orderItems;

This is the dictionary that will hold the items ordered by the user. Switch to IODOrder.m and import the IODItem class header at the top of the file:

#import "IODItem.h"

Next synthesise properties below the @implementation IODOrder line:

@synthesize orderItems;

Setting Up Basic Properties in IODViewController

Switch to IODViewController.h to add an instance variable and two properties. Replace the existing "@interface IODViewController : UIViewController" line with the following:

@class IODOrder;

@interface IODViewController : UIViewController {
    int currentItemIndex;
}

@property (strong, nonatomic) NSMutableArray* inventory;
@property (strong, nonatomic) IODOrder* order;

The currentItemIndex variable will keep track of which item the user is currently browsing in the inventory. Inventory is pretty self-explanatory; it's an array of the IODItems that we got from the web service. Order is an instance of the IODOrder class that stores the items currently in the user's order.

Switch to the IODViewController.m and do the following:

  1. Add the imports for IODItem and IODOrder
  2. Add the @synthesize for the inventory and order properties
  3. Initialize currentItemIndex to 0 during viewDidLoad
  4. Set the order property to a new IODOrder instance

When you're done, it should look like this:

#import "IODViewController.h"
#import "IODItem.h"      // <---- #1
#import "IODOrder.h"     // <---- #1

@implementation IODViewController
// ... Other synthesize statements ...

@synthesize inventory;     // <---- #2
@synthesize order;         // <---- #2

// ... didReceiveMemoryWarning - not relevant to discussion ...

#pragma mark - View lifecycle

- (void)viewDidLoad
{
    [super viewDidLoad];

    currentItemIndex = 0;            // <---- #3
    self.order = [IODOrder new];     // <---- #4
}

Give it a build. Everything should run smoothly, with no warnings.

Loading the Inventory

The method retrieveInventoryItems, which you will add shortly, will download and process the inventory items from the web service. This is a class method, not an instance method.

Note: A class method is indicated by the + sign at the beginning of the method definition. An instance method is indicated by a - sign.

Add the following line to the top of IODItem.m, just below the #imports:

#define kInventoryAddress @"http://adamburkepile.com/inventory/"

Note: If you are hosting the web service yourself, change the URL in the above line to point to your endpoint.

Add the following method above the @end in IODItem.m:

+ (NSArray*)retrieveInventoryItems {
    // 1 - Create variables
    NSMutableArray* inventory = [NSMutableArray new];
    NSError* err = nil;
    // 2 - Get inventory data
    NSArray* jsonInventory = [NSJSONSerialization JSONObjectWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:kInventoryAddress]] 
                                                         options:kNilOptions 
                                                           error:&err];
    // 3 - Enumerate inventory objects
    [jsonInventory enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
        NSDictionary* item = obj;
        [inventory addObject:[[IODItem alloc] initWithName:[item objectForKey:@"Name"] 
                                                  andPrice:[[item objectForKey:@"Price"] floatValue]
                                            andPictureFile:[item objectForKey:@"Image"]]];
    }];
    // 4 - Return a copy of the inventory data
    return [inventory copy];
}

Hey, your first Block! Let's take a closer look at this code to see exactly what it's doing:

  1. First, we define the array that will hold the return objects and an error pointer.
  2. We use a regular NSData object to download the data from the web service, and then pass that NSData object into the iOS's new JSON data service to decode the raw data into Objective-C object types (NSArrays, NSDictionaries, NSStrings, NSNumbers, etc).
  3. Next, we use the enumerateObjectsUsingBlock: method that we discussed earlier to convert the objects from regular NSDictionaries to IODItem class objects. We call the enumerateObjectsUsingBlock: method on the jsonInventory array and enumerate over it with a Block that casts the object passed to the Block as an NSDictionary object, uses that dictionary object to create a new IODItem, and finally adds that new item to the return inventory array.
  4. Finally, we return the inventory array. Note that we return a copy of the array instead of returning it directly, because we don't want to return a mutable version. The copy method creates an immutable version you can safely return.

Now switch back to the IODItem.h and add the prototype for the method:

+ (NSArray*)retrieveInventoryItems;