美文网首页
SDWebImage知识点总结一

SDWebImage知识点总结一

作者: woniu | 来源:发表于2018-05-23 17:24 被阅读35次

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

    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用到的回调设计

    • 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 <NSObject>
    
    @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知识点总结一

          本文链接:https://www.haomeiwen.com/subject/xqxdjftx.html