NSLinguisticTagger Notes

NSLinguisticTagger 是 iOS 5+/OS X 10.7+ 引入的自然语言智能分析类。一个简单的 sample:

NSString *text = @"The iPhone is a line of smartphones designed and marketed by Apple Inc. The iPhone runs Apple's iOS mobile operating system, originally named iPhone OS. The first iPhone was unveiled by then CEO of Apple Steve Jobs on January 9, 2007, and released on June 29, 2007. The most recent iPhone, the 5th generation iPhone 4S, was released in October 2011. iPhone是苹果公司旗下的一个智能手机系列,此系列手机搭载苹果公司研发的iOS手机操作系统。第一代iPhone于2007年1月9日由时任苹果公司CEO的史蒂夫·乔布斯发布,并在6月29日正式发售;最新的iPhone 4s于2011年10月4日发布,并于同年10月14日正式发售。"; // text from Wikipedia.

NSArray *schemes = [NSArray arrayWithObject:NSLinguisticTagSchemeNameTypeOrLexicalClass];
NSLinguisticTagger *tagger = [[NSLinguisticTagger alloc] initWithTagSchemes:schemes options:0];
tagger.string = text;
[tagger enumerateTagsInRange:NSMakeRange(0, text.length)
                      scheme:NSLinguisticTagSchemeNameTypeOrLexicalClass
                     options:NSLinguisticTaggerOmitWhitespace | NSLinguisticTaggerOmitPunctuation // 忽略空格和标点
                  usingBlock:^(NSString *tag, NSRange tokenRange, NSRange sentenceRange, BOOL *stop) {
                      NSLog(@"%@ is a %@", [text substringWithRange:tokenRange], tag);
                  }];

// OR

[text enumerateLinguisticTagsInRange:NSMakeRange(0, text.length)
                              scheme:NSLinguisticTagSchemeNameTypeOrLexicalClass
                             options:NSLinguisticTaggerOmitWhitespace | NSLinguisticTaggerOmitPunctuation
                         orthography:nil
                          usingBlock:^(NSString *tag, NSRange tokenRange, NSRange sentenceRange, BOOL *stop) {
                              NSLog(@"%@ is a %@", [text substringWithRange:tokenRange], tag);
                    }];

结果输出:

2012-09-11 11:56:45.192 LinguisticTaggerSample[3342:c07] The is a Determiner
2012-09-11 11:56:45.193 LinguisticTaggerSample[3342:c07] iPhone is a Noun
2012-09-11 11:56:45.193 LinguisticTaggerSample[3342:c07] is is a Verb
2012-09-11 11:56:45.193 LinguisticTaggerSample[3342:c07] a is a Determiner
2012-09-11 11:56:45.194 LinguisticTaggerSample[3342:c07] line is a Noun
2012-09-11 11:56:45.194 LinguisticTaggerSample[3342:c07] of is a Preposition
2012-09-11 11:56:45.194 LinguisticTaggerSample[3342:c07] smartphones is a Adverb
2012-09-11 11:56:45.194 LinguisticTaggerSample[3342:c07] designed is a Verb
2012-09-11 11:56:45.195 LinguisticTaggerSample[3342:c07] and is a Conjunction
2012-09-11 11:56:45.195 LinguisticTaggerSample[3342:c07] marketed is a Verb
2012-09-11 11:56:45.195 LinguisticTaggerSample[3342:c07] by is a Preposition
2012-09-11 11:56:45.196 LinguisticTaggerSample[3342:c07] Apple is a OrganizationName
2012-09-11 11:56:45.196 LinguisticTaggerSample[3342:c07] Inc is a OrganizationName
...
2012-09-11 11:56:45.203 LinguisticTaggerSample[3342:c07] Steve is a PersonalName
2012-09-11 11:56:45.203 LinguisticTaggerSample[3342:c07] Jobs is a PersonalName
...
2012-09-11 11:56:45.222 LinguisticTaggerSample[3342:c07] iPhone is a Noun
2012-09-11 11:56:45.223 LinguisticTaggerSample[3342:c07] 是 is a Particle
2012-09-11 11:56:45.223 LinguisticTaggerSample[3342:c07] 苹果 is a Verb
2012-09-11 11:56:45.223 LinguisticTaggerSample[3342:c07] 公司 is a Particle
2012-09-11 11:56:45.224 LinguisticTaggerSample[3342:c07] 旗下 is a Verb
2012-09-11 11:56:45.224 LinguisticTaggerSample[3342:c07] 的 is a Particle
2012-09-11 11:56:45.224 LinguisticTaggerSample[3342:c07] 一 is a Verb
2012-09-11 11:56:45.288 LinguisticTaggerSample[3342:c07] 个 is a Particle
2012-09-11 11:56:45.288 LinguisticTaggerSample[3342:c07] 智能 is a Verb
2012-09-11 11:56:45.289 LinguisticTaggerSample[3342:c07] 手机 is a Particle
2012-09-11 11:56:45.289 LinguisticTaggerSample[3342:c07] 系列 is a Verb
...
2012-09-11 11:56:45.310 LinguisticTaggerSample[3342:c07] 史 is a Verb
2012-09-11 11:56:45.311 LinguisticTaggerSample[3342:c07] 蒂 is a Particle
2012-09-11 11:56:45.311 LinguisticTaggerSample[3342:c07] 夫 is a Verb
2012-09-11 11:56:45.311 LinguisticTaggerSample[3342:c07] 乔 is a Particle
2012-09-11 11:56:45.312 LinguisticTaggerSample[3342:c07] 布 is a Verb
2012-09-11 11:56:45.312 LinguisticTaggerSample[3342:c07] 斯 is a Particle

