由AFNetworking引出的姿势们

前言

在公司实习了三个月,因为学校这边还有课,于是请假一个月回学校。课余时间太多,不能没有进步,于是就和某基友约定每周写一篇博客。作为一个 iOS 初学者,有不少东西在 ToDo List 中需要学习,正好就可以借这段空闲时间好好补一补,说干就干!

「「首当其冲」」的就是 ToDo List 中的 AFNetworking 了,我在网络和多线程这一块掌握得最差,于是就先从这块开始攻破了。

本文里面虽然有不少东西以前都听说过或者触及过一点,但抱着学习的心态我会扎实地过一遍,如果大神们看着比较简单,烦请绕道。当然,非常感谢各位指出错误之处。

直播

我在写这篇博客的时候希望能够有抱着同样心态的同学们跟我一起学习, 感觉很棒.
于是我开了个直播
只要在写这篇文章,我就会开着。

概要


入手

在开始阅读 AFNetworking 源码的时候,不知道从何入手,于是随意挑了一个 UIButton+AFNetworking 看看。首先来看 .h 文件:

method list

首先我们可以看到这里有个 sharedImageCache 方法, 看着就像是一个单例, 但是注意到这并不是 UIButton 的单例, 而是 AFImageCache 的实例.点开看看:

+ (id <AFImageCache>)sharedImageCache {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu"
    return objc_getAssociatedObject(self, @selector(sharedImageCache)) ?: [UIImageView sharedImageCache];
#pragma clang diagnostic pop
}

这其中就有两个东西值得一看:

1. Clang Diagnostic

在上面的代码中发现有一段 pragma

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu"
...
#pragma clang diagnostic pop

大意从字面上来看, 很容易理解, 就是忽略编译警告. 但抱着求知的态度, 咱要往详细的探究一下. 首先我找到了喵神的谈谈Objective-C的警告
其中谈到了-Wgnu这一类的 option 所代表的含义,然后看到这里有个列表:Options to Request or Suppress Warnings,下次如果有这方面的需求,就可以在这里面查一下了。

另外 在喵神那篇博客里看到个好玩的 #error#warning。有啥用呢?比如说可以拿来做 TODO 啊啥的。

2. Associated Object

前提姿势:在分类中不能声明属性

在代码中看到有 objc_getAssociatedObject 方法 --- 这就是传说中的 关联对象。找了 NSHipster 的文章:

以下三个方法就是关联对象相关的方法了:

objc_setAssociatedObject
objc_getAssociatedObject
objc_removeAssociatedObjects

他们都在 <objc/runtime.h> 中声明,用途是在 Category 中动态添加自定义属性。

在上面引用的的那篇NSHipster文章中, AssociatedObject的示例代码中, 是这样使用上述方法的:


// setter
objc_setAssociatedObject(self, @selector(associatedObject), object, OBJC_ASSOCIATION_RETAIN_NONATOMIC);    

// getter
objc_getAssociatedObject(self, @selector(associatedObject));

然后我在 Apple 的官方文档中找到这俩 API:

  • id objc_getAssociatedObject ( id object, const void *key );
  • void objc_setAssociatedObject ( id object, const void *key, id value, objc_AssociationPolicy policy );

里面都有一个参数 key 是一个 const void *, 按理来说我们搞一个static char 拿来用就可以了, 但在 NSHipster 中却是使用的 @selector。何解?

在继续看那篇文章的时候,NSHipster 做出了解释:

It is often recommended that they key be a static char—or better yet, the pointer to one. Basically, an arbitrary value that is guaranteed to be constant, unique, and scoped for use within getters and setters

However, a much simpler solution exists: just use a selector.

