medium techtalk — ios

152

Upload: jimmyatmedium

Post on 21-Feb-2017

216 views

Category:

Software


0 download

TRANSCRIPT

How might we create a simple and intuitive mobile consumption experience

for Medium?

How might we create a simple and intuitive mobile consumption

experience for Medium?

How might we create a delightful, simple and intuitive

mobile consumption experience for Medium?

History stack Future stack

Cover Flow

<html>?

• 60 FPS • Memory • Parallax

UIKit & TextKit

{ "content": { ... "bodyModel": { "elements": <Elements>, "sections": <Sections> }, ... },}

{ "name": "8419", "type": 1, "text": "...song Walking in LA?", "markups": [ { "type": 3, "start": 300, "end": 315, "href": "https://somewhere.com", "title": "", "rel": "" } ]}

{ "name": "24cc", "type": 4, "text": "", "markups": [], "layout": 1, "metadata": { "id": "1*WwSQ20QNvf-WovuMLLslEA.jpeg", "originalWidth": 453, "originalHeight": 301 }}

{ "name": "38d2", "startIndex": 6}

{ "name": "7b28", "startIndex": 31, "backgroundImage": { "id": "1*DhgXe8j0YqltMNM6-pRy6Q.jpeg", "originalWidth": 1920, "originalHeight": 1200 }, "textLayout": 2, "imageLayout": 2}

Cover Flow

View

Cover Flow

View

Post View

Cover Flow

View

Post View

Section View

Cover Flow

View

Post View

Section View

Section Image View

Cover Flow

View

Post View

Section View

Section Image View

Section Content View

Cover Flow

View

Post View

Section View

Section Content View

Element

Element

Element

Section Image View

Cover Flow

View

Post View

Section View

Section Content View

Element

Element

Element

Section Image View

Cover Flow

View

Post View

Section View

Section Content View

Element

Element

Element

Section Image View

Element

Cover Flow

View

Post View

Section View

Section Content View

Element

Element

Element

Section Image View Elem

ent

Cover Flow

View

Post View

Section View

Section Content View

Element

Element

Section Image View Elem

ent

Cover Flow

View

Post View

Section View

Section Content View

Element

Element

Section Image View Elem

ent

Section View

Section Image View

Section Content View

Element

Cover Flow

View

Post View

Section View

Section Content View

Element

Section Image View Elem

ent

Section View

Section Image View

Section Content View

Element

Element

Cover Flow

View

Post View

Section View

Section Image View

Section Content View

Element

Element

Cover Flow

View

Post View

Section View

Section Image View

Section Content View

Element

Element

Cover Flow

View

Post View

Section View

Section Image View

Section Content View

Element

Element

Section View

Section Image View

Cover Flow

View

Post View

Section View

Section Image View

Section Content View

Element

Section View

Section Image View

Section Content View

Element

Cover Flow

View

Post View

Section View

Section Image View

Section Content View

Element

Cover Flow

View

Post View

Section View

Section Image View

Section Content View

Element

Element

Cover Flow

View

Post View

Section View

Section Image View

Section Content View

Element

Element

Cover Flow

View

Post View

Section View

Section Image View

Section Content View

Element

Element

Thank You! Questions?

!

Grant Oladipo [email protected]

@kocodude

Shapes & things

Building the data layer of the Medium iOS app !

!

Elizabeth Ford @infraredflower

Title Body text Other stuff

User !

Collection

User’s name Bio List of stories

• 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…

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

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?

Speaking of which…

• How should we deal with merging two objects?

• Merging a full post and a partial post

Example

Shapes!

Shapes!

Saving objects

Merging objects

Complexity hidden by data layer

• merges objects • determines when to make

network requests

Complexity hidden by data layer

What about dealing with a changing API?

• explicit definitions for the resources returned by API endpoints

• tests based on these definitions

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)

• 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!

Thank You! Questions?

!

Elizabeth Ford [email protected]

@infraredflower

Meet the Singletons

Jimmy O’Neill Engineer, Medium

@jmyonl

+ (id)sharedResource { static id _instance; static dispatch_once_t onceToken; ! dispatch_once(&onceToken, ^{ _instance = [[Resource alloc] init]; }); ! return _instance; }

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.

UIApplication NSFileManager NSNotificationCenter NSURLSession

Since singletons are so commonplace in CocoaTouch, many developers overuse them.

