之前寫了一篇使用Realm的一些總結(jié)的文章,然后里面說會總結(jié)一下使用Realm+YYModel+AFNetworking
進行項目開發(fā)的經(jīng)驗撵枢,所以這篇就是啦。
想法
第一時間想到的就是市面上最常見的展示類應用精居,api的話我選擇了V2EX社區(qū)的api锄禽,簡單的用了一個獲取所有節(jié)點的api(v2ex社區(qū)api文檔看這里),簡單的使用UITableView
展示所有的節(jié)點靴姿。最終效果如下如所示:
開工
準備工作
- 新建項目沃但,使用
Cocoapods
管理依賴包,添加AFNetworking佛吓,Realm宵晚,YYModel
這三個庫垂攘,非常基礎的東西不多寫了淤刃。 - 準備好獲取所有節(jié)點的api:
http://www.v2ex.com/api/nodes/all.json
封裝AFNetworking(以下簡稱AFN)
由于Realm
原生不支持JSON
解析晒他,這篇文章也主要是是為了看如果將Realm與YYModel
結(jié)合使用,但是我還是決定先封裝一下HTTP請求逸贾。
AFN 3.0之后陨仅,網(wǎng)絡請求的發(fā)起主要依賴AFHTTPSessionManager
這個類,我們首先自定義一個單例類SessionManager
繼承自AFHTTPSessionManager
#import <AFNetworking/AFNetworking.h>
typedef void(^Success)(id data);
typedef void(^Failure)(NSURLSessionDataTask *task, NSError *error);
@interface SessionManager : AFHTTPSessionManager
+ (instancetype)shareManager;
//為了篇幅限制只在文章中展示get請求
- (NSURLSessionDataTask *)GETWithUrl:(NSString *)url params:(NSDictionary *)params success:(Success)success failure:(Failure)failure;
+ (NSURLSessionDataTask *)GETWithUrl:(NSString *)url params:(NSDictionary *)params success:(Success)success failure:(Failure)failure;
@end
#import "SessionManager.h"
static NSString *const kBaseURL = @"http://www.v2ex.com/api/";
typedef NS_ENUM(NSInteger, APIMethod) {
APIMethodGET,
APIMethodPOST,
APIMethodPUT,
APIMethodDELETE,
};
@implementation SessionManager
+ (instancetype)shareManager {
static SessionManager *manager = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
manager = [[self alloc] init];
});
return manager;
}
- (instancetype)init {
self = [super initWithBaseURL:[NSURL URLWithString:kBaseURL]];
self.requestSerializer = [AFHTTPRequestSerializer serializer];
self.responseSerializer = [AFHTTPResponseSerializer serializer];
self.requestSerializer.timeoutInterval = 30;
return self;
}
//instance method
- (NSURLSessionDataTask *)GETWithUrl:(NSString *)url params:(NSDictionary *)params success:(Success)success failure:(Failure)failure {
return [self requestWithMethod:APIMethodGET url:url params:params success:success failure:failure];
}
//class method
+ (NSURLSessionDataTask *)GETWithUrl:(NSString *)url params:(NSDictionary *)params success:(Success)success failure:(Failure)failure {
return [[self shareManager] GETWithUrl:url params:params success:success failure:failure];
}
- (NSURLSessionDataTask *)requestWithMethod:(APIMethod)method url:(NSString *)url params:(NSDictionary *)params success:(Success)success failure:(Failure)failure {
if (![url hasPrefix:@"http"]) {
url = [[self class] reponseUrl:url];
}
if ([url rangeOfString:@":id"].length > 0 ) {
NSAssert(NO, @"路徑 「%@」中有需要替換的id", url);
}
NSURLSessionDataTask *task = nil;
switch (method) {
case APIMethodGET: {
task = [self GET:url parameters:params progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
[self handleTask:task response:responseObject success:success];
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
[self handleTask:task error:error failure:failure];
}];
break;
}
//other case
//...
}
return task;
}
+ (NSString *)reponseUrl:(NSString *)url{
NSString *result = url;
result = [NSString stringWithFormat:@"%@\%@", kBaseURL, result];
return result;
}
#pragma mark - handle response
- (void)handleTask:(NSURLSessionDataTask *)task response:(id)responseOject success:(Success)success {
if (success) {
success(responseOject);
}
}
- (void)handleTask:(NSURLSessionDataTask *)task error:(NSError *)error failure:(Failure)failure{
//handle error
}
@end
說明一下:我們的請求manager
創(chuàng)建的時候铝侵,會基于http://www.v2ex.com/api/
這個baseUrl創(chuàng)建灼伤,習慣RESTful API
的對這個也習以為常了,同時在實例化的時候我們使用了requestSerializer 哟沫、responseSerializer
饺蔑,并且設置每次請求超時時長為30秒。
封裝API
現(xiàn)在對于AFN的基本封裝工作已經(jīng)完成嗜诀,接下來只需要搞定每個API了猾警,我們新建一個V2EXAPIManager
類繼承自剛才的SessionManager
,在接口的實現(xiàn)中補全接口:
#import "SessionManager.h"
#import <AFNetworking.h>
#import "Node.h"
@interface V2EXAPIManager : SessionManager
- (NSURLSessionDataTask *)getAllNodeSuccess:(Success)success failurl:(Failure)failure;
@end
#import "V2EXAPIManager.h"
static NSString *allNodelUrl = @"nodes/all.json";
@implementation V2EXAPIManager
- (NSURLSessionDataTask *)getAllNodeSuccess:(Success)success failurl:(Failure)failure {
return [self GETWithUrl:allNodelUrl params:nil success:^(id data) {
NSArray <Node*>*allNodes = [NSArray yy_modelArrayWithClass:[Node class] json:data];
if (success) {
success(allNodes);
}
} failure:^(NSURLSessionDataTask *task, NSError *error) {
if (failure) {
failure(task,error);
}
}];
}
@end
有了BaseUrl隆敢,接下來只需要的發(fā)起請求的時候发皿,拼接上具體的需要訪問服務器的資源,這里是nodes/all.json
拂蝎。
新建可用于持久化的Node模型
通過log我們可以看到穴墅,得到的是這樣json串,映射到iOS中則是一個包含多個字典的數(shù)組温自。
[
{
"id": 1,
"name": "babel",
"url": "http://www.v2ex.com/go/babel",
"title": "Project Babel",
"title_alternative": "Project Babel",
"topics": 1119,
"header": "Project Babel - 幫助你在云平臺上搭建自己的社區(qū)",
"footer": "V2EX 基于 Project Babel 驅(qū)動玄货。Project Babel 是用 Python 語言寫成的,運行于 Google App Engine 云計算平臺上的社區(qū)軟件悼泌。Project Babel 當前開發(fā)分支 2.5松捉。最新版本可以從 <a href=\"http://github.com/livid/v2ex\" target=\"_blank\">GitHub</a> 獲取。",
"created": 1272206882
},
...
...
]
接下來就是根據(jù)拿到的數(shù)據(jù)字段建立我們的Node
模型了,在使用Realm
的時候需要我們的數(shù)據(jù)模型繼承自RLMObject
馆里,同時為了使用YYModel
,我們需要遵循<YYModel>
協(xié)議:
#import <Realm/Realm.h>
#import <YYModel/YYModel.h>
@interface Node : RLMObject<YYModel>
@property NSString *ID;
@property NSString *name;
@property NSString *url;
@property NSString *title;
@property NSString *title_alternative;
@property NSInteger topics;
@property NSString *header;
@property NSString *footer;
@property NSDate *created;
@end
#import "Node.h"
@implementation Node
+ (NSString *)primaryKey {
return @"ID";
}
+ (NSArray<NSString *> *)indexedProperties {
return @[@"ID"];
}
//由于`id`字段會與系統(tǒng)關鍵字沖突隘世,我們可以手動轉(zhuǎn)換為大寫`ID`
+ (NSDictionary<NSString *,id> *)modelCustomPropertyMapper {
return @{@"ID":@"id"};
}
@end
為了方便以后的使用以及提高查詢速度,我們使用ID作為主鍵與索引字段鸠踪。
這樣就可以在請求到數(shù)據(jù)之后使用
NSArray <Node*>*allNodes = [NSArray yy_modelArrayWithClass:[Node class] json:data];
直接將數(shù)據(jù)轉(zhuǎn)換為存放節(jié)點Node
的數(shù)組丙者。
在控制器里面發(fā)起請求并持久化
- (void)getNodes {
[[V2EXAPIManager shareManager] getAllNodeSuccess:^(id data) {
NSArray *nodes;
if ([data isKindOfClass:[NSArray class]]) {
nodes = data;
}
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
@autoreleasepool {
//delete old data
RLMRealm *realm = [RLMRealm defaultRealm];
[realm beginWriteTransaction];
[realm deleteAllObjects];
[realm commitWriteTransaction];
//add new data
[realm beginWriteTransaction];
for (Node *node in nodes) {
[realm addObject:node];
}
[realm commitWriteTransaction];
//retrieve data and reload data in main thread
dispatch_async(dispatch_get_main_queue(), ^{
RLMRealm *mainThreadRealm = [RLMRealm defaultRealm];
self.nodes = [Node allObjectsInRealm:mainThreadRealm];
[self.tableview reloadData];
});
}
});
} failurl:^(NSURLSessionDataTask *task, NSError *error) {
self.nodes = [Node allObjects];
[self.tableview reloadData];
}];
}
其他將數(shù)據(jù)渲染到UI上的一些代碼就不往上貼了,當然营密,本例也是非的簡陋械媒,例如封裝請求的錯誤處理也沒有做,也沒有多余的UI設計评汰,不過我們可以看到滥沫,使用Realm+YYModel
完成一個從遠程請求數(shù)據(jù)并在本地持久化的需求非常簡單侣集,關鍵代碼甚至沒有幾行。
你可以在GitHub
直接clone
這個Repository