Software and Systems Engineering Standards: Verification and Validation of Sy...
Â
Elements for an iOS Backend
1. A Possible way for Client
Backend
lcerveau@nephorider.com
2. ⢠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)
⢠ConďŹguration: a client talks to a server
Foreword
4. !
⢠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?
⢠OfďŹine usage is a must
The marketing requirement road
5. !
⢠Client side is usually determined on a âpaneâ
basis. In iOS wording: âview controllersâ
⢠At ďŹrst 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
7. Backend Roles
!
⢠User management : switch, registration
⢠Network state management : online/ofďŹine
⢠Data fetch in the context of one user
⢠Communication backend frontend
8. 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
9. Reminder: API prerequisite
!
⢠Each returned object is having
⢠an unique identiďŹer : uuid
⢠an self describing type : __class_name
11. Talk & code
!
⢠Follows Obj-C/Apple conventions
⢠Use MM as an example preďŹx
⢠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)
13. Storage
!
⢠At ďŹrst 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
14. One user - one provider
⢠Letâs deďŹne an object âdoing all for a userâ as a
MMDataProvider. An entry point to manage all
data for a user
⢠Letâs deďŹne 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
16. 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 deďŹned 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
17. 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)
18. 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
22. 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
23. 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 ďŹelds,
instantiation with JSON Data, storage creation
24. 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;
25. 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;
26. 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
identiďŹer, 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
27. 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 âďŹx/enhance/
modifyâ objects from coming from the backend
28. 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 ;
}
30. API goodies : ďŹelds, version
⢠Use a NSSet to hold and manage present ďŹelds
⢠DeďŹne ďŹeld sets that can be used to ďŹnd 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
31. OfďŹine storage (problems)
⢠After a few versions it is always cool to have it
⢠This is an heavy testing ďŹeld!!!!!
⢠You can use CoreData but you should never believe
it is simple
⢠Simple SQLite 3 may be a good compromise
⢠Great beneďŹts are also in startup times
33. 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
34. 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
35. 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
36. Technology
⢠iOS 7 has made a lot of network progress. IMHO no
need for a third party library
⢠Learn NSURLSession!
⢠Background modes can be difďŹcult. 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
38. Communication Back Front
⢠Give a role to different way of communication
⢠To avoid deďŹnitely : NSNotiďŹcation for everything.
This easily becomes unmanageable (more than 130
notiďŹcations)
⢠Personal rules :
⢠NotiďŹcations 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
39. Upgrade management
⢠Dedicate one object to version management
⢠First usage, ďŹrst usage for current version,
⢠Mange data upgrade in an incremental way
40. 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;