A lot of this: Singletons are bad!!

!

Not a lot of this: Singletons can be bad because ____. Here’s a real example!

!

!

!

I’m going to focus on this: Singletons can be bad because ____. Here’s a real example!

- (AuthCredential *)mediumAuthCredential; !- (void)saveMediumAuthCredential:(AuthCredential *)credential;

AuthCredentialDataService

Keychain

AuthCredentialDataService

- (void)fetch:(NetRequest *)request completion:(void (^)(NetResponse *, NSError *))completion;

Net

AuthCredentialDataService

NetNet

- (void)fetchPostById:(NSString *)postId completion:(void(^)(Post *post, NSError *error))completion;

PostService

Net

AuthCredentialDataService

PostService

- (void)fetchPostViewDataWithPostId:(NSString *)postId completion:(void(^)(Post *post, PostViewModel *postViewModel, PostUserData *postUserData, NSError *error))callback;

PostViewService

PostService

Net

AuthCredentialDataService

PostViewService

PostViewController

PostViewService

PostService

Net

AuthCredentialDataService

PostViewController

PostViewController

PostViewService

PostService

Net

AuthCredentialDataService

PostViewController

PostViewController

PostViewService

PostService

Net

AuthCredentialDataService

Data dependenceTime dependence

AuthCredentialDataService

PostViewController

Time dependence isn’t an issue until the underlying

data starts changing.

PostViewController

PostViewService

PostService

Net

AuthCredentialDataServiceAuthCredentialDataService

PostViewController

If your app supports sign out, the underlying authentication

data is changing.

PostViewController

PostViewService

PostService

Net

AuthCredentialDataServiceAuthCredentialDataService

PostViewController

What problems does time dependence cause?

- Functional impurity - Leaky abstraction - Background task failures - Difficult to test

PostViewController

PostViewService

PostService

Net

AuthCredentialDataServiceAuthCredentialDataService

PostViewController

Example.

PostViewController

PostViewController

LoginViewController

PostViewController

PostViewService

PostService

Net

AuthCredentialDataService Logged in

1. User bookmark action is queued

PostViewController

PostViewService

PostService

Net

AuthCredentialDataService Logged in

dispatch_async(_backgroundQueue, ^{ [[Services postView] bookmark:_postId]; });

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]; });

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]; });

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]; });

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]; });

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]; });

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]; });

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]; });

How do we fix this?

Option 1: YOLO

Advantages!- Very little work - No testing needed - Funny later in your careerDisadvantages!- Horrible UX - Async bookmark operation still fails

- (void)logOut { // nuke everything exit(0); }

Option 2: Embrace time dependence

Advantages!- Doesn’t require refactoringDisadvantages!- 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]; }

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]; }

@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.

- (void)fetch:(NetRequest *)request completion:(void (^)(NetResponse *, NSError *))completion;

Net

AuthCredentialDataService

Net

Net

AuthCredentialDataService

Net

SessionNet

Net

Net

Session

- (void)fetch:(NetRequest *)request session:(Session *)session completion:(void (^)(NetResponse *, NSError *))completion;

Net

PostService

Net

Session

- (void)fetchPostById:(NSString *)postId session:(Session *)session completion:(void(^)(Post *post, NSError *error))completion;

PostService

PostViewService

PostService

Session

Net

- (void)fetchPostViewDataWithPostId:(NSString *)postId session:(Session *)session completion:(void(^)(Post *post, PostViewModel *postViewModel, PostUserData *postUserData, NSError *error))callback;

PostViewService

PostViewService

PostService

Net

PostViewController

Session

[[PostViewController alloc] initWithPostId:postId session:session];

PostViewController

Session

PostViewController

PostViewController

LoginViewController

1. User bookmark action is queued

PostViewService

PostService

Net

PostViewControllerLogged in Session

dispatch_async(_backgroundQueue, ^{ [[Services postView] bookmark:_postId session:_session]; });

1. User bookmark action is queued 2. User signs out

PostViewService

PostService

Net

PostViewControllerLogged in Session

LoginViewControllerLogged out

Session

dispatch_async(_backgroundQueue, ^{ [[Services postView] bookmark:_postId session:_session]; });

1. User bookmark action is queued 2. User signs out 3. Bookmark action is dequeued

PostViewService

PostService

Net

PostViewControllerLogged in Session

LoginViewControllerLogged out

Session

