beginning icloud development - cesare rocchi - whymca

Post on 06-May-2015

1.499 Views

Category:

Technology

1 Downloads

Preview:

Click to see full reader

TRANSCRIPT

Beginning iCloud developmentRocchi Cesare

@_funkyboy studiomagnolia.com

Outline

What is iCloud?

How does it work?

Are there alternatives?

Who am I?

UX designer and developer

mnml

< is >

execution matters

lean approach

1000 details coming together

Giveaway

1 of the Wenderlich’s

raywenderlich.com

iCloud

Storyboards

ARC

OpenGL ES 2.0

News Stand

Turn Based Gaming

GameCenter API

twitter.com/_funkyboycesare@studiomagnolia.com

Giveaway

Giveaway(yes, another)

www.icloudfordevelopers.com

Conflict Resolution

CoreData

UIDocument

Key-Value store

Custom Documents

www.icloudfordevelopers.com

twitter.com/_funkyboycesare@studiomagnolia.com

Who are you?

What is iCloud?

6028 Startown Rd, Maiden, NC

Stores and synchs stuff

It just works ...

... when it works

Seamlessness can be a limit

Pros (for devs)

No server setup

No costs

No rumination on synch

Cons (for devs)

Stick to a synch model

No http API

No control on upload

Pros and Cons for users

Expectation

Under the hood

Daemon

Monitors changes

Works on metadata

Shreds files

Special folder, synched

Synched when “appropriate”

Appropriate

Which OS?

Which connection?

Battery status?

Placeholders

Information Structure

Document

Key-value

CoreData

UIDocument

UIDocument

NSFilePresenter

Non-blocking read/write

-(void) openWithCompletionHandler:^(BOOL success) { }

- (BOOL)loadFromContents:(id)contents ofType:(NSString *)typeName error:(NSError **)outError { }

@interface SMNote : UIDocument

@implementation SMNote

- (BOOL)loadFromContents:(id)contents ofType:(NSString *)typeName error:(NSError **)outError {

if ([contents length] > 0) { self.myContent = [[NSString alloc] initWithBytes:[contents bytes] length:[contents length] encoding:NSUTF8StringEncoding]; } else {

// Default content self.myContent = @"Empty";

} return YES; }

- (BOOL) saveToURL:(NSURL *)url forSaveOperation:UIDocumentSaveForOverwriting completionHandler:^(BOOL success) { }

- (id)contentsForType:(NSString *)typeName error:(NSError **)outError {}

- (id)contentsForType:(NSString *)typeName error:(NSError **)outError { return [NSData dataWithBytes:[self.myContent UTF8String] length:[self.myContent length]];

}

Autosave

updateChangeCount:

use the methods of the undoManager

@implementation SMNote

@synthesize noteContent;

// Called whenever the application reads data - (BOOL)loadFromContents:(id)contents ofType:(NSString *)typeName error:(NSError **)outError { }

// Called whenever the application (auto)saves the content - (id)contentsForType:(NSString *)typeName error:(NSError **)outError { }

Opening a document

Opening a document

Build and run a query

Wait for results

Unfold results

#import "SMNote.h"

@interface SMAppDelegate : UIResponder <UIApplicationDelegate>

@property (strong, nonatomic) UIWindow *window;@property (strong, nonatomic) SMViewController *viewController;

@property (strong) SMNote *doc;@property (strong) NSMetadataQuery *query;

- (void)loadDocument;

@end

NSMetadataQuery

- (void)loadDocument { NSMetadataQuery *query = [[NSMetadataQuery alloc] init]; _query = query;

[query setSearchScopes:[NSArray arrayWithObject: NSMetadataQueryUbiquitousDocumentsScope]]; }

- (void)loadDocument { NSMetadataQuery *query = [[NSMetadataQuery alloc] init]; _query = query;

[query setSearchScopes:[NSArray arrayWithObject: NSMetadataQueryUbiquitousDocumentsScope]];

NSPredicate *pred = [NSPredicate predicateWithFormat: @"%K == %@",

NSMetadataItemFSNameKey, kFILENAME];

[query setPredicate:pred]; }

