Upload
whymca
View
1.498
Download
1
Embed Size (px)
Citation preview
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
Giveaway
Giveaway(yes, another)
www.icloudfordevelopers.com
Conflict Resolution
CoreData
UIDocument
Key-Value store
Custom Documents
www.icloudfordevelopers.com
twitter.com/[email protected]
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
http://studiomagnolia.com