Upgrade to Pro — share decks privately, control downloads, hide ads and more …

The Parse SDK: What's inside?

The Parse SDK: What's inside?

Talk in depth about key principles used when building Parse SDK for iOS, OS X. Mainly, talk about asynchronous programming with promises in Swift/ObjC as well as highlight decoupled architecture model used in Parse SDKs.

By Nikita Lutsenko. Original slides: https://github.com/nlutsenko/inside-parse-sdk

Powered by http://xebia.com

do{iOS} conference

November 09, 2015
Tweet

More Decks by do{iOS} conference

Other Decks in Programming

Transcript

  1. Inside Parse SDK
    Nikita Lutsenko
    @nlutsenko
    Facebook, Parse

    View full-size slide

  2. Hi
    » Nikita Lutsenko
    » @nlutsenko
    » Facebook
    » Slingshot
    » Parse

    View full-size slide

  3. Inside Parse SDK
    » ~730 Source Files
    » ~51000 Lines of Code
    » ~150 Classes+Protocols
    » 767 Unit Tests
    » Support for iOS, OS X
    » 2 Maintainers

    View full-size slide

  4. That's all folks.
    Thank you.
    !

    View full-size slide

  5. Not that simple...
    !

    View full-size slide

  6. Parse SDK
    » Object & File Storage
    » Query Engine
    » Local Datastore
    » User Authentication
    » Global Configuration
    » Analytics & Push Notifications
    » Much more...

    View full-size slide

  7. Parse SDKs now power
    800 million active
    app-device pairs per month

    View full-size slide

  8. Inside Parse SDK
    » Promises for Asynchronous Operations
    » Instance/Controller/State Architecture
    » Lazy-loaded Dependency Injection
    » Written in ObjC, works in Swift
    » ???
    » PROFIT!!!

    View full-size slide

  9. Promise everything!
    » Perform asynchronous work
    » Serially or in Parallel
    » Errors, cancellation, chaining
    » Unified across ObjC/Java/.NET
    » Light-weight and Extendable

    View full-size slide

  10. ! Bolts.framework
    » Composable Promise Framework
    » Tasks, Executors, Cancellation Tokens
    » Avaialble for ObjC, Java, .NET
    » Open Source
    » github.com/BoltsFramework
    » Coming to Swift!

    View full-size slide

  11. - (NSData *)dataWithContentsOfFile:(NSString *)file {
    NSError *error = nil;
    NSData *data = [NSData dataWithContentsOfFile:file
    options:NSDataReadingMappedIfSafe
    error:&error];
    return data;
    }

    View full-size slide

  12. - (NSData *)dataWithContentsOfFile:(NSString *)file
    error:(NSError **)error {
    NSData *data = [NSData dataWithContentsOfFile:file
    options:NSDataReadingMappedIfSafe
    error:&error];
    return data;
    }

    View full-size slide

  13. - (void)getDataWithContentsOfFile:(NSString *)file
    completion:(void (^)(NSData *data))completion {
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    NSError *error = nil;
    NSData *data = [NSData dataWithContentsOfFile:file
    options:NSDataReadingMappedIfSafe
    error:&error];
    completion(data);
    });
    }

    View full-size slide

  14. - (void)getDataWithContentsOfFile:(NSString *)file
    completion:(void (^)(NSData *data, NSError *error))completion {
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    NSError *error = nil;
    NSData *data = [NSData dataWithContentsOfFile:file
    options:NSDataReadingMappedIfSafe
    error:&error];
    completion(data, error);
    });
    }

    View full-size slide

  15. - (void)getDataWithContentsOfFile:(NSString *)file
    completion:(void (^)(NSData *data, NSError *error))completion { ... }
    - (void)doWorkForFile:(NSString *)file {
    [self getDataWithContentsOfFile:file completion:^(NSData *data, NSError *error) {
    if (error) {
    [self reportError:error];
    } else {
    [self continueWorkForData:data];
    }
    [self deleteFile:file];
    }];
    }

    View full-size slide

  16. - (void)getDataWithContentsOfFile:(NSString *)file
    completion:(void (^)(NSData *data, NSError *error))completion { ... }
    - (void)doWorkForFile:(NSString *)file {
    [self getDataWithContentsOfFile:file completion:^(NSData *data, NSError *error) {
    if (error) {
    [self reportError:error];
    [self deleteFile:file];
    } else {
    [self continueWorkForData:data completion:^(NSError *error) {
    if (error) {
    [self reportError:error];
    }
    [self deleteFile:file];
    }];
    }
    }];
    }

    View full-size slide

  17. - (void)getDataWithContentsOfFile:(NSString *)file
    completion:(void (^)(NSData *data, NSError *error))completion { ... }
    - (void)doWorkForFile:(NSString *)file {
    [self getDataWithContentsOfFile:file completion:^(NSData *data, NSError *error) {
    if (error) {
    [self reportError:error]; // << Duplicate error handling !
    [self deleteFile:file];
    } else {
    [self continueWorkForData:data completion:^(NSError *error) {
    if (error) {
    [self reportError:error]; // << Duplicate error handling !
    }
    [self deleteFile:file];
    }];
    }
    }];
    }

    View full-size slide

  18. - (void)getDataWithContentsOfFile:(NSString *)file
    completion:(void (^)(NSData *data, NSError *error))completion { ... }
    - (void)doWorkForFile:(NSString *)file {
    [self getDataWithContentsOfFile:file completion:^(NSData *data, NSError *error) {
    if (error) {
    [self reportError:error]; // << Duplicate error handling !
    [self deleteFile:file]; // << Duplicate code !
    } else {
    [self continueWorkForData:data completion:^(NSError *error) {
    if (error) {
    [self reportError:error]; // << Duplicate error handling !
    }
    [self deleteFile:file]; // << Duplicate code !
    }];
    }
    }];
    }

    View full-size slide

  19. - (void)getDataWithContentsOfFile:(NSString *)file
    completion:(void (^)(NSData *data, NSError *error))completion {
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    NSError *error = nil;
    NSData *data = [NSData dataWithContentsOfFile:file
    options:NSDataReadingMappedIfSafe
    error:&error];
    completion(data, error);
    });
    }

    View full-size slide

  20. - (BFTask *)getDataAsyncWithContentsOfFile:(NSString *)file { // << Return a BFTask !
    return [BFTask taskFromExecutor:[BFExecutor defaultPriorityBackgroundExecutor] withBlock:^id{
    NSError *error = nil;
    NSData *data = [NSData dataWithContentsOfFile:file
    options:NSDataReadingMappedIfSafe
    error:&error];
    if (error) {
    return [BFTask taskWithError:error];
    }
    return data;
    }];
    }

    View full-size slide

  21. - (BFTask *)getDataAsyncWithContentsOfFile:(NSString *)file { ... }
    - (void)doWorkForFile:(NSString *)file {
    [[[self getDataAsyncWithContentsOfFile:file] continueWithBlock:^id(BFTask *task) {
    if (error) {
    [self reportError:error]; // << Duplicate error handling !
    [self deleteFile:file]; // << Duplicate code !
    } else {
    [self continueWorkForData:data completion:^(NSError *error) {
    if (error) {
    [self reportError:error]; // << Duplicate error handling !
    }
    [self deleteFile:file]; // << Duplicate code !
    }];
    }
    }];
    }

    View full-size slide

  22. - (BFTask *)getDataAsyncWithContentsOfFile:(NSString *)file { ... }
    - (void)doWorkForFile:(NSString *)file {
    [[[self getDataAsyncWithContentsOfFile:file] continueWithSuccessBlock:^id(BFTask *task) {
    return [self continueWorkAsyncForData:data]; // << Return a BFTask !
    }] continueWithBlock:^id(BFTask *task) {
    if (task.faulted) {
    [self reportError:error];
    }
    [self deleteFile:file];
    return nil;
    }];
    }

    View full-size slide

  23. - (BFTask *)getDataAsyncWithContentsOfFile:(NSString *)file { ... }
    - (void)doWorkForFile:(NSString *)file {
    [[[self getDataAsyncWithContentsOfFile:file] continueWithSuccessBlock:^id(BFTask *task) {
    return [self continueWorkAsyncForData:data];
    }] continueWithBlock:^id(BFTask *task) {
    if (task.faulted) {
    [self reportError:error];
    }
    return [self deleteFileAsync:file]; // << Return a BFTask !
    }];
    }

    View full-size slide

  24. - (BFTask *)getDataAsyncWithContentsOfFile:(NSString *)file { ... }
    - (BFTask *)doWorkAsyncForFile:(NSString *)file { // << Return a BFTask !
    return [[[self getDataAsyncWithContentsOfFile:file] continueWithSuccessBlock:^id(BFTask *task) {
    return [self continueWorkAsyncForData:data];
    }] continueWithBlock:^id(BFTask *task) {
    if (task.faulted) {
    [self reportError:error];
    }
    return [self deleteFile:file];
    }];
    }

    View full-size slide

  25. That's all good...

    View full-size slide

  26. But how do you manage
    complexity? !?

    View full-size slide

  27. Instance vs Controller vs State !
    » Separation of Concerns and Responsbilities
    » Lazy Dependency Injection
    » No Mutable State
    » Thread-safe
    » Awesome!

    View full-size slide

  28. Instance vs Controller vs State
    » Instance !
    » Public API
    » Immutable State
    » State "
    » Current Object State
    » Controller #
    » Acts on State
    » Returns State

    View full-size slide

  29. @interface PFFile : NSObject
    @property NSString *urlString;
    @property NSString *name;
    - (BFTask *)getDataInBackground;
    - (BFTask *)saveInBackground;
    @end

    View full-size slide

  30. @interface PFFileState : PFBaseState
    @property (nonatomic, copy, readonly) NSString *name;
    @property (nullable, nonatomic, copy, readonly) NSString *urlString;
    @property (nullable, nonatomic, copy, readonly) NSString *mimeType;
    @end
    @interface PFMutableFileState : PFFileState
    @property (nonatomic, copy, readwrite) NSString *name;
    @property (nullable, nonatomic, copy, readwrite) NSString *urlString;
    @property (nullable, nonatomic, copy, readwrite) NSString *mimeType;
    @end

    View full-size slide

  31. Base State
    » Base class for every state.
    » NSCopying, NSMutableCopying.
    » Equality and Comparison
    » -isEqual:
    » -compare:
    » -hash

    View full-size slide

  32. @interface PFFileState : PFBaseState
    @property (nonatomic, copy, readonly) NSString *name;
    @property (nullable, nonatomic, copy, readonly) NSString *urlString;
    @property (nullable, nonatomic, copy, readonly) NSString *mimeType;
    @end
    @interface PFMutableFileState : PFFileState
    @property (nonatomic, copy, readwrite) NSString *name;
    @property (nullable, nonatomic, copy, readwrite) NSString *urlString;
    @property (nullable, nonatomic, copy, readwrite) NSString *mimeType;
    @end

    View full-size slide

  33. @interface PFFileState : PFBaseState
    @property (nonatomic, copy, readonly) NSString *name;
    @property (nullable, nonatomic, copy, readonly) NSString *urlString;
    @property (nullable, nonatomic, copy, readonly) NSString *mimeType;
    - (instancetype)initWithState:(PFFileState *)state;
    + (instancetype)stateWithState:(PFFileState *)state;
    - (BOOL)isEqual:(id)object;
    - (NSInteger)hash;
    - (NSComparisonResult)compare:(PFFileState *)object;
    - (NSDictionary *)dictionaryRepresentation;
    - (id)debugQuickLookObject;
    @end

    View full-size slide

  34. @implementation PFFileState
    + (NSDictionary *)propertyAttributes {
    return @{
    @"name" : [PFPropertyAttributes attributesWithAssociationType:PFPropertyInfoAssociationTypeCopy],
    @"urlString" : [PFPropertyAttributes attributesWithAssociationType:PFPropertyInfoAssociationTypeCopy],
    @"mimeType" : [PFPropertyAttributes attributesWithAssociationType:PFPropertyInfoAssociationTypeCopy],
    };
    }
    - (id)copyWithZone:(NSZone *)zone {
    return [[PFFileState allocWithZone:zone] initWithState:self];
    }
    - (instancetype)mutableCopyWithZone:(NSZone *)zone {
    return [[PFMutableFileState allocWithZone:zone] initWithState:self];
    }
    @end

    View full-size slide

  35. @implementation PFMutableFileState
    @dynamic name;
    @dynamic urlString;
    @dynamic mimeType;
    @end

    View full-size slide

  36. How about controllers?

    View full-size slide

  37. @interface PFFileController : NSObject
    @property (nonatomic, weak, readonly) id dataSource;
    + (instancetype)controllerWithDataSource:(id)dataSource;
    - (BFTask *)downloadFileAsyncWithState:(PFFileState *)fileState...
    - (BFTask *)uploadFileAsyncWithState:(PFFileState *)fileState...
    @end

    View full-size slide

  38. @protocol PFCommandRunnerProvider
    @property (nonatomic, strong, readonly) id commandRunner;
    @end
    @protocol PFFileManagerProvider
    @property (nonatomic, strong, readonly) PFFileManager *fileManager;
    @end

    View full-size slide

  39. @protocol PFNoYoloControllerProvider
    @property (nonatomic, strong, readonly) PFNoYoloController *noYoloController;
    @end
    @protocol PFYoloControllerProvider
    @property (null_resettable, nonatomic, strong) PFYoloController *yoloController;
    @end

    View full-size slide

  40. - (PFYoloController *)yoloController {
    __block PFYoloController *controller = nil;
    dispatch_sync(_controllerAccessQueue, ^{
    if (!_yoloController) {
    _yoloController = [PFYoloController controllerWithDataSource:self.dataSource];
    }
    controller = _yoloController;
    });
    return controller;
    }
    - (void)setYoloController:(PFYoloController *)controller {
    dispatch_sync(_controllerAccessQueue, ^{
    _yoloController = controller;
    });
    }

    View full-size slide

  41. Questions?
    @nlutsenko
    github.com/BoltsFramework
    github.com/ParsePlatform

    View full-size slide