没啥特殊之处,就是这样更简洁了!(就完了?摔

那么 policy 有哪些呢

对照表在此:


OBJC_ASSOCIATION_ASSIGN	===== (assign) || (unsafe_unretained)
OBJC_ASSOCIATION_RETAIN_NONATOMIC ===== (nonatomic, strong)
OBJC_ASSOCIATION_COPY_NONATOMIC ===== (nonatomic, copy)
OBJC_ASSOCIATION_RETAIN ===== (atomic, strong)
OBJC_ASSOCIATION_COPY ===== (atomic, copy)

差不多啦, Associated Object 方面的东西大概就是这样。(当然还有个 remove 我们就不细玩了。)

最后: 需要仔细一看的是 PatternsAnti-Patterns

3. NSOperationQueue 入门

废话们

额, 其实我一直都是用的 gcd, 没有用过 NSOperationQueue。所以就果断。。好好学学 OperationQueue 吧。

大爱 NSHipster 啊,我刚刚翻了一会儿官方文档,大致明白了 NSOperation 和 NSOperationQuene 的基本使用方法。

我感觉看看 NSHipster 的 NSOperation基本上就可以应付 AFNetworking 里面不少有关 OperationQueue 的东西了。【墙裂推荐啊→_→】


相关的类

  1. NSOperation
    • NSBlockOperation
    • NSInvocationOperation
  2. NSOperationQueue

NSOperation

NSOperation 这个类就是对应的一个操作,或者说叫任务。比如说下载一张图片,或者给手上的图片压缩、转码等等。

看看手册: The NSOperation class is an abstract class 这个类是一个抽象类,要么你继承 NSOperation 自定义一个 Operation 来使用,要么就使用官方已经封装好了的两个类:

  • NSBlockOperation
  • NSInvocationOperation

NSBlockOperation 的基本使用

NSBlockOperation 是一个比较爽的类,它可以再初始化的时候把 block 传进去,也就是说我们把任务写在 block 之中就可以了。

来看看代码:


NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"BlockOperation executed!");
}];

是滴, 这样就撸好了一个 NSBlockOperation 的实例,执行的时候就会输出 BlockOperation executed!

NSInvocationOperation 的基本使用

NSInvocationOperation 这个类和上面那个 NSBlockOperation 的区别就在于: 这个类的执行方法不是从 block 中来的,而是去调用对应的 selector,有种 delegate 的感觉。

有两种初始化的方法:

  • -initWithTarget:selector:object:
  • -initWithInvocation:

就主要看第一种吧, 第二种下来自己玩。上代码:


NSInvocationOperation *invocationOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(fxxk) object:nil];

// -----我是个分割线啊汪-----

- (void)fxxk {
    NSLog(@"InvocationOperation executed!");
}

懂了吧0v0!

但是你有没有试一试上面的代码是否真的能够运行?

先试试...→_→

试完了么?对啊, 并不能运行...为啥?因为根本就没有 kick off 啊...

NSOperation 中有一个 start 方法, 调用之后就可以玩了。

But,难道多线程的时候你要自己手动来决定何时、何线程下来调用 start 方法吗?必须不可能,不科学。所以由此引出了 NSOperationQueue ⬇️

NSOperationQueue

NSOperationQueue 顾名思义, 是一个放了一堆NSOperation的Queue。什么是 Queue ?--队列,具有FIFO(First In First Out)的特性(不知道的赶快去补补大学课程→_→)不过呢,在这里这个 Queue 不仅仅是一个队列,还是一个 priority queue。每个 Operation 都会有不同的 queuePriority
NSOperationQueue 在依次执行 Operation 的时候就会根据这个优先级来优先选择.

扯远了, 先说说如何简单的使用 NSOperationQueue 吧。在主线程的 Queue 中加入两个 Operation,代码如下:


    NSBlockOperation *networkOperation = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"I have downloaded a picture");
    }];

    NSBlockOperation *resizeOperation = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"I have resized the picture");
    }];

    NSOperationQueue *queue = [NSOperationQueue mainQueue];
    [queue addOperation:networkOperation];
    [queue addOperation:resizeOperation];

随便找了个 ViewController 的 viewDidLoad 方法来跑这段代码,然后打出日志:

2015-10-31 17:01:38.379 Yabo[1297:939628] I have downloaded a picture
2015-10-31 17:01:38.380 Yabo[1297:939628] I have resized the picture

当然,还有更优(jian)雅(jie)一点的写法:

[[NSOperationQueue mainQueue] addOperationWithBlock:^{
    NSLog(@"I have downloaded a picture");
}];

[[NSOperationQueue mainQueue] addOperationWithBlock:^{
    NSLog(@"I have resized the picture");
}];

简单地就写在这里,还有一些深入一点点的东西(NSOperationPriority, NSQualityOfService)就自己看吧,我觉得这里篇幅不能太长了,所以在后面读代码的时候遇到再提一提吧。