对英文的分析要好于中文。不同的 scheme 有不同的返回结果,包括文本语言等。详细文档 NSLinguisticTagger Class Reference.

PS:可以用这个做一个微博关键词分析,罗列出自己微博最多的关键词。

UITableView 性能优化笔记

Hacking Week 技术总结最后一篇,记一下 UITableView 性能优化需要注意和改进的地方。

  1. 网络图片异步加载,SDWebImage。
  2. 文字直接 drawInRect/drawAtPoint 绘制,参考 ABTableViewCell,AdvancedTableViewCells
  3. 本地图片也可以直接绘制,或者用 CALayer 来添加显示。
  4. cell 重用机制。
  5. cell 内容尽量避免透明效果。
  6. 如非必要,减少 reloadData 全部 cell,只 reloadRowsAtIndexPaths。
  7. 如果 cell 是动态行高,计算出高度后缓存。tableView 会在加载的时候把全部 cell 的高度通过 heightForRowAtIndexPath: 都计算出来,即使 cell 还没有展示。
  8. 如果 cell content 的展示位置也不固定,第一次计算后也要缓存。
  9. cell 高度固定的话直接用 cell.rowHeight 设置高度,不要再实现 tableView:heightForRowAtIndexPath: delegate.
  10. cell content 的解析操作(尤其是复杂的解析)异步进行+预执行,解析结果要缓存。
  11. 可以预先加载需要的网络资源(图片等),SDWebImagePrefetcher.

There are performance implications to using tableView:heightForRowAtIndexPath: instead of the rowHeight property. Every time a table view is displayed, it calls tableView:heightForRowAtIndexPath: on the delegate for each of its rows, which can result in a significant performance problem with table views having a large number of rows (approximately 1000 or more). via Apple Document

Use copy property for NSString

一个简短例子来说明一下为什么 NSString @property 最好用 copy 而不是 retain:

#import <Foundation/Foundation.h>

@interface Person : NSObject

@property (nonatomic, retain) NSString *name;
@property (nonatomic, copy) NSString *school;

@end

@implementation Person

@synthesize name, school;

@end

int main(int argc, char *argv[]) {
    @autoreleasepool {
        Person *p = [[Person alloc] init];

        NSMutableString *s1 = [NSMutableString stringWithString:@"fannheyward"];
        NSMutableString *s2 = [NSMutableString stringWithString:@"hfut"];
        p.name = s1;
        p.school = s2;
        NSLog(@"%@, %@", p.name, p.school); // fannheyward, hfut
        [s1 appendString:@"---Heybot"];
        [s2 appendString:@"---Heybot"];
        NSLog(@"%@, %@", p.name, p.school); // fannheyward---Heybot, hfut
    }
}

简单来说就是 NSString 可以通过 NSMutableString (isa NSString) 来进行修改,如果 @property 是 retain 的话就可以绕过 Person 实例来修改 name 值(因为 name 指向 s1),大部分时候这种情况都是不应该发生的,用 copy 就没有这个问题。

这样来说象 NSArray/NSDictionary 等可修改类型都应该用 copy

For attributes whose type is an immutable value class that conforms to the NSCopying protocol, you almost always should specify copy in your @property declaration.

参考 NSString property: copy or retain?

Heybot - My Gtalk Hubot

Github 是非常好的学习地方,Github Inc 这家公司也很有意思,一帮 Geek 程序员做了很多很好玩的东西,比如 Hubot, Play。Hubot 是一个机器人,可以音乐、搜索、搞怪逗乐等,在 Github 内部他们还用 Hubot 部署代码。开源版本的 Hubot 目前不支持代码部署等高级命令,不过可以自己写脚本(CoffeeScript)进行扩展。

