显示标签为“Tutorial”的博文。显示所有博文
显示标签为“Tutorial”的博文。显示所有博文

2012年1月4日星期三

How To Create a PDF with Quartz 2D in iOS 5 Tutorial Part 1

How To Create a PDF with Quartz 2D in iOS 5 Tutorial Part 1:
This is a blog post by iOS Tutorial Team member Tope Abayomi, an iOS developer and Founder of App Design Vault, your source for iPhone App Design.

Learn how to create a PDF programmatically!
Learn how to create a PDF programmatically!

Sometimes in your apps you might want to generate a PDF with data from the app for your users. For example, imagine you had an app that allowed users to sign a contract – you would want the users to be able to get a PDF with the final result.

But how do you generate a PDF programmatically? Well, it’s easy to do in iOS with the help of Quartz2D!

In this tutorial series, you’ll get hands-on experience with creating a simple PDF with Quartz2D. The PDF we’ll make will be for an invoice-making app, as you can see in the screenshot.

This tutorial assumes you are familiar with the basic new features in iOS 5 such as Storyboards and ARC. If you are new to iOS 5, check out some of the other iOS 5 tutorials on this site first.



Getting Started


Run Xcode and create a new project with the iOS\Application\Single View Application template. Enter PDFRenderer for the project name, choose iPhone for the Device Family, make sure Use Storyboard and Use Automatic Reference Counting are checked, and finish creating the project.



We are going to use two screens in this project. The first will simply have a button that will show the PDF when tapped. The second screen will be the PDF itself.

Select the MainStoryboard.storyboard file. In the main window, you will see a View Controller. We need this View Controller to be embedded in a Navigation Controller to start with, so click on the Editor Menu, then select Embed In/Navigation Controller.



The View Controller now has a segue from the Navigation Controller.



Now drag a UIButton from the objects tab to the View Controller, and rename the label “Draw PDF.”



If you run the application, you should see a simple View with a button displayed that says “Draw PDF,” but does nothing when tapped. We’ll take care of that shortly.

Now let’s add the second View that will hold the PDF.

Drag a new View Controller from the objects tab onto the Storyboard. Ctrl+Drag the “Draw PDF” button onto the new View Controller. When you release the mouse, you should see a popup similar to the one in the image below.



Select the Push option. This will create a segue onto the new View Controller so that it is displayed when the button is tapped. In other words, our button is now functional!

Run the application, tap the button, and you should see an empty View Controller pushed onto the screen. Storyboards rule!

Creating the PDF and Drawing Text


Now that we have the framework for our PDF, we’re ready to write some code.

Before we do that, select File\New\New File to add a new file to the project. Choose the iOS\Cocoa Touch\UIViewController subclass template, enter PDFViewController for the Class and UIViewController for the Subclass, make sure “With XIB for user interface” is NOT checked, and finish creating the file. We do not need a nib because we will use the View Controller created in the storyboard.

Connect the last View Controller we created to this new file by selecting the View Controller on the Storyboard and changing the class to PDFViewController in the identity inspector.



To draw some text in our PDF, we’re going to need to use the Core Text framework. To do this, select the PDFRenderer target and go to the Build Phases tab. Click the + sign below the Link Binaries With Libraries option, and then select the CoreText framework.



Then open PDFViewController.h and import the CoreText header:


#import <CoreText/CoreText.h>


OK time for the code! Add a new method to PDFViewController.m to create a “hello world” PDF. This is a long method, but don’t worry – we’ll explain it bit by bit afterwards.


-(void)drawText
{
NSString* fileName = @"Invoice.PDF";

NSArray *arrayPaths =
NSSearchPathForDirectoriesInDomains(
NSDocumentDirectory,
NSUserDomainMask,
YES);
NSString *path = [arrayPaths objectAtIndex:0];
NSString* pdfFileName = [path stringByAppendingPathComponent:fileName];

NSString* textToDraw = @"Hello World";
CFStringRef stringRef = (__bridge CFStringRef)textToDraw;

// Prepare the text using a Core Text Framesetter.
CFAttributedStringRef currentText = CFAttributedStringCreate(NULL, stringRef, NULL);
CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString(currentText);

CGRect frameRect = CGRectMake(0, 0, 300, 50);
CGMutablePathRef framePath = CGPathCreateMutable();
CGPathAddRect(framePath, NULL, frameRect);

// Get the frame that will do the rendering.
CFRange currentRange = CFRangeMake(0, 0);
CTFrameRef frameRef = CTFramesetterCreateFrame(framesetter, currentRange, framePath, NULL);
CGPathRelease(framePath);

// Create the PDF context using the default page size of 612 x 792.
UIGraphicsBeginPDFContextToFile(pdfFileName, CGRectZero, nil);

// Mark the beginning of a new page.
UIGraphicsBeginPDFPageWithInfo(CGRectMake(0, 0, 612, 792), nil);

// Get the graphics context.
CGContextRef currentContext = UIGraphicsGetCurrentContext();

// Put the text matrix into a known state. This ensures
// that no old scaling factors are left in place.
CGContextSetTextMatrix(currentContext, CGAffineTransformIdentity);

// Core Text draws from the bottom-left corner up, so flip
// the current transform prior to drawing.
CGContextTranslateCTM(currentContext, 0, 100);
CGContextScaleCTM(currentContext, 1.0, -1.0);

// Draw the frame.
CTFrameDraw(frameRef, currentContext);

CFRelease(frameRef);
CFRelease(stringRef);
CFRelease(framesetter);

// Close the PDF context and write the contents out.
UIGraphicsEndPDFContext();

}


The first six lines create a PDF filename for a file that will reside in the Documents folder. The file will be called Invoice.pdf.


NSString* fileName = @"Invoice.PDF";

NSArray *arrayPaths =
NSSearchPathForDirectoriesInDomains(
NSDocumentDirectory,
NSUserDomainMask,
YES);
NSString *path = [arrayPaths objectAtIndex:0];
NSString* pdfFileName = [path stringByAppendingPathComponent:fileName];


The next block of code creates a “Hello world” string that we will draw onto the PDF. It also converts the string to its CoreGraphics counterpart, CFStringRef.


NSString* textToDraw = @"Hello World";
CFStringRef stringRef = (__bridge CFStringRef)textToDraw;

// Prepare the text using a Core Text Framesetter.
CFAttributedStringRef currentText = CFAttributedStringCreate(NULL, stringRef, NULL);
CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString(currentText);


Now we create a CGRect that defines the frame where the text will be drawn.


CGRect frameRect = CGRectMake(0, 0, 300, 50);
CGMutablePathRef framePath = CGPathCreateMutable();
CGPathAddRect(framePath, NULL, frameRect);

// Get the frame that will do the rendering.
CFRange currentRange = CFRangeMake(0, 0);
CTFrameRef frameRef = CTFramesetterCreateFrame(framesetter, currentRange, framePath, NULL);
CGPathRelease(framePath);


Next we create a PDF context and mark the beginning of a PDF page. Each page of the PDF has to start with a call to UIGraphicsBeginPDFPageWithInfo.


// Create the PDF context using the default page size of 612 x 792.
UIGraphicsBeginPDFContextToFile(pdfFileName, CGRectZero, nil);

// Mark the beginning of a new page.
UIGraphicsBeginPDFPageWithInfo(CGRectMake(0, 0, 612, 792), nil);

// Get the graphics context.
CGContextRef currentContext = UIGraphicsGetCurrentContext();


The coordinates of Core Graphics drawings start from the bottom-left corner, while UIKit global coordinates start from the top-left. We need to flip the context before we begin drawing.


// Put the text matrix into a known state. This ensures
// that no old scaling factors are left in place.
CGContextSetTextMatrix(currentContext, CGAffineTransformIdentity);

// Core Text draws from the bottom-left corner up, so flip
// the current transform prior to drawing.
CGContextTranslateCTM(currentContext, 0, 100);
CGContextScaleCTM(currentContext, 1.0, -1.0);


Then we draw the actual frame with the text, release all the Core Graphics objects, and close the PDF context (hence writing the PDF to disk).


// Draw the frame.
CTFrameDraw(frameRef, currentContext);

CFRelease(frameRef);
CFRelease(stringRef);
CFRelease(framesetter);

// Close the PDF context and write the contents out.
UIGraphicsEndPDFContext();


If you are interested in learning more about how Core Text works and some more cool things you can do with it, check out our Core Text tutorial.

Add a UIWebView to Show the PDF File


The only thing left to do is to show our PDF file on the screen. To do that, add the following method to PDFViewController.m, which adds a UIWebView to the View Controller and shows the PDF file path we just created.


