iOS Programming: The Big Nerd Ranch Guide, 3/e (Big Nerd Ranch Guides) (46 page)

Read iOS Programming: The Big Nerd Ranch Guide, 3/e (Big Nerd Ranch Guides) Online

Authors: Aaron Hillegass,Joe Conway

Tags: #COM051370, #Big Nerd Ranch Guides, #iPhone / iPad Programming

BOOK: iOS Programming: The Big Nerd Ranch Guide, 3/e (Big Nerd Ranch Guides)
4.41Mb size Format: txt, pdf, ePub
Creating and using keys

When an image is added to the store, it will be put into a dictionary under a unique key, and the associated
BNRItem
object will be given that key. When the
DetailViewController
wants an image from the store, it will ask its
item
for the key and search the dictionary for the image. Add a property to
BNRItem.h
to store the key.

 
@property (nonatomic, readonly, strong) NSDate *dateCreated;
@property (nonatomic, copy) NSString *imageKey;
 

Synthesize this new property in the implementation file.

 
@implementation BNRItem
@synthesize imageKey;
 

The image keys need to be unique in order for your dictionary to work. While there are many ways to hack together a unique string, we’re going to use the Cocoa Touch mechanism for creating universally unique identifiers (UUIDs), also known as globally unique identifiers (GUIDs). Objects of type
CFUUIDRef
represent a UUID and are generated using the time, a counter, and a hardware identifier, which is usually the MAC address of the ethernet card.

 

Import
BNRImageStore.h
at the top of
DetailViewController.m
.

 
#import "DetailViewController.h"
#import "BNRItem.h"
#import "BNRImageStore.h"
 

