50. • How will we deal with the possibility of API
changes?
• especially since the app will be long-lived
• How do we minimize the number of requests
from the iOS app to the server?
• How can we avoid making separate API routes
for the iOS app (and every client thereafter)?
Questions…
51. Clearly bad ideas
• Make multiple requests to display
one Post, User, or Collection
• Make new iOS routes with exactly
what we need
• Only save full objects
52. Clearly bad ideas
• Save partial objects and if we need a
full object, just check to see if all the
fields are set in the object in the
cache
• Is the field supposed to be “” or
was it never set?
61. What about dealing with a changing API?
• explicit definitions for the
resources returned by API
endpoints
• tests based on these definitions
62. Possible areas for improvement
• one service to return any shape
(instead of a separate data
service for each type of object)
• autogenerate shape definitions
from protocol buffers (on both
server and client)
63. • easy to add features on the iOS side
• easy to make changes on the server
side without breaking the iOS client
• minimize # of server requests
Shapes!
67. A singleton restricts the instantiation of a class to one object.
They are often lazily instantiated.
They can be accessed globally by any module of a program.
80. Time dependence isn’t an
issue until the underlying
data starts changing.
PostViewController
PostViewService
PostService
Net
AuthCredentialDataServiceAuthCredentialDataService
PostViewController
81. If your app supports sign out,
the underlying authentication
data is changing.
PostViewController
PostViewService
PostService
Net
AuthCredentialDataServiceAuthCredentialDataService
PostViewController
82. What problems does time
dependence cause?
- Functional impurity
- Leaky abstraction
- Background task failures
- Difficult to test
PostViewController
PostViewService
PostService
Net
AuthCredentialDataServiceAuthCredentialDataService
PostViewController
89. 1. User bookmark action is queued
PostViewController
PostViewService
PostService
Net
AuthCredentialDataService Logged in
dispatch_async(_backgroundQueue, ^{
[[Services postView] bookmark:_postId];
});
90. 1. User bookmark action is queued
2. User signs out
PostViewController
PostViewService
PostService
Net
AuthCredentialDataService Logged out
LoginViewController
dispatch_async(_backgroundQueue, ^{
[[Services postView] bookmark:_postId];
});
91. 1. User bookmark action is queued
2. User signs out
3. Bookmark action is dequeued
PostViewController
PostViewService
PostService
Net
AuthCredentialDataService Logged out
LoginViewController
dispatch_async(_backgroundQueue, ^{
[[Services postView] bookmark:_postId];
});
92. 1. User bookmark action is queued
2. User signs out
3. Bookmark action is dequeued
PostViewController
PostViewService
PostService
Net
AuthCredentialDataService Logged out
LoginViewController
dispatch_async(_backgroundQueue, ^{
[[Services postView] bookmark:_postId];
});
93. 1. User bookmark action is queued
2. User signs out
3. Bookmark action is dequeued
PostViewController
PostViewService
PostService
Net
AuthCredentialDataService Logged out
LoginViewController
dispatch_async(_backgroundQueue, ^{
[[Services postView] bookmark:_postId];
});
94. 1. User bookmark action is queued
2. User signs out
3. Bookmark action is dequeued
PostViewController
PostViewService
PostService
Net
AuthCredentialDataService Logged out
LoginViewController
dispatch_async(_backgroundQueue, ^{
[[Services postView] bookmark:_postId];
});
95. 1. User bookmark action is queued
2. User signs out
3. Bookmark action is dequeued
4. Bookmark request 401s
PostViewController
PostViewService
PostService
Net
AuthCredentialDataService Logged out
LoginViewController
dispatch_async(_backgroundQueue, ^{
[[Services postView] bookmark:_postId];
});
96. 1. User bookmark action is queued
2. User signs out
3. Bookmark action is dequeued
4. Bookmark request 401s
5. PostViewController is cleaned up
PostViewService
PostService
Net
AuthCredentialDataService Logged out
LoginViewController
dispatch_async(_backgroundQueue, ^{
[[Services postView] bookmark:_postId];
});
98. Option 1: YOLO
Advantages!
- Very little work
- No testing needed
- Funny later in your career
Disadvantages!
- Horrible UX
- Async bookmark operation still fails
- (void)logOut {
// nuke everything
exit(0);
}
99. Option 2: Embrace time dependence
Advantages!
- Doesn’t require refactoring
Disadvantages!
- Requires global knowledge of all the apps components
- Affects other, disjunct service consumers
- Difficult to test
- Async bookmark operation still fails
- (void)logOut {
// Synchronously wipe all the user-dependent data
[[Services authCredentialDataService] wipeData];
// …
!
// Reload UI with new global data
[_appController reload];
}
100. Option 3: Dependency inject your user-dependent data
- Eliminates time dependence in the service layer
- Gracefully supports switching authentication contexts
- Easy to test interfaces with a mock Session object
- Async bookmark operation succeeds
- (void)logOut {
// Create an unauthenticated session
Session *newSession = [Session loggedOutSession];
!
// Reload UI with logged out session
[_appController loadSession:newSession];
}
101. @interface Session : NSObject
!
- (instancetype)initWithAuth:(AuthCredential *)authCredential;
!
@property (nonatomic, strong, readonly) NSString *sessionId;
@property (nonatomic, strong, readonly) AuthCredential *authCredential;
!
- (BOOL)isAuthenticated;
!
@end
The Session object directly models user-dependent state,
and is injected into objects that need it.
112. 1. User bookmark action is queued
PostViewService
PostService
Net
PostViewController
Logged in
Session
dispatch_async(_backgroundQueue, ^{
[[Services postView] bookmark:_postId session:_session];
});
113. 1. User bookmark action is queued
2. User signs out
PostViewService
PostService
Net
PostViewController
Logged in
Session
LoginViewController
Logged out
Session
dispatch_async(_backgroundQueue, ^{
[[Services postView] bookmark:_postId session:_session];
});
114. 1. User bookmark action is queued
2. User signs out
3. Bookmark action is dequeued
PostViewService
PostService
Net
PostViewController
Logged in
Session
LoginViewController
Logged out
Session
dispatch_async(_backgroundQueue, ^{
[[Services postView] bookmark:_postId session:_session];
});
115. 1. User bookmark action is queued
2. User signs out
3. Bookmark action is dequeued
PostViewService
PostService
Net
PostViewController
Logged in
Session
LoginViewController
Logged out
Session
dispatch_async(_backgroundQueue, ^{
[[Services postView] bookmark:_postId session:_session];
});
116. 1. User bookmark action is queued
2. User signs out
3. Bookmark action is dequeued
PostViewService
PostService
Net
PostViewController
Logged in
Session
LoginViewController
Logged out
Session
dispatch_async(_backgroundQueue, ^{
[[Services postView] bookmark:_postId session:_session];
});
117. 1. User bookmark action is queued
2. User signs out
3. Bookmark action is dequeued
4. Bookmark action succeeds
PostViewService
PostService
Net
PostViewController
Logged in
Session
LoginViewController
Logged out
Session
dispatch_async(_backgroundQueue, ^{
[[Services postView] bookmark:_postId session:_session];
});
118. 1. User bookmark action is queued
2. User signs out
3. Bookmark action is dequeued
4. Bookmark action succeeds
5. PostViewController and logged in
Session are cleaned up
PostViewService
PostService
Net
LoginViewController
Logged out
Session
dispatch_async(_backgroundQueue, ^{
[[Services postView] bookmark:_postId session:_session];
});
119. Think back to the original decision.
Net
AuthCredentialDataService
Keychain
Net
120. It appears innocent without context.
Net
AuthCredentialDataService
Keychain
Net
121. Let’s do our best to recognize!
the larger context earlier.
PostViewController
PostViewService
PostService
Net
AuthCredentialDataService
Data dependence
Time dependence
AuthCredentialDataService
PostViewController
132. Fast release requirements
• Over-the-air updates
• Crash report system
• Authorization server
• Feature toggles
• Versioning system
• Distribution channels
133. OTA updates, crash reports
and authorization
• Crashlytics
• TestFlight / Apple’s upcoming beta program
• HockeyApp
150. Lessons Learned [2]
• Make it easy for everyone to see which version of
the app each group has
• Easy visibility and communication on features of
each of these groups
• Automate everything, consider Jenkins CI over
Xcode Bots