-(void)showPDFFile
{
NSString* fileName = @"Invoice.PDF";

NSArray *arrayPaths =
NSSearchPathForDirectoriesInDomains(
NSDocumentDirectory,
NSUserDomainMask,
YES);
NSString *path = [arrayPaths objectAtIndex:0];
NSString* pdfFileName = [path stringByAppendingPathComponent:fileName];

UIWebView* webView = [[UIWebView alloc] initWithFrame:CGRectMake(0, 0, 320, 480)];

NSURL *url = [NSURL fileURLWithPath:pdfFileName];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
[webView setScalesPageToFit:YES];
[webView loadRequest:request];

[self.view addSubview:webView];   
}


Then add the implementation of viewDidLoad to call these new methods:


- (void)viewDidLoad
{
[self drawText];
[self showPDFFile];

[super viewDidLoad];
}


Now we’re ready to see some results! Build and run the project, and you should see “Hello World” on the screen when you zoom in!



A Quick Refactoring Process


Our drawing code does not really belong in the View Controller, so let’s farm that off into a new NSObject called PDFRenderer. Create a new file with the iOS\Cocoa Touch\Objective-C class template, enter PDFRenderer for the Class and NSObject for the subclass, and finish creating the file.

Open up PDFRenderer.h and import Core Text at the top of the file:


#import


Then move the drawText method from PDFViewController.m to PDFRenderer.m.

We will pass the filename into the new drawText method, so let’s create a new method in the PDFViewController.m file called getPDFFileName.


-(NSString*)getPDFFileName
{
NSString* fileName = @"Invoice.PDF";

NSArray *arrayPaths =
NSSearchPathForDirectoriesInDomains(
NSDocumentDirectory,
NSUserDomainMask,
YES);
NSString *path = [arrayPaths objectAtIndex:0];
NSString* pdfFileName = [path stringByAppendingPathComponent:fileName];

return pdfFileName;

}


Next, open PDFRenderer.m and remove this same block of code from the drawText method, and modify the method signature to take the filename as a parameter and make it a static method:


+(void)drawText:(NSString*)pdfFileName


Also predeclare this method in PDFRenderer.h.

Next, import PDFRenderer at the top of PDFViewController.m:


#import "PDFRenderer.h"


And modify viewDidLoad to call this new class and method:


- (void)viewDidLoad
{
NSString* fileName = [self getPDFFileName];

[PDFRenderer drawText:fileName];
[self showPDFFile];

[super viewDidLoad];
}


Build and run the project. Our quick refactoring shouldn’t have caused the application to behave any differently, but the code is better organized.

Drawing a Line Using Quartz 2D


The invoice we want to end up with is made up of text, lines and images. We’ve got text — now it’s time to practice drawing a line. To do that, we will use… wait for it… the drawLine method!

Add this new method to PDFRenderer.m:


+(void)drawLineFromPoint:(CGPoint)from toPoint:(CGPoint)to
{
CGContextRef context = UIGraphicsGetCurrentContext();

CGContextSetLineWidth(context, 2.0);

CGColorSpaceRef colorspace = CGColorSpaceCreateDeviceRGB();

CGFloat components[] = {0.2, 0.2, 0.2, 0.3};

CGColorRef color = CGColorCreate(colorspace, components);

CGContextSetStrokeColorWithColor(context, color);


CGContextMoveToPoint(context, from.x, from.y);
CGContextAddLineToPoint(context, to.x, to.y);

CGContextStrokePath(context);
CGColorSpaceRelease(colorspace);
CGColorRelease(color);

}


The above code sets the properties of the line we want to draw. The properties are the thickness of the line (2.0) and the color (transparent gray). It then draws the line between the CGPoints passed into the method.

We could now call this method from our View Controller. Notice, however, that the drawText method does not create a new PDF Graphics context or a new page by calling UIGraphicsBeginPDFContextToFile. So we need to make some modifications.

First, create a new method in the PDFRenderer file called drawPDF.


+(void)drawPDF:(NSString*)fileName
{
// Create the PDF context using the default page size of 612 x 792.
UIGraphicsBeginPDFContextToFile(fileName, CGRectZero, nil);
// Mark the beginning of a new page.
UIGraphicsBeginPDFPageWithInfo(CGRectMake(0, 0, 612, 792), nil);

CGPoint from = CGPointMake(0, 0);
CGPoint to = CGPointMake(200, 300);
[PDFRenderer drawLineFromPoint:from toPoint:to];

[self drawText];

// Close the PDF context and write the contents out.
UIGraphicsEndPDFContext();
}


This will create our graphics context, draw the text and a test line, and then end the context.

Note that the drawText method no longer takes the PDF filename as a parameter. Here is our new drawText method.


+(void)drawText
{

NSString* textToDraw = @"Hello World";
CFStringRef stringRef = (__bridge CFStringRef)textToDraw;
// Prepare the text using a Core Text Framesetter
CFAttributedStringRef currentText = CFAttributedStringCreate(NULL, stringRef, NULL);
CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString(currentText);

CGRect frameRect = CGRectMake(0, 0, 300, 50);
CGMutablePathRef framePath = CGPathCreateMutable();
CGPathAddRect(framePath, NULL, frameRect);

// Get the frame that will do the rendering.
CFRange currentRange = CFRangeMake(0, 0);
CTFrameRef frameRef = CTFramesetterCreateFrame(framesetter, currentRange, framePath, NULL);
CGPathRelease(framePath);

// Get the graphics context.
CGContextRef currentContext = UIGraphicsGetCurrentContext();

// Put the text matrix into a known state. This ensures
// that no old scaling factors are left in place.
CGContextSetTextMatrix(currentContext, CGAffineTransformIdentity);

// Core Text draws from the bottom-left corner up, so flip
// the current transform prior to drawing.
CGContextTranslateCTM(currentContext, 0, 100);
CGContextScaleCTM(currentContext, 1.0, -1.0);

// Draw the frame.
CTFrameDraw(frameRef, currentContext);

CFRelease(frameRef);
CFRelease(stringRef);
CFRelease(framesetter);
}


Add the predeclaration for drawPDF into PDFRenderer.h. Then modify the View Controller to call the correct method in viewDidLoad, which is drawPDF instead of drawText.

That’s it! Build and run the app, you should see both “Hello World” and a diagonal line, similar to the image below.



Yes, that’s funny-looking. And no, this is not “How to Draw Abstract Art with Quartz 2D.” Don’t worry, our PDF will achieve some polish in Part Two of the tutorial! :]

Where to Go From Here?


Here is a example project with all of the code from the above tutorial.

That brings us to the end of the first part of this tutorial. In Part Two, we will go into more advanced drawing techniques, like adding images and using a xib file to make the layout process easier. Please stay tuned!

If you have any questions or comments about what we’ve done so far, or if there is anything you’d like me to address in Part Two, please join in the forum discussion below!



This is a blog post by iOS Tutorial Team member Tope Abayomi, an iOS developer with a passion for easy to use, useful, aesthetically pleasing apps. Here are some of his videos teaching you how to design an app with custom design.

How To Create a PDF with Quartz 2D in iOS 5 Tutorial Part 1 is a post from: Ray Wenderlich

2011年12月30日星期五

iPhone Development – core data relationships tutorial part 1

iPhone Development – core data relationships tutorial part 1:
I’m going to start a short series on Core Data relationships and maybe throw in some general Core Data stuff too. Here in part one we’re just going to set our app up with core data and add two entities with a simple one to one relationship between them. A one to one relationship means that for every Fruit there will be one source and in our case here the reverse it true too, for every source there is one fruit.

Core Data Relationships



1.) Create a new Tab Bar Application named CoreDataRelationshipsTutorial.

2.) Change FirstView.xib so it looks similar to this.

FirstView.xib

3.) Add the core data framework to the app.

Add Core Data Framework

4.) Right click on Supporting Files and select New File, then choose Core Data select Data Model and hit Next. I just accepted the default name of Model and clicked save.

Add Data Model

5.) Select Model.xcdatamodeld and the visual editor will open. Click Add Entity and name it Fruit. Add an Attribute named fruitName of type String. Add another Entity named Source with an Attribute sourceName, which will also be of type String.

6.) Select Fruit and then click the plus symbol under Relationships. Name the relationship fruitSource. Set the destination to Source, there will be no inverse yet. In the relationship data model inspector uncheck the Optional checkbox. In the delete rule select Cascade.

Edit Data Model

7.) Now select Source and add a relationship named sourceFruit. Destination should be Fruit and set the inverse to artistCareer. Uncheck the Optional checkbox again.

Set up relationships

8.) Select Fruit under ENTITIES and then go under the file menu up top and select New File. Choose Core Data and NSManagedObject subclass,, click Next. Keep the default location and click Create.

Create Managed Objects

Repeat this same process after selecting Source under ENTITIES.

You should now see your new objects listed under Supporting Files.

xcode view

9.) Open up CoreDataRelationshipsTutorial-Prefix.pch and add an import for CoreDate. This saves us from having to import it into every file that will use it.

#import

#ifndef __IPHONE_3_0
#warning "This project uses features only available in iPhone SDK 3.0 and later."
#endif

#ifdef __OBJC__
#import
#import
#import
#endif

