Slide 1

Slide 1 text

Beginning iCloud development Rocchi Cesare

Slide 2

Slide 2 text

Outline What is iCloud? How does it work? Are there alternatives?

Slide 3

Slide 3 text

Who am I?

Slide 4

Slide 4 text

UX designer and developer

Slide 5

Slide 5 text

mnml

Slide 6

Slide 6 text

< is >

Slide 7

Slide 7 text

execution matters

Slide 8

Slide 8 text

lean approach

Slide 9

Slide 9 text

1000 details coming together

Slide 10

Slide 10 text

Giveaway

Slide 11

Slide 11 text

1 of the Wenderlich’s raywenderlich.com

Slide 12

Slide 12 text

No content

Slide 13

Slide 13 text

iCloud Storyboards ARC OpenGL ES 2.0 News Stand Turn Based Gaming GameCenter API

Slide 14

Slide 14 text

twitter.com/_funkyboy [email protected]

Slide 15

Slide 15 text

Who are you?

Slide 16

Slide 16 text

What is iCloud?

Slide 17

Slide 17 text

6028 Startown Rd, Maiden, NC

Slide 18

Slide 18 text

Stores and synchs stuff

Slide 19

Slide 19 text

It just works ...

Slide 20

Slide 20 text

... when it works

Slide 21

Slide 21 text

No content

Slide 22

Slide 22 text

Seamlessness can be a limit

Slide 23

Slide 23 text

Pros (for devs) No server setup No costs No rumination on synch

Slide 24

Slide 24 text

Cons (for devs) Stick to a synch model No http API No control on upload

Slide 25

Slide 25 text

Pros and Cons for users Expectation

Slide 26

Slide 26 text

Under the hood

Slide 27

Slide 27 text

No content

Slide 28

Slide 28 text

Daemon Monitors changes Works on metadata Shreds files

Slide 29

Slide 29 text

Special folder, synched

Slide 30

Slide 30 text

Synched when “appropriate”

Slide 31

Slide 31 text

Appropriate Which OS? Which connection?

Slide 32

Slide 32 text

Placeholders

Slide 33

Slide 33 text

No content

Slide 34

Slide 34 text

No content

Slide 35

Slide 35 text

Information Structure Document Key-value CoreData

Slide 36

Slide 36 text

UIDocument

Slide 37

Slide 37 text

UIDocument NSFilePresenter Non-blocking read/write

Slide 38

Slide 38 text

No content

Slide 39

Slide 39 text

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

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

@interface SMNote : UIDocument

Slide 42

Slide 42 text

@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; }

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

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

Slide 46

Slide 46 text

Autosave updateChangeCount: use the methods of the undoManager

Slide 47

Slide 47 text

@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 { }

Slide 48

Slide 48 text

Opening a document

Slide 49

Slide 49 text

Opening a document Build and run a query Wait for results Unfold results

Slide 50

Slide 50 text

#import "SMNote.h" @interface SMAppDelegate : UIResponder @property (strong, nonatomic) UIWindow *window; @property (strong, nonatomic) SMViewController *viewController; @property (strong) SMNote *doc; @property (strong) NSMetadataQuery *query; - (void)loadDocument; @end

Slide 51

Slide 51 text

NSMetadataQuery

Slide 52

Slide 52 text

- (void)loadDocument { NSMetadataQuery *query = [[NSMetadataQuery alloc] init]; _query = query; [query setSearchScopes:[NSArray arrayWithObject: NSMetadataQueryUbiquitousDocumentsScope]]; }

Slide 53

Slide 53 text

- (void)loadDocument { NSMetadataQuery *query = [[NSMetadataQuery alloc] init]; _query = query; [query setSearchScopes:[NSArray arrayWithObject: NSMetadataQueryUbiquitousDocumentsScope]]; NSPredicate *pred = [NSPredicate predicateWithFormat: @"%K == %@", NSMetadataItemFSNameKey, kFILENAME]; [query setPredicate:pred]; }

Slide 54

Slide 54 text

- (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]; }

Slide 55

Slide 55 text

Asynchronous!

Slide 56

Slide 56 text

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

Slide 57

Slide 57 text

- (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]; }

Slide 58

Slide 58 text

- (void)loadData:(NSMetadataQuery *)query { if ([query resultCount] == 1) { NSMetadataItem *item = [query resultAtIndex:0]; NSURL *url = [item valueForAttribute:NSMetadataItemURLKey]; SMNote *doc = [[SMNote alloc] initWithFileURL:url]; } }

Slide 59

Slide 59 text

- (void)loadData:(NSMetadataQuery *)query { if ([query resultCount] == 1) { NSMetadataItem *item = [query resultAtIndex:0]; NSURL *url = [item valueForAttribute:NSMetadataItemURLKey]; SMNote *doc = [[SMNote alloc] initWithFileURL:url]; self.doc = doc; [self.doc openWithCompletionHandler:^(BOOL success) { if (success) { NSLog(@"iCloud document opened"); } else { NSLog(@"failed opening document from iCloud"); } }]; } }

Slide 60

Slide 60 text