- (void)loadDocument { NSMetadataQuery *query = [[NSMetadataQuery alloc] init]; _query = query;

[query setSearchScopes:[NSArray arrayWithObject: NSMetadataQueryUbiquitousDocumentsScope]];

NSPredicate *pred = [NSPredicate predicateWithFormat: @"%K == %@",

NSMetadataItemFSNameKey, kFILENAME];

[query setPredicate:pred];

[[NSNotificationCenter defaultCenter] addObserver:self

selector:@selector(queryDidFinish:) name:NSMetadataQueryDidFinishGatheringNotification

object:query]; [query startQuery]; }

NSPredicate *pred = [NSPredicate predicateWithFormat: @"%K like 'Note_*'", NSMetadataItemFSNameKey];

Asynchronous!

- (void)queryDidFinish:(NSNotification *)notification { NSMetadataQuery *query = [notification object]; [query disableUpdates]; [query stopQuery]; [[NSNotificationCenter defaultCenter] removeObserver:self name:NSMetadataQueryDidFinishGatheringNotification object:query]; _query = nil; ! [self loadData:query]; }

- (void)loadData:(NSMetadataQuery *)query { if ([query resultCount] == 1) {

NSMetadataItem *item = [query resultAtIndex:0]; NSURL *url = [item valueForAttribute:NSMetadataItemURLKey];

SMNote *doc = [[SMNote alloc] initWithFileURL:url];

}

}

- (void)loadData:(NSMetadataQuery *)query { if ([query resultCount] == 1) {

NSMetadataItem *item = [query resultAtIndex:0]; NSURL *url = [item valueForAttribute:NSMetadataItemURLKey];

self.doc = [[SMNote alloc] initWithFileURL:url];

[self.doc openWithCompletionHandler:^(BOOL success) {

if (success) { NSLog(@"iCloud document opened"); } else { NSLog(@"failed opening document from iCloud"); } }];

}

}