Hubot 原生支持 Campfire、Shell 作接口,通过 npm 扩展可以用 Gtalk、IRC 等等。在 Heroku 上部署了一个用 Gtalk 作接口的 Heybot(Heyward’s Hubot),简单纪录一下。

wget https://github.com/downloads/github/hubot/hubot-2.3.2.tar.gz
tar xzvf hubot-2.3.2.tar.gz
cd hubot

vim Procfile 修改 adapter:
    app: bin/hubot -a gtalk -n Hubot
vim package.json 添加 hubot-gtalk 到 dependencies:
    "hubot-gtalk": ">= 0.0.1",

git init
git add *
git commit -m "init"

heroku apps:create
git push heroku master
heroku ps:scale app=1
heroku addons:add redistogo:nano
heroku config:add HUBOT_GTALK_USERNAME="xxx" HUBOT_GTALK_PASSWORD="xxx"
heroku ps:restart

添加 Gtalk 好友,hubot help 可以查看目前支持的命令。

NSPredicate Notes

在 Core Data 中可以给 NSFetchRequest 指定一个 predicate 来对数据进行过滤以方便查找,比如:

fetchRequest.predicate = [NSPredicate predicateWithFormat:@"id == %@", 123];

NSPredicate 的过滤查询规则不仅仅适用于 Core Data,字符串过滤也很方便。比如:

NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF CONTAINS %@", @"hello"];
BOOL b = [predicate evaluateWithObject:@"hello world"]; // YES

字符串支持的判断语法有 contains beginswith endswith like matches and/or/not/in

NSPredicate *predicate1 = [NSPredicate predicateWithFormat:@"SELF BEGINSWITH %@", @"hello"];
BOOL b = [predicate1 evaluateWithObject:@"hello world"]; // YES
BOOL n = [predicate1 evaluateWithObject:@"nohello world"]; // NO

like 匹配,支持 * 任意字符(可无),? 有且仅有一个字符:

NSPredicate *like = [NSPredicate predicateWithFormat:@"SELF LIKE %@", @"*like?"];
NSLog(@"%d", [like evaluateWithObject:@"alike"]); // 0-NO
NSLog(@"%d", [like evaluateWithObject:@"000liked"]); // 1-YES
NSLog(@"%d", [like evaluateWithObject:@"likes"]); // 1-YES

matches 正则匹配:

NSPredicate *match = [NSPredicate predicateWithFormat:@"SELF MATCHES '\\\\d+[a-z]'"];
NSLog(@"%d", [match evaluateWithObject:@"0A"]); // NO
NSLog(@"%d", [match evaluateWithObject:@"0a"]); // YES
NSLog(@"%d", [match evaluateWithObject:@"000000ab"]); // NO
NSLog(@"%d", [match evaluateWithObject:@"000000c"]); // YES

NSPredicate 可以组合起来用,这也是最为方便的地方,比如下面这个例子:

字符串以 CH 开头,长度大于 3 而小于 20 字符,包含至少一个数字,不包含 broken,不包含空格。

NSPredicate *one = [NSPredicate predicateWithFormat:@"SELF BEGINSWITH 'CH'"];
NSPredicate *two = [NSPredicate predicateWithFormat:@"SELF.length > 3 AND SELF.length < 20"];
NSPredicate *three = [NSPredicate predicateWithFormat:@"SELF MATCHES '.*\\\\d.*'"];
NSPredicate *four = [NSPredicate predicateWithFormat:@"NOT(SELF CONTAINS 'broken') AND NOT(SELF CONTAINS ' ')"];

NSArray *array = [NSArray arrayWithObjects:one, two, three, four, nil];
NSPredicate *predicate = [NSCompoundPredicate andPredicateWithSubpredicates:array];
NSLog(@"%d", [predicate evaluateWithObject:@"CH998broken"]); // NO
NSLog(@"%d", [predicate evaluateWithObject:@"CH998"]); //YES

@"attributeName == %@": the value of the key attributeName is the same as the value of the object(NSDate, NSNumber, NSDecimalNumber, or NSString). 完全相等判断。

@"%K == %@": the value of the key %K is the same as the value of the object %@. key 对应的值和给定的值相等。

@"name IN $NAME_LIST": the value of the key name is in the variable $NAME_LIST. @"'name' IN $NAME_LIST": the constant value ‘name’ (note the quotes around the string) is in the variable $NAME_LIST. 判断值是否在指定列表中,前者判断是 name 对应的值,后者 'name' 就是判断 name 字符串。

