• 仿SDWebImage


    仿SDWebImage

    目标:模拟 SDWebImage 的实现

    说明:整体代码与之前博客上的演练代码的基本一致,只是编写顺序会有变化!

    在模仿 SDWebImage 之前,首先需要补充一个知识点:NSOperation自定义操作

    下载操作实现

    #import "NSString+Path.h"
    
    @interface DownloadImageOperation()
    /// 要下载图像的 URL 字符串
    @property (nonatomic, copy) NSString *URLString;
    /// 完成回调 Block
    @property (nonatomic, copy) void (^finishedBlock)(UIImage *image);
    @end
    
    @implementation DownloadImageOperation
    
    + (instancetype)downloadImageOperationWithURLString:(NSString *)URLString finished:(void (^)(UIImage *))finished {
        DownloadImageOperation *op = [[DownloadImageOperation alloc] init];
    
        op.URLString = URLString;
        op.finishedBlock = finished;
    
        return op;
    }
    
    - (void)main {
        @autoreleasepool {
            // 利用断言要求必须传入完成回调,简化后续代码的分支
            NSAssert(self.finishedBlock != nil, @"必须传入回调 Block");
    
            // 1. NSURL
            NSURL *url = [NSURL URLWithString:self.URLString];
            // 2. 获取二进制数据
            NSData *data = [NSData dataWithContentsOfURL:url];
            // 3. 保存至沙盒
            if (data != nil) {
                [data writeToFile:self.URLString.appendCachePath atomically:YES];
            }
    
            if (self.isCancelled) {
                NSLog(@"下载操作被取消");
                return;
            }
    
            // 主线程回调
            [[NSOperationQueue mainQueue] addOperationWithBlock:^{
                self.finishedBlock([UIImage imageWithData:data]);
            }];
        }
    }

    断言

    • 断言是所有 C 语言开发者的最爱
    • 断言能够在程序编码时提前预判必须满足某一个条件
    • 如果条件不满足,直接让程序崩溃,从而让程序员尽早发现错误
    • 断言仅在调试时有效
    • 断言可以简化程序的分支逻辑

    测试下载操作

    - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    
        int seed = arc4random_uniform((UInt32)self.appList.count);
        AppInfo *app = self.appList[seed];
    
        // 取消之前的下载操作
        if (![app.icon isEqualToString:self.currentURLString]) {
            // 取消之前操作
            [self.operationCache[self.currentURLString] cancel];
        }
    
        // 记录当前操作
        self.currentURLString = app.icon;
    
        // 创建下载操作
        DownloadImageOperation *op = [DownloadImageOperation downloadImageOperationWithURLString:app.icon finished:^(UIImage *image) {
            self.iconView.image = image;
    
            // 从缓冲池删除操作
            [self.operationCache removeObjectForKey:app.icon];
        }];
    
        // 将操作添加到缓冲池
        [self.operationCache setObject:op forKey:app.icon];
        // 将操作添加到队列
        [self.downloadQueue addOperation:op];
    }

    框架结构设计

    下载管理器

    • 单例实现
    + (instancetype)sharedManager {
        static id instance;
    
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            instance = [[self alloc] init];
        });
        return instance;
    }

    之所以设计成单例,是为了实现全局的图像下载管理

    • 移植属性和懒加载代码
    /// 下载队列
    @property (nonatomic, strong) NSOperationQueue *downloadQueue;
    /// 下载操作缓存
    @property (nonatomic, strong) NSMutableDictionary *operationCache;
    
    // MARK: - 懒加载
    - (NSMutableDictionary *)operationCache {
        if (_operationCache == nil) {
            _operationCache = [NSMutableDictionary dictionary];
        }
        return _operationCache;
    }
    
    - (NSOperationQueue *)downloadQueue {
        if (_downloadQueue == nil) {
            _downloadQueue = [[NSOperationQueue alloc] init];
        }
        return _downloadQueue;
    }
    • 定义方法
    ///  下载指定 URL 的图像
    ///
    ///  @param URLString 图像 URL 字符串
    ///  @param finished  下载完成回调
    - (void)downloadImageOperationWithURLString:(NSString *)URLString finished:(void (^)(UIImage *image))finished;
    • 方法实现
    - (void)downloadImageOperationWithURLString:(NSString *)URLString finished:(void (^)(UIImage *))finished {
    
        // 检查操作缓冲池
        if (self.operationCache[URLString] != nil) {
            NSLog(@"正在玩命下载中,稍安勿躁");
            return;
        }
    
        // 创建下载操作
        DownloadImageOperation *op = [DownloadImageOperation downloadImageOperationWithURLString:URLString finished:^(UIImage *image) {
            // 从缓冲池删除操作
            [self.operationCache removeObjectForKey:URLString];
    
            // 执行回调
            finished(image);
        }];
    
        // 将操作添加到缓冲池
        [self.operationCache setObject:op forKey:URLString];
        // 将操作添加到队列
        [self.downloadQueue addOperation:op];
    }

    修改 ViewController 中的代码

    • 删除相关属性和懒加载方法
    • 用下载管理器接管之前的下载方法
    // 创建下载操作
    [[DownloadImageManager sharedManager] downloadImageOperationWithURLString:self.currentURLString finished:^(UIImage *image) {
        self.iconView.image = image;
    }];
    • 增加取消下载功能
    ///  取消指定 URL 的下载操作
    - (void)cancelDownloadWithURLString:(NSString *)URLString {
        // 1. 从缓冲池中取出下载操作
        DownloadImageOperation *op = self.operationCache[URLString];
    
        if (op == nil) {
            return;
        }
    
        // 2. 如果有取消
        [op cancel];
        // 3. 从缓冲池中删除下载操作
        [self.operationCache removeObjectForKey:URLString];
    }

    运行测试!

    缓存管理

    • 定义图像缓存属性
    /// 图像缓存
    @property (nonatomic, strong) NSMutableDictionary *imageCache;
    • 懒加载
    - (NSMutableDictionary *)imageCache {
        if (_imageCache == nil) {
            _imageCache = [NSMutableDictionary dictionary];
        }
        return _imageCache;
    }
    • 检测图像缓存方法准备
    ///  检查图像缓存
    ///
    ///  @return 是否存在图像缓存
    - (BOOL)chechImageCache {
        return NO;
    }
    • 方法调用
    // 如果存在图像缓存,直接回调
    if ([self chechImageCache]) {
        finished(self.imageCache[URLString]);
        return;
    }
    • 缓存方法实现
    - (BOOL)chechImageCache:(NSString *)URLString {
    
        // 1. 如果存在内存缓存,直接返回
        if (self.imageCache[URLString]) {
            NSLog(@"内存缓存");
            return YES;
        }
    
        // 2. 如果存在磁盘缓存
        UIImage *image = [UIImage imageWithContentsOfFile:URLString.appendCachePath];
        if (image != nil) {
            // 2.1 加载图像并设置内存缓存
            NSLog(@"从沙盒缓存");
            [self.imageCache setObject:image forKey:URLString];
            // 2.2 返回
            return YES;
        }
    
        return NO;
    }

    运行测试

    自定义 UIImageView

    • 目标:

      • 利用下载管理器获取指定 URLString 的图像,完成后设置 image
      • 如果之前存在未完成的下载,判断是否与给定的 URLString 一致
      • 如果一致,等待下载结束
      • 如果不一致,取消之前的下载操作
    • 定义方法

    ///  设置指定 URL 字符串的网络图像
    ///
    ///  @param URLString 网络图像 URL 字符串
    - (void)setImageWithURLString:(NSString *)URLString;
    • 方法实现
    @interface WebImageView()
    ///  当前正在下载的 URL 字符串
    @property (nonatomic, copy) NSString *currentURLString;
    @end
    
    @implementation WebImageView
    
    - (void)setImageWithURLString:(NSString *)URLString {
    
        // 取消之前的下载操作
        if (![URLString isEqualToString:self.currentURLString]) {
            // 取消之前操作
            [[DownloadImageManager sharedManager] cancelDownloadWithURLString:self.currentURLString];
        }
    
        // 记录当前操作
        self.currentURLString = URLString;
    
        // 创建下载操作
        __weak typeof(self) weakSelf = self;
        [[DownloadImageManager sharedManager] downloadImageOperationWithURLString:URLString finished:^(UIImage *image) {
            weakSelf.image = image;
        }];
    }
    
    @end
    • 修改 ViewController 中的调用代码
    - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    
        int seed = arc4random_uniform((UInt32)self.appList.count);
        AppInfo *app = self.appList[seed];
    
        [self.iconView setImageWithURLString:app.icon];
    }

    运行时机制 —— 关联对象

    // MARK: - 运行时关联对象
    const void *HMCurrentURLStringKey = "HMCurrentURLStringKey";
    
    - (void)setCurrentURLString:(NSString *)currentURLString {
        objc_setAssociatedObject(self, HMCurrentURLStringKey, currentURLString, OBJC_ASSOCIATION_COPY_NONATOMIC);
    }
    
    - (NSString *)currentURLString {
        return objc_getAssociatedObject(self, HMCurrentURLStringKey);
    }
    • 为了防止 Cell 重用,取消之前下载操作的同时,清空 image
    self.image = nil;

    SDWebImage常见面试题

    1> 图片文件缓存的时间有多长:1周

    _maxCacheAge = kDefaultCacheMaxCacheAge

    2> SDWebImage 的内存缓存是用什么实现的?

    NSCache

    3> SDWebImage 的最大并发数是多少?

    maxConcurrentDownloads = 6
    * 是程序固定死了,可以通过属性进行调整!

    4> SDWebImage 支持动图吗?GIF

    #import <ImageIO/ImageIO.h>
    [UIImage animatedImageWithImages:images duration:duration];

    5> SDWebImage是如何区分不同格式的图像的

    • 根据图像数据第一个字节来判断的!

      • PNG:压缩比没有JPG高,但是无损压缩,解压缩性能高,苹果推荐的图像格式!
      • JPG:压缩比最高的一种图片格式,有损压缩!最多使用的场景,照相机!解压缩的性能不好!
      • GIF:序列桢动图,特点:只支持256种颜色!最流行的时候在1998~1999,有专利的!

    6> SDWebImage 缓存图片的名称是怎么确定的!

    • md5

      • 如果单纯使用 文件名保存,重名的几率很高!
      • 使用 MD5 的散列函数!对完整的 URL 进行 md5,结果是一个 32 个字符长度的字符串!

    7> SDWebImage 的内存警告是如何处理的!

    • 利用通知中心观察
    • - UIApplicationDidReceiveMemoryWarningNotification 接收到内存警告的通知
      • 执行 clearMemory 方法,清理内存缓存!
    • - UIApplicationWillTerminateNotification 接收到应用程序将要终止通知
      • 执行 cleanDisk 方法,清理磁盘缓存!
    • - UIApplicationDidEnterBackgroundNotification 接收到应用程序进入后台通知
      • 执行 backgroundCleanDisk 方法,后台清理磁盘!
      • 通过以上通知监听,能够保证缓存文件的大小始终在控制范围之内!
      • clearDisk 清空磁盘缓存,将所有缓存目录中的文件,全部删除!
        实际工作,将缓存目录直接删除,再次创建一个同名空目录!
  • 相关阅读:
    [Node.js] CommonJS Modules
    [Node.js] npm init && npm install
    [AngularJS] Hijacking Existing HTML Attributes with Angular Directives
    [Node.js] Level 7. Persisting Data
    [Express] Level 5: Route file
    [Express] Level 5: Route Instance -- refactor the code
    [Express] Level 4: Body-parser -- Delete
    [Express] Level 4: Body-parser -- Post
    [Express] Level 3: Massaging User Data
    [Express] Level 3: Reading from the URL
  • 原文地址:https://www.cnblogs.com/jiahao89/p/5118276.html
Copyright © 2020-2023  润新知