Beginning icloud development - Cesare Rocchi - WhyMCA
Upcoming SlideShare
Loading in...5
×

Like this? Share it with your network

Share
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Be the first to comment
    Be the first to like this
No Downloads

Views

Total Views
1,947
On Slideshare
1,887
From Embeds
60
Number of Embeds
1

Actions

Shares
Downloads
26
Comments
0
Likes
0

Embeds 60

http://www.whymca.org 60

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
    No notes for slide

Transcript

  • 1. Beginning iClouddevelopmentRocchi Cesare @_funkyboy studiomagnolia.com
  • 2. Outline What is iCloud? How does it work? Are there alternatives?
  • 3. Who am I?
  • 4. UX designer and developer
  • 5. mnml
  • 6. < is >
  • 7. execution matters
  • 8. lean approach
  • 9. 1000 details coming together
  • 10. Giveaway
  • 11. 1 of the Wenderlich’s raywenderlich.com
  • 12. ARC GameCenter API Storyboards News Stand iCloudTurn Based Gaming OpenGL ES 2.0
  • 13. twitter.com/_funkyboycesare@studiomagnolia.com
  • 14. Giveaway
  • 15. Giveaway(yes, another)
  • 16. www.icloudfordevelopers.com
  • 17. UIDocument Key-Value store ConflictResolution CoreData Custom Documents www.icloudfordevelopers.com
  • 18. twitter.com/_funkyboycesare@studiomagnolia.com www.icloudfordevelopers.com
  • 19. Who are you?
  • 20. What is iCloud?
  • 21. 6028 Startown Rd, Maiden, NC
  • 22. Stores and synchs stuff
  • 23. It just works ...
  • 24. ... when it works
  • 25. Seamlessness can be a limit
  • 26. Pros (for devs) No server setup No costs No rumination on synch
  • 27. Cons (for devs) Stick to a synch model No http API No control on upload
  • 28. Pros and Cons for usersExpectation
  • 29. Under the hood
  • 30. DaemonMonitors changesWorks on metadataShreds files
  • 31. Special folder, synched
  • 32. Synched when “appropriate”
  • 33. AppropriateWhich OS?Which connection?Battery status?
  • 34. Placeholders
  • 35. Information Structure Document Key-value CoreData
  • 36. UIDocument
  • 37. UIDocumentNSFilePresenterNon-blocking read/write
  • 38. -(void) openWithCompletionHandler:^(BOOL success) { }
  • 39. - (BOOL)loadFromContents:(id)contents ofType:(NSString *)typeName error:(NSError **)outError { }
  • 40. @interface SMNote : UIDocument
  • 41. @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;}
  • 42. - (BOOL) saveToURL:(NSURL *)url forSaveOperation:UIDocumentSaveForOverwriting completionHandler:^(BOOL success) { }
  • 43. - (id)contentsForType:(NSString *)typeName error:(NSError **)outError {}
  • 44. - (id)contentsForType:(NSString *)typeName error:(NSError **)outError { return [NSData dataWithBytes:[self.myContent UTF8String] length:[self.myContent length]];}
  • 45. AutosaveupdateChangeCount:use the methods of the undoManager
  • 46. @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 {}
  • 47. Opening a document
  • 48. Opening a documentBuild and run a queryWait for resultsUnfold results
  • 49. #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
  • 50. NSMetadataQuery
  • 51. - (void)loadDocument { NSMetadataQuery *query = [[NSMetadataQuery alloc] init]; _query = query; [query setSearchScopes:[NSArray arrayWithObject: NSMetadataQueryUbiquitousDocumentsScope]];}
  • 52. - (void)loadDocument { NSMetadataQuery *query = [[NSMetadataQuery alloc] init]; _query = query; [query setSearchScopes:[NSArray arrayWithObject: NSMetadataQueryUbiquitousDocumentsScope]]; NSPredicate *pred = [NSPredicate predicateWithFormat: @"%K == %@", NSMetadataItemFSNameKey, kFILENAME]; [query setPredicate:pred];}
  • 53. - (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];}
  • 54. NSPredicate *pred = [NSPredicate predicateWithFormat: @"%K like Note_*", NSMetadataItemFSNameKey];
  • 55. Asynchronous!
  • 56. - (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];}
  • 57. - (void)loadData:(NSMetadataQuery *)query { if ([query resultCount] == 1) { NSMetadataItem *item = [query resultAtIndex:0]; NSURL *url = [item valueForAttribute:NSMetadataItemURLKey]; SMNote *doc = [[SMNote alloc] initWithFileURL:url]; }}
  • 58. - (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"); } }]; }}
  • 59. else { NSURL *ubiq = [[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:nil]; NSURL *ubiquitousPackage = [[ubiq URLByAppendingPathComponent: @"Documents"] URLByAppendingPathComponent:kFILENAME]; SMNote *doc = [[SMNote alloc] initWithFileURL:ubiquitousPackage];}
  • 60. 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:UIDocumentSaveForCreatingcompletionHandler:^(BOOL success) { if (success) { [doc openWithCompletionHandler:^(BOOL success) { NSLog(@"new document opened from iCloud"); }]; } }]; }
  • 61. - (BOOL)application:(UIApplication *)applicationdidFinishLaunchingWithOptions:(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;}
  • 62. - (BOOL)application:(UIApplication *)applicationdidFinishLaunchingWithOptions:(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;}
  • 63. - (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;}
  • 64. Switching on/off
  • 65. - (NSURL *) localNotesURL { return [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];}- (NSURL *) ubiquitousNotesURL { return [[[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:nil] URLByAppendingPathComponent:@"Documents"];}
  • 66. - (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!
  • 67. - (void) startMigration { NSOperationQueue *iCloudQueue = [NSOperationQueue new]; NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(setNoteUbiquity) object:nil]; [iCloudQueue addOperation:op];}
  • 68. Custom documents
  • 69. SMNotesDocument SMNote SMNote SMNote ...
  • 70. @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
  • 71. #import "SMNote.h"@interface SMNotesDocument : UIDocument@property (nonatomic, strong) NSMutableArray *entries;@property (nonatomic, strong) NSFileWrapper *fileWrapper;@end
  • 72. #import "SMNote.h"@interface SMNotesDocument : UIDocument@property (nonatomic, strong) NSMutableArray *entries;@property (nonatomic, strong) NSFileWrapper *fileWrapper;@end
  • 73. - (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];}
  • 74. - (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;}
  • 75. - (BOOL)loadFromContents:(id)contents ofType:(NSString *)typeName error:(NSError **)outError { NSFileWrapper *wrapper = (NSFileWrapper *)contents; NSDictionary *d = [wrapper fileWrappers]; NSFileWrapper *entriesWrap = [d objectForKey:@"notes.dat"];}
  • 76. - (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}
  • 77. Uniform Type Identifier
  • 78. Key-value
  • 79. Key-value 1Mb
  • 80. Key-value 1Mb was 64Kb !
  • 81. - (void) saveNoteAsCurrent { [[NSUbiquitousKeyValueStore defaultStore] setString:self.currentNote.noteId forKey:@"com.studiomagnolia.currentNote"]; [[NSUbiquitousKeyValueStore defaultStore] synchronize];}
  • 82. - (void) saveNoteAsCurrent { [[NSUbiquitousKeyValueStore defaultStore] setString:self.currentNote.noteId forKey:@"com.studiomagnolia.currentNote"]; [[NSUbiquitousKeyValueStore defaultStore] synchronize];} NSString *currentNoteId = [[NSUbiquitousKeyValueStore defaultStore] stringForKey: @"com.studiomagnolia.currentNote"];
  • 83. NSUbiquitousKeyValueStore* store = [NSUbiquitousKeyValueStore defaultStore];[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(updateCurrentNoteIfNeeded:) name:NSUbiquitousKeyValueStoreDidChangeExternallyNotification object:store];[store synchronize];
  • 84. Conflict Resolution
  • 85. Conflict ResolutionUp to the devdocumentState
  • 86. DocumentStatesUIDocumentStateNormalUIDocumentStateClosedUIDocumentStateInConflictUIDocumentStateSavingErrorUIDocumentStateEditingDisabled
  • 87. UIDocumentStateChangedNotification
  • 88. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(noteHasChanged:) name:UIDocumentStateChangedNotification object:nil];
  • 89. 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;}
  • 90. UI conflict vsiCloud conflict
  • 91. Resolution policy last wins prompt user automatic merge
  • 92. Resolution policy last wins prompt user automatic merge NSFileVersion
  • 93. NSError *err;NSURL *url = [[NSFileManager defaultManager] URLForPublishingUbiquitousItemAtURL:[self.currentNote fileURL] expirationDate:&expirationInOneHourSinceNow error:&err];
  • 94. Tips & Tricks
  • 95. Patience!
  • 96. Test on wireless & 3G
  • 97. Regenerate provisioning
  • 98. Delete previous data
  • 99. Restart device
  • 100. API throttle!
  • 101. App policyBe gentle with storage<App_home>/tmp<App_home>/Library/Caches/
  • 102. App policyDocuments is backed upmark files as “do not backup”
  • 103. // 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;}
  • 104. // 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;}
  • 105. “To iCloud or not to iCloud?”
  • 106. Alternatives
  • 107. Alternatives dropbox parse.com cloudmine stackmob custom
  • 108. Dropboxdocumentsauthenticationno notifications
  • 109. Dropboxother platformsno CR (revision #)expectation
  • 110. Parse
  • 111. ParseORM approachRecently releasedNo cost of infrastructure
  • 112. ParsePay as you useLimit of calls/mo
  • 113. PFObject *note = [PFObject objectWithClassName:@"Note"];[note setObject:@"Ciao" forKey:@"title"];[note setObject:@"Note on Parse" forKey:@"content"];[note save];//[note saveInBackground];//[note saveEventually];
  • 114. [note saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error){ if (error) { NSLog(@"Note not saved"); } else { NSLog(@"Note saved successfully"); }}];
  • 115. ParseOther platformsREST APIPush notificationsObject browser
  • 116. 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
  • 117. 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];
  • 118. RecapUIDocumentKey-Value storeAlternatives
  • 119. “You can’t always get what you wantbut if you try sometime, you just might find ...”
  • 120. “You can’t always get what you wantbut if you try sometime, you just might find ...” Rolling Stones
  • 121. Contacttwitter.com/_funkyboycesare@studiomagnolia.comhttp://studiomagnolia.com