SDWebImage知识点总结一

目不能两视而明,耳不能两听而聪。----------《荀子·劝学》

SDWebImage知识点总结一_第1张图片
SDWebImage框架图

技术点归纳

与图片相关的,这些知识都是与图片相关的,无需一定要掌握。

1、PNG图片的判断。可以看SDImageCacheImageDataHasPNGPreffix方法。
2、Image Data判断图片类型以及根据data创建图片。可以查看NSData+ImageContentTypeUIImage+MultiFormat类。
3、图片解压(以及解压带来内存问题)。SDWebImageDecoder类中的decodedImageWithImage:方法的实现,牵扯到底层图片相关的操作。
4、gif图片的处理。虽然SDGIF的支持比较差劲。但是老外的纯技术精神,不得不佩服。请看issue#945

iOS开发技术,这些东西都是项目中比较常用的技术,一定要掌握。

1、NSFileManager的操作。在获取缓存大小相关内容时,需要我们熟练掌握NSFileManager类的相关用法。
2、NSCache类。在SDissue上面,由NSCache缓存引起的问题(如内存警告等)还是有很多的,后面才得到改善。
3、NSOperationNSOperationQueueNSThread@synchronized线程及操作队列相关类。
4、GCDdispatch_barrier_syncdispatch_apply等函数。
5、NSURLRequestNSURLResponseNSURLConnectionNSURLCache等网络请求相关类。
6、后台操作。
7、Runloop
8、Runtime
9、KVO
下面我们就主要的知识点来进行剖析:

一、SDWebImage中@autoreleasepool的应用

dispatch_async(self.ioQueue, ^{
        if (operation.isCancelled) {
            return;
        }

        @autoreleasepool {
            UIImage *diskImage = [self diskImageForKey:key];
            if (diskImage && self.shouldCacheImagesInMemory) {
                NSUInteger cost = SDCacheCostForImage(diskImage);
                [self.memCache setObject:diskImage forKey:key cost:cost];
            }

            dispatch_async(dispatch_get_main_queue(), ^{
                doneBlock(diskImage, SDImageCacheTypeDisk);
            });
        }
    });
/*
@autoreleasepool {
    // code do something, creates some autoreleases objects
}
*/

1、autoreleasepool在什么时候使用?

  • autorelease机制是基于UIFrameWork。因此写非UI框架程序时需要自己管理对象的生命周期,如AppKit等Cocoa框架、命令行工具。。
  • autorelease触发时机发生在下一次RunLoop的时候。因此如果在下一个大循环里不断创建临时对象,那么这些对象在下一次RunLoop回来之前将没有机会被释放,十分消耗内存。在种情况下,我们在循环内部使用@autoreleasepool {},将autorelease对象释放掉,从而避免内存峰值的出现。
for (int i = 0; i < 1000000; i++) {
        @autoreleasepool {
            NSString *str = @"abc";
            str = [str stringByAppendingString:@"zhangsan"];
        }        
}

下面的方法中,对象不断被创建,在for循环结束之前,这些对象都会被系统放到最近的自动释放池里面,等待回收,因此就会消耗大量的内存,可能造成内存枯竭。而等到循环结束之后,内存的用量会突然下降。
而如果把循环内的代码包裹在我们创建的@autoreleasepool {}中,那么在循环中创建的对象就会放到这个池子中,而不是在线程的主池里面。@autoreleasepool {}的作用范围是在{}内部,所以创建的对象会得到及时的释放,防止内存的暴涨。

for (int i = 0; i < 10000; i++) {
   NSString *str = @"abc";
}
  • 创建新的线程。Cocoa的应用都会维护自己autoreleasepool。因此,非Cocoa程序创建线程时,需要显式添加autoreleasepool。
  • 长时间在后台运行
扩展:
1、在什么线程下面需要使用autoreleasepool呢?
  • GCD开辟子线程不需要手动创建autoreleasepool,因为GCD的每个队列都会自行创建管理autoreleasepool。但是,凡事都有例外,我们无法保证什么时候它drain(文档中没有说明),有可能在一个Block执行结束后,也可能很多个Block执行结束后。因此如果仅仅生成少量对象,那就没有必要去自己生成NSAutoreleasePool;否则就自己生成一个NSAutoreleasePool来控制drain pool(排水池)。传送门
 dispatch_async(self.ioQueue, ^{
        if (operation.isCancelled) {
            return;
        }
        @autoreleasepool {
            UIImage *diskImage = [self diskImageForKey:key];
            if (diskImage && self.shouldCacheImagesInMemory) {
                NSUInteger cost = SDCacheCostForImage(diskImage);
                [self.memCache setObject:diskImage forKey:key cost:cost];
            }
            dispatch_async(dispatch_get_main_queue(), ^{
                doneBlock(diskImage, SDImageCacheTypeDisk);
            });
        }
    });
  • NSThread开辟子线程需要手动创建autoreleasepool。
  • 自定义NSOperationQueue需要在重写的main()方法中创建一个@autoreleasepool,将要做的操作放到@autoreleasepool{}中.
    tips:创建自动释放池的原因是因为在异步执行的情况下,不能访问主线程的自动释放池,所以应该自己创建一个自动释放池。