else { NSURL *ubiq = [[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:nil]; NSURL *ubiquitousPackage = [[ubiq URLByAppendingPathComponent: @"Documents"]

URLByAppendingPathComponent:kFILENAME]; SMNote *doc = [[SMNote alloc] initWithFileURL:ubiquitousPackage]; }

else { NSURL *ubiq = [[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:nil]; NSURL *ubiquitousPackage = [[ubiq URLByAppendingPathComponent: @"Documents"]

URLByAppendingPathComponent:kFILENAME]; SMNote *doc = [[SMNote alloc] initWithFileURL:ubiquitousPackage]; self.doc = doc; [doc saveToURL: [doc fileURL] forSaveOperation:UIDocumentSaveForCreating completionHandler:^(BOOL success) {

if (success) { [doc openWithCompletionHandler:^(BOOL success) { NSLog(@"new document opened from iCloud"); }]; } }];

}

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {

... [self.window makeKeyAndVisible]; NSURL *ubiq = [[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:nil];

if (ubiq) {

NSLog(@"iCloud access at %@", ubiq); [self loadDocument];

} else {

NSLog(@"No iCloud access");

} return YES;}

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {

... [self.window makeKeyAndVisible]; NSURL *ubiq = [[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:nil];

if (ubiq) {

NSLog(@"iCloud access at %@", ubiq); [self loadDocument];

} else {

NSLog(@"No iCloud access");

} return YES;}

- (void)viewDidLoad {

[super viewDidLoad]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(dataReloaded:) name:@"noteModified"

object:nil];}

- (void)dataReloaded:(NSNotification *)notification { self.doc = notification.object; self.noteView.text = self.doc.noteContent; }

Switching on/off

- (NSURL *) localNotesURL { return [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject]; }

- (NSURL *) ubiquitousNotesURL { return [[[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:nil] URLByAppendingPathComponent:@"Documents"]; }

- (void) setNoteUbiquity { NSURL *baseUrl = [self localNotesURL]; if (_useiCloud) baseUrl = [self ubiquitousNotesURL]; NSURL *destUrl = [baseUrl URLByAppendingPathComponent: [note.fileURL lastPathComponent]];

[[NSFileManager defaultManager] setUbiquitous:_useiCloud itemAtURL:note.fileURL destinationURL:destUrl error:NULL]; }

Don’t call it on the main thread!

- (void) startMigration {

NSOperationQueue *iCloudQueue = [NSOperationQueue new]; NSInvocationOperation *op =

[[NSInvocationOperation alloc] initWithTarget:self selector:@selector(setNoteUbiquity) object:nil];

[iCloudQueue addOperation:op]; }

Custom documents

SMNotesDocument

SMNote SMNote SMNote

...

@interface SMNote : NSObject <NSCoding>

@property (copy, nonatomic) NSString *noteId;@property (copy, nonatomic) NSString *noteContent;@property (strong, nonatomic) NSDate *createdAt;@property (strong, nonatomic) NSDate *updatedAt;

@end

#import "SMNote.h"

@interface SMNotesDocument : UIDocument

@property (nonatomic, strong) NSMutableArray *entries;@property (nonatomic, strong) NSFileWrapper *fileWrapper;

@end

#import "SMNote.h"

@interface SMNotesDocument : UIDocument

@property (nonatomic, strong) NSMutableArray *entries;@property (nonatomic, strong) NSFileWrapper *fileWrapper;

@end

- (id)contentsForType:(NSString *)typeName error:(NSError **)outError {

NSMutableDictionary *w = [NSMutableDictionary dictionary]; NSMutableData *data = [NSMutableData data]; NSKeyedArchiver *arch = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data]; [arch encodeObject:_entries forKey:@"entries"]; [arch finishEncoding]; }

- (id)contentsForType:(NSString *)typeName error:(NSError **)outError {

NSMutableDictionary *w = [NSMutableDictionary dictionary]; NSMutableData *data = [NSMutableData data]; NSKeyedArchiver *arch = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data]; [arch encodeObject:_entries forKey:@"entries"]; [arch finishEncoding]; NSFileWrapper *entriesWrapper = [[NSFileWrapper alloc]

initRegularFileWithContents:data]; [w setObject:entriesWrapper forKey:@"notes.dat"]; // add other wrappers if you like NSFileWrapper *res = [[NSFileWrapper alloc] initDirectoryWithFileWrappers:w]; return res; }

- (BOOL)loadFromContents:(id)contents ofType:(NSString *)typeName error:(NSError **)outError { NSFileWrapper *wrapper = (NSFileWrapper *)contents; NSDictionary *d = [wrapper fileWrappers]; NSFileWrapper *entriesWrap = [d objectForKey:@"notes.dat"]; }

- (BOOL)loadFromContents:(id)contents ofType:(NSString *)typeName error:(NSError **)outError { NSFileWrapper *wrapper = (NSFileWrapper *)contents; NSDictionary *d = [wrapper fileWrappers]; NSFileWrapper *entriesWrap = [d objectForKey:@"notes.dat"]; NSData *data = [entriesWrap regularFileContents]; NSKeyedUnarchiver *arch = [[NSKeyedUnarchiver alloc] initForReadingWithData:data]; _entries = [arch decodeObjectForKey:@"entries"]; // Notify the view

}

Uniform Type Identifier

Key-value

Key-value1Mb

Key-value1Mb

was 64Kb !

- (void) saveNoteAsCurrent {

[[NSUbiquitousKeyValueStore defaultStore] setString:self.currentNote.noteId forKey:@"com.studiomagnolia.currentNote"];

[[NSUbiquitousKeyValueStore defaultStore] synchronize];

}

- (void) saveNoteAsCurrent {

[[NSUbiquitousKeyValueStore defaultStore] setString:self.currentNote.noteId forKey:@"com.studiomagnolia.currentNote"];

[[NSUbiquitousKeyValueStore defaultStore] synchronize];

}

NSString *currentNoteId = [[NSUbiquitousKeyValueStore defaultStore] stringForKey: @"com.studiomagnolia.currentNote"];

NSUbiquitousKeyValueStore* store = [NSUbiquitousKeyValueStore defaultStore];

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(updateCurrentNoteIfNeeded:) name:NSUbiquitousKeyValueStoreDidChangeExternallyNotification object:store];

[store synchronize];

Conflict Resolution

Conflict Resolution

Up to the dev

documentState

DocumentStates

UIDocumentStateNormal

UIDocumentStateClosed

UIDocumentStateInConflict

UIDocumentStateSavingError

UIDocumentStateEditingDisabled

UIDocumentStateChangedNotification

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(noteHasChanged:) name:UIDocumentStateChangedNotification object:nil];

UIDocumentState s = [n documentState]; switch (s) {

case UIDocumentStateNormal: NSLog(@"Everything is fine"); break;

case UIDocumentStateInConflict: NSLog(@"There is a conflict"); break;

...

default: NSLog(@"Unknown state"); break;

}

UI conflict vs

iCloud conflict

Resolution policy

last wins

prompt user

automatic merge

Resolution policy

last wins

prompt user

automatic merge

NSFileVersion

NSError *err; NSURL *url = [[NSFileManager defaultManager] URLForPublishingUbiquitousItemAtURL:[self.currentNote fileURL] expirationDate:&expirationInOneHourSinceNow error:&err];

Tips & Tricks

Patience!

Test on wireless & 3G

Regenerate provisioning

Delete previous data

Restart device

API throttle!

App policy

Be gentle with storage

<App_home>/tmp

<App_home>/Library/Caches/

App policy

Documents is backed up

mark files as “do not backup”

// iOS 5.0.1

#import <sys/xattr.h>

- (BOOL)addSkipBackupAttributeToItemAtURL:(NSURL *)URL { const char* filePath = [[URL path] fileSystemRepresentation]; const char* attrName = "com.apple.MobileBackup"; u_int8_t attrValue = 1; int result = setxattr(filePath, attrName, &attrValue,

sizeof(attrValue), 0, 0); return result == 0;

}

// iOS 5.1

- (BOOL)addSkipBackupAttributeToItemAtURL:(NSURL *)URL { NSError *error = nil;

BOOL success = [URL setResourceValue: [NSNumber numberWithBool:YES] forKey: NSURLIsExcludedFromBackupKey

error: &error]; if(!success){

NSLog(@"Error excluding %@ from backup %@", [URL lastPathComponent], error);

}

return success;}

“To iCloud or not to iCloud?”

Alternatives

Alternatives

dropbox

parse.com

cloudmine

stackmob

custom

Dropbox

documents

authentication

no notifications

Dropbox

other platforms

no CR (revision #)

expectation

Parse

Parse

ORM approach

Recently released

No cost of infrastructure

Parse

Pay as you use

Limit of calls/mo

PFObject *note = [PFObject objectWithClassName:@"Note"];

[note setObject:@"Ciao" forKey:@"title"];

[note setObject:@"Note on Parse" forKey:@"content"];

[note save];//[note saveInBackground];//[note saveEventually];

[note saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) { if (error) {

NSLog(@"Note not saved");

} else {

NSLog(@"Note saved successfully");

}}];

Parse

Other platforms

REST API

Push notifications

Object browser

curl -X POST \-H "X-Parse-Application-Id: ${APPLICATION_ID}" \-H "X-Parse-REST-API-Key: ${REST_API_KEY}" \-H "Content-Type: application/json" \-d '{"note": 001, "title": "Ciao", "content": “Note on parse” }' \https://api.parse.com/1/classes/GameScore

PFObject *note = [PFObject objectWithClassName:@"Note"];

[note setObject:@"Ciao" forKey:@"title"];

[note setObject:@"Note on parse" forKey:@"content"];

PFObject *myTag = [PFObject objectWithClassName:@"Tag"];

[myTag setObject:@"important" forKey:@"tagName"];

// Add a relation[note setObject:myTag forKey:@"tag"];

// Saves both[note saveInBackground];

Recap

UIDocument

Key-Value store

Alternatives

“You can’t always get what you want

but if you try sometime, you just might find ...”

“You can’t always get what you want

but if you try sometime, you just might find ...”

Rolling Stones

Contact

twitter.com/_funkyboy

cesare@studiomagnolia.com

http://studiomagnolia.com

top related