Hello everyone. Topic that I’d like to discuss today is quite simple. What is it about and why do I need to talk about it? Usually when user gets registered / logged in there is no strict way to describe this state. Obviously when the app authorized the user we can retrieve bunch of information about it like: APIClient.sharedClient().token != nil or UserStore.sharedStore().currentUser != nil, or performing a DB query where User.isCurrent == true or all this at the same time. Literally there are lot of runtime attributes indicating about logged in user but nothing that combines it into a single object. Basically User Session is that missing abstraction that gives us a way to definitely describe current user state: logged in or not. I belive most of readers faced with logout case, whe you have stop all running services, probably remove user’s disk data, etc. I saw many places to handle logout cleanup but none of them feels comfortable: App Delegates, UIViewController that handles logout and so on. User Session is something that:
- an object that definitely describes current login state
- gives us a correct place to both start and stop services
- we’ll talk today
Implementation
User Session abstraction neither new topic or a rocket science. It is used in most of the backend services, however it didn’t receive the attention it deserves in iOS-community (IMO). For core implementation we need 3 classes:
User session itself
User session controller to open, close and restore sessions as well as keep strong reference to current session
User session prototype to encapsulate any login data (user, token, etc) before new session is opened
User Session Prototype
Lets start from user session and its prototype. Assume that both sign in / up request returns the following response:
In order to be passed around this JSON it needs to be encapsulated into an object, not a plain Dictionary:
UserSessionPrototype.swift
1234567891011121314
structUserSessionPrototype{letidentifier:StringletuserInfo:[String:AnyObject]init(response:[String:AnyObject]){// Dirty parsing to simplify our exampleletresult=response["result"]as![String:AnyObject]letuser=result["user"]as![String:AnyObject]identifier=user["id"]as!StringuserInfo=user}}
UserSessionPrototype contains just 2 properties:
identifier of the user, which will be used to build UserSession.identifier. This is quite useful for preserving of session’s data between logout / login actions
userInfo: simple Dictionary to keep all user data for session bootstrap
Prototype not necessarily should be so simple. It may combine any login data that is required for correct session bootstrap such as tokens, session trial limit and so on.
User Session
What about UserSession? Obviously it should support init with prototype, restoration. What else? It needs to be able to bootstrap initial state and place to handle start & stop of some services (timeline background update, etc).
classUserSession{letidentifier:String// to uniquely identify sessionprivateletsessionPrototype:UserSessionPrototype?// MARK: - Initinit(prototype:UserSessionPrototype){identifier=prototype.identifier// any hashing function such as sha1 can be added heresessionPrototype=prototype}init?(restorationIdentifieridentifier:String){self.identifier=identifiersessionPrototype=nilletcanRestore=true// check whether session for passed identifier can be successfuly restored or notif!canRestore{returnnil}}// MARK: - Open / Closefuncopen(){ifletprototype=sessionPrototype{bootstrapFromPrototype(prototype)}// point to start any related services}funcclose(){// point to stop any related services}// MARK: - BootstrappingprivatefuncbootstrapFromPrototype(prototype:UserSessionPrototype){// map user data to a new user, setup db, etc}}
Now lets talk about session restoration: in order to perform correct init you’re free to check anything. In my real-world case I checked whether keychain has a valid token for passed identifier.
What else we need to add to the session? Probably sort of a flag to definitely describe session’s current state. It can be effectively used for further development like auth token refresh, logout and so on:
Undefined - session hasn’t been opened yet
Opened - default session state
Closed - session has been closed
Invalid - user session bootstap failed or auth token has been invalidated
enumState{caseUndefined,Opened,Closed,Invalid}private(set)varstate:State=.Undefinedfuncopen()->Bool{precondition(state==.Undefined)ifletprototype=sessionPrototypewhere!bootstrapFromPrototype(prototype){state=.Invalidreturnfalse}// point to start any related servicesstate=.Openedreturntrue}funcclose(){precondition(state==.Opened||state==.Invalid)// point to stop any related servicesstate=.Closed}// MARK: - BootstrappingprivatefuncbootstrapFromPrototype(prototype:UserSessionPrototype)->Bool{// map user data to a new user, setup db, etcreturntrue// bootstrap completed successfuly}
User Session Controller
Now it is time to add controller to keep an eye on active session. As was mentioned above main responsibilities are:
Let’s start from opening of a new session. In this example I’m not going to cover full flow of sign up / in since it is quite straightforward. For now lets assume, that we have APIClient, that talks to the backend. Result of login operation is a UserSessionPrototype that we implemented earlier or NSError in case of network error:
UserSessionController.swift
1234567891011121314
privateletapiClient=AuthorizationAPIClient()funcopenSession(#username:String,password:String,completion:(UserSession?,NSError?)->Void){letrequestCompletion:(UserSessionPrototype?,NSError?)={prototype,errorinifletprototype=prototype{self.userSession=UserSession(prototype:prototype)completion(self.userSession,nil)}else{completion(nil,error)// preferrable to map error to the UserSessionControllet's level}}apiClient.loginWithUsername(username,password:password,completion:requestCompletion)}
It is generally preferable to return an object that can cancel async operation. Void as a return type of any async operation should be ommited, but to keep our example simple I’m ignoring this rule
Session closing is a oneline function:
UserSessionController.swift
123
funccloseSession(){userSession=nil}
Now it is time for restoration! What do we need for it? Obviously to preserve opened user session’s identifier between apllication launches. Best place to update persistent identifier value is userSession’s didSet. For storage lets go with NSUserDefaults:
// MARK: - InitletuserDefaults:NSUserDefaultsinit(userDefaults:NSUserDefaults){self.userDefaults=userDefaults}// MARK: - RestorationprivatestaticletuserSessionKey="your.app.bundle.id.userSession"privatevaruserSessionIdentifier:String?{get{returnuserDefaults.objectForKey(UserSessionController.userSessionKey)as?String}set{userDefaults.setObject(newValue,forKey:UserSessionController.userSessionKey)userDefaults.synchronize()}}funcrestoreUserSession()->UserSession?{assert(userSession==nil,"It is illegal restore session while ")ifletidentifier=userSessionIdentifier,letsession=UserSession(restorationIdentifier:identifier){self.userSession=sessionreturnsession}returnnil}varcanRestoreUserSession:Bool{returnuserSessionIdentifier!=nil}// MARK: - UserSessionprivate(set)varuserSession:UserSession?{didSet{oldValue?.close()userSession?.open()userSessionIdentifier=userSession?.identifier}}
Also we need to add initialization of ‘UserSessionController’ into the root of the app:
AppDelegate.swift
12345678910111213141516
@UIApplicationMainclassAppDelegate:UIResponder,UIApplicationDelegate{varwindow:UIWindow?letuserSessionController=UserSessionController(userDefaults:NSUserDefaults.standardUserDefaults())funcapplication(application:UIApplication,didFinishLaunchingWithOptionslaunchOptions:[NSObject:AnyObject]?)->Bool{ifuserSessionController.canRestoreUserSession{// lets restore it}else{// we need to show login}returntrue}}
Extras
Ok it seems that we’re done with basic implementation. Is there anything useful we can add out of the box? User’s data management convenience! By a simple path utility added to the session we can easily distinguish any user’s files physically, so you no longer have to care about possible collisions.
Briefly our Documents and ‘Caches’ path will be dependand on UserSession.identifier:
Now when you’re going to setup CoreData stack there is a great utility for “path consulting”. For sure, final desicion of where to store any user’s data is up to the implementation but existence of such path utility pushes developer to do it right ;)
Summary
UserSession is a great %your_app_name% citizen:
amazing starting point to stop using sharedManagers and take control over of your services!
gives you entry point to perform any neccessary logout cleanup
helps to store your data more organized
can indicates about invalidated auth tokens by updating state value
abstraction that I’d like to know about few years earlier
Today’s note I’d like to dedicate the topic of storing files with CoreData.
Most of the databases available for iOS allow us to specify fields of BLOBs type.
As all of You know it is not recommended to store within database itself due to query performance. For real numbers check out Internal Versus External BLOBs in SQLite article.
When it comes to CoreData - BLOBs available as well. CoreData has extra options for attribute description and of them is Store in External Record File.
By checking this feature we say “Ok, CoreData, I’d like to keep those BLOBs in a separate file”. Starting from this point all required bookkeeping will be done automatically for You and no changes to code should be applied! Files stored either internally or externally due to Persistent Store logic without any effort.
Unfortunately, sometime we do need to have some access to those files due number of reasons. And obviously framework doesn’t allow us to do so via public API. Ok, what next? We need to implement the file management on our own.
The filename
It means to implement save, delete and so on behind the scene. There are few ways to do so:
Mimic Store in External Record File option and perform saves and deletes at appropriate moments within NSManagedObject. Active model.
Make our model passive and store only related attributes. Files management performed outside of the object.
For both cases we need to have at least filename. And here we come to the main point of this article: filename should be unique, readonly, not related to the primary key or any attribute that handled externally. With no exceptions. Some explanation of every item:
Unique - to prevent possible conflicts on the disk level. It simplifies management of the files.
Readonly - Imagine next scheme of database Post <-> Attachment. At the moment of post editing user can add and delete attachments with no limits. readwrite allow to reuse the same Attachment object and some of the developers will accept this approach, while readonly forces to delete previous object and create a new one from scratch. Second approach less error-prone, since every file linked with a separate object, no way to corrupt data.
Not related to the primary key - in the beginning I was planning to reuse objectID as it is already unique. However, NSManagedObject.objectIDs before save and after are not equal (see temporaryID) and we need to handle that change. Extra complexity, agree? So I decided to use my custom PK - it won’t be changed at all. The issue I faced with this approach is quite rare but worth to mention it here.
In a single moment of time I had next set of objects:
As You can see the same attachment presented in two NSManagedObjectContexts.
How that happened:
Attachment was fetched on both contexts
Main context deletes Attachment, save: performed. Changes propagated to background context via notification but no save executed
Later the same object inserted into main thread by import from network
At some point save: received by background context
-[NSManagedObject prepareForDeletion] invoked on already deleted Attachment from background context
Since both deleted and inserted Attachments have the same filename - file of inserted Attachment removed by deleted.
Weird, hah?
Some code
All You need is to declare managed object class as:
12345678
@interfaceAttachment : NSManagedObject// readonly for public use@property(nonatomic,strong,readonly)NSString*filename;// other attributes@end
While .m looks like that:
12345678910111213141516171819
@interfaceAttachment()// redeclare as readwrite for internal use@property(nonatomic,strong)NSString*filename;@end@implementationAttachment@dynamicfilename;-(void)awakeFromInsert{[superawakeFromInsert];// here we can assign some default valuesself.filename=[[NSUUIDUUID]UUIDString];}@end
Some short description of code above: public interface allow client code to read filename but not modify it. The only place of modification is insert, which is handled in -awakeFromInsert.
Summary
Declaring Your objects as immutable makes Your code even more simple and elegant, since there is no place for possible conflicts, rewrites and data corruption anymore. Less cases to handle - less code. Less code - less errors, easier to maintain and update.
Once upon a time I got interested in reason of why dispatch_object and ARC are compatible. It was about 2 years ago and since that moment I totally forgot about anything related to dispatch_queue and ARC. However, about a week ago Autocomplete added [queue isKindOfClass:] for dispatch_queue_t by accident. Moreover - no errors or warning was produced!
Thats confused me for a moment but when I went to the declaration everything start to fall into place. So this post is all about my weekend’s evening :)
A little bit of history
Going back to 2011 Apple introduces ARC - users don’t have to use retain, release or autorelease anymore, weak references available, developers happy.
Year later with announce of iOS 6 Apple moving dispatch_object unders ARC as well. It means no more dispatch_release and dispatch_retain! Memory management code is not identical for ARC and non-ARC environment but everything else is the same!
So how they did it?
All in declaration
First of all lets take a look at dispatch_queue_t declaration:
queue.h
1
DISPATCH_DECL(dispatch_queue);
Looking at DISPATCH_DECL declaration we can found huge number of #if defines, which help to emit different code for every environment: C, Objective-C and C++.
Going forward I would like to highlight part of libdispatch’s inheritance structure: root os_object, which provides basic memory management functionality. Then dispatch_object - root for libdispatch and number of inherited objects like dispatch_queue, dispatch_group and so on. We need to understand, that under C this inheritance is a fake because of C-lang nature, but the overall structure should be the same for all 3 langs.
For Objective-C if we print object’s description in debugger the OS_dispatch_queue class will be revealed. Superclass - OS_dispatch_object. And it is totally not surprising from declaration of DISPATCH_DECL which is in fact a macro for OS_OBJECT_DECL_SUBCLASS(name, dispatch_object):
So behind dispatch_name_t lies NSObject<OS_dispatch_name>. But debugger says about OS_dispatch_name, as a class not a protocol. Looks like someone is hidding the real declaration from us! Fortunately, libdispatch is Open Source and we can find out, who those OS_dispatch_objects guys are. We’re looking for some macros or declaration regarding dispatch_object or similar.
You can download libdispatch and inspect source on your own. It contains a lot of stuff to look at! For example list of supported platforms has: macosx, iphoneos, iphoneossimulator, iphoneosnano, iphoneosnanosimulator.
Haven’t seen them before :)
After some inspection I found, that there are some macros, which define OS_dispatch_* classes. Lets open dispatch_internal.h. There we can find DISPATCH_CLASS_DECL(queue); among other stuff. Jump to definition opens object_internal.h
Under the hood all the OS_object does is perform memory management handling by passing retain, release to os-object-specific retain count implementation and disposing handling. Also there is some weak-references handling, but to be honest i haven’t dig it a lot, since it is out of topic.
As you remember Obj-C support for libdispatch implemented atop of cross-platform C core. Therefore in order to utilize single memory management code they simply pass all the retain-release calls to os-specific functions like _os_object_retain and _os_object_release respectively.
@interfaceOS_dispatch_object : OS_object<OS_dispatch_object>@endexternstructdispatch_object_vtable_s{// will be covered later}_dispatch_object_vtable;structdispatch_object_s{// will be covered later};
Things get interesting here! Besides class declaration there are 2 extra structures, which is used during allocation! But before we look at how these structures are actually used, I would like to reveal implementation of OS_dispatch_object first:
object.m
1234567891011121314151617181920212223
@implementationOS_dispatch_object-(id)init{self=[superinit];[selfrelease];self=nil;// trollface.jpgreturnself;}-(void)_xref_dispose{_dispatch_xref_dispose(self);[super_xref_dispose];}-(void)_dispose{return_dispatch_dispose(self);// calls _os_object_dealloc()}-(NSString*)debugDescription{// nothing interesting for now}@end
As you can see main purpose of this class is to perform some extra cleanup during disposing. Also -init implemented in such a funny way because of all allocation and initialization done in corresponding C-functions, like dispatch_queue_create.
Allocation and Init
Briefly reader should be aware of two separate versions of alloc-code which are separated by #define checks: C version with calloc and the one we’re going to review in a moment - Obj-C.
Lets jump to one of the well-known functions - dispatch_queue_create:
I deliberately simplifying and inlining alloc code since we’re interested only in how Obj-C compatibility implemented.
It is time to reveal meaning of ‘dispatch_object_vtable_s’. To be honest I was confused for a minute when opened this function for the first time. I hope you remember dispatch_object_vtable_s and dispatch_object_s. But before line-by-line description I’d like to show you, how ‘dispatch_data_t’ gets allocated:
1234567
dispatch_data_t_dispatch_data_alloc(size_tn,size_textra){size_textra_size=/* some size computation */dispatch_queue_tdq=class_createInstance((Class)&OBJC_CLASS_$_OS_dispatch_data,extra_size);returndata;}
This code should be much more verbose. Useful for us here is the explanation of the meaning of &_dispatch_queue_vtable.
Now it is time to expand already mentioned dispatch_object_vtable_s and dispatch_object_s:
object_internal.h
123456789101112131415161718192021222324
structdispatch_object_s;externstructdispatch_object_vtable_s{// Apple: Must match size of compiler-generated OBJC_CLASS structure rdar://10640168void*_os_obj_objc_class_t[5];unsignedlongconstdo_type;constchar*constdo_kind;size_t(*constdo_debug)(structdispatch_object_s*,char*,size_t);void(*constdo_invoke)(structdispatch_object_s*);unsignedlong(*constdo_probe)(structdispatch_object_s*);void(*constdo_dispose)(structdispatch_object_s*);}_dispatch_object_vtable;structdispatch_object_s{conststructdispatch_object_vtable_s*do_vtable,intvolatiledo_ref_cnt,intvolatiledo_xref_cntstructdispatch_object_s*volatiledo_next;structdispatch_queue_s*do_targetq;void*do_ctxt;void*do_finalizer;unsignedintvolatiledo_suspend_cnt;};
As you can see from Apple’s comment the vtable holds dispatch-specific fields as well as reserves space for Obj-C compiler-generated structure, which one is prefixed by OBJC_CLASS_$_. dispatch_object_s in turn mimics NSObject where isa pointer comes as a first field.
The best way to describe extra size computation is to compare memory layout of OS_dispatch_object vs dispatch_object_s:
Since OS_dispatch_object doesn’t have anything except isa in it the instance size will be equal to the size of that pointer. However, dispatch_object_s has a lot more data. Hence libdispatch needs somehow add extra bytes to instance. Fortunately, Obj-C runtime allow us to allocate extra bytes for Class’s instance as a single chunk of memory via class_createInstance(Class cls, size_t extraBytes).
What it gives us? Due to how objects represented in the memory we can treat OS_dispatch_object as dispatch_queue_s *. In the end pointer’s type is just a hint. It helps both developer and compiler to treat particular memory region as Int * or maybe as NSObject *. The only requirement here is to respect bounds of the destination memory region. Therefore OS_dispatch_queue allocated with appropriate size easily passed into dispatch_async as dispatch_queue_s *.
Alias
The next question is how Obj-C runtime fetch correct class metadata from &dispatch*vtable pointer. Briefly it is done via linker aliases when dispatch_queue_vtable_s gets co-located with OS_dispatch_queue class structure.
Lets open libdispatch.xcconfig on OBJC_LDFLAGS key:
1
OBJC_LDFLAGS=-Wl,-alias_list,$(SRCROOT)/xcodeconfig/libdispatch_objc.aliases/* more flags here */
The content of libdispatch_objc.aliases describes aliases between Obj-C class structures and vtables:
12345
_OBJC_CLASS_$_OS_dispatch_semaphore__dispatch_semaphore_vtable_OBJC_CLASS_$_OS_dispatch_group__dispatch_group_vtable_OBJC_CLASS_$_OS_dispatch_queue__dispatch_queue_vtable// and so on
For more understanding take a look at how Obj-C class and vtable gets co-located:
Linker co-locate dispatch_object_vtable_s and OS_dispatch_object at the same address within Mach-O executable. Therefore pointer to vtable gets treated by Obj-C Runtime as a Class.
Unfortunately I was unable to reproduce the same behaviour in my sample app. Search on the web lists mostly Apple’s OpenSource website. Hope someone from readers will get more lucky there.
Summary
As you may see things under the hood look more complicated than public interface declares.
Apple engineers did really great job in order to make 3rd party developers life even easier. For sure knowledge of how libdispatch internal works won’t help us in everyday development. However complex libraries done by real professionals always interesting to explore isn’t it?