else { NSURL *ubiq = [[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:nil]; NSURL *ubiquitousPackage = [[ubiq URLByAppendingPathComponent: @"Documents"] URLByAppendingPathComponent:kFILENAME]; SMNote *doc = [[SMNote alloc] initWithFileURL:ubiquitousPackage]; }

Slide 61

Slide 61 text

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"); }]; } }]; }

Slide 62

Slide 62 text

- (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; }

Slide 63

Slide 63 text

- (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; }

Slide 64

Slide 64 text

- (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; }

Slide 65

Slide 65 text

Switching on/off

Slide 66

Slide 66 text

- (NSURL *) localNotesURL { return [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject]; } - (NSURL *) ubiquitousNotesURL { return [[[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:nil] URLByAppendingPathComponent:@"Documents"]; }

Slide 67

Slide 67 text

- (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!

Slide 68

Slide 68 text

- (void) startMigration { NSOperationQueue *iCloudQueue = [NSOperationQueue new]; NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(setNoteUbiquity) object:nil]; [iCloudQueue addOperation:op]; }

Slide 69

Slide 69 text

Custom documents

Slide 70

Slide 70 text

@interface SMNote : NSObject @property (copy, nonatomic) NSString *noteId; @property (copy, nonatomic) NSString *noteContent; @property (strong, nonatomic) NSDate *createdAt; @property (strong, nonatomic) NSDate *updatedAt; @end

Slide 71

Slide 71 text

#import "SMNote.h" @interface SMNotesDocument : UIDocument @property (nonatomic, strong) NSMutableArray *entries; @property (nonatomic, strong) NSFileWrapper *fileWrapper; @end

Slide 72

Slide 72 text

#import "SMNote.h" @interface SMNotesDocument : UIDocument @property (nonatomic, strong) NSMutableArray *entries; @property (nonatomic, strong) NSFileWrapper *fileWrapper; @end

Slide 73

Slide 73 text

- (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]; }

Slide 74

Slide 74 text

- (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; }

Slide 75

Slide 75 text

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

Slide 76

Slide 76 text

- (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 }

Slide 77

Slide 77 text

Uniform Type Identifier

Slide 78

Slide 78 text

No content

Slide 79

Slide 79 text

Key-value

Slide 80

Slide 80 text

Key-value 64kb :(

Slide 81

Slide 81 text

- (void) saveNoteAsCurrent { [[NSUbiquitousKeyValueStore defaultStore] setString:self.currentNote.noteId forKey:@"com.studiomagnolia.currentNote"]; [[NSUbiquitousKeyValueStore defaultStore] synchronize]; }

Slide 82

Slide 82 text

- (void) saveNoteAsCurrent { [[NSUbiquitousKeyValueStore defaultStore] setString:self.currentNote.noteId forKey:@"com.studiomagnolia.currentNote"]; [[NSUbiquitousKeyValueStore defaultStore] synchronize]; } NSString *currentNoteId = [[NSUbiquitousKeyValueStore defaultStore] stringForKey: @"com.studiomagnolia.currentNote"];

Slide 83

Slide 83 text

NSUbiquitousKeyValueStore* store = [NSUbiquitousKeyValueStore defaultStore]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(updateCurrentNoteIfNeeded:) name:NSUbiquitousKeyValueStoreDidChangeExternallyNotification object:store]; [store synchronize];

Slide 84

Slide 84 text

Conflict Resolution

Slide 85

Slide 85 text

Conflict Resolution Up to the dev documentState

Slide 86

Slide 86 text

DocumentStates UIDocumentStateNormal UIDocumentStateClosed UIDocumentStateInConflict UIDocumentStateSavingError UIDocumentStateEditingDisabled

Slide 87

Slide 87 text

UIDocumentStateChangedNotification

Slide 88

Slide 88 text

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

Slide 89

Slide 89 text

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; }

Slide 90

Slide 90 text

UI conflict vs iCloud conflict

Slide 91

Slide 91 text

Resolution policy last wins prompt user automatic merge

Slide 92

Slide 92 text

Resolution policy last wins prompt user automatic merge NSFileVersion

Slide 93

Slide 93 text

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

Slide 94

Slide 94 text

No content

Slide 95

Slide 95 text

Tips & Tricks

Slide 96

Slide 96 text

Patience!

Slide 97

Slide 97 text

Test on wireless & 3G

Slide 98

Slide 98 text

Regenerate provisioning

Slide 99

Slide 99 text

Delete previous data

Slide 100

Slide 100 text

No content

Slide 101

Slide 101 text

Restart device

Slide 102

Slide 102 text

API throttle!

Slide 103

Slide 103 text

App policy Be gentle with storage /tmp /Library/Caches/

Slide 104

Slide 104 text

“To iCloud or not to iCloud?”

Slide 105

Slide 105 text

“To iCloud or not to iCloud?”

Slide 106

Slide 106 text

Alternatives

Slide 107

Slide 107 text

Alternatives dropbox parse.com cloudmine stackmob

Slide 108

Slide 108 text

Dropbox documents authentication no notifications

Slide 109

Slide 109 text

Dropbox other platforms no CR (revision #) expectation

Slide 110

Slide 110 text

Parse

Slide 111

Slide 111 text

Parse ORM approach Still beta No cost of infrastructure

Slide 112

Slide 112 text

Parse Pay as you use Limit of objects/mo Limit of calls/mo

Slide 113

Slide 113 text

PFObject *note = [PFObject objectWithClassName:@"Note"]; [note setObject:@"Ciao" forKey:@"title"]; [note setObject:@"Note on Parse" forKey:@"content"]; [note save]; //[note saveInBackground]; //[note saveEventually];

Slide 114

Slide 114 text

[note saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) { if (error) { NSLog(@"Note not saved"); } else { NSLog(@"Note saved successfully"); } }];

Slide 115

Slide 115 text

Parse Other platforms REST API Push notifications data browser

Slide 116

Slide 116 text

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

Slide 117

Slide 117 text

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];

Slide 118

Slide 118 text

Recap UIDocument Key-Value store Alternatives

Slide 119

Slide 119 text

Contact twitter.com/_funkyboy [email protected] http://studiomagnolia.com