SDWebImage 详解

2018-08-15  本文已影响94人  蓝月空谷

SDWebImage 框架中使用的知识点整理

<1>.NSCache
苹果官方文档中介绍NSCache是一种可变集合,用于临时存储在资源不足时可能被释放的键值对。
和其他可变集合不同之处有:

1.NSCache在系统内存很低时,会自动释放一些对象(而且是没有顺序的,所以SDWebImage中还使用了NSMapTable作为缓存的备份,
当在NSCache找不到时,再去NSMapTable中查找)。
2.NSCache是线程安全的,所以SDWebImage中NSCache做增删操作没有加锁。
3.与NSMutableDictionary对象不同,缓存不会复制放入其中的键对象。

在SDWebImage源码文件SDImageCache.m 中,使用NSCache做内存缓存。

// A memory cache which auto purge the cache on memory warning and support weak cache.
@interface SDMemoryCache <KeyType, ObjectType> : NSCache <KeyType, ObjectType>

@end

// Private
@interface SDMemoryCache <KeyType, ObjectType> ()

@property (nonatomic, strong, nonnull) SDImageCacheConfig *config;
@property (nonatomic, strong, nonnull) NSMapTable<KeyType, ObjectType> *weakCache; // strong-weak cache
@property (nonatomic, strong, nonnull) dispatch_semaphore_t weakCacheLock; // a lock to keep the access to `weakCache` thread-safe

- (instancetype)init NS_UNAVAILABLE;
- (instancetype)initWithConfig:(nonnull SDImageCacheConfig *)config;

@end

其中weakCache就是上文说的用来做补充的缓存集合
通过重写这四个方法,同步对weakCache做增删操作

- (void)setObject:(ObjectType)obj forKey:(KeyType)key; // 0 cost
- (void)setObject:(ObjectType)obj forKey:(KeyType)key cost:(NSUInteger)g;
- (void)removeObjectForKey:(KeyType)key;

- (void)removeAllObjects;

为什么用NSMapTable做备份的原因上面已经说过了,可为什么不直接使用NSMapTable,而要使用NSCache?除了上面说的线程安全,内存紧张时会自动释放存储对象,NSCache还有totalCostLimit和countLimit两个属性控制消耗限制和数量限制。

<2>. @autoreleasepool

自动释放池块提供了一种机制,你放弃对象的所有权的同时,可以避免立即释放它(例如从方法返回对象时)。Application Kit在事件循环的每个循环开始时在主线程上创建一个自动释放池,并在最后将其排出,从而释放处理事件时生成的任何自动释放的对象。每个线程(包括主线程)都维护自己的NSAutoreleasePool对象堆栈。在创建新池时,它们会添加到堆栈顶部。当池被释放时,它们将从堆栈中删除。自动释放的对象放置在当前线程的顶部自动释放池中。当线程终止时,它会自动排出与自身关联的所有自动释放池。通常情况下你不需要创建自己的释放池,除非应用程序在事件循环中创建了大量临时自动释放的对象,则创建“本地”自动释放池以帮助最小化峰值内存占用量。

苹果开发文档中实例

NSArray *urls = <# An array of file URLs #>;
for (NSURL *url in urls) {
 
    @autoreleasepool {
        NSError *error;
        NSString *fileContents = [NSString stringWithContentsOfURL:url
                                         encoding:NSUTF8StringEncoding error:&error];
        /* Process the string, creating and autoreleasing more objects. */
    }
}

如果必须使用自动释放池块之外的临时对象,则可以通过向块内的对象发送保留消息,然后在块之后将其发送自动释放,如此示例所示:

– (id)findMatchingObject:(id)anObject {
   id match;
   while (match == nil) {
       @autoreleasepool {
           /* Do a search that creates a lot of temporary objects. */
           match = [self expensiveSearchForObject:anObject];
           if (match != nil) {
               [match retain]; /* Keep match around. */
           }
       }
   }
   return [match autorelease];   /* Let match go and return it. */
}

发送retain以在自动释放池中匹配并在自动释放池块延长匹配的生命周期后向其发送自动释放,并允许它在循环外接收消息并返回到findMatchingObject:的调用者。

实例:

源码 SDWebImageCoderHelper.m
for (size_t i = 0; i < frameCount; i++) {
        @autoreleasepool {
            SDWebImageFrame *frame = frames[i];
            float frameDuration = frame.duration;
            CGImageRef frameImageRef = frame.image.CGImage;
            NSDictionary *frameProperties = @{(__bridge NSString *)kCGImagePropertyGIFDictionary : @{(__bridge NSString *)kCGImagePropertyGIFDelayTime : @(frameDuration)}};
            CGImageDestinationAddImage(imageDestination, frameImageRef, (__bridge CFDictionaryRef)frameProperties);
        }
    }

使用Clang将main.m转换为main.cpp文件
终端输入 clang -rewrite-objc main.m ,得到__AtAutoreleasePool的结构

extern "C" __declspec(dllimport) void * objc_autoreleasePoolPush(void);
extern "C" __declspec(dllimport) void objc_autoreleasePoolPop(void *);

struct __AtAutoreleasePool {
  __AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}
  ~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}
  void * atautoreleasepoolobj;
};