2、autorelease的对象何时被释放?
  • 对象执行autorelease方法会将对象添加到自动释放池中。
  • 当自动释放池销毁时,自动释放池中的所有对象作release操作。
  • 对象执行autorelease方法后,自身引用计数不会改变,而且会返回对象本身。

2、autoreleasepool原理是什么?

AutoreleasePool(自动释放池)是OC中的一种内存自动回收机制,它可以延迟加入AutoreleasePool中的变量release的时机。在正常情况下,创建的变量会在超出其作用域的时候release,但是如果将变量加入AutoreleasePool,那么release将延迟执行。

自动释放池的执行过程:objc_autoreleasePoolPush() —> [object autorelease] —> objc_autoreleasePoolPop(void *)。

int main(int argc, const char * argv[]) {

    /* @autoreleasepool */ {
        void *atautoreleasepoolobj = objc_autoreleasePoolPush();
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_kb_06b822gn59df4d1zt99361xw0000gn_T_main_d39a79_mi_0);
        objc_autoreleasePoolPop(atautoreleasepoolobj);
    }

    return 0;
}
每一个线程都会维护自己的 autoreleasepool 堆栈。换句话说 autoreleasepool 是与线程紧密相关的,每一个 autoreleasepool 只对应一个线程,子线程退出的时候会清空autoreleasepool。
tips:子线程的autoreleasepool也需要手动获取,但区分情况,一般系统提供的block如usingBlock和GCD提供的block内部都会自动包裹一个autoreleasepool,不用手动加。但是你自己通过其他方式创建的子线程,在线程内部需要手动获取autoreleasepool,防止局部内存使用峰值过高或发生其他内存问题,最后,autoreleasepool释放时,也会对其管理的对象发送release消息。

MarkDown语法扩展

二、SDWebImage中的@synchronized(同步锁)

//SDWebImageDownloaderOperation中取消线程的执行
- (void)cancel {
    @synchronized (self) {
        if (self.thread) {
            [self performSelector:@selector(cancelInternalAndStop) onThread:self.thread withObject:nil waitUntilDone:NO];
        }
        else {
            [self cancelInternal];
        }
    }
}
  • @synchronized对代码上锁,保证代码的原子性,从而保证多线程的安全。@synchronized 结构在工作时为传入的对象分配了一个递归锁,防止同一个线程重复调用时产生死锁。
    另外,还有很多锁,如:互斥锁、递归锁、自旋锁、条件锁等。我在之前的文章中有分析,就不一一解释了。传送门1
  • 正确使用同步锁。传送门2
    精准粒度控制:
    虽然说@synchronized比较慢,但@synchronized和其他同步锁的性能相比并没有很夸张的慢,对于使用者来说几乎忽略不计。 慢的原因其实是没有做好粒度控制。锁的本质是为了让我们的一段代码获得原子性,不同的数据要使用不同的锁,尽量将粒度控制在最细的程度。
@synchronized (tokenA) {
    [arrA addObject:obj];
}

@synchronized (tokenB) {
    [arrB addObject:obj];
}

三、SDWebImage的内存警告处理

  • 使用通知来观察内存的警告,收到UIApplicationDidReceiveMemoryWarningNotification内存警告通知,执行clearMenory方法,清理内存缓存。
[[NSNotificationCenter defaultCenter] addObserver:self
                                         selector:@selector(clearMemory)
                                             name:UIApplicationDidReceiveMemoryWarningNotification
                                           object:nil];
@property (strong, nonatomic) NSCache *memCache;

- (void)clearMemory {
    [self.memCache removeAllObjects];
}
知识点扩展NSCache简介:
  • 应用场景:
    iOS中需要频繁读取的数据,都可以用NSCache把数据缓存到内存中提高读取性能。
  • 特点:
    1、NSCache具有自动删除的功能,以减少系统占用的内存;
    2、NSCache是线程安全的,不需要加线程锁;
    3、键对象不会像 NSMutableDictionary 中那样被复制。(键不需要实现 NSCopying 协议)。
    NSCache知识点总结链接

四、SDWebImage的Disk缓存时长、清理操作时间点以及清理原则。

  • SDWebImage缓存方式枚举
    1、不允许SDWebImage缓存,而是从web上下载。
    2、从磁盘缓存。
    3、从内存缓存。