10.) Now let’s add all the necessary Core Data code to the app delegate files.

First the header file. Import our FirstViewController, then declare private instance variables for our NSManagedObjectContext, NSManagedObjectModel and NSPersistentStoreCoordinator. Create an IBOutlet with out FirstViewController, and declare two methods that we’ll implement.

#import
#import "FirstViewController.h"

@interface CoreDataRelationshipsTutorialAppDelegate : NSObject
{

@private
NSManagedObjectContext *managedObjectContext;
NSManagedObjectModel *managedObjectModel;
NSPersistentStoreCoordinator *persistentStoreCoordinator;

}

@property (nonatomic, retain) IBOutlet UIWindow *window;
@property (nonatomic, retain) IBOutlet UITabBarController *tabBarController;

@property (nonatomic, retain) IBOutlet FirstViewController *firstViewController;

@property (nonatomic, retain, readonly) NSManagedObjectContext *managedObjectContext;
@property (nonatomic, retain, readonly) NSManagedObjectModel *managedObjectModel;
@property (nonatomic, retain, readonly) NSPersistentStoreCoordinator *persistentStoreCoordinator;

- (NSURL *)applicationDocumentsDirectory;
- (void)saveContext;

@end

11.) Now open the app delegate implementation file. Synthesize our firstViewController, then set it’s managedObjectContext to the one created in the app delegate. You may see an error on the line that sets the managedObjectContext because we haven’t set that up in FirstViewController yet.

#import "CoreDataRelationshipsTutorialAppDelegate.h"

@implementation CoreDataRelationshipsTutorialAppDelegate

@synthesize window=_window;
@synthesize tabBarController=_tabBarController;
@synthesize firstViewController;

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
firstViewController.managedObjectContext = self.managedObjectContext;

self.window.rootViewController = self.tabBarController;
[self.window makeKeyAndVisible];
return YES;
}

Implement all these methods.

/**
Returns the URL to the application's Documents directory.
*/
- (NSURL *)applicationDocumentsDirectory
{
return [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
}

- (void)saveContext
{

NSError *error = nil;
NSManagedObjectContext *objectContext = self.managedObjectContext;
if (objectContext != nil)
{
if ([objectContext hasChanges] && ![objectContext save:&error])
{
// add error handling here
NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
abort();
}
}
}

#pragma mark -
#pragma mark Core Data stack

/**
Returns the managed object context for the application.
If the context doesn't already exist, it is created and bound to the persistent store coordinator for the application.
*/
- (NSManagedObjectContext *)managedObjectContext
{

if (managedObjectContext != nil)
{
return managedObjectContext;
}

NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
if (coordinator != nil)
{
managedObjectContext = [[NSManagedObjectContext alloc] init];
[managedObjectContext setPersistentStoreCoordinator:coordinator];
}
return managedObjectContext;
}

/**
Returns the managed object model for the application.
If the model doesn't already exist, it is created from the application's model.
*/
- (NSManagedObjectModel *)managedObjectModel
{
if (managedObjectModel != nil)
{
return managedObjectModel;
}
managedObjectModel = [[NSManagedObjectModel mergedModelFromBundles:nil] retain];

return managedObjectModel;
}

/**
Returns the persistent store coordinator for the application.
If the coordinator doesn't already exist, it is created and the application's store added to it.
*/
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator
{

if (persistentStoreCoordinator != nil)
{
return persistentStoreCoordinator;
}

NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"CoreDataTabBarTutorial.sqlite"];

NSError *error = nil;
persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
if (![persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error])
{
NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
abort();
}

return persistentStoreCoordinator;
}

12.) Open up FirstViewController.h and let’s set it up with the necessary code and instance variables.

#import

@interface FirstViewController : UIViewController
{

NSFetchedResultsController  *fetchedResultsController;
NSManagedObjectContext      *managedObjectContext;

}

@property (nonatomic, retain) NSString *fruitNameString;
@property (nonatomic, retain) NSString *fruitSourceString;

@property (nonatomic, retain) NSFetchedResultsController    *fetchedResultsController;
@property (nonatomic, retain) NSManagedObjectContext        *managedObjectContext;

- (IBAction) saveData;

@end

13.) Now for the implementation file. Let’s import our managed objects.

#import "FirstViewController.h"
#import "Fruit.h"
#import "Source.h"

Then synthesize the instance variables.

@synthesize fetchedResultsController, managedObjectContext;
@synthesize fruitNameString, fruitSourceString;

Let’s go ahead and set the values of those two strings in ViewDidLoad.

- (void)viewDidLoad
{
[super viewDidLoad];
fruitNameString = [[NSString alloc] initWithString:@"Apple"];
fruitSourceString = [[NSString alloc] initWithString:@"Apple Tree"];
}

14.) Implement the saveData method.

- (IBAction) saveData
{
NSLog(@"saveData");
Fruit *fruit = (Fruit *)[NSEntityDescription insertNewObjectForEntityForName:@"Fruit" inManagedObjectContext:managedObjectContext];
fruit.fruitName = fruitNameString;
Source *source = (Source *)[NSEntityDescription insertNewObjectForEntityForName:@"Source" inManagedObjectContext:managedObjectContext];
source.sourceName = fruitSourceString;

// Because we set the relationship fruitSource as not optional we must set the source here
fruit.fruitSource = source;

NSError *error;

// here's where the actual save happens, and if it doesn't we print something out to the console
if (![managedObjectContext save:&error])
{
NSLog(@"Problem saving: %@", [error localizedDescription]);
}

// **** log objects currently in database ****
// create fetch object, this object fetch's the objects out of the database
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:@"Fruit" inManagedObjectContext:managedObjectContext];
[fetchRequest setEntity:entity];
NSArray *fetchedObjects = [managedObjectContext executeFetchRequest:fetchRequest error:&error];

for (NSManagedObject *info in fetchedObjects)
{
NSLog(@"Fruit name: %@", [info valueForKey:@"fruitName"]);
Source *tempSource = [info valueForKey:@"fruitSource"];
NSLog(@"Source name: %@", tempSource.sourceName);

}
[fetchRequest release];
}

15.) Release our objects in the dealloc method and set them to nil in viewDidUnload.

- (void)viewDidUnload
{
[super viewDidUnload];
fetchedResultsController = nil;
managedObjectContext = nil;
fruitNameString = nil;
fruitSourceString = nil;
}

- (void)dealloc
{
[fetchedResultsController release];
[managedObjectContext release];
[fruitNameString release];
[fruitSourceString release];

[super dealloc];
}

16.) Open up FirstView.xib and connect the UIButton to our saveData IBAction.

17.) Open up MainWindow.xib, select the app delegate and connect firstViewController outlet to FirstViewController under the Tab Bar Controller.

Link view controller

18.) Now you can run the app and hit the Save Data button. Look in the console to see the results of the fetch.

console

The important things to note from this tutorial are these.

When we created the relationship from Fruit to Source we made it so that it was not optional. Therefore during our saveData method we had to set the fruitSource to something.

Fruit *fruit = (Fruit *)[NSEntityDescription insertNewObjectForEntityForName:@"Fruit" inManagedObjectContext:managedObjectContext];
fruit.fruitName = fruitNameString;
Source *source = (Source *)[NSEntityDescription insertNewObjectForEntityForName:@"Source" inManagedObjectContext:managedObjectContext];
source.sourceName = fruitSourceString;

Try commenting out that last line

//    source.sourceName = fruitSourceString;

And then running it again. What happens? Crash and burn. Because the relationship is not optional you must set the sourceName.

You can also see from the block of code above that we use reuse the same managedObjectContext to create both of the managed objects. Then we set the values and just saved the context. Doing this saved both objects (entities in Core Data).

Another thing to take note of happens in the fetch process. You notice that we only fetch the Fruit entity. But because of the relationship between Fruit and Source we can access the Source entity. We don’t need to do a separate fetch on Source.

NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:@"Fruit" inManagedObjectContext:managedObjectContext];
[fetchRequest setEntity:entity];
NSArray *fetchedObjects = [managedObjectContext executeFetchRequest:fetchRequest error:&error];

for (NSManagedObject *info in fetchedObjects)
{
NSLog(@"Fruit name: %@", [info valueForKey:@"fruitName"]);
Source *tempSource = [info valueForKey:@"fruitSource"];
NSLog(@"Source name: %@", tempSource.sourceName);
[tempSource release];
}

Okay that does it for this tutorial. Next time we will look at a one to many relationship.

As always here’s the code.

iPhone App Development Tutorial – Core Data part 2 – One to Many Relationship

iPhone App Development Tutorial – Core Data part 2 – One to Many Relationship:
In this tutorial we will look at creating a one to many relationship in core data. It’s really very easy and not that much different than creating the one to one relationship. The difference comes in how the relationship allows the entities to interact with each other. In part one we had two entities, Fruit and Source. These two entities had a one to one relationship to each other. For each Fruit there was only one Source, and the inverse was true as well, for each Source there was only one Fruit. Now we are going to create two new entities, Artist and Album. Each artist can have multiple albums, but each album can have only one artist. We’ll just pretend that no artists ever collaborate on albums together for the sake of this tutorial :)