参考资料:

Core Data Notes

两篇很不错的 Core Data Tutorial, Getting StartedHow to use NSFetchedResultsController

NSPersistentStoreCoordinator 是持久化存储, NSManagedObjectModel 指明存储数据结构和关系,NSManagedObjectContext 来读取、存储操作。

- (NSManagedObjectContext *)managedObjectContext
{
    if (_managedObjectContext != nil) {
        return _managedObjectContext;
    }

    NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
    if (coordinator != nil) {
        _managedObjectContext = [[NSManagedObjectContext alloc] init];
        [_managedObjectContext setPersistentStoreCoordinator:coordinator];
    }
    return _managedObjectContext;
}

- (NSManagedObjectModel *)managedObjectModel
{
    if (_managedObjectModel != nil) {
        return _managedObjectModel;
    }
    NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"CDTest" withExtension:@"momd"];
    _managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
    return _managedObjectModel;
}

- (NSPersistentStoreCoordinator *)persistentStoreCoordinator
{
    if (_persistentStoreCoordinator != nil) {
        return _persistentStoreCoordinator;
    }

    NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"CDTest.sqlite"];

    NSError *error = nil;
    _persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
    if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error]) {
        NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
    }

    return _persistentStoreCoordinator;
}

新增数据:

Person *person = [NSEntityDescription insertNewObjectForEntityForName:@"Person"
                                               inManagedObjectContext:_managedObjectContext];
person.name = @"fannheyward";
person.age = [NSNumber numberWithInt:25];
[_managedObjectContext save:NULL];

通过 NSFetchRequest 查找,配合 NSPredicate 对数据进行过滤判断:

NSFetchRequest *request = [[NSFetchRequest alloc] initWithEntityName:@"Person"];
request.predicate = [NSPredicate predicateWithFormat:@"age == %@", age];;
NSArray *arr = [_managedObjectContext executeFetchRequest:request error:NULL];
for (NSManagedObject *obj in arr) {
    //...
}

NSFetchedResultsController 和 UITableView 做了很好的整合,可以根据 tableView 位置进行动态查询取数据。比如一共 100 个 cell,传统方式需要一次性全部拿到 DataSource 数据到内存,数据量过大的话会占用不少内存;用 NSFetchedResultsController 可以设置一次取数据的大小,然后根据滑动位置动态读取数据。

- (NSFetchedResultsController *)fetchController
{
    if (_fetchController != nil) {
        return _fetchController;
    }

    NSFetchRequest *request = [[NSFetchRequest alloc] init];
    NSEntityDescription *entity = [NSEntityDescription entityForName:@"Place"
                                              inManagedObjectContext:_managedContext];
    request.entity = entity;
    request.fetchBatchSize = 15;

    NSSortDescriptor *sort = [[NSSortDescriptor alloc] initWithKey:@"date" ascending:NO];
    request.sortDescriptors = [NSArray arrayWithObject:sort];

    _fetchController = [[NSFetchedResultsController alloc] initWithFetchRequest:request
                                                           managedObjectContext:_managedContext
                                                             sectionNameKeyPath:nil
                                                                      cacheName:@"Place"];
    _fetchController.delegate = self;

    NSError *error = nil;
    if (![_fetchController performFetch:&error]) {
        DLog(@"fetch error: %@", [error description]);
        abort();
    }

    return _fetchController;
}

和 UITableView 的整合:

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    id <NSFetchedResultsSectionInfo> info = [[_fetchController sections] objectAtIndex:section];
    return [info numberOfObjects];
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    //...
    Place *place = [_fetchController objectAtIndexPath:indexPath];

    return cell;
}

SDWebImage 笔记