typedef NS_ENUM(NSInteger, SDImageCacheType) {
    /**
     * The image wasn't available the SDWebImage caches, but was downloaded from the web.
     */
    SDImageCacheTypeNone,
    /**
     * The image was obtained from the disk cache.
     */
    SDImageCacheTypeDisk,
    /**
     * The image was obtained from the memory cache.
     */
    SDImageCacheTypeMemory
};
  • Disk缓存时长默认为一周
//SDImageCache.m
static const NSInteger kDefaultCacheMaxCacheAge = 60 * 60 * 24 * 7; // 1 week
  • Disk清理操作时间点
    应用被终结以及应用进入后台。
//SDImageCache.m
[[NSNotificationCenter defaultCenter] addObserver:self
                                         selector:@selector(deleteOldFiles)
                                             name:UIApplicationWillTerminateNotification
                                           object:nil];

[[NSNotificationCenter defaultCenter] addObserver:self
                                         selector:@selector(backgroundDeleteOldFiles)
                                             name:UIApplicationDidEnterBackgroundNotification
                                           object:nil];

由于进入后台之后执行清除数据操作,但是应用在很短时间内就会被挂起导致操作无法完成,所及就涉及到申请后台运行时间来执行操作的问题了。

- (void)backgroundCleanDisk {
    Class UIApplicationClass = NSClassFromString(@"UIApplication");
    if(!UIApplicationClass || ![UIApplicationClass respondsToSelector:@selector(sharedApplication)]) {
        return;
    }
    UIApplication *application = [UIApplication performSelector:@selector(sharedApplication)];
// 后台任务标识--注册一个后台任务
    __block UIBackgroundTaskIdentifier bgTask = [application beginBackgroundTaskWithExpirationHandler:^{
        // Clean up any unfinished task business by marking where you
        // stopped or ending the task outright.
        [application endBackgroundTask:bgTask];
        bgTask = UIBackgroundTaskInvalid;
    }];

    // Start the long-running task and return immediately.
    [self cleanDiskWithCompletionBlock:^{
        //结束后台任务
        [application endBackgroundTask:bgTask];
        bgTask = UIBackgroundTaskInvalid;
    }];
}

申请后台运行时间在iOS 7.0之前是600s,在iOS 7.0之后就变成180s了,但是这些时间已经足够我们完成一般的操作了。UIBackgroundTaskIdentifier知识点传送门

  • 清理磁盘的原则
    清理缓存的规则分两步,第一步先清除掉过期的缓存文件,如果清除过期的文件之后磁盘空间还是不够,那么就继续按照文件存储的事件从早到晚排序删除文件,直到剩余空间达到要求。
扩展知识点:
1、static const与#define(宏定义)

之前常用的字符串常量,一般是抽成宏,但是苹果不推荐我们抽成宏,推荐我们使用const常量,那么为什么呢?。

  • 优缺点:
    编译时刻:宏是预编译(编译之前处理),const是编译阶段。
    编译检查:宏不做检查,不会报编译错误,只是替换,const会编译检查,会报编译错误。
    宏的好处:宏能定义一些函数,方法。 const不能。
    宏的坏处:使用大量宏,容易造成编译时间久,每次都需要重新替换
  • 异同点:
    相同点:
    a:都不能再被修改
    不同点:
    a:static const 修饰变量只有一份内存,高效。
    b:宏定义只是简单的替换,每次使用都需要创建一份内存,比较消耗内存。
    扩展知识点传送门
    宏(define)与常量(const)

五、SDWebImage存储在那里嫩?

  • 缓存在沙盒目录library/Caches
  • 默认情况下,二级目录为
    ~/Library/Caches/default/com.hackemist.SDWebImageCache.default
  • 我们也可以自定义目录
- (id)init {
    return [self initWithNamespace:@"default"];
}

- (id)initWithNamespace:(NSString *)ns {
    NSString *path = [self makeDiskCachePath:ns];
    return [self initWithNamespace:ns diskCacheDirectory:path];
}