We’re going to use the app that I / we created in the Core Data duplicate entities tutorial. In that tutorial we set up an app that did not allow us to enter the same Artist twice. It seems like a good app to start with, only I’m even going to take care of a few more things for us To save time. I’ve set up the second tab with a UITableView and hooked it up with the view controller. This table view will hold the the list of artists that have been saved in the app. I’ve also created a new view with view controllers that will be used to enter Album information for an artist.

Screen Shot



What is going to happen is when we go to the second tab a search will return all Artists saved and display them in the UITableView. We can then click on an Artist and go to the enter Album informations view. The Artist object will be passed to this new view so that when we enter info for an album and save it, it will be saved with a relationship to the artist we selected. In this way we can create many albums for an artist, but an album can only have one artist. Thus the one to many relationship.

Here’s the code we’ll start with.

Let’s get going.

1.) If you run the app right now you can see the second tab with a hard coded string populating each table view cell.

Select An Artist Table Hardcoded

And though there’s no way to get to the new view yet, here is what it looks like.

Enter Album Info View

So that’s what we are starting with.

2.) The first thing we are going to do is a search in SecondViewController and populate our table with the Artists returned. Open up SecondViewController.h and add an NSMutableArray to hold our Artists and declare an ivar for NSMananagedObjectContext.

#import

@interface SecondViewController : UIViewController
{
NSMutableArray *artistsArray;
}

@property (nonatomic, retain) IBOutlet UITableView *artistTable;
@property (nonatomic, retain) NSManagedObjectContext        *managedObjectContext;

@end

Then go to the implementation file and synthesize the managedObjectContext ivar.

#import "SecondViewController.h"

@implementation SecondViewController

@synthesize artistTable;
@synthesize managedObjectContext;

3.) Go to app delegate header file and import SecondViewController, then declare an ivar of it and

#import
#import "FirstViewController.h"
#import "SecondViewController.h"

@interface CoreDataTutorial5AppDelegate : NSObject
{

@private
NSManagedObjectContext *managedObjectContext;
NSManagedObjectModel *managedObjectModel;
NSPersistentStoreCoordinator *persistentStoreCoordinator;

}

@property (nonatomic, retain) IBOutlet UIWindow *window;
@property (nonatomic, retain) IBOutlet UITabBarController *tabBarController;

@property (nonatomic, retain, readonly) NSManagedObjectContext *managedObjectContext;
@property (nonatomic, retain, readonly) NSManagedObjectModel *managedObjectModel;
@property (nonatomic, retain, readonly) NSPersistentStoreCoordinator *persistentStoreCoordinator;

@property (nonatomic, retain) IBOutlet FirstViewController *firstViewController;
@property (nonatomic, retain) IBOutlet SecondViewController *secondViewController;

- (NSURL *)applicationDocumentsDirectory;
- (void)saveContext;

@end

finally synthesize it in the app delegate implementation file.

#import "CoreDataTutorial5AppDelegate.h"

@implementation CoreDataTutorial5AppDelegate

@synthesize window=_window;
@synthesize tabBarController=_tabBarController;
@synthesize firstViewController, secondViewController;

Then open MainWindow.xib and connect the secondViewController ivar to the SecondViewController under Tab Bar Controller.

Now that they are connected let’s set the managed object context in secondViewController. Do this in the app delegate implementation file application:didFinishLaunchingWithOptions method.

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.firstViewController.managedObjectContext = self.managedObjectContext;
self.secondViewController.managedObjectContext = self.managedObjectContext;
self.window.rootViewController = self.tabBarController;
[self.window makeKeyAndVisible];
return YES;
}

4.) We’ve now passed the managedObjectContext to SecondViewController and we can implement the search method. We’re going to implement the viewWillAppear method and do our search there.

- (void)viewWillAppear:(BOOL)animated
{
NSLog(@"viewWillAppear");
NSError *error;

NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:@"Artist" inManagedObjectContext:managedObjectContext];
[fetchRequest setEntity:entity];
NSArray *fetchedObjects = [managedObjectContext executeFetchRequest:fetchRequest error:&error];
artistsArray = [[NSMutableArray alloc] initWithArray:fetchedObjects];

[fetchRequest release];
[artistTable reloadData];

}

Then we’ll use our array to set the number of rows in the section.

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [artistsArray count];
}

And finally we’ll populate our table view cells with the Artists name. But first import the Artist object.

#import "SecondViewController.h"
#import "Artist.h"

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = @"Cell";

UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
}

// Configure the cell...
Artist *artist = [artistsArray objectAtIndex:indexPath.row];
cell.textLabel.text = artist.artistName;
return cell;
}

Run it and make sure everything is working up to this point.

Select an Artist Table

5.) Now that we have retrieved the Artists and are displaying them, let’s add the new view and pass the Artist to it when selecting a cell in the table view. We’ll need to import EnterAlbumInfoViewController to the SecondViewController header file and declare an ivar of it.

#import
#import "EnterAlbumInfoViewController.h"

@interface SecondViewController : UIViewController
{
NSMutableArray *artistsArray;
}

@property (nonatomic, retain) IBOutlet UITableView *artistTable;
@property (nonatomic, retain) NSManagedObjectContext        *managedObjectContext;

@property (nonatomic, retain) EnterAlbumInfoViewController *enterAlbumInfoViewController;

@end

Then synthesize it in the implementation file.

#import "SecondViewController.h"
#import "Artist.h"

@implementation SecondViewController

@synthesize artistTable;
@synthesize managedObjectContext;
@synthesize enterAlbumInfoViewController;

Now go to the didSelectRowAtIndexPath method and present the new view modally, at the same time we will pass the Artist to it along with the managedObjectContext.

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if (self.enterAlbumInfoViewController == nil)
{
EnterAlbumInfoViewController *temp = [[EnterAlbumInfoViewController alloc] initWithNibName:@"EnterAlbumInfoViewController" bundle:[NSBundle mainBundle]];
self.enterAlbumInfoViewController = temp;
[temp release];
}

Artist *artist = [artistsArray objectAtIndex:indexPath.row];
self.enterAlbumInfoViewController.artistNameString = artist.artistName;
self.enterAlbumInfoViewController.artist = artist;
self.enterAlbumInfoViewController.managedObjectContext = self.managedObjectContext;

[self presentModalViewController:self.enterAlbumInfoViewController animated:YES];
}

I forgot to implement a method for the cancel button so let’s do that real quick. This goes in EnterAlbumInfoViewController.

- (IBAction)cancelView
{
[self dismissModalViewControllerAnimated:YES];
}

6.) Before we go any further we need to add the Album Entity to our Data Model. Open up the visual data model editor by clicking on DataModel.xcdatamodel. Add an Entity named Album with three attributes. Here’s how it should look when done.

Album Entity

Now we need to create the relationships. Select Artist and add a relationship to Album named album. This relationship will be a To-Many Relationship so check that box. This means each artist can have many albums. Also make this Optional since an artist doesn’t need to have an album when we create the artist.

album relationship

Then select Album and create the inverse. This time uncheck the optional checkbox and make sure the To-Many Relationship box is not checked. Each Album will have just one artist.

artist relationship

When done create the Album NSManagedObject subclass, and recreate the Artist object. You’ll also need to change the name of the sqlite db on the back end of the Core Data. I just changed the name in this line of the persistentStoreCoordinator method of the app delegate.

NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"CoreDataTutorial63.sqlite"];

After doing this you’ll need to re-enter the Artist info.

7.) Now we should be ready to save the Album info. Implement the saveAlbumInfo in EnterAlbumInfoViewController. You’ll need to import “Album.h” too.

- (IBAction)saveAlbumInfo
{
NSLog(@"saveAlbumInfo");

Album *album = (Album *)[NSEntityDescription insertNewObjectForEntityForName:@"Album" inManagedObjectContext:managedObjectContext];
album.albumName = self.albumNameTextField.text;
album.albumReleaseDate = self.albumReleaseDateTextField.text;
album.albumGenre = self.albumGenreTextField.text;
album.artist = self.artist;

NSError *error;

// here's where the actual save happens, and if it doesn't we print something out to the console
if (![managedObjectContext save:&error])
{
NSLog(@"Problem saving: %@", [error localizedDescription]);
}

// **** log objects currently in database ****
// create fetch object, this object fetch's the objects out of the database
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:@"Album" inManagedObjectContext:managedObjectContext];
[fetchRequest setEntity:entity];
NSArray *fetchedObjects = [managedObjectContext executeFetchRequest:fetchRequest error:&error];

