How to pull data from the server, simplify access and make it permanent without slowing down the interface of my application?

When creating my application, Marco Polo (getmarcopolo.com), I found that one of the most difficult parts of the application is to extract data from the server without slowing down the interface and without crashing it. Now I realized that I want to share my knowledge with other developers who have the same problem.

When retrieving data from a server, a number of factors must be considered:

  • Data Integrity - Data is never skipped from the server.

  • Data transfer - data is cached and can be accessed even offline

  • No interference to the interface (main thread) - achieved through multithreading

  • Speed ​​- achieved using concurrency flow

  • No thread collisions - achieved using sequential thread queues

So the question is, how do you reach all 5?

I answered this below, but would like to hear feedback on how to improve the process (with the help of this example), since I feel that finding it now is not so simple.

+3
source share
2 answers

I will use the marco update example in the notification feed. I will also talk about the Apple GCD library (see https://developer.apple.com/library/mac/documentation/Performance/Reference/GCD_libdispatch_Ref/Reference/reference.html ). First we create a singleton (see http://www.galloway.me.uk/tutorials/singleton-classes/ ):

@implementation MPOMarcoPoloManager

+ (MPOMarcoPoloManager *)instance {

    static MPOMarcoPoloManager *_instance = nil;

    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
            _instance = [[self alloc] init];
    });

    return _instance;
}

@end

[ MPOMarcoPoloManager] , singleton. , marco polos. 'static dispatch_once_t onceToken; dispatch_once (& onceToken, ^ {' .

- , . NSArray , "instance":

@interface MPOMarcoPoloManager : NSObject

+ (MPOMarcoPoloManager *)instance;

@property (strong, nonatomic) NSArray *marcoPolos;

@end

, , . , . 1. serverQueue , 2. localQueue , . , 3. NSArray , NSCoding (. http://nshipster.com/nscoding/) 4. ,

@interface MPOMarcoPoloManager()

@property dispatch_queue_t serverQueue;
@property dispatch_queue_t localQueue;

@end

@implementation MPOMarcoPoloManager

+ (MPOMarcoPoloManager *)instance {

    static MPOMarcoPoloManager *_instance = nil;

    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
            _instance = [[self alloc] init];
    });

    return _instance;
}

- (id)init {
    self = [super init];

    if (self) {

        _marcoPolos = [NSKeyedUnarchiver unarchiveObjectWithFile:self.marcoPolosArchivePath];

        if(!self.marcoPolos) {
            _marcoPolos = [NSArray array];
        }

        //serial queue
        _localQueue = dispatch_queue_create([[NSBundle mainBundle] bundleIdentifier].UTF8String, NULL);

        //Parallel queue
        _serverQueue = dispatch_queue_create(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), NULL);
    }

    return self;
}

- (NSString *)marcoPolosArchivePath {
    NSArray *cacheDirectories = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);

    NSString *cacheDirectory = [cacheDirectories objectAtIndex:0];

    return [cacheDirectory stringByAppendingFormat:@"marcoPolos.archive"];
}

- (BOOL)saveChanges {
    BOOL success = [NSKeyedArchiver archiveRootObject:self.marcoPolos toFile:[self marcoPolosArchivePath]];
    return success;
}

@end

, , . refreshMarcoPolosInBackgroundWithCallback: ((^) ( NSArray *, NSError *)) :

...
- (void)refreshMarcoPolosInBackground:((^)(NSArray *result, NSError *error))callback;
...