- (id)initWithNamespace:(NSString *)ns diskCacheDirectory:(NSString *)directory {
    if ((self = [super init])) {
        NSString *fullNamespace = [@"com.hackemist.SDWebImageCache." stringByAppendingString:ns];

        // initialise PNG signature data
        kPNGSignatureData = [NSData dataWithBytes:kPNGSignatureBytes length:8];

        // Create IO serial queue
        _ioQueue = dispatch_queue_create("com.hackemist.SDWebImageCache", DISPATCH_QUEUE_SERIAL);

        // Init default values
        _maxCacheAge = kDefaultCacheMaxCacheAge;

        //......
    return self;
}
  • 在真机中查看磁盘缓存
    SDWebImage知识点总结一_第2张图片
    步骤1

    SDWebImage知识点总结一_第3张图片
    步骤2

    沙盒存储传送门
    SDWebImage知识点总结一_第4张图片
    沙河存储详解

六、SDWebImage用到的回调设计

  • Block
    当使用次数不多,且联系紧密时,推荐使用Block
typedef void(^SDWebImageQueryCompletedBlock)(UIImage *image, SDImageCacheType cacheType);

typedef void(^SDWebImageCheckCacheCompletionBlock)(BOOL isInCache);

typedef void(^SDWebImageCalculateSizeBlock)(NSUInteger fileCount, NSUInteger totalSize);
/**
 * Remove all expired cached image from disk. Non-blocking method - returns immediately.
 * @param completionBlock An block that should be executed after cache expiration completes (optional)
 */
- (void)cleanDiskWithCompletionBlock:(SDWebImageNoParamsBlock)completionBlock;

/**
 * Asynchronously calculate the disk cache's size.
 */
- (void)calculateSizeWithCompletionBlock:(SDWebImageCalculateSizeBlock)completionBlock;

/**
 *  Async check if image exists in disk cache already (does not load the image)
 *
 *  @param key             the key describing the url
 *  @param completionBlock the block to be executed when the check is done.
 *  @note the completion block will be always executed on the main queue
 */
- (void)diskImageExistsWithKey:(NSString *)key completion:(SDWebImageCheckCacheCompletionBlock)completionBlock;

  • Delegate
    方法会被多用多次,推荐使用Delegate,可以将方法常驻,随时监听方法的执行。
@protocol SDWebImagePrefetcherDelegate 

@optional

/**
 * Called when an image was prefetched.
 *
 * @param imagePrefetcher The current image prefetcher
 * @param imageURL        The image url that was prefetched
 * @param finishedCount   The total number of images that were prefetched (successful or not)
 * @param totalCount      The total number of images that were to be prefetched
 */
- (void)imagePrefetcher:(SDWebImagePrefetcher *)imagePrefetcher didPrefetchURL:(NSURL *)imageURL finishedCount:(NSUInteger)finishedCount totalCount:(NSUInteger)totalCount;

/**
 * Called when all images are prefetched.
 * @param imagePrefetcher The current image prefetcher
 * @param totalCount      The total number of images that were prefetched (whether successful or not)
 * @param skippedCount    The total number of images that were skipped
 */
- (void)imagePrefetcher:(SDWebImagePrefetcher *)imagePrefetcher didFinishWithTotalCount:(NSUInteger)totalCount skippedCount:(NSUInteger)skippedCount;

@end
  • 项目常用举例
    1、UITableView的delegate中的监控滑动,代理方法需要被不断地执行。
    2、UIView动画、GCD、NSBlockOperation,都是执行一次就释放。
    以上两点可以作为我们日常使用的参考原则。

七、SDWebImage中用到的枚举

  • NS_ENUM 通用枚举
    NS_ENUM定义的枚举不能几个同时存在,只能选择一个。
doneBlock(diskImage, SDImageCacheTypeDisk);
  • NS_OPTIONS 位移枚举
    NS_OPTIONS定义的枚举在需要的地方可以同时存在多个。
[gifImageView sd_setImageWithURL:url placeholderImage:image options:SDWebImageRefreshCached | SDWebImageRetryFailed progress:^(NSInteger receivedSize, NSInteger expectedSize, NSURL * _Nullable targetURL) {

            } completed:^(UIImage * _Nullable image, NSError * _Nullable error, SDImageCacheType cacheType, NSURL * _Nullable imageURL) {
 
            }];

SDWebImage示例如下:

typedef NS_OPTIONS(NSUInteger, SDWebImageDownloaderOptions) {
    SDWebImageDownloaderLowPriority = 1 << 0,     // 值为2的0次方

    SDWebImageDownloaderProgressiveDownload = 1 << 1,  // 值为2的1次方

    SDWebImageDownloaderUseNSURLCache = 1 << 2,

    SDWebImageDownloaderIgnoreCachedResponse = 1 << 3,

    SDWebImageDownloaderContinueInBackground = 1 << 4,

    SDWebImageDownloaderHandleCookies = 1 << 5,

    SDWebImageDownloaderAllowInvalidSSLCertificates = 1 << 6,

    SDWebImageDownloaderHighPriority = 1 << 7,
};
typedef NS_ENUM(NSInteger, SDImageCacheType) {
    /**
     * The image wasn't available the SDWebImage caches, but was downloaded from the web.
     */
    SDImageCacheTypeNone,
    /**
     * The image was obtained from the disk cache.
     */
    SDImageCacheTypeDisk,
    /**
     * The image was obtained from the memory cache.
     */
    SDImageCacheTypeMemory
};

SDWebImage源码链接
参考资料1
SDWebImage4.0源码探究1
SDWebImage4.0源码探究2
autoreleasepool使用
AutoreleasePool的原理和实现

你可能感兴趣的:(SDWebImage知识点总结一)