for (NSManagedObject *info in fetchedObjects)
{
NSLog(@"Album name: %@", [info valueForKey:@"albumName"]);
NSLog(@"Album age: %@", [info valueForKey:@"albumReleaseDate"]);
NSLog(@"Album gender: %@", [info valueForKey:@"albumGenre"]);

}
[fetchRequest release];

[self dismissModalViewControllerAnimated:YES];
}

8.) Go ahead and run the app and test it out to make sure everything is working up to this point.

9.) Now let’s add a third tab that will allow us to search for an artist and display all of their albums. Add a Tab Bar Item to the Tab bar and the view should look like this.

Search View

Where it has a UISearchBar and a UITableView.

10.) We’ll have to pass the managed object context to this view just as we did with the first and second view controllers. Go to the app delegate header file and import the new view controller. Then create an ivar for it.

#import
#import "FirstViewController.h"
#import "SecondViewController.h"
#import "SearchViewController.h"

@interface CoreDataTutorial5AppDelegate : NSObject
{

@private
NSManagedObjectContext *managedObjectContext;
NSManagedObjectModel *managedObjectModel;
NSPersistentStoreCoordinator *persistentStoreCoordinator;

}

@property (nonatomic, retain) IBOutlet UIWindow *window;
@property (nonatomic, retain) IBOutlet UITabBarController *tabBarController;

@property (nonatomic, retain, readonly) NSManagedObjectContext *managedObjectContext;
@property (nonatomic, retain, readonly) NSManagedObjectModel *managedObjectModel;
@property (nonatomic, retain, readonly) NSPersistentStoreCoordinator *persistentStoreCoordinator;

@property (nonatomic, retain) IBOutlet FirstViewController *firstViewController;
@property (nonatomic, retain) IBOutlet SecondViewController *secondViewController;
@property (nonatomic, retain) IBOutlet SearchViewController *searchViewController;

- (NSURL *)applicationDocumentsDirectory;
- (void)saveContext;

@end

11.) Synthesize our new ivar in the implementation file. and pass the managed object context in the didFinishLaunchingWithOptions method.

#import "CoreDataTutorial5AppDelegate.h"

@implementation CoreDataTutorial5AppDelegate

@synthesize window=_window;
@synthesize tabBarController=_tabBarController;
@synthesize firstViewController, secondViewController, searchViewController;

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.firstViewController.managedObjectContext = self.managedObjectContext;
self.secondViewController.managedObjectContext = self.managedObjectContext;
self.searchViewController.managedObjectContext = self.managedObjectContext;
self.window.rootViewController = self.tabBarController;
[self.window makeKeyAndVisible];
return YES;
}

12.) Open MainWindow.xib and connect the searchViewController outlet to the SearchViewController in the Tab Bar Controller.

13.) Open SearchViewController.h and declare two NSArray ivars. One to hold our fetchedObjects (the search results), and one to hold the list of albums from the artiste returned by our search. Declare an ivar for Artist and import Artist into the header file. Create IBOutlets fort our table view and search bar and finally create ivars for the fetchedResultsController and the managedObjectContext.

#import
#import "Artist.h"

@interface SearchViewController : UIViewController
{
NSArray *fetchedObjects;
NSArray *albumArray;
Artist *artist;
}

@property (nonatomic, retain) IBOutlet UITableView *searchResultsTableView;
@property (nonatomic, retain) IBOutlet UISearchBar *mySearchBar;
@property (nonatomic, retain) NSFetchedResultsController    *fetchedResultsController;
@property (nonatomic, retain) NSManagedObjectContext        *managedObjectContext;

@end

14.) Now open up SearchViewController.m and import both Artist and Album and synthesize all our ivars.

#import "SearchViewController.h"
#import "Artist.h"
#import "Album.h"

@implementation SearchViewController

@synthesize searchResultsTableView, mySearchBar, fetchedResultsController, managedObjectContext;

Then implement viewWillAppear. We’ll use this to clear our search bar.

- (void)viewWillAppear:(BOOL)animated
{
mySearchBar.text = @"";
}

15.) Now let’s implement the viewDidLoad method. We’ll set up our core data elements here.

- (void)viewDidLoad
{
[super viewDidLoad];

//    NSFetchRequest needed by the fetchedResultsController
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];

//    NSSortDescriptor tells defines how to sort the fetched results
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"artistName" ascending:YES];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
[fetchRequest setSortDescriptors:sortDescriptors];

//    fetchRequest needs to know what entity to fetch
NSEntityDescription *entity = [NSEntityDescription entityForName:@"Artist" inManagedObjectContext:managedObjectContext];
[fetchRequest setEntity:entity];
[sortDescriptors release];
[sortDescriptor release];

fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:nil cacheName:@"Root"];

[fetchRequest release];
}

16.) Next implement the searchBarButtonClicked method. This is where the search takes place and we’ll set the albumArray here.

- (void) searchBarSearchButtonClicked:(UISearchBar *)theSearchBar
{
NSLog(@"searchBarSearchButtonClicked");

NSError *error = nil;

// We use an NSPredicate combined with the fetchedResultsController to perform the search
if (self.mySearchBar.text !=nil)
{
NSPredicate *predicate =[NSPredicate predicateWithFormat:@"artistName  contains[cd] %@", self.mySearchBar.text];
[fetchedResultsController.fetchRequest setPredicate:predicate];
}

if (![[self fetchedResultsController] performFetch:&error])
{
// Handle error
NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
exit(-1);  // Fail
}

// this array is just used to tell the table view how many rows to show
fetchedObjects = fetchedResultsController.fetchedObjects;

// Handle the case where search returns nothing
if ([fetchedObjects count] > 0)
{
artist = [fetchedObjects objectAtIndex:0];

NSSet *artistSet = artist.album;
albumArray = [artistSet allObjects];
}
else
{
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Search Results" message:@"Your search produced no results, please try again." delegate:self cancelButtonTitle:@"OK" otherButtonTitles:nil, nil];
[alert show];
[alert release];

}

// dismiss the search keyboard
[mySearchBar resignFirstResponder];

// reload the table view
[searchResultsTableView reloadData];
}

17.) Use the albumArray to set the numberRowsInSection.

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [albumArray count];
}

18.) And finally implement the cellForRowAtIndexPath method like this to display the artist and their albums.

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = @"Cell";

UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier] autorelease];
}

// Configure the cell...
cell.textLabel.text = artist.artistName;

NSSet *artistSet = artist.album;
NSArray *objectsArray = [artistSet allObjects];

for (int i = 0; i < [objectsArray count]; i++)
{
Album *album = [objectsArray objectAtIndex:indexPath.row];
cell.detailTextLabel.text = album.albumName;

}

return cell;
}

Here's what it looks like when done.

Finished Screen Shot

And here's the code.

2011年12月28日星期三

Building a Universal Framework for iOS

Building a Universal Framework for iOS:
Apple has invested quite a bit of time into making it easy to compile for a number of different architectures in XCode. For instance, compiling a library into its armv6, armv7, and i386 variants is just a matter of specifying the supported architecture types. However, there isn’t a built-in mechanism to take the binaries built for the various architectures and merge them into a universal iOS framework.

Before we go through the steps of building a universal iOS framework we should first review what a framework is and why they are useful.



What is a ‘framework’ and why are they useful?


Apple defines a framework as:

… a hierarchical directory that encapsulates shared resources, such as a dynamic shared library, nib files, image files, localized strings, header files, and reference documentation in a single package.

So instead of having header files and binaries in disperate locations a framework brings everything together into one package (a directory with a known structure). Packaging a library as a framework simplifies things for developers because it not only provides a binary to link against but it includes all of the necessary header files to reference as well.

What is a ‘universal’ framework?


A universal framework can be defined as a framework that contains a binary which has been built for a number of architectures (armv6, armv7, i386) and can be statically1 linked against. This is particularly useful in iOS development because an application can be built for the simulator (i386) and the device (armv6, armv7).

1 Statically linking a library resolves symbols at compile time and embeds the library into the application. It is not possible, currently, to create a dynamic framework for iOS.

Building a ‘universal’ framework


Firstly, there is a project called iOS Universal Framework that simplifies the process of building a universal framework by providing an XCode template. However, I think it is still a meaningful exercise to understand how a universal framework is built using XCode.

Lets get started:

Create a new project

  1. File -> New -> New Project (Command-Shift-N)
  2. Select Frameworks & Libraries under iOS
  3. Select “Cocoa Touch Static Library” and click “Next”
  4. Provide a name for the library



Configure architectures

By default the static library is configured to only build for armv7 so we need to add armv6 and i386 to ensure that the static library is built for older devices (iPhone 3G/Original, early generation iPod Touch) and the simulator.



Create aggregate target

An aggregate target aggregates other targets together. In effect, it wraps a number of script executables and copy file tasks in a specified order.

  1. File -> New Target
  2. Select “Other” under iOS
  3. Select “Aggregate”
  4. Give it a name (MyLibrary-iOS for example)



Build static libraries