. , serverQueue ( ), localQueue ( ). , C (. https://developer.apple.com/library/ios/documentation/cocoa/Conceptual/Blocks/Articles/00_Introduction.html), . , , , , ( ).

...
- (void)refreshMarcoPolosInBackground:((^)(NSArray *result, NSError *error))callback {

//error checking ommitted

    //Run the server call on the global parallel queue
    dispatch_async(_serverQueue, ^{

        NSArray *objects = nil;
        NSError *error = nil;

        //This can be any method with the declaration "- (NSArray *)fetchMarcoPolo:(NSError **)callbackError" that connects to a server and returns objects
        objects = [self fetchMarcoPolo:&error];

        //If something goes wrong, callback the error on the main thread and stop
        if(error) {
            dispatch_async(dispatch_get_main_queue(), ^{
                callback(nil, error);
            });
            return;
        }

        //Since the server call was successful, manipulate the data on the serial queue to ensure no thread collisions
        dispatch_async(_localQueue, ^{

            //Create a mutable copy of our public array to manipulate
            NSMutableArray *mutableMarcoPolos = [NSMutableArray arrayWithArray:_marcoPolos];

            //PFObject is a class from Parse.com
            for(PFObject *parseMarcoPoloObject in objects) {

                BOOL shouldAdd = YES;

                MPOMarcoPolo *marcoPolo = [[MPOMarcoPolo alloc] initWithParseMarcoPolo:parseMarcoPoloObject];
                for(int i = 0; i < _marcoPolos.count; i++) {
                    MPOMarcoPolo *localMP = _marcoPolos[i];
                    if([marcoPolo.objectId isEqualToString:localMP.objectId]) {

                        //Only update the local model if the object pulled from the server was updated more recently than the local object
                        if((localMP.updatedAt && [marcoPolo.updatedAt timeIntervalSinceDate:localMP.updatedAt] > 0)||
                           (!localMP.updatedAt)) {
                            mutableMarcoPolos[i] = marcoPolo;
                        } else {
                            NSLog(@"THERE NO NEED TO UPDATE THIS MARCO POLO");
                        }
                        shouldAdd = NO;
                        break;
                    }
                }

                if(shouldAdd) {
                    [mutableMarcoPolos addObject:marcoPolo];
                }
            } 

            //Perform any sorting on mutableMarcoPolos if needed

            //Assign an immutable copy of mutableMarcoPolos to the public data structure
            _marcoPolos = [NSArray arrayWithArray:mutableMarcoPolos];

            dispatch_async(dispatch_get_main_queue(), ^{
                callback(marcoPolos, nil);
            });

        });

    });

}

...

, - , , , . , , . :

...
- (void)setMarcoPoloAsViewed:(MPOMarcoPolo *)marcoPolo inBackgroundWithlocalCallback:((^)())localCallback
              serverCallback:((^)(NSError *error))serverCallback;
...

. , , , , . , , .

- (void)setMarcoPoloAsViewed:(MPOMarcoPolo *)marcoPolo inBackgroundWithlocalCallback:(MPOOrderedSetCallback)localCallback
              serverCallback:(MPOErrorCallback)serverCallback {

//error checking ommitted

    dispatch_async(_localQueue, ^{

        //error checking ommitted

        //Update local marcoPolo object
        for(MPOMarcoPolo *mp in self.marcoPolos) {
            if([mp.objectId isEqualToString:marcoPolo.objectId]) {

                mp.updatedAt = [NSDate date];
                //MPOMarcoPolo objcts have an array viewedUsers that contains all users that have viewed this marco. I use parse, so I'm going to add a MPOUser object that is created from [PFUser currentUser] but this can be any sort of local model manipulation you need
                [mp.viewedUsers addObject:[[MPOUser alloc] initWithParseUser:[PFUser currentUser]]];

                //callback on the localCallback, so that the interface can update
                dispatch_async(dispatch_get_main_queue(), ^{    
                    //code to be executed on the main thread when background task is finished
                    localCallback(self.marcoPolos, nil);
                });

                break;
            }
        }

    });

    //Update the server on the global parallel queue
    dispatch_async(_serverQueue, ^{

        NSError *error = nil;
        PFObject *marcoPoloParseObject = [marcoPolo parsePointer];
        [marcoPoloParseObject addUniqueObject:[PFUser currentUser] forKey:@"viewedUsers"];

        //Update marcoPolo object on server
        [marcoPoloParseObject save:&error];
        if(!error) {

            //Marco Polo has been marked as viewed on server. Inform the interface
            dispatch_async(dispatch_get_main_queue(), ^{
                serverCallback(nil);
            });

        } else {

            //This is a Parse feature that your server API may not support. If it does not, just callback the error.
            [marcoPoloParseObject saveEventually];

            NSLog(@"Error: %@", error);
            dispatch_async(dispatch_get_main_queue(), ^{
                serverCallback(error);
            });
        }

    });
}

, , , . localQueue , , .

+4

dataManager, , , .

:

anywhereInApp.m

[dataManager getData: someSearchPrecate withCompletionBlock: someBlock];

dataManager.m

- (void) getData: somePredicate withCompletionBlock: someblock{
      [self.coreDataManager fetchData: somePredicate withCompletionBlock: some block];
      [self.restkitManager fetchData: somePredicate withCompletionBlock: some block];
}

​​ .

reskitmanager HTTP- .

, .

, , . venn , , .

0

All Articles