最近回头一看,发现我们的项目现在对图片处理都是用YYWebImage 的处理方式方式的,用了不短时间了,却没有好好了解下,今天特此学习下。首先然而怎么下手呢?如何提高阅读源代码的能力?结合自己,决定在第一篇,带着一个问题,去简单了解。
问题:为什么使用下面这个方法去获取图片?
- (void)yy_setImageWithURL:(NSURL *)imageURL
placeholder:(UIImage *)placeholder
options:(YYWebImageOptions)options
progress:(YYWebImageProgressBlock)progress
transform:(YYWebImageTransformBlock)transform
completion:(YYWebImageCompletionBlock)completion
先简单了解下这几个参数的含义
* imageURL:图片的URL
* placeholder: 备用图片
* options: YYWebImageOptions(图片下载时同时的操作,可以仔细看一下,很强大的
(展示进度,缓存方式,HTPPS的处理,忽略其他,失败时处理等等))
* progress: 图片下载的进度(receivedSize/expectedSize)
* transform: 对图片是否需要处理(大小、圆角之类的添加),为更适应图片的展示
* completion:完成后,可以了解的信息(url,from,error)
然后进入去看一下详细的实现
1、围绕着_YYWebImageSetter进行一系列的判断。
2、异步中到 YYWebImageManager 的网络处理
3、同时对 operation 进行处理,在YYWebImageOperation(NSOperation的子类)中。
所以,得先对:YYWebImageManager、YYWebImageOperation 有个大致了解。
YYWebImageManager
用来创建和管理网络图片任务的管理器,这个类其实就一个作用,管理生成一个YYWebImageOperation实例
第一,我们从其三个枚举就可以大致了解它一些东西啦
* YYWebImageOptions //控制图片请求的模式
* YYWebImageFromType //用来告诉我们图片来源
* YYWebImageStage //用来告诉我们图片下载的完成度的
第二,再看看其几个 Block
* YYWebImageProgressBlock //从远程下载完成过程的回调
* YYWebImageTransformBlock // 图片从远程下载完成之前会执行这个block,用来执行一些额外的操作
* YYWebImageCompletionBlock //在当图片下载完成或者取消的时候调用
第三,看看它的初始化和属性,有很多,选择一个最重要的。也就是将上述 枚举和 block 结合的体现。
- (YYWebImageOperation *)requestImageWithURL:(NSURL *)url
options:(YYWebImageOptions)options
progress:(YYWebImageProgressBlock)progress
transform:(YYWebImageTransformBlock)transform
completion:(YYWebImageCompletionBlock)completion;
第四,看这个初始化的实现
- (YYWebImageOperation *)requestImageWithURL:(NSURL *)url
options:(YYWebImageOptions)options
progress:(YYWebImageProgressBlock)progress
transform:(YYWebImageTransformBlock)transform
completion:(YYWebImageCompletionBlock)completion {
//设置request
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
request.timeoutInterval = _timeout;
request.HTTPShouldHandleCookies = (options & YYWebImageOptionHandleCookies) != 0;
request.allHTTPHeaderFields = [self headersForURL:url];
request.HTTPShouldUsePipelining = YES;
// 设置缓存方式
request.cachePolicy = (options & YYWebImageOptionUseNSURLCache) ?
NSURLRequestUseProtocolCachePolicy : NSURLRequestReloadIgnoringLocalCacheData;
// 生成一个我们需要的YYWebImageOperation对象
YYWebImageOperation *operation = [[YYWebImageOperation alloc] initWithRequest:request
options:options
cache:_cache
cacheKey:[self cacheKeyForURL:url]
progress:progress
transform:transform ? transform : _sharedTransformBlock
completion:completion];
// 如果有用户名跟密码,生成系统提供的NSURLCredential
if (_username && _password) {
operation.credential = [NSURLCredential credentialWithUser:_username password:_password persistence:NSURLCredentialPersistenceForSession];
}
// 真正开始操作
if (operation) {
NSOperationQueue *queue = _queue;
if (queue) {
[queue addOperation:operation];
} else {
[operation start];
}
}
return operation;
}
注意 request 各个属性的设置,特别是缓存策略,后期可以再了解 YYCache,以及NSURLCredential的身份认证。
YYWebImageOperation
YYWebImageOperation 类是NSOperation的子类,用来通过请求获取图片。
第一,对operation中一些状态进行正确的处理
- (void)_finish
- (void)_startOperation
- (void)_startRequest:(id)object
- (void)_cancelOperation
第二,通过不同的方式下载得到图片进行的处理
//从磁盘缓存中接受图片
- (void)_didReceiveImageFromDiskCache:(UIImage *)image
// 从网络下载的图片
- (void)_didReceiveImageFromWeb:(UIImage *)image
第三,看NSURLColleciton 的代理方法
//即将缓存请求结果
- (NSCachedURLResponse *)connection:(NSURLConnection *)connection willCacheResponse:(NSCachedURLResponse *)cachedResponse
//请求已经收到相应
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
//收到数据回调
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
//连接已经结束加载
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
第四,真正重写的NSOperation的方法,进行处理
- (void)start
- (void)cancel
在此我们可以大致了解作者在对管理下载队列的时候的想法,以及自定义一个operation时的大致想法。
总的说来,这一篇只是大致的思路,要去看详细源码,注意里面一些细节的才是重点,第二篇笔记重点来挖掘细节。
额外问题的发现
1、static 内联函数的使用 ?
static inline void _yy_dispatch_sync_on_main_queue(void (^block)()) {
if (pthread_main_np()) {
block();
} else {
dispatch_sync(dispatch_get_main_queue(), block);
}
}
简单理解, 宏一样的函数,方便使用。深入理解C语言的define和内联函数
另外也可了解下iOS OC内联函数 inline。
- static 标识此内联联函数只能在本文件中使用,限制了内联函数的作用域。
- 相对于宏来说,static inline具有和宏同样级别的开销,而且还提供了类型安全,没有长度和格式的具体限制。
Ps: static 函数的用法
static void URLBlacklistInit() {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
URLBlacklist = [NSMutableSet new];
URLBlacklistLock = dispatch_semaphore_create(1);
});
}
用 static 这样表示该函数声明为内部函数(又叫静态函数),这样该函数就只能在其定义所在的文件中使用。如果在不同的文件中有同名的内部函数,则互不干扰。
2、类名和函数名 前面加下划线的运用 ?
@interface _YYWebImageSetter : NSObject
+ (void)_delaySetActivity:(NSTimer *)timer
- (void)_startOperation
一般是我们是用前缀避免命名空间冲突,如 yy_setImageURL:
之类的,但是加上下划线有时个什么情况呢?
而且苹果公司喜欢单用一个下划线作为私有的前缀。此时如果我们也这样想,假如苹果公司提供的某个类中继承了某一个子类,那我们在子类里可能会无意间覆写了父类的同名方法,因此官方推荐是不要但写一个下划线做私有方法的前缀。
作者为什么这么写呢,对于部分变量名,类名还好理解,但是对私有方法的处理,可以仔细看看。
3、objc_getAssociatedObject大量的使用
这个就是为了解决在分类中添加实例变量的快速方法啦。Objective-C Associated Objects 的实现原理
4、@implementation 后直接使用成员变量?
@implementation User : NSObject {
NSString *_name;
}
以前没有这样使用过,但是是合法的哦。对比@interface和@implementation
5、自旋锁 & 递归锁
OSSpinLock(自旋锁),自旋锁在保证了多线程同时访问本类的时候不会导致数据出错的同时性能高效。
NSRecursiveLock(递归锁),递归锁防止死锁,因为请求可能是有多个的。
使用案例
static OSSpinLock URLBlacklistLock;//黑名单锁
/**
* 把url添加进黑名单
*/
static void URLInBlackListAdd(NSURL *url) {
if (!url || url == (id)[NSNull null]) return;
URLBlacklistInit();
OSSpinLockLock(&URLBlacklistLock);
[URLBlacklist addObject:url];
OSSpinLockUnlock(&URLBlacklistLock);
}
@property (nonatomic, strong) NSRecursiveLock *lock; //递归锁
- (void)dealloc {
[_lock lock];
//
[_lock unlock];
}
了解更多的锁的信息,可以看看这篇结合 thread 讲的 -----Thread基础知识
备注参考:
https://github.com/ibireme/YYWebImage