gpt4 book ai didi

ios - 如何从服务器提取数据、使其易于访问并持久保存而不减慢应用程序的界面速度?

转载 作者:塔克拉玛干 更新时间:2023-11-02 07:46:45 25 4
gpt4 key购买 nike

在构建我的应用程序 Marco Polo (getmarcopolo.com) 时,我发现该应用程序最具挑战性的部分之一是从服务器提取数据,同时不会降低界面速度并且不会崩溃。我现在已经解决了这个问题,并希望与遇到相同问题的任何其他开发者分享我的知识。

从服务器提取数据时,需要考虑许多因素:

  1. 数据完整性 - 服务器永远不会遗漏任何数据

  2. 数据持久化——数据被缓存,即使离线也可以访问

  3. 不干扰界面(主线程)——使用多线程实现

  4. 速度 - 使用线程并发实现

  5. 没有线程冲突 - 使用串行线程队列实现

那么问题是,您如何实现所有 5 个目标?

我已经在下面回答了这个问题,但很想听听有关如何改进流程的反馈(通过这个例子),因为我觉得现在在一个地方找到它不是很容易。

最佳答案

我将使用刷新通知提要中的宏的示例。我还会提到 Apple 的 GCD 库(参见 https://developer.apple.com/library/mac/documentation/Performance/Reference/GCD_libdispatch_Ref/Reference/reference.html )。首先,我们创建一个单例(参见 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 实例],并访问单例中的属性。它还确保始终只有一个马可波罗实例。 'static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{'保证线程稳定性。

下一步是添加我们将公开访问的数据结构。在这种情况下,将 marcos 的 NSArray 添加到头文件,以及“实例”的公共(public)声明:

@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 *result, NSError *error)) 的声明添加到头文件中:

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

现在是实现刷新的时候了。请注意,所有服务器调用都在 serverQueue(并行)上执行,任何数据操作都在 localQueue(串行)上完成。方法完成后,我们使用所谓的 C block (参见 https://developer.apple.com/library/ios/documentation/cocoa/Conceptual/Blocks/Articles/00_Introduction.html)将结果回调到主线程。任何作用于后台线程的任务都应该有一个回调到主线程来通知接口(interface)刷新已经完成(无论是否成功)。

...
- (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'S 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;
...

现在是实现该方法的时候了。请注意,本地操作是在串行队列上完成的,然后立即回调到主线程,允许界面更新而无需等待服务器连接。然后它更新服务器,并在一个单独的回调中回调主线程以通知接口(interface)服务器保存已完成。

- (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's 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 的必要性可能并不明显,但当有许多不同类型的操作可用时,它就变得至关重要。

关于ios - 如何从服务器提取数据、使其易于访问并持久保存而不减慢应用程序的界面速度?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/21692263/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com