Under the “Build Phases” section of the aggregate target we are going to add a build phase that compiles a static library for i386 and ARM. In the next step we’ll merge the binaries into a fat binary.

  1. Select MyLibrary-iOS target
  2. Select “Build Phases”
  3. Click “Add Build Phase”
  4. Select “Add Run Script”
  5. Name it “Build Static Libs”







xcodebuild -project ${PROJECT_NAME}.xcodeproj -sdk iphonesimulator -target ${PROJECT_NAME} -configuration ${CONFIGURATION} clean build
xcodebuild -project ${PROJECT_NAME}.xcodeproj -sdk iphoneos -target ${PROJECT_NAME} -configuration ${CONFIGURATION} clean build







Build universal binary

We are going to add another “Run Script” phase that builds the framework itself. The script is going to:

Create a directory structure that mimics the same directory structure seen in Apple’s dynamic frameworks:





SIMULATOR_LIBRARY_PATH="${BUILD_DIR}/${CONFIGURATION}-iphonesimulator/lib${PROJECT_NAME}.a" &&
DEVICE_LIBRARY_PATH="${BUILD_DIR}/${CONFIGURATION}-iphoneos/lib${PROJECT_NAME}.a" &&
UNIVERSAL_LIBRARY_DIR="${BUILD_DIR}/${CONFIGURATION}-iphoneuniversal" &&
UNIVERSAL_LIBRARY_PATH="${UNIVERSAL_LIBRARY_DIR}/${PRODUCT_NAME}" &&
FRAMEWORK="${UNIVERSAL_LIBRARY_DIR}/${PRODUCT_NAME}.framework" &&
# Create framework directory structure.
rm -rf "${FRAMEWORK}" &&
mkdir -p "${UNIVERSAL_LIBRARY_DIR}" &&
mkdir -p "${FRAMEWORK}/Versions/A/Headers" &&
mkdir -p "${FRAMEWORK}/Versions/A/Resources" &&







Merge the static libraries built for the various architectures into a fat binary using a tool called lipo:





# Generate universal binary for the device and simulator.
lipo "${SIMULATOR_LIBRARY_PATH}" "${DEVICE_LIBRARY_PATH}" -create -output "${UNIVERSAL_LIBRARY_PATH}" &&







Move the appropriate files into place:





# Move files to appropriate locations in framework paths.
cp "${UNIVERSAL_LIBRARY_PATH}" "${FRAMEWORK}/Versions/A" &&
ln -s "A" "${FRAMEWORK}/Versions/Current" &&
ln -s "Versions/Current/Headers" "${FRAMEWORK}/Headers" &&
ln -s "Versions/Current/Resources" "${FRAMEWORK}/Resources" &&
ln -s "Versions/Current/${PRODUCT_NAME}" "${FRAMEWORK}/${PRODUCT_NAME}"









The full script can be found here.

Copy header files into place

  1. Select “Add Build Phase”
  2. Click “Add Copy Files”
  3. Set “Destination” to “Absolute Path”
  4. Set subpath to ${BUILD_DIR}/${CONFIGURATION}-iphoneuniversal/${PRODUCT_NAME}.framework/Versions/A/Headers/
  5. Add header files that should be public to the list



Configure aggregate target build against the ‘Release’ configuration

  1. Product -> Edit Scheme
  2. Choose aggregate target (MyLibrary-iOS)
  3. Select “Run”
  4. Choose “Release” under Build Configuration



Build and verify framework

  1. Select aggregate target
  2. Build it (Command-B)

Verify that the binary was built for the correct architectures using lipo:





lipo -info build/Release-iphoneuniversal/MyLibrary-iOS.framework/MyLibrary-iOS
Architectures in the fat file: build/Release-iphoneuniversal/MyLibrary-iOS.framework/MyLibrary-iOS are: i386 armv6 armv7







Ensure that the header files are copied into place (XCode is known to mess this up on occasion):





tree build/Release-iphoneuniversal/MyLibrary-iOS.framework/
build/Release-iphoneuniversal/MyLibrary-iOS.framework/
├── Headers -> Versions/Current/Headers
├── MyLibrary-iOS -> Versions/Current/MyLibrary-iOS
├── Resources -> Versions/Current/Resources
└── Versions
├── A
│ ├── Headers
│ │ └── MyLibrary.h
│ ├── MyLibrary-iOS
│ └── Resources
└── Current -> A
7 directories, 3 files

Conclusion


Creating a universal framework certainly requires a fair amount of upfront work. However, it is a great mechanism to distribute your library to the masses without making them work to use it. There is not any configuration (header paths) or tweaking (warnings, ARC vs non-ARC files) required on the part of the user. Which means less work for you having to respond to issues and complaints.

2011年12月26日星期一

Migrating your code to Objective-C ARC

Migrating your code to Objective-C ARC:
Recently, Apple introduced several new developer stuff including Xcode 4, ARC, LLVM Compiler 3.0 and iOS 5. From some of the questions on Stack overflow, I could understand that, most of the ARC related confusions arise due to the fact that, developers don’t know if “ABC” is a feature/restriction of LLVM 3.0 or iOS 5 or ARC.

Retain cycles, auto-release pools, @autorelease blocks, oh man! So many new things? What am I going to do? You are right. ARC, or Objective-C Automatic Reference Counting is almost as magical as the iPad. No really!

In this post, I’ve made an attempt to demystify the air around this. Before starting, I’ll have to warn you that, this is a fairly long post. If you are too bored, Instapaper this article and read it later. But, hopefully, at the end of this, I believe, you will have a better understanding on how ARC works and be able to work around the innumerable errors it spits out when you convert your project.

Having said that, let’s get started.

What is ARC


ARC is a feature of the new LLVM 3.0 compiler that helps you to write code without worrying much about memory management. Memory management can be broadly classified into two, garbage collected and reference counted models. Before going to the details, let’s briefly discuss these two models and understand why ARC is even needed.

Problems with the current model.


The current memory model we use in Objective-C is manual reference counting on iOS and Garbage collection on Mac.

There are certain problems with both these memory models which probably was the reason why ARC was developed.

Garbage collection

Garbage collection is a higher level language feature probably introduced in Java (or technically, Java Virtual Machine) and implemented in a variety of other programming platforms including Microsoft’s Common Language Runtime. While Garbage collection worked well for higher level languages, Objective-C, which is still C under the hood, didn’t really fly high. Pointers (or rather references) in other languages like Java were actually objects that managed retain count and automatically releases itself when the count reaches zero. One of the design goals of C was to be optimized for performance and not “easy of use”. While pointer objects (read smart pointers) are great object oriented abstractions, they have an adverse effect on the performance of the code and since Objective-C was intended primarily for native programming where developers are used to use pointers, pointers within a structure, pointer to a pointer (for dereferencing a out parameter), it was just too difficult to introduce something like a smart pointer that would require a lot of mindset change from the developers who prefer a deterministic memory management model (Reference counting) over a non-deterministic memory management model (Garbage collection). Nevertheless, GC (Generational GC) was introduced in Objective-C 2.0 for Mac. While Generational GC doesn’t suffer from “Stop the world” issues like the mark and sweep alogrithm, they don’t collect every released variable and an occasional mark and sweep collection is still needed.

Reference Counting

The memory management model used in iOS is called as reference counting model, or more precisely, manual reference counting.

In manual reference counting model, you as a developer, have to deallocate every object you allocated. When you don’t do this, you either leak memory or over release it, causing a crash. While that counting sounds easy, Most of the memory leaks happen when you transfer ownership of objects across scope boundaries. That’s a creator method that allocates an object for you and expects the caller to deallocate it. To circumvent this problem, Objective-C introduced a concept called autorelease. auto-released variables are added to the auto-release pool and are released at the end of the runloop. While this sounds too good, auto-release pools do incur an additional overhead.

For example, comparing the two code blocks,


{
NSDictionary *dict = [[NSDictionary alloc] init];
// do something with the dictionary here
// I'm done with the dictionary, I don't need it anymore
[dict release];
// more code
}


and


{
NSDictionary *dict = [NSDictionary dictionary]; // auto-released object
// do something with the dictionary here
// I'm done with the dictionary, I don't need it anymore

// more code
}


the first block is an example of optimized use of memory where as the second depends on auto-release pools. While this block of code doesn’t really incur significant memory overhead, code like these slowly adds together and makes your reference counted model heavily dependent on auto-release pools. That is, objects that you know could be deallocated, will still linger around in the auto-release pool for a little longer.

Automatic Reference Counting

Say hello to ARC. ARC is a compiler feature that auto inserts retain and release for you. So in the first code block of the above example, you no longer have to write the release method and ARC auto-inserts for you before compilation.


{
NSDictionary *dict = [[NSDictionary alloc] init];
// do something with the dictionary here
// I'm done with the dictionary, I don't need it anymore
[dict release]; // ARC inserted
// more code
}


