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
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.
Synthesize this new property in the implementation file.
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
.
In
DetailViewController.m
, update
imagePickerController:didFinishPickingMediaWithInfo:
to generate a UUID when a new picture is taken.
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:
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:
.
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
.
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.
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.
Here are the memory management rules when it comes to Core Foundation objects.
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:
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.
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.
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.
Build and run the application again. The behavior should remain the same, but the memory benefits are significant.