从 objc_autoreleasePoolPush、objc_autoreleasePoolPop可以看出来是堆栈进栈出栈的操作。高频率的创建变量后,加入自动释放池,可以避免短时间之内存暴涨

 @autoreleasepool {
        //进栈
        __AtAutoreleasePool __autoreleasepool = objc_autoreleasePoolPush();

       // 新建对象加入自动释放池
        
        //出栈
        objc_autoreleasePoolPop(__autoreleasepool)
}

<3>.CGImageSourceRef

<4>.NSOperation,NSOperationQueue

<5>.dispatch_semaphore_t

源码 SDWebImageDownloader.m 
#define LOCK(lock) dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);
#define UNLOCK(lock) dispatch_semaphore_signal(lock);

@property (strong, nonatomic, nonnull) dispatch_semaphore_t operationsLock; // a lock to keep the access to `URLOperations` thread-safe
@property (strong, nonatomic, nonnull) dispatch_semaphore_t headersLock; // a lock to keep the access to `HTTPHeaders` thread-safe

_operationsLock = dispatch_semaphore_create(1);
_headersLock = dispatch_semaphore_create(1);

__weak typeof(self) wself = self;
operation.completionBlock = ^{
            __strong typeof(wself) sself = wself;
            if (!sself) {
                return;
            }
            LOCK(sself.operationsLock);
            [sself.URLOperations removeObjectForKey:url];
            UNLOCK(sself.operationsLock);
 };        
- (void)cancel {
    @synchronized(self) {
        self.cancelled = YES;
        if (self.cacheOperation) {
            [self.cacheOperation cancel];
            self.cacheOperation = nil;
        }
        if (self.downloadToken) {
            [self.manager.imageDownloader cancel:self.downloadToken];
        }
        [self.manager safelyRemoveOperationFromRunning:self];
    }
}

<6>.dispatch_main_async_safe

#ifndef dispatch_queue_async_safe
#define dispatch_queue_async_safe(queue, block)\
    if (dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL) == dispatch_queue_get_label(queue)) {\
        block();\
    } else {\
        dispatch_async(queue, block);\
    }
#endif

#ifndef dispatch_main_async_safe
#define dispatch_main_async_safe(block) dispatch_queue_async_safe(dispatch_get_main_queue(), block)
#endif



<7>.
__weak typeof(self)weakSelf = self;
__strong typeof(weakSelf)strongSelf = weakSelf;
if (!strongSelf) {
   return;
}                          

<8>.Objective-C Associated Objects

Associated Objects 主要用来给分类增加属性

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

从名字可以看出两个方法分别用于实现属性的set和get。
objc_setAssociatedObject 用于给对象添加属性,传入 nil 可以移除已添加的属性;
objc_getAssociatedObject 用于获取属性;

分类 UIImage + FLAnimatedImage 中源码
- (FLAnimatedImage *)sd_FLAnimatedImage {
    return objc_getAssociatedObject(self, @selector(sd_FLAnimatedImage));
}

- (void)setSd_FLAnimatedImage:(FLAnimatedImage *)sd_FLAnimatedImage {
    objc_setAssociatedObject(self, @selector(sd_FLAnimatedImage), sd_FLAnimatedImage, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

其中@selector(sd_FLAnimatedImage) 就是方法参数中的key,SDWebImage 有些也使用类似 static char sd_FLAnimatedImage,两种方式都可行,但是还是推荐使用@selector(),这样不用另外在其他申明参数,保持代码命名的一致性。(其中objc_setAssociatedObject ()还可以使用_cmd作为key)

其中OBJC_ASSOCIATION_RETAIN_NONATOMIC 就是方法参数中的objc_AssociationPolicy policy

Behavior @property Equivalent Description
OBJC_ASSOCIATION_ASSIGN @property (assign) or @property (unsafe_unretained) Specifies a weak reference to the associated
OBJC_ASSOCIATION_RETAIN_NONATOMIC @property (nonatomic, strong) Specifies a strong reference to the associated object.
OBJC_ASSOCIATION_COPY_NONATOMIC @property (nonatomic, copy) Specifies that the associated object is copied.
OBJC_ASSOCIATION_RETAIN @property (atomic, strong) pecifies a strong reference to the associated object.
OBJC_ASSOCIATION_COPY @property (atomic, copy) Specifies that the associated object is copied.

Mattt大神 文章 associated-objects


iOS 动态库与静态库的区别 framework


dispatch_group_enter和dispatch_group_leave

FOUNDATION_EXPORT NSString * _Nonnull const SDWebImageDownloadStartNotification;
FOUNDATION_EXPORT NSString * _Nonnull const SDWebImageDownloadReceiveResponseNotification;
FOUNDATION_EXPORT NSString * _Nonnull const SDWebImageDownloadStopNotification;
FOUNDATION_EXPORT NSString * _Nonnull const SDWebImageDownloadFinishNotification;


NSString *const SDWebImageDownloadStartNotification = @"SDWebImageDownloadStartNotification";
NSString *const SDWebImageDownloadReceiveResponseNotification = @"SDWebImageDownloadReceiveResponseNotification";
NSString *const SDWebImageDownloadStopNotification = @"SDWebImageDownloadStopNotification";
NSString *const SDWebImageDownloadFinishNotification = @"SDWebImageDownloadFinishNotification";


static inline





上一篇下一篇

猜你喜欢

热点阅读