SDWebImage 支持异步的图片下载+缓存,提供了 UIImageView+WebCacha 的 category,方便使用。纪录一下 SDWebImage 加载图片的流程。

  1. 入口 setImageWithURL:placeholderImage:options: 会先把 placeholderImage 显示,然后 SDWebImageManager 根据 URL 开始处理图片。
  2. 进入 SDWebImageManager-downloadWithURL:delegate:options:userInfo:,交给 SDImageCache 从缓存查找图片是否已经下载 queryDiskCacheForKey:delegate:userInfo:.
  3. 先从内存图片缓存查找是否有图片,如果内存中已经有图片缓存,SDImageCacheDelegate 回调 imageCache:didFindImage:forKey:userInfo: 到 SDWebImageManager。
  4. SDWebImageManagerDelegate 回调 webImageManager:didFinishWithImage: 到 UIImageView+WebCache 等前端展示图片。
  5. 如果内存缓存中没有,生成 NSInvocationOperation 添加到队列开始从硬盘查找图片是否已经缓存。
  6. 根据 URLKey 在硬盘缓存目录下尝试读取图片文件。这一步是在 NSOperation 进行的操作,所以回主线程进行结果回调 notifyDelegate:
  7. 如果上一操作从硬盘读取到了图片,将图片添加到内存缓存中(如果空闲内存过小,会先清空内存缓存)。SDImageCacheDelegate 回调 imageCache:didFindImage:forKey:userInfo:。进而回调展示图片。
  8. 如果从硬盘缓存目录读取不到图片,说明所有缓存都不存在该图片,需要下载图片,回调 imageCache:didNotFindImageForKey:userInfo:
  9. 共享或重新生成一个下载器 SDWebImageDownloader 开始下载图片。
  10. 图片下载由 NSURLConnection 来做,实现相关 delegate 来判断图片下载中、下载完成和下载失败。
  11. connection:didReceiveData: 中利用 ImageIO 做了按图片下载进度加载效果。
  12. connectionDidFinishLoading: 数据下载完成后交给 SDWebImageDecoder 做图片解码处理。
  13. 图片解码处理在一个 NSOperationQueue 完成,不会拖慢主线程 UI。如果有需要对下载的图片进行二次处理,最好也在这里完成,效率会好很多。
  14. 在主线程 notifyDelegateOnMainThreadWithInfo: 宣告解码完成,imageDecoder:didFinishDecodingImage:userInfo: 回调给 SDWebImageDownloader。
  15. imageDownloader:didFinishWithImage: 回调给 SDWebImageManager 告知图片下载完成。
  16. 通知所有的 downloadDelegates 下载完成,回调给需要的地方展示图片。
  17. 将图片保存到 SDImageCache 中,内存缓存和硬盘缓存同时保存。写文件到硬盘也在以单独 NSInvocationOperation 完成,避免拖慢主线程。
  18. SDImageCache 在初始化的时候会注册一些消息通知,在内存警告或退到后台的时候清理内存图片缓存,应用结束的时候清理过期图片。
  19. SDWI 也提供了 UIButton+WebCacheMKAnnotationView+WebCache,方便使用。
  20. SDWebImagePrefetcher 可以预先下载图片,方便后续使用。

AFNetworking 学习笔记

  1. 这篇笔记是在 AFN v0.10.1 时候写的,AFN v1.0 以后加入了不少新东西,比如 SSL 支持,不过整体结构没有变化。
  2. 后续跟进了一篇 AFNetworking Notes 2

AFNetworking

上图来自 @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,提供 AFJSONEncodeAFJSONDecode 两个方法,通过 NSClassFromStringNSSelectorFromString 来查找项目中使用的 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 参考帖子:

  1. Should I refer to self.property in the init method with ARC?
  2. Using properties to access iVars in init?
  3. Initializing a property, dot notation
  4. Objective-C Dot Syntax and Init

What I have learned from Cheddar for iOS

  1. Code Style.
  2. DRY,整理适合自己的代码库(SSToolkit)。
  3. application:didFinishLaunchingWithOptions: 里尽量少操作,减少 launch 时间。只做界面展示工作,数据层用 dispatch_async 异步操作。
  4. 多用 [image stretchableImageWithLeftCapWidth:5 topCapHeight:0] 图片拉伸,减小 App size。效果上并没有缺失很多。很多效果都可以用代码实现,不一定非得贴图。
  5. 数据层封装不同的对象,方便各种调用。直接用 dict 传来传去不够清晰。
  6. Core Data 和 UIViewController 可以很好的结合,深度封装后的确很方便,参见 SSManagedViewController = UIViewController+SSManagedObject(NSManagedObject),SSDataKit。但这样感觉 ViewController 很沉重,也可能是因为我对 Core Data 不熟悉,以后有机会加深一下 CD 的学习使用。
  7. KVO 是个好东西。
  8. 定义一些内部 scheme 来做界面跳转,x-cheddar-tag.
  9. UIColor+CheddariOSAdditions.h-cheddarTextColor,定义整体风格配色,很方便使用。UIFont+CheddariOSAdditions.h 同理。
  10. cellHeightForText:dispatch_once_t 生成一个单例 label,然后 sizeThatFits: 计算。
  11. prepareForReuse 释放数据。
  12. CDKHTTPClient 学习 AFN 的好例子。单实例,用 block 封装接口。Block is better than delegate, simple, clear and powerful.