利用runtime,在分类中添加属性
在SDWebImage为UIImage类做的扩展中,会记录当前加载的url。
那么image url是怎么在Category中存储和获取的呢?很简单,其实是运用了runtime的两个函数, 将id对象与image对象关联起来:
/**
* Sets an associated value for a given object using a given key and association policy.
*
* @param object The source object for the association.
* @param key The key for the association.
* @param value The value to associate with the key key for object. Pass nil to clear an existing association.
* @param policy The policy for the association. For possible values, see “Associative Object Behaviors.”
*
* @see objc_setAssociatedObject
* @see objc_removeAssociatedObjects
*/
OBJC_EXPORT void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
OBJC_AVAILABLE(10.6, 3.1, 9.0, 1.0);
/**
* Returns the value associated with a given object for a given key.
*
* @param object The source object for the association.
* @param key The key for the association.
*
* @return The value associated with the key \e key for \e object.
*
* @see objc_setAssociatedObject
*/
OBJC_EXPORT id objc_getAssociatedObject(id object, const void *key)
OBJC_AVAILABLE(10.6, 3.1, 9.0, 1.0);
这个是一对儿函数,可以将id对象与object关联,以及通过key做索引,取得这个id对象。
这样,其实我们可以利用property的set/get方法,为category添加属性,例子:
#import <UIKit/UIKit.h>
@interface UIViewController (test)
@property(nonatomic, strong) NSString *myName;
@end
#import "UIViewController+test.h"
#import <objc/runtime.h>
static char myNameKey;
@implementation UIViewController (test)
- (void)setMyName:(NSString *)myName
{
objc_setAssociatedObject(self, &myNameKey, myName, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (NSString *)myName
{
return objc_getAssociatedObject(self, &myNameKey);
}
@end
这里注意两点:
- 这里的关联key是一个void *,因此我们可以将key当做一个指向静态存储区的指针:
static char myNameKey;
key : &myNameKey
- 要断开关联,我们只需要再调用关联函数,使用同样的key但是将关联id置为nil即可:
objc_setAssociatedObject(self, &myNameKey, nil, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
此外还有一个断开所有关联的函数:
/**
* Removes all associations for a given object.
*
* @param object An object that maintains associated objects.
*
* @note The main purpose of this function is to make it easy to return an object
* to a "pristine state”. You should not use this function for general removal of
* associations from objects, since it also removes associations that other clients
* may have added to the object. Typically you should use \c objc_setAssociatedObject
* with a nil value to clear an association.
*
* @see objc_setAssociatedObject
* @see objc_getAssociatedObject
*/
OBJC_EXPORT void objc_removeAssociatedObjects(id object)
OBJC_AVAILABLE(10.6, 3.1, 9.0, 1.0);
利用返回的对象 对异步操作进行管理,用户可以调用该对象的cancel来取消掉当前的异步操作
我们看一下,SDWebImage返回了几种对象:
- 加载image,这里会有两种可能的操作(读取Cache, download image),所以,SDWebImage返回了
SDWebImageCombinedOperation 对象
@interface SDWebImageCombinedOperation : NSObject <SDWebImageOperation>
@property (assign, nonatomic, getter = isCancelled) BOOL cancelled;
@property (copy, nonatomic) SDWebImageNoParamsBlock cancelBlock; // 用于执行cancle 回调
@property (strong, nonatomic) NSOperation *cacheOperation; // 用于cancle 读cache的操作
@end
这个对象遵循SDWebImageOperation协议。
该协议只有一个方法,就是cancel。
当用户调用cancle方法时,SDWebImageCombinedOperation对象会读 Cache 操作,download操作都取消掉。设计的思路很巧妙,有兴趣可以看一下。
我这里说下大体思路:
- (void)cancel {
self.cancelled = YES; // 设置cancel的标记位,若异步操作返回,先判断当前的combined对象的cancle标记位,若YES,则直接返回,而不调用finish block
if (self.cacheOperation) { // 这里调用代表读cache操作的operation的cancel方法
[self.cacheOperation cancel];
self.cacheOperation = nil;
}
if (self.cancelBlock) {
self.cancelBlock(); // 在cancle block中会调用download 返回的nsoperation 对象的cancel;
// TODO: this is a temporary fix to #809.
// Until we can figure the exact cause of the crash, going with the ivar instead of the setter
self.cancelBlock = nil;
_cancelBlock = nil;
}
}
- 当读取Cache时,会返回一个NSOperation对象
/**
* Query the disk cache asynchronously.
*
* @param key The unique key used to store the wanted image
*/
- (NSOperation *)queryDiskCacheForKey:(NSString *)key done: (SDWebImageQueryCompletedBlock)doneBlock;
- 当download image时,返回一个符合SDWebImageOperation协议的对象(SDWebImage其实是返回了一个NSOperation对象)
/**
* Creates a SDWebImageDownloader async downloader instance with a given URL
*
* The delegate will be informed when the image is finish downloaded or an error has happen.
*
* @see SDWebImageDownloaderDelegate
*
* @param url The URL to the image to download
* @param options The options to be used for this download
* @param progressBlock A block called repeatedly while the image is downloading
* @param completedBlock A block called once the download is completed.
* If the download succeeded, the image parameter is set, in case of error,
* error parameter is set with the error. The last parameter is always YES
* if SDWebImageDownloaderProgressiveDownload isn't use. With the
* SDWebImageDownloaderProgressiveDownload option, this block is called
* repeatedly with the partial image object and the finished argument set to NO
* before to be called a last time with the full image and finished argument
* set to YES. In case of error, the finished argument is always YES.
*
* @return A cancellable SDWebImageOperation
*/
- (id <SDWebImageOperation>)downloadImageWithURL:(NSURL *)url
options:(SDWebImageDownloaderOptions)options
progress:(SDWebImageDownloaderProgressBlock)progressBlock
completed:(SDWebImageDownloaderCompletedBlock)completedBlock;
利用GCD的串行队列以及dispatch_async异步派发,在保证线程安全的同时,提高效率
我们有些操作,需要保证是线程安全的。
对于线程安全,有时候我们可能直观的想到加锁。
但是加锁有两个不好的地方:
- 容易造成死锁。
- 加锁会阻塞当前线程,使运行效率下降。
其实利用GCD,我们也可以实现线程安全,并避免使用锁的弊端。
在SDWebImage中,当读取disk上的缓存时,为了防止IO冲突,需要一个线程安全的环境来进行image的读写。SDWebImage是这么做的:
- 创建一个串行队列:
@interface SDImageCache ()
...
@property (SDDispatchQueueSetterSementics, nonatomic) dispatch_queue_t ioQueue;
...
// Create IO serial queue
_ioQueue = dispatch_queue_create("com.hackemist.SDWebImageCache", DISPATCH_QUEUE_SERIAL);
- 在进行IO操作时,利用dispatch_async,将任务提交到ioQueue中,同时由于是异步派发,因此不会阻塞当前线程,提高了效率。
dispatch_async(self.ioQueue, ^{
// do iO task
});
利用GCD实现读写锁
利用GCD的读写锁,关键使用到了
- 栅栏:
dispatch_barrier_sync/async
- 一个并发队列
在SDWebImage中,对于每一个下载的回调block,SDWebImageDownloader都会存到:
@property (strong, nonatomic) NSMutableDictionary *URLCallbacks;
其中,URLCallbacks的key是每个下载的url,而对应的value则是一个NSArray类型,该array存储了所有同一个url下载请求所对应的call back block。
由于这个URLCallbacks可能会被多线程读写,因此需要线程安全。我们可以做一个读写锁,使得读并发,写独占。
SDWebImage是这么实现的:
- 生成一个并发队列,用于访问URLCallbacks
// This queue is used to serialize the handling of the network responses of all the download operation in a single queue
@property (SDDispatchQueueSetterSementics, nonatomic) dispatch_queue_t barrierQueue;
_barrierQueue = dispatch_queue_create("com.hackemist.SDWebImageDownloaderBarrierQueue", DISPATCH_QUEUE_CONCURRENT);
- 在对URLCallbacks读的地方,调用
dispatch_sync
,并发读取
dispatch_sync(sself.barrierQueue, ^{
callbacksForURL = [sself.URLCallbacks[url] copy];
});
- 在对URLCallbacks写的地方,调用
dispatch_barrier_sync
, 实现独占访问。
dispatch_barrier_sync(sself.barrierQueue, ^{
callbacksForURL = [sself.URLCallbacks[url] copy];
if (finished) {
[sself.URLCallbacks removeObjectForKey:url];
}
});
网友评论