When you create an autoreleased object, like in the second block of code, ARC compiler is clever enough not to add a release call. Sound great, so how should I go about doing this ARC thing? Just delete all release/retain codes and pray? Unfortunately, it isn’t that easy. ARC is not just some auto insert or macro expander kind of tool. It forces you to think in terms of object graphs instead of memory allocation, retain or release. Let’s delve a little deeper into ARC.

Compiler level feature


ARC is a compiler level feature. I repeat. ARC IS A COMPILER LEVEL FEATURE. This means, when you use ARC, you don’t have to worry about upgrading your deployment target and so on. However, only the latest LLVM 3.0 compiler supports ARC. If you are still stuck with GCC, you are out of luck. (Meh!) Some more points that you should know about ARC are,

  • ARC is backward compatible with libraries and framework compiled with under non-ARC.
  • ARC can be used within your project on a file-by-file basis. So you can mix and match ARC code with non-ARC code.
  • You can also integrate ARC compiled libraries into your project that doesn’t use ARC and vice-versa.
  • You use a compiler switch to turn ARC on and off.
  • (The keyword here is compiler switch)
  • You can also set the complete target to build with ARC by default (and use non-ARC compiler only when instructed so.) This is shown in the illustration below.
  • ARC  Xcode

The two main compiler switches that you would often use are when you build your application with a third party library that is not ARC compliant and vice versa, are

  • -fno-objc-arc
  • -fobjc-arc

-f is the switch and no-objc-arc and objc-arc are the options that you are turning on. As evident from the names, the first one turns off ARC and the second turns on.

For example, if your application is ARC enabled but a third party library is not, you use the first switch -fno-objc-arc to exclude the third party library. Conversely, if your application is not yet ARC enabled (gasp!) but the third party library you are integrating is, you use the second switch -fobjc-arc You add these flags to the project from the Build phases tab as shown below.Xcode 2 1

Also a run time feature


Wait! You just told me (and repeated) that ARC is a compiler level feature? Now what? Sorry, I hear you, but, unfortunately, things aren’t that easy and it doesn’t just stop here. ARC also backs up on a runtime feature called zero-ing weak references. Oh, damn, another keyword! I should have introduced this before. But that’s ok. We will revisit about the run-time dependency of ARC, a little later in this post.

ARC Ownership qualifiers


As I showed you earlier, ARC automatically inserts releases and retains in your code in a pre-compilation step. But for ARC to know when to release your objects and when to retain them, you need to somehow tell the life of your variables. You use ownership qualifiers for that. A strong understanding of ownerships is vital to understand and use ARC properly. Once you understand this concept, you will be thinking in terms of object graphs instead of retain/release. Secondly, when you use ARC, all variables local or ivars are initialized to nil automatically for you. This means, there is little chance of having a dangling reference in your application.

  • __strong
  • __weak
  • __unsafe_unretained
  • __autoreleasing

The first qualifier, __strong, is the default and you might not even be using this explicitly. It is use to tell the ARC compiler that, the declared variable “owns” the reference. The opposite of this is __weak, which tells the ARC compiler that the declared variable doesn’t own the reference.

The __weak is synonymous to the “assign” modifier. You normally use assign modifier for IBOutlets and delegates. Under ARC, this is replaced with __weak. However, there is a caveat. __weak requires you to deploy the app on a runtime that supports zero-ing weak references. This includes, iOS 5 and Lion. Snow Leopard and older operating systems or iOS 4 and older operating systems don’t support zero-ing weak references. This obviously means you cannot use __weak ownership modifiers if you plan to deploy to older operating systems. Fret not. ARC includes another ownership qualifier, __unsafe_unretained that is synonymous to __weak, except that when the pointer is de-referenced, it is not set to nil, but remains dangling. A while ago, I told something about zero-ing weak references? When the runtime supports zero-ing weak references, your __weak variables are automatically set to nil when they are released. This is the only feature that requires a higher deployment target (iOS 5/Lion). Otherwise, you are good to deploy on iOS 4/Snow Leopard.

A couple other important things to know about __weak vs __unsafe_unretained is that, the compiler doesn’t allow you to use __weak when your deployment target is set to a operating system that doesn’t support zero-ing weak references. The Convert to Objective-C ARC wizard uses __weak only when your deployment target supports zero-ing weak references. So if your deployment target is iOS 4, the Objective-C convert ion wizard will replace assign modifiers with __unsafe_unretained instead of __weak.

The last ownership qualifier, __auto_releasing is used mostly when passing a reference to a function for writing out. You would use this in places where you normally use pointer indirection like returning a NSError object via an out parameter.

Properties in your header file can also have the above ownership qualifiers except the __auto_releasing. When applied to properties, ARC automatically generates the correct code in dealloc to release them when the object dies.

Lastly, and more importantly, all of ARC managed objects are initialized to nil when they are created. So, again, no more dangling pointers because you forgot a initialize statement. However, do note that this initialization doesn’t initialize primitive data types. So a declaration like,


int a;


might contain a garbage value for a.

Whew! That’s pretty taxing. Take a break. We just started.

ARC knows more Objective-C than you


ARC also taps into a the Objective-C language naming conventions and infers the ownership of the returned object.

In Objective-C, a method that stats with any one of the following prefix,

  • init,
  • alloc,
  • copy,
  • mutableCopy and
  • new

are considered to be transferring ownership of the returned object to the caller.

This means, in your application, when you create a method, ARC automatically infers whether to return a autoreleased object or a +1 retained object from your method name. In fact, in most cases, instead of returning auto-release objects, ARC just inserts a manual release in the calling code, automatically for you. However, there is a small caveat. Let’s assume that you have a method that starts with “copy”, as in


-(NSString*) copyRightString;


ARC assumes that it would transfer the ownership of the returned string to the caller and inserts a release automatically. Everything works well, if both the called method and the calling method are compiled using ARC.

But if your “copyRightString” method is in a third party library that isn’t compiled with ARC, you will over-release the returned string. This is because, on the calling code, ARC compiler inserts a release to balance out the retain count bumped up by the “copy” method. Conversely, if the third party library is compiled with ARC and your method isn’t, you will have a memory leak. You can however override this behavior by adding one of the following attribute to your methods.

  • NS_RETURNS_NON_RETAINED
  • NS_RETURNS_RETAINED

So your method will now look like this.


-(NSString*) copyRightString NS_RETURNS_NON_RETAINED;


You can also rename the method name to copyrightString (note the case) or getCopyRightString instead of adding an attribute. However, I wouldn’t recommend the former method as it breaks the cocoa naming conventions (prefixing a method with “get” or “set” is Java-ish)

You will see methods having the NS_RETURNS_* prefixes throughout the header files in Apple’s own UIKit.framework or the foundation classes. Now that you know what happens behind the scenes and how compiler treats these decorations, you can solve crazy memory issues, like a crash when you call a copyRightString in your method in a third party library.

With that, let’s get ready for climbing the next peak.

Toll-free bridging


ARC doesn’t manage Core Foundation objects. They say, there is no free lunch. ARC, takes it one step further. There is no free-casting between Core Foundation objects and equivalent Objective-C objects (NS* objects). Yes, that’s right. You cannot cast a Core Foundation object to an equivalent Objective-C object (NS* object) without telling ARC how to manage ownerships.

Let’s now see how to specify ownership transfers when you cast a Core Foundation object.

The following ownership transfer modifiers should be provided when you cast a Objective-C object to a Core Foundation object.

  • __bridge
  • __bridge_retained
  • __bridge_transfer

When you migrate a project to ARC, you would have seen error messages like the one below.

Toll free bridging
ARC Error because of a missing bridge attribute in a Toll-free bridging code

You might also have proceeded by accepting the suggestions provided by the LLVM compiler. But now, let’s dig deeper and understand the “why” behind it.

The modifier, __bridge tells the ARC compiler that, it’s a plain simple, bridging cast. That means, you ask the ARC compiler to do nothing extra when the transfer is made. You might think, if that is the case, Apple could have made this the default choice. But it was not made probably because, it’s to preposterous to make such an assumption. Making such a bold assumption means, you would easily leak memory as there isn’t a easier way to tell when you are actually releasing a Core Foundation object unlike a Objective-C object.

The second modifier, __bridge_retained is used to tell the ARC compiler that the Objective-C object should be transferred to Core Foundation by bumping the retain count by 1 and it should be treated as if it is a newly created object (as opposed to a auto-released object). You use this modifier if the method was probably named like a creation method (starting with init, copy, mutableCopy etc.,) or if you are going to release the Objective-C object inside of Core Foundation using methods like CFRelease.

The last modifier, __bridge_transfer is used to tell the ARC compiler that the Core Foundation object is to be transferred to ARC with a retain count of 1. This is used if you created a Core Foundation object using one of the CF***Create methods and want the ARC compiler to handle the memory management for you. That’s you are transferring a Core Foundation object to ARC with a retain count of 1.