In
DetailViewController.m
, update
imagePickerController:didFinishPickingMediaWithInfo:
to generate a UUID when a new picture is taken.

 
- (void)imagePickerController:(UIImagePickerController *)picker
        didFinishPickingMediaWithInfo:(NSDictionary *)info
{
    UIImage *image = [info objectForKey:UIImagePickerControllerOriginalImage];
    
// Create a CFUUID object - it knows how to create unique identifier strings
    CFUUIDRef newUniqueID = CFUUIDCreate(kCFAllocatorDefault);
 

The prefix
CF
means
CFUUIDRef
comes from the Core Foundation framework (and remember that the
Ref
suffix means that it is a pointer). Core Foundation is a collection of C

classes

and functions. Core Foundation objects are created by calling a function that begins with the type of object being created and contains the word
Create
(
CFUUIDCreate
). When creating a Core Foundation object, the first argument specifies how memory is allocated. In practice, you pass
kCFAllocatorDefault
and let the system make that choice.

 

Once created, a
CFUUIDRef
is just an array of bytes and, if represented as a string, will look something like this:

 
28153B74-4D6A-12F6-9D61-155EA4C32167
 

This UUID will be used in two ways: it will be the key in the
BNRImageStore
’s dictionary and in a later chapter, it will be the name of the image file on the filesystem. Because keys in a dictionary and paths on the filesystem are typically strings, we want to represent the UUID as a string instead of an array of bytes.

 

You can create a string object from a
CFUUIDRef
by calling the C function
CFUUIDCreateString
. In
DetailViewController.m
, add the following line of code in
imagePickerController:didFinishPickingMediaWithInfo:
.

 
- (void)imagePickerController:(UIImagePickerController *)picker
        didFinishPickingMediaWithInfo:(NSDictionary *)info
{
    UIImage *image = [info objectForKey:UIImagePickerControllerOriginalImage];
    CFUUIDRef newUniqueID = CFUUIDCreate (kCFAllocatorDefault);
    // Create a string from unique identifier
    CFStringRef newUniqueIDString =
            CFUUIDCreateString (kCFAllocatorDefault, newUniqueID);

Notice that
newUniqueIDString
’s type is
CFStringRef
. The
imageKey
property of
BNRItem
is an
NSString
. Clearly, you need some way to move between
CFStringRef
and
NSString
to set the
imageKey
property.

 

Fortunately, many classes in Core Foundation are
toll-free bridged
with their Objective-C counterpart. For example,
CFStringRef
is toll-free bridged with
NSString
;
CFArrayRef
with
NSArray
. Instances of classes that are toll-free bridged look exactly the same as their counterpart in memory. Therefore, you can use a simple C-style typecast to treat a toll-free bridged Core Foundation object as an Objective-C object.

 

Typecast
newUniqueIDString
and set it as the
imageKey
of the selected
BNRItem
in
imagePickerController:didFinishPickingMediaWithInfo:
. Also, place this image in the
BNRImageStore
.

 
- (void)imagePickerController:(UIImagePickerController *)picker
        didFinishPickingMediaWithInfo:(NSDictionary *)info
{
    UIImage *image = [info objectForKey:UIImagePickerControllerOriginalImage];
    CFUUIDRef newUniqueID = CFUUIDCreate (kCFAllocatorDefault);
    CFStringRef newUniqueIDString =
            CFUUIDCreateString (kCFAllocatorDefault, newUniqueID);
    
// Use that unique ID to set our item's imageKey
    NSString *key = (__bridge NSString *)newUniqueIDString;
    [item setImageKey:key];
    // Store image in the BNRImageStore with this key
    [[BNRImageStore sharedStore] setImage:image
                                         forKey:[item imageKey]];
 

Notice the use of
__bridge
in this typecast. To understand what this keyword does, you must understand how memory is managed for Core Foundation objects.

 
Core Foundation and toll-free bridging

When a variable that points to an Objective-C object is destroyed, ARC knows that object has lost an owner. ARC doesn’t do this with Core Foundation objects. Thus, when a Core Foundation object loses a pointer, you must call a function that tells the object to lose an owner before you lose the pointer. This function is
CFRelease
.

 

If you do not call
CFRelease
before losing a pointer, the pointed-to object still thinks it has an owner. Losing a pointer to an object before telling it to lose an owner results in a memory leak: you can no longer access that object, and it still has an owner. Add code to
imagePickerController:didFinishPickingMediaWithInfo:
to tell the objects pointed to by
newUniqueIDString
and
newUniqueID
to lose an owner since these are both local variables that will be destroyed when this method ends.

 
    [[BNRImageStore sharedStore] setImage:image
                                         forKey:[item imageKey]];
    CFRelease(newUniqueIDString);
    CFRelease(newUniqueID);
    [imageView setImage:image];
    [self dismissViewControllerAnimated:YES completion:nil];
}
 

Here are the memory management rules when it comes to Core Foundation objects.

 
  • A variable only owns the object it points to if the function that created the object has the word
    Create
    or
    Copy
    in it.
 
  • If a pointer owns a Core Foundation object, you must call
    CFRelease
    before you lose that pointer. Remember that a pointer can be lost if it is set to point at something else (including
    nil
    ) or if the pointer itself is being destroyed.
 
  • Once you call
    CFRelease
    on a pointer, you cannot access that pointer again.
 

As you can see, the rules of memory management are a bit more complicated when dealing with Core Foundation because you don’t have the luxury of ARC. However, you typically won’t use Core Foundation objects as much as Objective-C objects. As long as you stick to these rules, you will be okay.

 

Now, back to the
__bridge
keyword. ARC doesn’t know how to manage memory with Core Foundation objects very well, so it gets confused if you typecast a Core Foundation pointer into its Objective-C counterpart. Placing
__bridge
in front of the cast tells ARC,

Hey, don’t even worry about it.

Thus, when ARC sees this line of code, it doesn’t give ownership to the
key
variable as it normally would:

 
NSString *key = (__bridge NSString *)newUniqueIDString;
 

Once
key
is an Objective-C pointer, ARC can do its work as normal. When this object is passed to
setImageKey:
,
BNRItem
’s
imageKey
instance variable takes ownership of that object.

 
Wrapping up BNRImageStore

Now that the
BNRImageStore
can store images and
BNRItem
s have a key to get that image (
Figure 12.11
), we need to teach
DetailViewController
how to grab the image for the selected
BNRItem
and place it in its
imageView
.

 

Figure 12.11  Cache

 

The
DetailViewController
’s
view
will appear at two times: when the user taps a row in
ItemsViewController
and when the
UIImagePickerController
is dismissed. In both of these situations, the
imageView
should be populated with the image of the
BNRItem
being displayed. In
DetailViewController.m
, add code to
viewWillAppear:
to do this.

 
- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];
    [nameField setText:[item itemName]];
    [serialNumberField setText:[item serialNumber]];
    [valueField setText:[NSString stringWithFormat:@"%d",
                                    [item valueInDollars]]];
    NSDateFormatter *dateFormatter = [[[NSDateFormatter alloc] init]
                                            autorelease];
    [dateFormatter setDateStyle:NSDateFormatterMediumStyle];
    [dateFormatter setTimeStyle:NSDateFormatterNoStyle];
    [dateLabel setText:
            [dateFormatter stringFromDate:[item dateCreated]]];
    NSString *imageKey = [item imageKey];
    if (imageKey) {
        // Get image for image key from image store
        UIImage *imageToDisplay =
                [[BNRImageStore sharedStore] imageForKey:imageKey];
        // Use that image to put on the screen in imageView
        [imageView setImage:imageToDisplay];
    } else {
        // Clear the imageView
        [imageView setImage:nil];
    }
}

Notice that if no image exists in the image store for that key (or there is no key for that item), the pointer to the image will be
nil
. When the image is
nil
, the
UIImageView
just won’t display an image.

 

Build and run the application. Create a
BNRItem
and select it from the
UITableView
. Then, tap the camera button and take a picture. The image will appear as it should.

 

There is another detail to take care of: if you select a new image for a
BNRItem
, the old one will still be in the
BNRImageStore
. At the top of
imagePickerController:didFinishPickingMediaWithInfo:
in
DetailViewController.m
, add some code to tell the
BNRImageStore
to remove the old image.

 
- (void)imagePickerController:(UIImagePickerController *)picker
        didFinishPickingMediaWithInfo:(NSDictionary *)info
{
    NSString *oldKey = [item imageKey];
    // Did the item already have an image?
    if (oldKey) {
        // Delete the old image
        [[BNRImageStore sharedStore] deleteImageForKey:oldKey];
    }
    UIImage *image = [info objectForKey:UIImagePickerControllerOriginalImage];

Build and run the application again. The behavior should remain the same, but the memory benefits are significant.

 

Other books

Winter Kills by Richard Condon
A Story Lately Told by Anjelica Huston
There All Along by Dane, Lauren, Hart, Megan
Wrong Girl by Lauren Crossley
One Foot in the Grave by Peter Dickinson
Discovering by Wendy Corsi Staub