Download - Elements for an iOS Backend
![Page 2: Elements for an iOS Backend](https://reader034.vdocuments.site/reader034/viewer/2022050906/554a53aab4c90531228b4c5f/html5/thumbnails/2.jpg)
• This talk follows the one about API Design and starts from its principle
• Although it is mainly focused on iOS (and code example will be), principles can be applied to other environment. In particular for modern JS framework like Angular (and the creation of a service)
• Configuration: a client talks to a server
Foreword
![Page 3: Elements for an iOS Backend](https://reader034.vdocuments.site/reader034/viewer/2022050906/554a53aab4c90531228b4c5f/html5/thumbnails/3.jpg)
General Context
![Page 4: Elements for an iOS Backend](https://reader034.vdocuments.site/reader034/viewer/2022050906/554a53aab4c90531228b4c5f/html5/thumbnails/4.jpg)
!
• Make sure the client can talk to the server and fetch/display data
• Why the world fetch data each time????? We should start faster
• Did you think about Facebook login?
• Offline usage is a must
The marketing requirement road
![Page 5: Elements for an iOS Backend](https://reader034.vdocuments.site/reader034/viewer/2022050906/554a53aab4c90531228b4c5f/html5/thumbnails/5.jpg)
!
• Client side is usually determined on a “pane” basis. In iOS wording: “view controllers”
• At first fetch is done on a per pane basis with simple network calls
• When it evolves it is good to have a backend managing all
Consequences
![Page 6: Elements for an iOS Backend](https://reader034.vdocuments.site/reader034/viewer/2022050906/554a53aab4c90531228b4c5f/html5/thumbnails/6.jpg)
Architecture evolution
Pane1 Pane2 Pane1 Pane2
Backend
![Page 7: Elements for an iOS Backend](https://reader034.vdocuments.site/reader034/viewer/2022050906/554a53aab4c90531228b4c5f/html5/thumbnails/7.jpg)
Backend Roles
!
• User management : switch, registration
• Network state management : online/offline
• Data fetch in the context of one user
• Communication backend frontend
![Page 8: Elements for an iOS Backend](https://reader034.vdocuments.site/reader034/viewer/2022050906/554a53aab4c90531228b4c5f/html5/thumbnails/8.jpg)
Global Application!
• May be good to have a special objects managing the navigation (Pane Manager)
• This one can be triggered from anywhere (e.g also with an application special URL)
• Let the view controllers have a view controller logic
![Page 9: Elements for an iOS Backend](https://reader034.vdocuments.site/reader034/viewer/2022050906/554a53aab4c90531228b4c5f/html5/thumbnails/9.jpg)
Reminder: API prerequisite
!
• Each returned object is having
• an unique identifier : uuid
• an self describing type : __class_name
![Page 10: Elements for an iOS Backend](https://reader034.vdocuments.site/reader034/viewer/2022050906/554a53aab4c90531228b4c5f/html5/thumbnails/10.jpg)
Reminder: API prerequisite
![Page 11: Elements for an iOS Backend](https://reader034.vdocuments.site/reader034/viewer/2022050906/554a53aab4c90531228b4c5f/html5/thumbnails/11.jpg)
Talk & code!
• Follows Obj-C/Apple conventions
• Use MM as an example prefix
• For now, no sample code available, contact me for further questions
• Example uses only Apple API calls, no third parties components (but they may be worth be looked at)
![Page 12: Elements for an iOS Backend](https://reader034.vdocuments.site/reader034/viewer/2022050906/554a53aab4c90531228b4c5f/html5/thumbnails/12.jpg)
User management
![Page 13: Elements for an iOS Backend](https://reader034.vdocuments.site/reader034/viewer/2022050906/554a53aab4c90531228b4c5f/html5/thumbnails/13.jpg)
Storage!
• At first separate data for each user. Do not try to optimize by saying some data is common
• Store in “Application Support” or “Documents” folder, Complement with a storage in Cache for data that can be lost, with same structure
![Page 14: Elements for an iOS Backend](https://reader034.vdocuments.site/reader034/viewer/2022050906/554a53aab4c90531228b4c5f/html5/thumbnails/14.jpg)
One user - one provider• Let’s define an object “doing all for a user” as a
MMDataProvider. An entry point to manage all data for a user
• Let’s define an object managing MMDataProvider as MMDataProviderManager. It holds the main server endpoint
• The manager will also be responsible for user switch as well as network state listening (SCNetworkReachability). If the app features elements like location services, they should be here also
![Page 15: Elements for an iOS Backend](https://reader034.vdocuments.site/reader034/viewer/2022050906/554a53aab4c90531228b4c5f/html5/thumbnails/15.jpg)
As objects
MMDataProviderManager
Reachability Location Manager
Dictionary : userID/
MMDataProvider
@property (nonatomic,strong) NSString *currentUserUUID;
- (MMDataProvider *)providerForUser:(NSString *)userUUID;
Main access through
Social “engines”:FB, Google+,etc…
![Page 16: Elements for an iOS Backend](https://reader034.vdocuments.site/reader034/viewer/2022050906/554a53aab4c90531228b4c5f/html5/thumbnails/16.jpg)
Session management• Users will be created as session are started and
linked to possible already existing storage
• The MMDataProviderManager is the only one storing the last save user, which can be read at start for direct login
• Special userID can be defined to keep the front end code light : kMMCurrentUser, kMMLastUser, kMMAnonymousUser….
• The manager will be the main point to manage session and internally ask each provider to start/stop itself
![Page 17: Elements for an iOS Backend](https://reader034.vdocuments.site/reader034/viewer/2022050906/554a53aab4c90531228b4c5f/html5/thumbnails/17.jpg)
Registration
• As no user/provider exists before registration, the manager is the one handling the process
• In terms of implementation, one must take care of possible “network cookies” side effect.
• Usually multiple registration methods should exists : login/password, token, Facebook, Anonymous (boils down to one user with a device linked UUID)
![Page 18: Elements for an iOS Backend](https://reader034.vdocuments.site/reader034/viewer/2022050906/554a53aab4c90531228b4c5f/html5/thumbnails/18.jpg)
A note about Facebook login
• The iOS Facebook SDK is easy to put in place but usually stores its data inside the preferences
• It may be necessary to push tokens to the server. This should be done by subclassing the FBSessionTokenCachingStrategy that will read and write data to a server
• Development tokens behaves differently than production ones
![Page 19: Elements for an iOS Backend](https://reader034.vdocuments.site/reader034/viewer/2022050906/554a53aab4c90531228b4c5f/html5/thumbnails/19.jpg)
MMDataProviderManager
- (BOOL)registerUserWithMethod:(MMRegistrationMethod)method parameters:(NSDictionary *)parameterDictionary
Social Engine
Platform Registration
StartSessionForUserID
Finalize creation of MMDataProvider
![Page 20: Elements for an iOS Backend](https://reader034.vdocuments.site/reader034/viewer/2022050906/554a53aab4c90531228b4c5f/html5/thumbnails/20.jpg)
StartSessionForUserID
userID == kMMLastUser?
Current User Provider Stop
Session
Read Last User in defaults
New User Provider start
SessionBail
FoundNot Found
![Page 21: Elements for an iOS Backend](https://reader034.vdocuments.site/reader034/viewer/2022050906/554a53aab4c90531228b4c5f/html5/thumbnails/21.jpg)
Object Model
![Page 22: Elements for an iOS Backend](https://reader034.vdocuments.site/reader034/viewer/2022050906/554a53aab4c90531228b4c5f/html5/thumbnails/22.jpg)
Local and remote
• There may be differences in local objects than remote one. Runtime versus Persistent
• As a consequence thinking about “let’s sync everything” should be done in a cautious way
• Remote __class_name and uuid will drive instantiations
![Page 23: Elements for an iOS Backend](https://reader034.vdocuments.site/reader034/viewer/2022050906/554a53aab4c90531228b4c5f/html5/thumbnails/23.jpg)
Base class: MMBaseObject• Holds as class variables the matching between
local class and server __class_name
• Useful to have additionally a local object type as int for fast comparison
• Default implementation method may do nothing, or even be forbidden (use of abstract class). For exemple local storage in a DB
• At the base level : handling of UUID, present fields, instantiation with JSON Data, storage creation
![Page 24: Elements for an iOS Backend](https://reader034.vdocuments.site/reader034/viewer/2022050906/554a53aab4c90531228b4c5f/html5/thumbnails/24.jpg)
Objective-C implementation/* Registration of MMXXX class at load time */ + (void)load { [MMBaseObject registerClass:NSStringFromClass([self class]) forType:kMMObjectTypeUser JSONClassName:@"user" persistentDBType:@"USER"];; }
/* Main object instantiation entry point */ [MMBaseObject createMMObjectsFromJSONResult:tmpJSON parsedTypes:&tmpbjectTypes context:(void *)context]; !
/* Abstract method for Storage creation */ + (char *)persistentSQLCreateStatement;
![Page 25: Elements for an iOS Backend](https://reader034.vdocuments.site/reader034/viewer/2022050906/554a53aab4c90531228b4c5f/html5/thumbnails/25.jpg)
Objective-C implementation/* To be implemented by subclass */ - (id)initWithJSONContent:(id) JSONContent; !/* To be implemented by subclass */ - (void)updateWithJSONContent:(id) JSONContent; !/* Write to SQL Database */ - (BOOL)writeToDatabaseWithHandle:(sqlite3 *)dbHandle; !/* remove to SQL Database */ - (BOOL)removeFromDataBaseWithHandle:(sqlite3 *)dbHandle; !/* Create with init dictionary SQL Database */ - (id)initWithDatabaseInformation:(NSDictionary *)information;
![Page 26: Elements for an iOS Backend](https://reader034.vdocuments.site/reader034/viewer/2022050906/554a53aab4c90531228b4c5f/html5/thumbnails/26.jpg)
Collections• An additionnel object should exist storing list of
items. We call it a collection, it is purely local
• Will be useful for handling of slices
• In addition to its UUID it should have a secondary identifier, describing what it is linked too (e.g a slice endpoint, an HTTP request)
• It should be able to hold multiple orders, which may be more or less complete
• It should be KVO/KVC compliant
![Page 27: Elements for an iOS Backend](https://reader034.vdocuments.site/reader034/viewer/2022050906/554a53aab4c90531228b4c5f/html5/thumbnails/27.jpg)
Parsing• Having declared a base class, parsing can be
generic
• The parser is called with the result of every request
• A context should be provided to the parser. For example if a sliced endpoint is queried, this can be the collection class in order to enhance it
• The parser itself is recursive.
• It can contain a preparing phase to “fix/enhance/modify” objects from coming from the backend
![Page 28: Elements for an iOS Backend](https://reader034.vdocuments.site/reader034/viewer/2022050906/554a53aab4c90531228b4c5f/html5/thumbnails/28.jpg)
Parsing implementation
/* Entry point for JSON parsing and MMObject instantiations */ + (void)createMMObjectsFromJSONResult:(id)jsonResult parsedTypes:(MMObjectType *)parsedTypes contextProvider:(MMDataProvider *)provider contextTask:(MMHTTPTask*)task parsingContext:(void *)context { MMObjectType allParsedType = _ParseAPIObjectWithExecutionBlock(jsonResult, provider, task); if (parsedTypes) { *parsedTypes = allParsedType; } return ; }
![Page 29: Elements for an iOS Backend](https://reader034.vdocuments.site/reader034/viewer/2022050906/554a53aab4c90531228b4c5f/html5/thumbnails/29.jpg)
if ([inputObj isKindOfClass:[NSArray class]]) { [inputObj enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { MMObjectType tmpObjectType = _ParseAPIObjectWithExecutionBlock(obj, provider, task); result |= tmpObjectType; }]; } else if ([inputObj isKindOfClass:[NSDictionary class]]) { NSDictionary *tmpDictionary = (NSDictionary *)inputObj; NSString *objectAPIType = tmpDictionary[@"__class_name"]; NSString *objectUUID = tmpDictionary[@"uuid"] ; if (objectUUID) { MMBaseObject *tmpObject = nil; BOOL objectIsHere = [provider.dataStore containsObjectWithUUID:objectUUID]; if (objectIsHere) { tmpObject = [provider.dataStore objectWithUUID :objectUUID]; [tmpObject updateWithJSONContent:tmpDictionary]; result |= tmpObject.type; } else { if (!objectAPIType) return result; tmpObject = nil; NSString *objectClass = [MMBaseObject classNameForStringAPIType:objectAPIType]; if (!objectClass) return result; tmpObject = [[NSClassFromString(objectClass) alloc] initWithJSONContent:tmpDictionary]; result |= tmpObject.type; [provider.dataStore addObject:tmpObject replace:NO]; } [tmpDictionary enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) { if([obj isKindOfClass:[NSArray class]] || [obj isKindOfClass:[NSDictionary class]]) { MMObjectType tmpObjectType = _ParseAPIObjectWithExecutionBlock(obj,provider, task); result |= tmpObjectType; } }]; } else { //this is a slice if (tmpDictionary[@"data"] && tmpDictionary[@"limit"] && tmpDictionary[@"offset"]){ _ParseAPIObjectWithExecutionBlock(tmpDictionary[@"data"],provider, task); } } } return result;
![Page 30: Elements for an iOS Backend](https://reader034.vdocuments.site/reader034/viewer/2022050906/554a53aab4c90531228b4c5f/html5/thumbnails/30.jpg)
API goodies : fields, version
• Use a NSSet to hold and manage present fields
• Define field sets that can be used to find what is missing
• User server object versioning to avoid unneeded parsing
• One point to pay attention : date parsing is costly, use per thread date formatter caching
![Page 31: Elements for an iOS Backend](https://reader034.vdocuments.site/reader034/viewer/2022050906/554a53aab4c90531228b4c5f/html5/thumbnails/31.jpg)
Offline storage (problems)
• After a few versions it is always cool to have it
• This is an heavy testing field!!!!!
• You can use CoreData but you should never believe it is simple
• Simple SQLite 3 may be a good compromise
• Great benefits are also in startup times
![Page 32: Elements for an iOS Backend](https://reader034.vdocuments.site/reader034/viewer/2022050906/554a53aab4c90531228b4c5f/html5/thumbnails/32.jpg)
Network fetch
![Page 33: Elements for an iOS Backend](https://reader034.vdocuments.site/reader034/viewer/2022050906/554a53aab4c90531228b4c5f/html5/thumbnails/33.jpg)
Abstract or not abstract
• Abstract: the front end simply says “get me those objects and if not here the are fetched”
• Non abstract: the front end check if there are needed objects, and if not decide to fetch them
• Non abstract: network calls need to be launched manually which is a good way of learning an API
I prefer not abstract
![Page 34: Elements for an iOS Backend](https://reader034.vdocuments.site/reader034/viewer/2022050906/554a53aab4c90531228b4c5f/html5/thumbnails/34.jpg)
Abstract or not abstract
• Abstract: the front end simply says “get me those objects and if not here the are fetched”
• Non abstract: the front end check if there are needed objects, and if not decide to fetch them
• Non abstract: network calls need to be launched manually which is a good way of learning an API
I prefer not abstract
![Page 35: Elements for an iOS Backend](https://reader034.vdocuments.site/reader034/viewer/2022050906/554a53aab4c90531228b4c5f/html5/thumbnails/35.jpg)
Implementation• One unique interface
/* Main interface to do queries and all */ - (NSString *)launchRequestToEndPointPath:(NSString *)endPointPath andHTTPMethod:(NSString *)HTTPMethod useSecureConnection:(BOOL)isSecure inBackground:(BOOL)background withBody:(NSString *)body preparsingBlock:(MMPreparseBlock)preparsingBlock completionBlock:(MMCompletionBlock)completionBlock
• Endpoint path : the API path minus server. Learn the API!!!
• Use of blocks avoid to spread code in all places
![Page 36: Elements for an iOS Backend](https://reader034.vdocuments.site/reader034/viewer/2022050906/554a53aab4c90531228b4c5f/html5/thumbnails/36.jpg)
Technology
• iOS 7 has made a lot of network progress. IMHO no need for a third party library
• Learn NSURLSession!
• Background modes can be difficult. You are usually not the owner of time. Never try to go against the OS all is here to be understood. But clearly it takes time
![Page 37: Elements for an iOS Backend](https://reader034.vdocuments.site/reader034/viewer/2022050906/554a53aab4c90531228b4c5f/html5/thumbnails/37.jpg)
In the application
![Page 38: Elements for an iOS Backend](https://reader034.vdocuments.site/reader034/viewer/2022050906/554a53aab4c90531228b4c5f/html5/thumbnails/38.jpg)
Communication Back Front• Give a role to different way of communication
• To avoid definitely : NSNotification for everything. This easily becomes unmanageable (more than 130 notifications)
• Personal rules : • Notifications are for application important
changes (Network, User session start and stop) • KVO is king for data transmission. Be careful of
threading • Use block to mark end of network operation
![Page 39: Elements for an iOS Backend](https://reader034.vdocuments.site/reader034/viewer/2022050906/554a53aab4c90531228b4c5f/html5/thumbnails/39.jpg)
Upgrade management
• Dedicate one object to version management
• First usage, first usage for current version,
• Mange data upgrade in an incremental way
![Page 40: Elements for an iOS Backend](https://reader034.vdocuments.site/reader034/viewer/2022050906/554a53aab4c90531228b4c5f/html5/thumbnails/40.jpg)
Upgrade management/* Use the Objective-C runtime */ - (BOOL) runUpgradeScenario { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Warc-performSelector-leaks" __block BOOL result = NO; ! if(NO == self.firstTimeForCurrentVersion && NO == self.firstTime) return result; ! ! }
NSMutableDictionary *allUpgrades= [NSMutableDictionary dictionary]; NSMutableDictionary *allStarts= [NSMutableDictionary dictionary]; //Find all upgrade methods unsigned int outCount; Method * allMethods = class_copyMethodList([self class], &outCount); for(unsigned int idx = 0; idx < outCount; idx++) { Method aMethod = allMethods[idx]; NSString *aMethodName = NSStringFromSelector(method_getName(aMethod)); if([aMethodName hasPrefix:@"_upgradeFrom"]) { NSString *upgradeVersionString = [aMethodName substringWithRange:NSMakeRange([@"_upgradeFrom" length], 3)]; [allUpgrades setObject:aMethodName forKey:upgradeVersionString]; } else if ([aMethodName hasPrefix:@"_startAt"]) { NSString *startVersionString = [aMethodName substringWithRange:NSMakeRange([@"_startAt" length], 3)]; [allStarts setObject:aMethodName forKey:startVersionString]; } } if(allMethods) free(allMethods);
if(self.firstTime) { //sort them and perform the most "recent" one SEL startSelector = NSSelectorFromString([allStarts[[[allStarts keysSortedByValueUsingSelector:@selector(compare:)]lastObject]]]); [self performSelector:startSelector withObject:nil]; result = YES; } else if(self.firstTimeForCurrentVersion) { //Sort them and apply the one that needs to be applied [[allUpgrades keysSortedByValueUsingSelector:@selector(compare:)] enumerateObjectsUsingBlock:^(NSString *obj, NSUInteger idx, BOOL *stop) { if([obj intValue] > _previous3DigitVersion) { result = YES; [self performSelector:NSSelectorFromString([allUpgrades objectForKey:obj]) withObject:nil]; } }]; } #pragma clang diagnostic pop return result;
![Page 41: Elements for an iOS Backend](https://reader034.vdocuments.site/reader034/viewer/2022050906/554a53aab4c90531228b4c5f/html5/thumbnails/41.jpg)
Thank You!