As a side note on this, avoid using __bridge_retained and __bridge_transfer to trick the compiler to add retain and releases for you. Use it to improve your code readability and minimizing the number of manual memory management calls. (Move on if you don’t understand this line. You will start understanding this automatically when you start using this in your own code)

How does ARC work internally?


ARC ain’t magic, if you know how it works. But a little knowledge is a dangerous thing. Knowing how the ARC compiler works will help you more in understanding the error messages and compiler warnings spat out by it.

The ARC compiler has two main parts, a front end compiler and an optimizer.

ARC front end


The ARC front end compiler checks for every “owned” Objective-C object and inserts release appropriately. By owned object, I mean, an object whose ownership qualifier has been set. For example, if the “owned” object is a local variable, ARC front end compiler inserts a release at the end of the scope. This is because, by default all local variables are “strong” ly owned. If the object is a instance variable, the ARC front end compiler inserts a release statement in the dealloc method, if the ownership type is strong. For unsafe_unretained or weak ownership ARC doesn’t do anything. It also takes care of calling the [super dealloc] for you and intact ARC compiler doesn’t allow you to explicitly call dealloc.

The ARC front end compiler also takes care of generating errors when it encounters a variable (local or instance) whose ownership qualifier is not set or when you explicitly calling dealloc.

ARC optimizer


The function of the ARC optimizer is to optimize the retain and release statements by removing them if they are inserted multiple times by the ARC front end compiler. It is this optimizer that ensures that performance is not affected by calling retain and release multiple times.

The actual Migration using Xcode 4.2


Xcode 4.2 has a wizard to automatically migrate your code for use with the ARC compiler. This means, the wizard rewrites some of your code, removes calls to retain/release and removes dealloc methods and calls to [super dealloc] for you.

The first step is to open your project, select Edit -> Refactor -> Convert to Objective-C ARC from the menu.

Refactor option
Migrating to Objective-C ARC using Xcode 4.2

When you select this option, you will be asked to select a target. If you have only one target, it’s fine. If you have multiple targets in your application, you have to perform the ARC migration on every target. After you select a target, the wizard by default selects all source code files that belong to that project for ARC migration. If you are using third party libraries that are not yet ARC ready, you can uncheck those files in this step. This is illustrated in the screenshot below.

Cannot convert
Selecting your files for ARC exclusion

In the above project, since I know that ASIHttpRequest is not yet ARC compatible, I’m selecting them and command-clicking them to show the option to uncheck all of them. When you do this, the wizard automatically adds a -fno-objc-arc compiler flag for all these files.

The next step is to start the pre-checking process. The pre-checking process compiles the project and analyzes for potential problems before performing the actual migration. You might almost and always get a error message like this.

Cannot convert
The dreaded error message!

Of course, 58 errors in this screenshot is actually quite low. You should expect anywhere in the range of 300+ for a mid sized project. But fret not, they aren’t complicated at all to fix.

Common ARC migration errors


The number of errors that might prevent you from converting your project to ARC is usually high if your code is “old” or if it doesn’t adhere to Objective-C design patterns. For example, accessing a iVar. While it’s technically ok, you should almost and always use properties to access them outside of init and dealloc methods. If you have been using properties, ARC migration would be painless. If you were old skool, you have to feel the pain now. In this last section, I’ll show you the most commonly occurring errors when you migrate your project.

Cast of Objective-C pointer to C Pointer type


This error is generated because ARC doesn’t do toll-free bridging for you. As I explained before in the section, Toll-free bridging, requires the developer to explicitly specify ownership transfer qualifiers.

Use the various ownership transfer qualifiers I showed you before to fix this problem.

performSelector may cause a leak because its selector is unknown


We now know that Objective-C ARC compiler knows more Objective-C than you. This error message is because of that. The ARC compiler tries to identify the method family and determine whether to add a retain or release to the returned value from the caller code. This means, if your method starts with init, alloc, copy, mutableCopy or new, the ARC compiler will add a release to the calling code after the variable scope ends. Since are using a selector to call a method dynamically at runtime, ARC doesn’t really know if the method called returns a +1 retained object or a auto-released object. As such, ARC cannot reliably insert a retain or release to the returned object after its scope ends. This warning is shown to warn you of potential memory leaks.

If you are sure that your code works fine without memory leaks, you can ignore this warning. To suppress this warning, you can turn off the compiler flag -Warc-performSelector-leaks warning on a line by line basis like this.


#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
[self performSelector:self.mySel];
#pragma clang diagnostic pop


Unfortunately, you cannot annotate a dynamic selector using __attribute__ ((objc_method_family(*))).

Receiver type “*” doesn’t declare the method with selector “*”


ARC mandates that every method you call should be declared properly. With GCC, this was a warning. But LLVM makes this step mandatory since ARC needs to accurately identify and that is why you see an error like this.

Undeclared Selectors
Error that you see when you don't declare a receiver type

This error is also because of the fact that ARC needs to identify the method family to determine if it has to add a retain or release to the returned object. For example, in the above code, the method, returnMyGreatObject might return a NS_RETURNS_RETAINED. In this case, the ARC compiler has to insert a release after the returned object goes out of scope. The ARC compiler can know this only when you declare it formally. This is why, under ARC method declarations are mandatory. If you have been declaring methods formally under GCC, even when the compiler didn’t enforce (so that the code was aesthetically beautiful) you wouldn’t see this error at all. As I said before, the number of ARC migration errors is directly proportional to the quality of code you write. Fixing this error is fairly simple and all you have to do is to declare every method formally in the header file or on a private category extension.

Common workarounds that you use in ARC on code that otherwise looks normal


In some cases, while using ARC, you might end up writing code that looks as if it’s written to “please” the ARC compiler rather than writing natural code. Unfortunately, nothing can be done to this and we all have to live with this. The next two sections explain when you might need to write unnecessary code like this to please the compiler.

Capturing “*” strongly is likely to lead to a retain cycle


Capture
ARC and retain cycles

The last category of warning message is shown when a retain cycle is detected in your code. An example is shown below.

This code was probably leaking the request object before ARC and increasing your memory footprint. But, thanks to ARC. You now know that code like these cause retain cycles that cannot be released automatically. Circumventing a retain cycle issue almost and always ends up breaking the cycle with a weak reference.

Fixing this error is fairly simple and in this case, you can get a weak reference to the request object and copy it to the block. Within the block, convert it again to a strong reference. This is illustrated below.

Capture fixed
Workaround for ARC and retain cycle issue

In the above code block, you can also replace references to __unsafe_unretained with __weak if you are deploying to a runtime that supports zero-ing weak references.

Avoiding retain cycles using __block


Sometimes, you need an object to live till as long as the completion handler on it can live. For example, a Block based UIAlertView can call a completion handler after the user presses a button on the UIAlertView.

For example,


UIAlertView *alertView = [UIAlertView alertViewWithTitle:@"Test" buttons:[NSArray arrayWithObjects:@"Ok", @"Cancel", nil] completionHandler:^(int tappedButtonIndex)  {

// do something based on the button tapped on alertView
}];
[alertView show];


In the above case, the alertView gets deallocated by ARC as soon as it’s shown and the call to completionHandler never gets executed (or even crashes).

To prevent this, you can use the __block decoration on UIAlertView declaration and copy it inside the block like


__block UIAlertView *alertView = [UIAlertView alertViewWithTitle:@"Test" buttons:[NSArray arrayWithObjects:@"Ok", @"Cancel", nil] completionHandler:^(int tappedButtonIndex)  {

// do something based on the button tapped on alertView
alertView = nil;
}];
[alertView show];


ARC takes care of releasing it when you nil it inside the completionHandler. You will find this pattern used a lot when you work with completionHandlers in TwTweetComposeViewController or even UIViewController presentViewController:animated:completion: methods.

That last question


When should you migrate?


NOW

The performance benefits you get by using ARC is remarkable. Apple claims that the @autoreleasepool is over 6 times faster than NSAutoReleasePool objects used in your non-ARC code. This is because, @autoreleasepools don’t allocate objects and all it does is must bump up the pointer retain counts. Similarly, NSObjects’ retain and release are optimized that you can expect a performance boost of anywhere around 2.5x. The third important performance benefit you will see is in methods that return autoreleased object. Under ARC, this variable is no longer transferred using the auto-release pool and what instead happens is a ownership transfer. Again this is upto 20x faster.

Hence, don’t wait till your dependent third party frameworks are migrated to ARC. You can always exclude them and go ahead and convert your code to ARC now.

Where to go from here?


  • WWDC 2011 – Session 322 Introduction to Automatic Reference Counting
  • WWDC 2011 – Session 322 Objective-C Advancements in depth
  • Stop: You are warned. This link is only for hard code geeks. http://clang.llvm.org/docs/AutomaticReferenceCounting.html
  • WWDC 2011 – Session 308 – Blocks and Grand Central Dispatch in Practice

One last word, treat this post as a living document. I’ll be updating the last few sections on new workarounds as and when I find a fix for them.



Mugunth