AFNetworking 学习笔记
- 这篇笔记是在 AFN v0.10.1 时候写的,AFN v1.0 以后加入了不少新东西,比如 SSL 支持,不过整体结构没有变化。
- 后续跟进了一篇 AFNetworking Notes 2
上图来自 @mattt 对 AFN 的介绍:Everybody Loves AFNetworking And So Can You!. 学习 AFN,简单记录一下以加深自己理解。
AFN 的基础部分是 AFURLConnectionOperation,一个 NSOperation subclass,实现了 NSURLConnection 相关的 delegate+blocks,网络部分是由 NSURLConnection 完成,然后利用 NSOperation 的 state (isReady→isExecuting→isFinished) 变化来进行网络控制。网络请求是在一个指定的线程(networkRequestThread)完成。
AFURLConnectionOperation 是一个很纯粹的网络请求 operation,可以对他进行 start/cancel/pause/resume 操作,可以获取对应的 NSURLRequest 和 NSURLResponse 数据。支持 NSInputStream/NSOutputStream,提供了 uploadPress 和 downloadProgress 以方便其他使用。
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"http://httpbin.org/ip"]];
AFURLConnectionOperation *operation = [[AFURLConnectionOperation alloc] initWithRequest:request];
operation.completionBlock = ^ {
NSLog(@"Complete: %@", operation.responseString);
};
[operation start];
插播:@mattt 在 NSHipster 里有一篇 NSOperation 详细介绍了 NSOperation 的 state、priority、dependency 等,对理解 AFURLConnectionOperation 很有帮助。
理解了 AFURLConnectionOperation 再看 AFHTTPRequestOperation 就简单很多。AFHTTPRequestOperation 是 AFURLConnectionOperation 的子类,针对 HTTP+HTTPS 协议做了一层封装,比如 statusCode、Content-Type 等,添加了请求成功和失败的回调 block,提供了 addAcceptableContentTypes:
以方便上层使用。
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"http://httpbin.org/robots.txt"]];
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(@"Success: %@", operation.responseString);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(@"Failure: %@", error);
}];
[operation start];
AFJSONRequestOperation 是 AFHTTPRequestOperation 的子类,针对 JSON 类型请求做了特殊处理,在有了 AFHTTPRequestOperation+AFURLConnectionOperation 的基础工作后,AFJSONRequestOperation 已经非常方便直接使用了。指定 acceptableContentTypes:
以支持 JSON,responseJSON
直接返回已经解析好的 JSON 数据对象。下载到 JSON 数据后在一单独线程 queue(json_request_operation_processing_queue)对 JSON 数据进行解析处理,处理完成后由主线程回调 success block。
AFN 的 JSON encode/decode 处理做的非常巧妙,现在有很多 JSON 解析库,第三方的 JSONKit、SBJSON 等,iOS 5+ 自带的 NSJSONSerialization,不同的项目可能会因为不同的需求而用不同的库,AFN 就封装了一个 AFJSONUtilities,提供 AFJSONEncode
和 AFJSONDecode
两个方法,通过 NSClassFromString
和 NSSelectorFromString
来查找项目中使用的 JSON 库然后进行 encode/decode。
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"http://httpbin.org/get"]];
AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request
success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
NSLog(@"Success :%@", JSON);
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) {
NSLog(@"Failure: %@", error);
}];
[operation start];
AFXMLRequestOperation/AFPropertyListRequestOperation/AFImageRequestOperation 和 AFJSONRequestOperation 类似,针对 XML、Plist、image 类型请求做了一些处理。其中 AFImageRequestOperation 额外有一个 imageProcessingBlock,取到图片后可以在一个单独线程 queque 对图片进行处理,比如缩放、切圆角、图片特效等,然后再交给 main_queue success block.
AFN 还提供了一个 UIImageView+AFNetworking category,可以用 setImageWithURL:
来设置图片。这个 cagetory 和 SDWebImage 类似但更简单一些,图片下载由 AFN 完成,图片缓存由 NSCache 处理。
直接用上面这些已经可以方便的做网络请求,AFN 在这些基础上还提供了一个 AFHTTPClient,把 HTTP 请求的 Headers、User-Agent 等再次包装,方便使用。AFHTTPClient 是一个单例,对请求参数做了 URL 编码;维护一个 NSOperationQueue,不同的请求生成各自的 AFHTTPRequestOperation 然后 enqueueHTTPRequestOperation:
添加的队列顺序执行;registerHTTPOperationClass:
方法用来注册上面的 JSON/XML/Plist/image operation,拿到请求结果后交给对应的 operation 处理。AFHTTPClient 还针对 GET/POST/HEAD/PUT/DELETE 等不同的请求做了不同的 URL 参数和 Headers 处理,包括 multipart/form-data 类型。
AFHTTPClient 支持批量添加 operations,生成一个 batchedOperation,把所有 operations 作为 batchedOperation 的 dependency,再依次把所有 operations 和 batchedOperation 都添加到 operationQueue,这样每一个 operation 完成后都可以做一个 progressBlock 来返回当前已完成的 operations 数和总数,等所有 operations 都完成后会做 batchedOperation 的 completionBlock,就可以在这一批 operations 都完成后做一些善后处理。
AFHTTPClient 提倡对同一应用(同一 baseURL)的网络请求封装自己的 HTTPClient 子类,这样会方便很多。参考 WBKHTTPClient.
AFN 还提供了很多模块,可以很方便的和 AFN 整合做一些工作,比如 OAuth,Amazon S3 等,详见 AFNetworking-Extensions.
AFN 作者 @mattt 做东西很有自己一套思想在里面,推荐 What I Learned From AFNetworking’s GitHub Issues,视频。
Don't use accessor methods in init and dealloc
苹果在 WWDC 2012 Session 413 - Migrating to Modern Objective-C 里强调不要在 init 和 dealloc 里使用 accessor methods:
Always use accessor methods. Except in initializer methods and dealloc.
之前没有注意过这种情况,稍微搜索学习了一下。文档 Memory Management 里确实有这说法:
The only places you shouldn’t use accessor methods to set an instance variable are in initializer methods and dealloc.
提倡下面这种写法:
- (id)init {
self = [super init];
if (self) {
_count = [[NSNumber alloc] initWithInteger:0];
}
return self;
}
- (id)initWithCount:(NSNumber *)startingCount {
self = [super init];
if (self) {
_count = [startingCount copy];
}
return self;
}
dealloc 不能用比较好理解,self.property 是向 property 发了一个消息,有可能该对象的生命周期已经结束,不能再接受消息。init 不能用比较靠谱的说法是如果有 subClass 并重载了 accessor,那么 init 里 self.property 就无效;另外也可能会有其他影响,比如 KVC notifications 等。
SO 参考帖子:
What I have learned from Cheddar for iOS
- Code Style.
- DRY,整理适合自己的代码库(SSToolkit)。
application:didFinishLaunchingWithOptions:
里尽量少操作,减少 launch 时间。只做界面展示工作,数据层用 dispatch_async 异步操作。- 多用
[image stretchableImageWithLeftCapWidth:5 topCapHeight:0]
图片拉伸,减小 App size。效果上并没有缺失很多。很多效果都可以用代码实现,不一定非得贴图。 - 数据层封装不同的对象,方便各种调用。直接用 dict 传来传去不够清晰。
- Core Data 和 UIViewController 可以很好的结合,深度封装后的确很方便,参见 SSManagedViewController = UIViewController+SSManagedObject(NSManagedObject),SSDataKit。但这样感觉 ViewController 很沉重,也可能是因为我对 Core Data 不熟悉,以后有机会加深一下 CD 的学习使用。
- KVO 是个好东西。
- 定义一些内部 scheme 来做界面跳转,
x-cheddar-tag
. UIColor+CheddariOSAdditions.h
-cheddarTextColor
,定义整体风格配色,很方便使用。UIFont+CheddariOSAdditions.h
同理。cellHeightForText:
用dispatch_once_t
生成一个单例 label,然后sizeThatFits:
计算。prepareForReuse
释放数据。CDKHTTPClient
学习 AFN 的好例子。单实例,用 block 封装接口。Block is better than delegate, simple, clear and powerful.
iOS Background Task Notes
iOS 4+ 支持 audio、location、voip 后台常驻任务,除此以外 App 还可以向系统申请额外一段时间(十分钟)在后台执行某些任务,比如进入后台后发送操作日志等。
注册消息通知,或者直接实现 - (applicationDidEnterBackground:(UIApplication *)application
delegate。
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(appDidEnterBackground)
name:UIApplicationDidEnterBackgroundNotification
object:nil];
向系统申请 background task 并执行:
- (void)appDidEnterBackground
{
if (![UIDevice currentDevice].multitaskingSupported) {
return;
}
UIApplication *app = [UIApplication sharedApplication];
__block UIBackgroundTaskIdentifier bgTask = [app beginBackgroundTaskWithExpirationHandler:^{
dispatch_async(dispatch_get_main_queue(), ^{
if (bgTask != UIBackgroundTaskInvalid) {
[app endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}
});
}];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//Do tasks you want.
dispatch_async(dispatch_get_main_queue(), ^{
if (bgTask != UIBackgroundTaskInvalid) {
[app endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}
});
});
}
注意:beginBackgroundTaskWithExpirationHandler:
生成的 task 在执行完以后必须要用 endBackgroundTask:
告诉系统任务已结束,不然在申请时间用完以后 App 会被系统直接终止,而不是挂起(suspended)。
20120730
公司最早的一批人,就剩我了。
相信她们的选择,祝福她们的明天,可心里还是难受。
你们一定要过的比今天好。
Symbol not found: _objc_storeStrong
Crash log:
dyld: lazy symbol binding failed: Symbol not found: _objc_storeStrong
Referenced from: /var/mobile/Applications/6E4A4771-B39A-48B9-A7B7-0EA0108DCAF4/X.app/X
Expected in: /usr/lib/libobjc.A.dylib
dyld: Symbol not found: _objc_storeStrong
Referenced from: /var/mobile/Applications/6E4A4771-B39A-48B9-A7B7-0EA0108DCAF4/X.app/X
Expected in: /usr/lib/libobjc.A.dylib
在 Non-ARC 项目中使用 ARC-enabled 库的时候,需要对库文件在 Build Phases->Compile Sources 添加 -fobjc-arc
Compiler Flags,在 Build Settings->Other Linker Flags 添加 -fobjc-arc
.
via libobjc.A.dylib compile error on iOS 4.3, Static library with ARC support linked to non-ARC project causing linker errors.
UISegmentedControl error on iOS 4.x
Code 1:
self.segmentedControl = [[[UISegmentedControl alloc] init] autorelease];
[_segmentedControl setSelectedSegmentIndex:0];
[_segmentedControl addTarget:self
action:@selector(segmentedControlSwitch)
forControlEvents:UIControlEventValueChanged];
Code 2:
self.segmentedControl = [[[UISegmentedControl alloc] init] autorelease];
[_segmentedControl addTarget:self
action:@selector(segmentedControlSwitch)
forControlEvents:UIControlEventValueChanged];
[_segmentedControl setSelectedSegmentIndex:0];
注意 [_segmentedControl setSelectedSegmentIndex:0];
的位置,在 iOS 4.x 下,Code 2 代码在设置 selectedSegmentIndex
的时候会执行一次 segmentedControlSwitch
方法。在 iOS 5+ 没有这个问题。
随想
当你坚持着自己的想法在做一件自己想做事情的时候,当你坚持着自己的原则拒绝去做一些事情的时候,总会有一些声音有意无意地告诉你:你这个傻逼,这个社会就是这样。
这个社会太浮躁了现在,巨大的通胀让每个人都想着去赚快钱,大家都很难静下心来做一件让自己开心的事情。嗯,让自己开心的事情。
Tim O’Reilly:
Money is like gasoline during a road trip. You don’t want to run out of gas on your trip, but you’re not doing a tour of gas stations. You have to pay attention to money, but it shouldn’t be about the money.
UITaleView 多选
效果就是 cell.contentView 右移,左侧留一空圆,点击选中,再点取消选中。
[_rootTable setEditing:YES animated:YES];
进入多选,然后实现 delegate:
- (UITableViewCellEditingStyle)tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath
{
// 关键所在
return UITableViewCellEditingStyleDelete | UITableViewCellEditingStyleInsert;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if (tableView.editing) {
if (_dataArray && [_dataArray count]>indexPath.row) {
NSDictionary *dict = [_dataArray objectAtIndex:indexPath.row];
[_pickedArray addObject:dict];
}
}
}
- (void)tableView:(UITableView *)tableView didDeselectRowAtIndexPath:(NSIndexPath *)indexPath
{
if (tableView.editing) {
if (_dataArray && [_dataArray count]>indexPath.row) {
NSDictionary *dict = [_dataArray objectAtIndex:indexPath.row];
[_pickedArray removeObject:dict];
}
}
}
Preview and Copy text from QuickLook
- Enable QuickLook for all plain text file with QLStephen
- Open Terminal and run following code:
defaults write com.apple.finder QLEnableTextSelection -bool true; killall Finder
- Done.