dispatch_async(_backgroundQueue, ^{ [[Services postView] bookmark:_postId session:_session]; });

1. User bookmark action is queued 2. User signs out 3. Bookmark action is dequeued

PostViewService

PostService

Net

PostViewController

Logged in Session

LoginViewControllerLogged out

Session

dispatch_async(_backgroundQueue, ^{ [[Services postView] bookmark:_postId session:_session]; });

1. User bookmark action is queued 2. User signs out 3. Bookmark action is dequeued

PostViewService

PostService

Net

PostViewController

Logged in Session

LoginViewControllerLogged out

Session

dispatch_async(_backgroundQueue, ^{ [[Services postView] bookmark:_postId session:_session]; });

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

LoginViewControllerLogged out

Session

dispatch_async(_backgroundQueue, ^{ [[Services postView] bookmark:_postId session:_session]; });

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

LoginViewControllerLogged out

Session

dispatch_async(_backgroundQueue, ^{ [[Services postView] bookmark:_postId session:_session]; });

Think back to the original decision.

Net

AuthCredentialDataService

Keychain

Net

It appears innocent without context.

Net

AuthCredentialDataService

Keychain

Net

Let’s do our best to recognize!the larger context earlier.

PostViewController

PostViewService

PostService

Net

AuthCredentialDataService

Data dependenceTime dependence

AuthCredentialDataService

PostViewController

Singletons are global state.

Singletons assume that the lifetime of the state they encapsulate is equal to the lifetime of the program.

Singletons are not evil.

But often, dependency injection is a sounder option.

Thank You! Questions?

!

Jimmy O’Neill [email protected]

@jmyonl

Designing an iOS Distribution Pipeline

Vinicius Baggio Fuentes

Delivery on the Web

• Feature Toggle

• Tracking and measuring

• Rolling releases

• Multiple deploys a day

Short deployment time is key

How can we do the same for apps?

Challenges

• Pre-App Store distribution is complicated

• Slow response time on App Store approvals

Fast release requirements• Over-the-air updates

• Crash report system

• Authorization server

• Feature toggles

• Versioning system

• Distribution channels

OTA updates, crash reports and authorization

• Crashlytics

• TestFlight / Apple’s upcoming beta program

• HockeyApp

Feature toggle

• A/B Testing • Long running feature development • Emergencies

VARIANT_ENABLE_RECOMMEND_NOTES_COMPOSITION

Feature toggle tools

• GroundControl, SkyLab & related projects

• Roll-your-own (not recommended)

Versioning system:semantic versioning

master 640a0a5 638b3b7

master 640a0a5

build-1.3

638b3b7

dde3123

build-1.

3.0

master 640a0a5

build-1.3

638b3b7 03dc0b5

dde3123

build-1.

3.0

master 640a0a5

build-1.3

638b3b7 03dc0b5 4aee8cd

dde3123 bb24c74

build-1.

3.0

master 640a0a5

build-1.3

638b3b7 03dc0b5 4aee8cd 04d773b

dde3123 bb24c74 f1e48af

build-1.

3.1

build-1.

3.0

master 640a0a5

build-1.3

build-1.4

638b3b7 03dc0b5 4aee8cd 04d773b

dde3123 bb24c74 f1e48af

build-1.

3.1

f1e48af

build-1.

3.0

build-1.

4.0

Distribution channels

Distribution channels

Developers Medium Staff

On merge

Version 1.4.90

Distribution channels

Version 1.4.90

Developers Medium Staff Trusted testers

On merge After a day

Distribution channels

Version 1.4.90

Developers Medium Staff Trusted testers Beta testers

On merge After a day After a week

Lessons Learned

• Xcconfig files and -derivedDataPath compiler flag helps builds being deterministic

• Plist manipulation in Python, nomad-cli tools (esp. Shenzhen, Cupertino)

Xcconfig example

#include "base.xcconfig" #include "Servers/medium.xcconfig" #include "Build/config-debug.xcconfig" !

MEDIUM_DISTRIBUTION_CHANNEL = mediumStaff

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

Thanks!• References:

• www.nomad-cli.com

• jenkins-ci.org

• crashlytics.com

• https://github.com/mattt/GroundControl

• http://semver.org

• hockeyapp.net

• http://techcrunch.com/2014/06/02/ios-testflight/

Vinícius Baggio Fuentes [email protected] @vinibaggio

Thanks for coming!