几乎每一个应用开发者都需要经历的就是将从 web service 获取到的数据转变到 Core Data 中。这篇文章阐述了如何去做。我们在这里讨论的每一个问题在之前的文章中都已经描述过了,并且 Apple 在他们的文档中也提过。然而,从头到尾回顾一遍对我们来说还是很有益的。
程序所有的代码都在 GitHub 上。
计划
我们将会建立一个简单、只读的应用程序,用来显示 CocoaPods 说明的完整列表。这些说明都显示在 table view 中,所有 pod 的说明都是以分页的形式,从 web service 取得,并以 JSON 对象返回。
我们这样来做
1.首先,我们创建一个 PodsWebservice 类,用来从 web service 请求所有的说明。
2.接着,创建一个 Importer 对象取出说明并将他们导入 Core Data。
3.最终,我们展示如何让最重要的工作在后台线程中运行。
从 Web Service 取得对象
首先,创建一个单独的类从 web service 取得数据是很不错的。我们已经写了一个简单的 web server 示例,用来获取 CocoaPods 说明并将它们生成 JSON;请求 /specs 这个 URL 会返回一个按字母排序的 pod 说明列表。web service 是分页的,所以我们需要分开请求每一页。一个响应的示例如下:
1 2 3 4 5 6 7 8 |
{ "number_of_pages": 559, "result": [{ "authors": { "Ash Furrow": "ash@ashfurrow.com" }, "homepage": "https://github.com/500px/500px-iOS-api", "license": "MIT", "name": "500px-iOS-api", ... |
我们想要创建只有一个 fetchAllPods: 方法的类,它有一个回调 block,这将会被每一个页面调用。这也可以通过代理实现;但为什么我们选择用 block,你可以读一读这篇有关消息传递机制的文章。
1 2 3 |
@interface PodsWebservice : NSObject - (void)fetchAllPods:(void (^)(NSArray *pods))callback; @end |
这个回调会被每个页面调用。实现这个方法很简单。我们创建一个帮助方法,fetchAllPods:page:,它会为一个页面取得所有的 pods,一旦加载完一页就让它再调用自己。注意一下,为了简洁,我们这里不考虑处理错误,但是你可以在 GitHub 上完整的项目中看到。处理错误总是很重要的,至少打印出错误,这样你可以很快检查到哪些地方没有像预期一样工作:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
- (void)fetchAllPods:(void (^)(NSArray *pods))callback page:(NSUInteger)page { NSString *urlString = [NSString stringWithFormat:@"http://localhost:4567/specs?page=%d", page]; NSURL *url = [NSURL URLWithString:urlString]; [[[NSURLSession sharedSession] dataTaskWithURL:url completionHandler: ^(NSData *data, NSURLResponse *response, NSError *error) { id result = [NSJSONSerialization JSONObjectWithData:data options:0 error:NULL]; if ([result isKindOfClass:[NSDictionary class]]) { NSArray *pods = result[@"result"]; callback(pods); NSNumber* numberOfPages = result[@"number_of_pages"]; NSUInteger nextPage = page + 1; if (nextPage < numberOfPages.unsignedIntegerValue) { [self fetchAllPods:callback page:nextPage]; } } }] resume]; } |
要做的就是这些了。我们解析 JSON,做一些非常粗糙的检查(验证结果是一个字典),然后调用回调函数。
将对象装进 Core Data
现在我们可以将 JSON 装进我们的 Core Data store 中了。为了分清,我们创建一个 Importer 对象来调用 web service,并且创建或者更新对象。将这些放到一个单独的类中很不错,因为这样我们的 web service 和 Core Data 部分完全解耦。如果我们想要给 store 提供一个不同的 web service 或者在别的某个地方重用 web service,我们现在并不需要手动处理这两种情况。同时,不要在 view controller 中编写逻辑代码,以后我们可以在别的 app 中更容易复用这些组件。
我们的 Importer 有两个方法:
1 2 3 4 5 |
@interface Importer : NSObject - (id)initWithContext:(NSManagedObjectContext *)context webservice:(PodsWebservice *)webservice; - (void)import; 程序所有的代码都在 GitHub 上。 计划 我们这样来做 从 Web Service 取得对象
我们想要创建只有一个 fetchAllPods: 方法的类,它有一个回调 block,这将会被每一个页面调用。这也可以通过代理实现;但为什么我们选择用 block,你可以读一读这篇有关消息传递机制的文章。
这个回调会被每个页面调用。实现这个方法很简单。我们创建一个帮助方法,fetchAllPods:page:,它会为一个页面取得所有的 pods,一旦加载完一页就让它再调用自己。注意一下,为了简洁,我们这里不考虑处理错误,但是你可以在 GitHub 上完整的项目中看到。处理错误总是很重要的,至少打印出错误,这样你可以很快检查到哪些地方没有像预期一样工作:
要做的就是这些了。我们解析 JSON,做一些非常粗糙的检查(验证结果是一个字典),然后调用回调函数。 将对象装进 Core Data 我们的 Importer 有两个方法:
通过初始化方法将 context 注入到对象中是一个非常强有力的技巧。当编写测试的时候,我们可以很容易的注入一个不同的 context。同样适用于 web service:我们可以很容易的用一个不同的对象模拟 web service。 import 方法负责处理逻辑。我们调用 fetchAllPods: 方法,并且对于每一批 pod 说明,我们都会将它们导入到 context 中。通过将逻辑代码包装到 performBlock:,context 会确保所有的事情都在正确的线程中执行。然后我们迭代这些说明,并且会为每一个说明生成一个唯一标识符(这些标识符可以是任何独一无二的,只要能确定到唯一一个 model object,正如在 Drew 的文章中解释那样。然后我们试着找到 model object,如果不存在则创建一个。loadFromDictionary: 方法需要一个 JSON 字典,并根据字典中的值更新 model object: |