搬好小板凳看SDWebImage源码解析(一)

看了下离开上次写简书博客的时日,已经仙逝了多个多月了,很羞愧。正好如今项目不忙,抽点时间探究下第三方库,朋友提出总计写成博客就这么开篇了。内容篇幅会相比较长,所以希望各位看官搬好小板凳看SDWebImage源码解析,假若没有定性真的是很难始终不渝下去。希望我们可以坚韧不拔跟着博主一块学完SDWebImage源码序列。

内容简介

精益创业代表了一种持续形成革新的新点子,它来自“精益生产”的意见,提倡集团开展“验证性学习”,先向市场生产极简的原型产品,然后在持续地试验和读书中,以细小的成本和管事的格局声明产品是否符合用户需要,并迭代优化产品,灵活调整方向。

一.准备知识

在业内学习源码前,先讲一些SDWebImage中用到的生僻知识点,有些用的很频繁,可是众六人对这么些知识点模糊不清,假诺不搞清楚会大大影响阅读效能,比如枚举NS_OPTIONS的二进制位运算。

作者简介

Eric•莱斯,IMUV联合创办者及CTO,宾夕法尼亚州立商高校驻校集团家,其“精益创业”的视角被《伦敦时报》、《华尔街日报》、《加州新德里分校州立生意评论》等多家媒体广泛报道。他还为多家新创公司、大型商厦及风险投资集团提供商业及产品战略方面的问讯服务。

1> NS_OPTIONS与位运算

NS_OPTIONS用来定义位移相关操作的枚举值,当一个枚举变量需要带领多种值的时候就需要,我们得以参考UI基特.Framework的头文件,可以看出大量的枚举定义。例如在SDWebImage下面就会触发到SDWebImageOptions枚举值:

typedef NS_OPTIONS(NSUInteger, SDWebImageOptions) {
    SDWebImageRetryFailed = 1 << 0,
    SDWebImageLowPriority = 1 << 1,
    SDWebImageCacheMemoryOnly = 1 << 2,
    SDWebImageProgressiveDownload = 1 << 3,
    SDWebImageRefreshCached = 1 << 4,
    SDWebImageContinueInBackground = 1 << 5,
    SDWebImageHandleCookies = 1 << 6,
    SDWebImageAllowInvalidSSLCertificates = 1 << 7,
    SDWebImageHighPriority = 1 << 8,
    SDWebImageDelayPlaceholder = 1 << 9,
    SDWebImageTransformAnimatedImage = 1 << 10,
    SDWebImageAvoidAutoSetImage = 1 << 11,
    SDWebImageScaleDownLargeImages = 1 << 12
};

“<<”是位运算中的左移运算符,第一个值SDWebImageRetryFailed = 1
<<
0,十进制1转化为二进制:0b00000001,这里<<0将所有二进制位左移0位,那么如故0b00000001,最终SDWebImageRetryFailed
值为1.
其次个枚举值SDWebImageLowPriority
=1<<1,这里是将1的二进制所有位向左移动1位,空缺的用0补齐,那么0b00000001变成0b00000010,十进制为2则SDWebImageLowPriority值为2。

左移1位示意图

依次类推:
SDWebImageCacheMemoryOnly向左移动2位等于4,
SDWebImageProgressiveDownload向左移动3位等于8.
上面写一个,customImageView是大家自定义的imageView实例,在SDWebImage的SDWebImageManager.m具体运用中:

   [customImageView sd_setImageWithURL:url placeholderImage:nil options:SDWebImageRetryFailed | SDWebImageCacheMemoryOnly];

瞩目到代码中用到了”|”,‘|’是位运算中的或运算,需要多少个操作数,效用是将五个数的一律位举办逻辑或运算,即只要六个对应位有一个位1,则运算后此位为1,如若六个对应位都为0。例如十进制1的二进制0b00000001
| 十进制2的二进制0b00000010,结果为0b00000011十进制为3。下图示例:

或运算

当options值为SDWebImageRetryFailed |
SDWebImageCacheMemoryOnly时,执行或运算0b00000001| 0b00000100 =
0b00000101 十进制是5.
这就是说在切实的点子内部options怎么拔取啊?上边的代码SD将options和SDWebImageRetryFailed做”&”运算:

    if (url.absoluteString.length == 0 || (!(options & SDWebImageRetryFailed) && isFailedUrl)) {
    }

‘&’是位运算中的与运算,当对应位数同为1结出才为1.诸如十进制1的二进制0b00000001&十进制2的二进制0b00000010,结果为0b00000000十进制是0.

与运算

下面代码中,SD将这儿的options二进制0b00000101和SDWebImageRetryFailed的二进制进行&运算,假使options包含了SDWebImageRetryFailed则结果为真。SD通篇都基于options做了广大事情,因而了然可选枚举和位运算分外关键。

第一篇 愿景

2> NSURLCredential

当移动端和服务器在传输过程中,服务端有可能在回到Response时顺手表达,询问
HTTP
请求的发起方是何人,这时候发起方应提供科学的用户名和密码(即认证消息)。这时候就需要NSURLCredential身份申明,更加具体可以查看这篇博客

第一章 开端

3>涉及的宏定义

创业管理

想做就做的姿态并不可取。创业活动需要采纳管理规约,才能从大家赢得的创业机会中取得成果。

3-1>FOUNDATION_EXPORT:

用来定义常量,和#define功用一样,只不过在检测字符串的值是否等于是比#define的效用更高,因为正如的是指针地址。

精益创业的根基

精益创业的称号来源于精益生产。后者是由丰田公司的大野耐一和商丘重夫发展出来的。它的尺度中概括了吸取每位员工的学问和创制力、把每批次的范围压缩、实施生产和库存管理,以及加快循环周期。精益生产让天下驾驭价值创设活动和浪费之间的差距。

精益创业的法子:你需要的不是基于众多一旦制定复杂的计划,而是能够由此旋转方向盘举办持续调整,我们把这么些进程称为开发—测量—认知的举报循环。通过那样的驾驶过程,我们可以了然啥时候以及是否到了急转弯时刻,我把那么些时刻称为转型时刻。

新创集团有一个清楚的样子,一个脑海中的目标地,称为新创公司的愿景。为了促成愿景,公司制定了战略性,产品就是其世界一战略的最终结果。产品在优化的进程中穿梭变更,我称其为调动引擎。有时候可能需要改变战略,但总的愿景却很少变化。创业者的历次挫败就是一个询问怎么到达既定彼岸的时机。

实质上意况下,
新创集团是一密密麻麻活动的整合。很多事会同时暴发:引擎在运行,吸纳新买主并服务已有消费者;我们正在调整,试着改正产品、营销和营业措施。创业者的挑衅在于平衡有着这么些移动。

3-2>NS_DESIGNATED_INITIALIZER :

NS_DESIGNATED_INITIALIZER宏来实现指定构造器,平日是想告知调用者要用这么些形式去开首化类对象,便于规范API。

第二章 定义

3-2>__deprecated_msg

用来指示此措施或性质已经丢掉。

@property (nonatomic, assign) BOOL shouldUseCredentialStorage __deprecated_msg("Property deprecated. Does nothing. Kept only for backwards compatibility");

何人才是创业公司家

从不用背景、志向远大的年青人,到大商厦中经验充分的远见卓识者,以及那么令她们顶住责任的人。

4>initialize

initialize静态方法会在首先次使用该类此前由运行期系统调用,而且仅调用一次,属于懒加载范畴,假诺不利用则不会调用,可以在点子内部做一些先导化操作,可是load方法是借使开动程序就会调用。关于initialize和load更加详实的看这里

一经我是创业者,何谓新创集团

新创集团是一个由人结合的机关,在最好不确定的境况下,开发新产品或新劳动。顾客在和商社的并行中体验到的别样事或物,都应该被肯定为商家的产品。在此外动静下,协会架构都要为顾客提供一种新的价值来源,并关心其出品对消费者的熏陶。

5>dispatch_barrier_sync

GCD中的知识点,承上启下,当把任务A添加到队列中利用dispatch_barrier_sync时,它会等待在它面前插入队列的任务先举行完,然后等职责履行完再实践后边的职责。更加详实的可点这里

眼下打点了这个,如果还有任何必要讲的话,下文会再做解释。大家有不了解的知识点也足以在评论中还原,我会挑痛点相比较多的当即更新到博客中。

SnapTax的故事

她们十分擅长逐渐健全现有产品,为已有客户服务。可是集团在支付突破性的新产品上挣扎不已,而正是这种颠覆式立异才能制作出新的增长源头。

二.主旨源码解析

七千人的精益创业

一家小卖部唯一持久的长时间经济增长之道,就是无休止开发颠覆式改进。领导者需要创建条件,允许员工们举办创业活动中需要做的实验。

SDWebImage的核心Workflow:

SDWebImage-Workflow

咱俩要研讨的源码重虽然围绕这多少个骨干类进行。
开卷指出TIPS:SDWebImage的源码多,逻辑复杂,我的提出是读者下载一份源码,源码和本文同步阅读,因为只要没看过源码的调用逻辑,单看本文的解读不会形成连串。会在源码上加注释,并且copy到随笔中,运行项目参考调用栈配合本文效果会更好。

第三章 学习

重建学习的定义,称之为经求证的认知。成功推行一项毫无意义的计划是导致失利的致命原因,而经证实的体味则是釜底抽薪这么些题材的重点措施。

1>UIImageView+WebCache/UIView+WebCache

UIImageView+WebCache对外使用的API入口,这个类的接口设计把设计格局五大条件之一的接口分离原则反映的淋漓尽致。首先说一下怎样是接口分离原则:
接口分离原则:为特定效率提供特定的接口,不要使用单一的总接口包括所有机能,而是应该依照效能把这么些接口分割,减弱依赖,不可能迫使用户去倚重那么些他们不使用的接口。
在.h中可以见到:

- (void)sd_setImageWithURL:(nullable NSURL *)url NS_REFINED_FOR_SWIFT;
- (void)sd_setImageWithURL:(nullable NSURL *)url
          placeholderImage:(nullable UIImage *)placeholder;
- (void)sd_setImageWithURL:(nullable NSURL *)url
          placeholderImage:(nullable UIImage *)placeholder
                   options:(SDWebImageOptions)options

这般调用者需要如何效用就去调用特定的API,清晰易增加,在.m中会设计一个总的接口包含所有机能。UIImageView+WebCache紧倘若一个接口,没有太多需要研究的,在.m中总接口中又调用了UIView的壮大方法,接下去讲一下UIView+WebCache。
UIView+WebCache提供了具体的图样加载请求,UIButton和UIImageView都可调用sd_internalSetImageWithURL来兑现,下边具体看下sd_internalSetImageWithURL的兑现。具体的源码意思都做了诠释:

- (void)sd_internalSetImageWithURL:(nullable NSURL *)url
                  placeholderImage:(nullable UIImage *)placeholder
                           options:(SDWebImageOptions)options
                      operationKey:(nullable NSString *)operationKey
                     setImageBlock:(nullable SDSetImageBlock)setImageBlock
                          progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
                         completed:(nullable SDExternalCompletionBlock)completedBlock
                           context:(nullable NSDictionary *)context {
    //根据参数operationKey取消当前类所对应的下载Operation对象,如果operationKey为nil key取NSStringFromClass([self class])
    NSString *validOperationKey = operationKey ?: NSStringFromClass([self class]);
    //具体的取消操作在UIView+WebCacheOperation中实现
    [self sd_cancelImageLoadOperationWithKey:validOperationKey];

    //利用关联对象给当前self实例绑定url key=imageURLKey value=url
    objc_setAssociatedObject(self, &imageURLKey, url, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    //这里就用到了我们开篇讲的位运算,利用&与运算判断调用者是否需要设置占位图,需要则set
    if (!(options & SDWebImageDelayPlaceholder)) {
        dispatch_main_async_safe(^{
            [self sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock];
        });
    }

    if (url) {
        // check if activityView is enabled or not
        // 判断之前是否利用关联对象给self设置了显示菊花加载,如果有则add
        if ([self sd_showActivityIndicatorView]) {
            [self sd_addActivityIndicator];
        }

        __weak __typeof(self)wself = self;
        //调用SDWebImageManager的loadImageWithURL方法去加载图片,返回值是SDWebImageCombinedOperation
        id <SDWebImageOperation> operation = [SDWebImageManager.sharedManager loadImageWithURL:url options:options progress:progressBlock completed:^(UIImage *image, NSData *data, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
            __strong __typeof (wself) sself = wself;
            //在这里移除菊花
            [sself sd_removeActivityIndicator];
            if (!sself) { return; }
            BOOL shouldCallCompletedBlock = finished || (options & SDWebImageAvoidAutoSetImage);
            //是否不显示图片两个条件满足其一即可 1>调用者手动主动配置,哪怕image不为nil 2>没有图片并且不delaye占位图情况
            BOOL shouldNotSetImage = ((image && (options & SDWebImageAvoidAutoSetImage)) ||
                                      (!image && !(options & SDWebImageDelayPlaceholder)));
            SDWebImageNoParamsBlock callCompletedBlockClojure = ^{
                if (!sself) { return; }
                if (!shouldNotSetImage) {
                    [sself sd_setNeedsLayout];
                }
                //如果设置了不自动显示图片,则回调让调用者手动添加显示图片 程序return
                if (completedBlock && shouldCallCompletedBlock) {
                    completedBlock(image, error, cacheType, url);
                }
            };

            // case 1a: we got an image, but the SDWebImageAvoidAutoSetImage flag is set
            // OR
            // case 1b: we got no image and the SDWebImageDelayPlaceholder is not set
            if (shouldNotSetImage) {//如果设置了不自动显示图片,则回调让调用者手动添加显示图片 程序return
                dispatch_main_async_safe(callCompletedBlockClojure);
                return;
            }

            UIImage *targetImage = nil;
            NSData *targetData = nil;
            if (image) {
                // case 2a: we got an image and the SDWebImageAvoidAutoSetImage is not set
                targetImage = image;
                targetData = data;
            } else if (options & SDWebImageDelayPlaceholder) {//如果没有image,并且调用者设置了delaye显示默认图那这里targetImage设置为placeholder
                // case 2b: we got no image and the SDWebImageDelayPlaceholder flag is set
                targetImage = placeholder;
                targetData = nil;
            }
            BOOL shouldUseGlobalQueue = NO;
            //外部参数context如果设置了全局队列中setImage,那shouldUseGlobalQueue为YES,否则默认在dispatch_get_main_queue
            if (context && [context valueForKey:SDWebImageInternalSetImageInGlobalQueueKey]) {
                shouldUseGlobalQueue = [[context valueForKey:SDWebImageInternalSetImageInGlobalQueueKey] boolValue];
            }
            dispatch_queue_t targetQueue = shouldUseGlobalQueue ? dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0) : dispatch_get_main_queue();

            dispatch_queue_async_safe(targetQueue, ^{//队列中设置image给imageView或者button
                [sself sd_setImage:targetImage imageData:targetData basedOnClassOrViaCustomSetImageBlock:setImageBlock];
                dispatch_main_async_safe(callCompletedBlockClojure);
            });
        }];
        //绑定operation到当前self,key=validOperationKey,value=operation
        [self sd_setImageLoadOperation:operation forKey:validOperationKey];
    } else {v
        dispatch_main_async_safe(^{
            //移除菊花 抛出url为nil的回调
            [self sd_removeActivityIndicator];
            if (completedBlock) {
                NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey : @"Trying to load a nil url"}];
                completedBlock(nil, error, SDImageCacheTypeNone, url);
            }
        });
    }
}

下边是在这一历程的粗略流程图协助精晓:

IMVU经证实的回味

麦特卡夫定律:一个通信网络的一体化价值,约和这一个体系用户数量的平方成正比。在一个网络环境中,只有一部电话根本未曾价值,唯有其外人也持有电话时,你的对讲机和这个网络环境才有价值。第一个产品无论是是不是个错误,如若没有支付它,大家就不会左右对消费者的机要认知,也不会知道大家的韬略有欠缺。如果我们没一门心绪关注怎么着增加功用、修补漏洞把产品做得更好,大家是否能更早认识到这么些经验教训?

2>SDWebImageManager

SDWebImageManager类是SDWebImage中的要旨类,首要负责调用SDWebImageDownloader举行图片下载,以及在下载之后接纳SDImageCache举行图纸缓存。并且此类仍可以跳过UIImageViewe/Cache或者UIView/Cache单独使用,不仅局限于一个UIView。
SDWebImageManager.h注解:

@class SDWebImageManager;

@protocol SDWebImageManagerDelegate <NSObject>

@optional

//当缓存没有发现当前图片,那么会查看调用者是否实现改方法,如果return一个no,则不会继续下载这张图片
- (BOOL)imageManager:(nonnull SDWebImageManager *)imageManager shouldDownloadImageForURL:(nullable NSURL *)imageURL;

//当图片下载完成但是未添加到缓存里面,这时候调用该方法可以给图片旋转方向,注意是异步执行, 防止组织主线程
- (nullable UIImage *)imageManager:(nonnull SDWebImageManager *)imageManager transformDownloadedImage:(nullable UIImage *)image withURL:(nullable NSURL *)imageURL;

@end

@interface SDWebImageManager : NSObject
//SDWebImageManagerDelegate的delegate
@property (weak, nonatomic, nullable) id <SDWebImageManagerDelegate> delegate;
//缓存中心
@property (strong, nonatomic, readonly, nullable) SDImageCache *imageCache;
//下载中心
@property (strong, nonatomic, readonly, nullable) SDWebImageDownloader *imageDownloader;
//这个缓存block的作用是,在block内部进行缓存key的生成并return,key就是根据图片url根据规则生成,sd的缓存策略就是key是图片url,value就是image
@property (nonatomic, copy, nullable) SDWebImageCacheKeyFilterBlock cacheKeyFilter;
//返回SDWebImageManager的单例
+ (nonnull instancetype)sharedManager;
//根据特定的cache和downloader生成一个新的SDWebImageManager
- (nonnull instancetype)initWithCache:(nonnull SDImageCache *)cache downloader:(nonnull SDWebImageDownloader *)downloader NS_DESIGNATED_INITIALIZER;
//下载图片的关键方法,第一个参数图片url,第二个参数设置下载多样操作,第三个参数下载中进度block,第四个参数下载完成后回调
- (nullable id <SDWebImageOperation>)loadImageWithURL:(nullable NSURL *)url
                                              options:(SDWebImageOptions)options
                                             progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
                                            completed:(nullable SDInternalCompletionBlock)completedBlock;
//缓存图片根据指定的url和image
- (void)saveImageToCache:(nullable UIImage *)image forURL:(nullable NSURL *)url;
//取消所有当前的operation
- (void)cancelAll;
//检查是否有图片正在下载
- (BOOL)isRunning;
//异步检查图片是否已经缓存
- (void)cachedImageExistsForURL:(nullable NSURL *)url
                     completion:(nullable SDWebImageCheckCacheCompletionBlock)completionBlock;
//检查图片是否缓存 在磁盘中
- (void)diskImageExistsForURL:(nullable NSURL *)url
                   completion:(nullable SDWebImageCheckCacheCompletionBlock)completionBlock;
//给定一个url返回缓存的字符串key
- (nullable NSString *)cacheKeyForURL:(nullable NSURL *)url;

@end

SDWebImageManager.m注解:

@interface SDWebImageCombinedOperation : NSObject <SDWebImageOperation>
//是否取消当前所有操作
@property (assign, nonatomic, getter = isCancelled) BOOL cancelled;
//没有参数取消回调
@property (copy, nonatomic, nullable) SDWebImageNoParamsBlock cancelBlock;
//执行缓存的操作
@property (strong, nonatomic, nullable) NSOperation *cacheOperation;

@end

@interface SDWebImageManager ()
//缓存对象
@property (strong, nonatomic, readwrite, nonnull) SDImageCache *imageCache;
//下载对象
@property (strong, nonatomic, readwrite, nonnull) SDWebImageDownloader *imageDownloader;
//集合存储所有下载失败的图片url
@property (strong, nonatomic, nonnull) NSMutableSet<NSURL *> *failedURLs;
//存储正在执行下载图片操作的数组
@property (strong, nonatomic, nonnull) NSMutableArray<SDWebImageCombinedOperation *> *runningOperations;

@end

@implementation SDWebImageManager
//生成一个SDWebImagemanager的单例
+ (nonnull instancetype)sharedManager {
    static dispatch_once_t once;
    static id instance;
    dispatch_once(&once, ^{
        instance = [self new];
    });
    return instance;
}
//初始化SDImageCache/SDWebImageDownloade
- (nonnull instancetype)init {
    SDImageCache *cache = [SDImageCache sharedImageCache];
    SDWebImageDownloader *downloader = [SDWebImageDownloader sharedDownloader];
    return [self initWithCache:cache downloader:downloader];
}
//初始化以及属性绑定
- (nonnull instancetype)initWithCache:(nonnull SDImageCache *)cache downloader:(nonnull SDWebImageDownloader *)downloader {
    if ((self = [super init])) {
        _imageCache = cache;
        _imageDownloader = downloader;
        _failedURLs = [NSMutableSet new];
        _runningOperations = [NSMutableArray new];
    }
    return self;
}
//根据URL获取缓存中的key
- (nullable NSString *)cacheKeyForURL:(nullable NSURL *)url {
    if (!url) {
        return @"";
    }

    if (self.cacheKeyFilter) {
        return self.cacheKeyFilter(url);
    } else {
        return url.absoluteString;
    }
}
//检查缓存中是否缓存了当前url对应的图片-先判断内存缓存、再判断磁盘缓存
- (void)cachedImageExistsForURL:(nullable NSURL *)url
                     completion:(nullable SDWebImageCheckCacheCompletionBlock)completionBlock {
    NSString *key = [self cacheKeyForURL:url];
    //判断内存缓存是否存在
    BOOL isInMemoryCache = ([self.imageCache imageFromMemoryCacheForKey:key] != nil);

    if (isInMemoryCache) {
        // making sure we call the completion block on the main queue
        dispatch_async(dispatch_get_main_queue(), ^{
            if (completionBlock) {
                completionBlock(YES);
            }
        });
        return;
    }
    //判断磁盘缓存中是否存在
    [self.imageCache diskImageExistsWithKey:key completion:^(BOOL isInDiskCache) {
        // the completion block of checkDiskCacheForImageWithKey:completion: is always called on the main queue, no need to further dispatch
        if (completionBlock) {
            completionBlock(isInDiskCache);
        }
    }];
}
//根据URL判断磁盘缓存中是否存在图片
- (void)diskImageExistsForURL:(nullable NSURL *)url
                   completion:(nullable SDWebImageCheckCacheCompletionBlock)completionBlock {
    NSString *key = [self cacheKeyForURL:url];

    [self.imageCache diskImageExistsWithKey:key completion:^(BOOL isInDiskCache) {
        // the completion block of checkDiskCacheForImageWithKey:completion: is always called on the main queue, no need to further dispatch
        if (completionBlock) {
            completionBlock(isInDiskCache);
        }
    }];
}
//进行图片下载操作
- (id <SDWebImageOperation>)loadImageWithURL:(nullable NSURL *)url
                                     options:(SDWebImageOptions)options
                                    progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
                                   completed:(nullable SDInternalCompletionBlock)completedBlock {
    // Invoking this method without a completedBlock is pointless
    //completedBlock为nil,则触发断言,程序crash
    NSAssert(completedBlock != nil, @"If you mean to prefetch the image, use -[SDWebImagePrefetcher prefetchURLs] instead");

    // Very common mistake is to send the URL using NSString object instead of NSURL. For some strange reason, Xcode won't
    // throw any warning for this type mismatch. Here we failsafe this error by allowing URLs to be passed as NSString.
    if ([url isKindOfClass:NSString.class]) {
        url = [NSURL URLWithString:(NSString *)url];
    }

    // Prevents app crashing on argument type error like sending NSNull instead of NSURL
    if (![url isKindOfClass:NSURL.class]) {
        url = nil;
    }
    //封装下载操作的对象
    __block SDWebImageCombinedOperation *operation = [SDWebImageCombinedOperation new];
    __weak SDWebImageCombinedOperation *weakOperation = operation;

    BOOL isFailedUrl = NO;
    if (url) {
        //为了防止在多线程访问出现问题,创建互斥锁
        @synchronized (self.failedURLs) {
            isFailedUrl = [self.failedURLs containsObject:url];
        }
    }
    //如果url为nil,或者没有设置失败url重新下载的配置且该url已经下载失败过,那么返回失败的回调
    if (url.absoluteString.length == 0 || (!(options & SDWebImageRetryFailed) && isFailedUrl)) {
        [self callCompletionBlockForOperation:operation completion:completedBlock error:[NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorFileDoesNotExist userInfo:nil] url:url];
        return operation;
    }
    //创建互斥锁,添加operation到数组中
    @synchronized (self.runningOperations) {
        [self.runningOperations addObject:operation];
    }
    NSString *key = [self cacheKeyForURL:url];
    //使用缓存对象,根据key去寻找查找
    operation.cacheOperation = [self.imageCache queryCacheOperationForKey:key done:^(UIImage *cachedImage, NSData *cachedData, SDImageCacheType cacheType) {
        //如果当前操作被取消,则remove且return
        if (operation.isCancelled) {
            [self safelyRemoveOperationFromRunning:operation];
            return;
        }
        //(如果没有图片缓存或者设置了重新刷新缓存)且调用代理允许下载图片
        if ((!cachedImage || options & SDWebImageRefreshCached) && (![self.delegate respondsToSelector:@selector(imageManager:shouldDownloadImageForURL:)] || [self.delegate imageManager:self shouldDownloadImageForURL:url])) {
            //如果有(其实是缓存的)并且调用者设置了重新刷新缓存,那么先把图片结果回调出去,然后继续去下载图片再更新缓存
            if (cachedImage && options & SDWebImageRefreshCached) {
                // If image was found in the cache but SDWebImageRefreshCached is provided, notify about the cached image
                // AND try to re-download it in order to let a chance to NSURLCache to refresh it from server.
                [self callCompletionBlockForOperation:weakOperation completion:completedBlock image:cachedImage data:cachedData error:nil cacheType:cacheType finished:YES url:url];
            }

            // download if no image or requested to refresh anyway, and download allowed by delegate
            //如果缓存没有图片或者请求刷新,并且通过代理下载图片,那么则下载图片
            //下面是根据调用者传进来的option,来匹配设置了哪些,就给downloaderOptions赋值哪些option
            SDWebImageDownloaderOptions downloaderOptions = 0;
            if (options & SDWebImageLowPriority) downloaderOptions |= SDWebImageDownloaderLowPriority;
            if (options & SDWebImageProgressiveDownload) downloaderOptions |= SDWebImageDownloaderProgressiveDownload;
            if (options & SDWebImageRefreshCached) downloaderOptions |= SDWebImageDownloaderUseNSURLCache;
            if (options & SDWebImageContinueInBackground) downloaderOptions |= SDWebImageDownloaderContinueInBackground;
            if (options & SDWebImageHandleCookies) downloaderOptions |= SDWebImageDownloaderHandleCookies;
            if (options & SDWebImageAllowInvalidSSLCertificates) downloaderOptions |= SDWebImageDownloaderAllowInvalidSSLCertificates;
            if (options & SDWebImageHighPriority) downloaderOptions |= SDWebImageDownloaderHighPriority;
            if (options & SDWebImageScaleDownLargeImages) downloaderOptions |= SDWebImageDownloaderScaleDownLargeImages;

            if (cachedImage && options & SDWebImageRefreshCached) {
                // force progressive off if image already cached but forced refreshing
                downloaderOptions &= ~SDWebImageDownloaderProgressiveDownload;
                // ignore image read from NSURLCache if image if cached but force refreshing
                downloaderOptions |= SDWebImageDownloaderIgnoreCachedResponse;
            }
            //在这里真正调用imageDownloader去下载图片了
            SDWebImageDownloadToken *subOperationToken = [self.imageDownloader downloadImageWithURL:url options:downloaderOptions progress:progressBlock completed:^(UIImage *downloadedImage, NSData *downloadedData, NSError *error, BOOL finished) {
                __strong __typeof(weakOperation) strongOperation = weakOperation;
                //操作取消则不做任何处理
                if (!strongOperation || strongOperation.isCancelled) {
                    // Do nothing if the operation was cancelled
                    // See #699 for more details
                    // if we would call the completedBlock, there could be a race condition between this block and another completedBlock for the same object, so if this one is called second, we will overwrite the new data
                } else if (error) {
                    [self callCompletionBlockForOperation:strongOperation completion:completedBlock error:error url:url];

                    if (   error.code != NSURLErrorNotConnectedToInternet
                        && error.code != NSURLErrorCancelled
                        && error.code != NSURLErrorTimedOut
                        && error.code != NSURLErrorInternationalRoamingOff
                        && error.code != NSURLErrorDataNotAllowed
                        && error.code != NSURLErrorCannotFindHost
                        && error.code != NSURLErrorCannotConnectToHost
                        && error.code != NSURLErrorNetworkConnectionLost) {
                        //下载失败则添加图片url到failedURLs集合
                        @synchronized (self.failedURLs) {
                            [self.failedURLs addObject:url];
                        }
                    }
                }
                else {
                    //虽然下载失败,但是如果设置了可以重新下载失败的url则remove该url
                    if ((options & SDWebImageRetryFailed)) {
                        @synchronized (self.failedURLs) {
                            [self.failedURLs removeObject:url];
                        }
                    }
                    //是否需要缓存在磁盘
                    BOOL cacheOnDisk = !(options & SDWebImageCacheMemoryOnly);

                    if (options & SDWebImageRefreshCached && cachedImage && !downloadedImage) {
                        // Image refresh hit the NSURLCache cache, do not call the completion block
                    //图片下载成功并且判断是否需要转换图片
                    } else if (downloadedImage && (!downloadedImage.images || (options & SDWebImageTransformAnimatedImage)) && [self.delegate respondsToSelector:@selector(imageManager:transformDownloadedImage:withURL:)]) {
                        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
                            //根据代理获取转换后的图片
                            UIImage *transformedImage = [self.delegate imageManager:self transformDownloadedImage:downloadedImage withURL:url];
                            //如果转换图片存在且下载图片操作已完成 则在缓存对象中存储图片
                            if (transformedImage && finished) {
                                BOOL imageWasTransformed = ![transformedImage isEqual:downloadedImage];
                                // pass nil if the image was transformed, so we can recalculate the data from the image
                                [self.imageCache storeImage:transformedImage imageData:(imageWasTransformed ? nil : downloadedData) forKey:key toDisk:cacheOnDisk completion:nil];
                            }

                            [self callCompletionBlockForOperation:strongOperation completion:completedBlock image:transformedImage data:downloadedData error:nil cacheType:SDImageCacheTypeNone finished:finished url:url];
                        });
                    } else {
                        //下载完成且有image则缓存图片
                        if (downloadedImage && finished) {
                            [self.imageCache storeImage:downloadedImage imageData:downloadedData forKey:key toDisk:cacheOnDisk completion:nil];
                        }
                        [self callCompletionBlockForOperation:strongOperation completion:completedBlock image:downloadedImage data:downloadedData error:nil cacheType:SDImageCacheTypeNone finished:finished url:url];
                    }
                }
                //如果下载和缓存都完成了则删除操作队列中的operation
                if (finished) {
                    [self safelyRemoveOperationFromRunning:strongOperation];
                }
            }];
            @synchronized(operation) {
                // Need same lock to ensure cancelBlock called because cancel method can be called in different queue
                operation.cancelBlock = ^{
                    [self.imageDownloader cancel:subOperationToken];
                    __strong __typeof(weakOperation) strongOperation = weakOperation;
                    [self safelyRemoveOperationFromRunning:strongOperation];
                };
            }
        } else if (cachedImage) {
            // 有图片且线程没有被取消,则返回有图片的completedBlock
            __strong __typeof(weakOperation) strongOperation = weakOperation;
            [self callCompletionBlockForOperation:strongOperation completion:completedBlock image:cachedImage data:cachedData error:nil cacheType:cacheType finished:YES url:url];
            [self safelyRemoveOperationFromRunning:operation];
        } else {
            // Image not in cache and download disallowed by delegate
            //没有在缓存中并且代理方法也不允许下载则回调失败
            __strong __typeof(weakOperation) strongOperation = weakOperation;
            [self callCompletionBlockForOperation:strongOperation completion:completedBlock image:nil data:nil error:nil cacheType:SDImageCacheTypeNone finished:YES url:url];
            [self safelyRemoveOperationFromRunning:operation];
        }
    }];

    return operation;
}
//将图片存入缓存
- (void)saveImageToCache:(nullable UIImage *)image forURL:(nullable NSURL *)url {
    if (image && url) {
        NSString *key = [self cacheKeyForURL:url];
        [self.imageCache storeImage:image forKey:key toDisk:YES completion:nil];
    }
}
//取消所有的下载操作
- (void)cancelAll {
    @synchronized (self.runningOperations) {
        NSArray<SDWebImageCombinedOperation *> *copiedOperations = [self.runningOperations copy];
        [copiedOperations makeObjectsPerformSelector:@selector(cancel)];
        [self.runningOperations removeObjectsInArray:copiedOperations];
    }
}
//判断当前是否有下载图片
- (BOOL)isRunning {
    BOOL isRunning = NO;
    @synchronized (self.runningOperations) {
        isRunning = (self.runningOperations.count > 0);
    }
    return isRunning;
}
//线程安全的移除下载operation
- (void)safelyRemoveOperationFromRunning:(nullable SDWebImageCombinedOperation*)operation {
    @synchronized (self.runningOperations) {
        if (operation) {
            [self.runningOperations removeObject:operation];
        }
    }
}

- (void)callCompletionBlockForOperation:(nullable SDWebImageCombinedOperation*)operation
                             completion:(nullable SDInternalCompletionBlock)completionBlock
                                  error:(nullable NSError *)error
                                    url:(nullable NSURL *)url {
    [self callCompletionBlockForOperation:operation completion:completionBlock image:nil data:nil error:error cacheType:SDImageCacheTypeNone finished:YES url:url];
}

- (void)callCompletionBlockForOperation:(nullable SDWebImageCombinedOperation*)operation
                             completion:(nullable SDInternalCompletionBlock)completionBlock
                                  image:(nullable UIImage *)image
                                   data:(nullable NSData *)data
                                  error:(nullable NSError *)error
                              cacheType:(SDImageCacheType)cacheType
                               finished:(BOOL)finished
                                    url:(nullable NSURL *)url {
    dispatch_main_async_safe(^{
        if (operation && !operation.isCancelled && completionBlock) {
            completionBlock(image, data, error, cacheType, finished, url);
        }
    });
}

@end


@implementation SDWebImageCombinedOperation
//set方法
- (void)setCancelBlock:(nullable SDWebImageNoParamsBlock)cancelBlock {
    // check if the operation is already cancelled, then we just call the cancelBlock
    if (self.isCancelled) {
        if (cancelBlock) {
            cancelBlock();
        }
        _cancelBlock = nil; // don't forget to nil the cancelBlock, otherwise we will get crashes
    } else {
        _cancelBlock = [cancelBlock copy];
    }
}
//SDWebImageOperation协议方法
- (void)cancel {
    @synchronized(self) {
        self.cancelled = YES;
        if (self.cacheOperation) {
            [self.cacheOperation cancel];
            self.cacheOperation = nil;
        }
        if (self.cancelBlock) {
            self.cancelBlock();
            self.cancelBlock = nil;
        }
    }
}

@end

下面是SDWebImageManager的主要节点流程图:

价值VS浪费

咱俩的卖力有稍许创设了价值,有稍许被荒废了?这多少个题材是精益生产的基本所在。精益的沉思方法把价值定义为向消费者提供便宜,除此之外的任何事物都是浪费。但在新创集团中,谁是主顾、顾客觉得怎么着东西有价值都是未知数。大家需要一个新的价值定义。大家原先可以先做个试验,提供消费者使用新产品的时机,然后评估他们的一言一行。了然顾客所需之外的任何努力都可以毫无。经证实的咀嚼必须要以从真正顾客这里采访到的实证数据为根基。

3>SDImageCache

SDImageCache是SDWebImage的缓存中央。分三局部构成memory内存缓存,disk硬盘缓存和无缓存组成。
SDImageCache.h文件声明:

typedef NS_ENUM(NSInteger, SDImageCacheType) {
    无缓存类型
    SDImageCacheTypeNone,
    磁盘缓存
    SDImageCacheTypeDisk,
    内存缓存
    SDImageCacheTypeMemory
};
@interface SDImageCache : NSObject
#pragma mark - Properties
//缓存配置对象,包含所有配置项
@property (nonatomic, nonnull, readonly) SDImageCacheConfig *config;
//设置内存容量大小
@property (assign, nonatomic) NSUInteger maxMemoryCost;
//设置内存缓存最大值limit
@property (assign, nonatomic) NSUInteger maxMemoryCountLimit;

#pragma mark - Singleton and initialization
//返回SDImageCache单例
+ (nonnull instancetype)sharedImageCache;
//根据特定的namespace返回一个新的缓存对象
- (nonnull instancetype)initWithNamespace:(nonnull NSString *)ns;
//根据特定的namespace和directory返回一个新的缓存对象
- (nonnull instancetype)initWithNamespace:(nonnull NSString *)ns
                       diskCacheDirectory:(nonnull NSString *)directory NS_DESIGNATED_INITIALIZER;

#pragma mark - Cache paths
//生成缓存路径
- (nullable NSString *)makeDiskCachePath:(nonnull NSString*)fullNamespace;
//添加一个只读的缓存路径
- (void)addReadOnlyCachePath:(nonnull NSString *)path;

#pragma mark - Store Ops
//根据key去异步缓存image,缓存在内存和磁盘
- (void)storeImage:(nullable UIImage *)image
            forKey:(nullable NSString *)key
        completion:(nullable SDWebImageNoParamsBlock)completionBlock;
//根据key去异步缓存image,toDisk未NO不存储在磁盘
- (void)storeImage:(nullable UIImage *)image
            forKey:(nullable NSString *)key
            toDisk:(BOOL)toDisk
        completion:(nullable SDWebImageNoParamsBlock)completionBlock;
//根据key去异步缓存image,toDisk未NO不存储在磁盘 多加一个imageData图片data
- (void)storeImage:(nullable UIImage *)image
         imageData:(nullable NSData *)imageData
            forKey:(nullable NSString *)key
            toDisk:(BOOL)toDisk
        completion:(nullable SDWebImageNoParamsBlock)completionBlock;
//根据key去异步缓存image,缓存在磁盘
- (void)storeImageDataToDisk:(nullable NSData *)imageData forKey:(nullable NSString *)key;

#pragma mark - Query and Retrieve Ops
//异步检查图片是否缓存在磁盘中
- (void)diskImageExistsWithKey:(nullable NSString *)key completion:(nullable SDWebImageCheckCacheCompletionBlock)completionBlock;
//在缓存中查询对应key的数据
- (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key done:(nullable SDCacheQueryCompletedBlock)doneBlock;
//在内存缓存中查询对应key的图片
- (nullable UIImage *)imageFromMemoryCacheForKey:(nullable NSString *)key;
//在磁盘缓存中查询对应key的图片
- (nullable UIImage *)imageFromDiskCacheForKey:(nullable NSString *)key;
//在缓存中查询对应key的图片
- (nullable UIImage *)imageFromCacheForKey:(nullable NSString *)key;

#pragma mark - Remove Ops
//删除缓存中指定key的图片
- (void)removeImageForKey:(nullable NSString *)key withCompletion:(nullable SDWebImageNoParamsBlock)completion;
//删除缓存中指定key的图片 磁盘是可选项
- (void)removeImageForKey:(nullable NSString *)key fromDisk:(BOOL)fromDisk withCompletion:(nullable SDWebImageNoParamsBlock)completion;

#pragma mark - Cache clean Ops
//清空所有的内存缓存
- (void)clearMemory;
//异步清除所有的磁盘缓存
- (void)clearDiskOnCompletion:(nullable SDWebImageNoParamsBlock)completion;
//异步清除所有的失效的缓存图片-因为可以设定缓存时间,超过则失效
- (void)deleteOldFilesWithCompletionBlock:(nullable SDWebImageNoParamsBlock)completionBlock;

#pragma mark - Cache Info
//得到磁盘缓存的大小size
- (NSUInteger)getSize;
//得到在磁盘缓存中图片的数量
- (NSUInteger)getDiskCount;
//异步计算磁盘缓存的大小
- (void)calculateSizeWithCompletionBlock:(nullable SDWebImageCalculateSizeBlock)completionBlock;

#pragma mark - Cache Paths
//获取给定key的缓存路径 需要一个根缓存路径
- (nullable NSString *)cachePathForKey:(nullable NSString *)key inPath:(nonnull NSString *)path;
//获取给定key的默认缓存路径
- (nullable NSString *)defaultCachePathForKey:(nullable NSString *)key;

@end

SDImageCache.m注解:

// See https://github.com/rs/SDWebImage/pull/1141 for discussion
//自定义内存缓存类
@interface AutoPurgeCache : NSCache
@end

@implementation AutoPurgeCache

- (nonnull instancetype)init {
    self = [super init];
    if (self) {
#if SD_UIKIT
        //添加通知,当受到内存警告则移除所有的缓存对象
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(removeAllObjects) name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
#endif
    }
    return self;
}

- (void)dealloc {
#if SD_UIKIT
    [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
#endif
}

@end

//内联函数获得该图片的缓存大小 注意乘以屏幕的比例
FOUNDATION_STATIC_INLINE NSUInteger SDCacheCostForImage(UIImage *image) {
#if SD_MAC
    return image.size.height * image.size.width;
#elif SD_UIKIT || SD_WATCH
    return image.size.height * image.size.width * image.scale * image.scale;
#endif
}

@interface SDImageCache ()

#pragma mark - Properties
//内存缓存对象
@property (strong, nonatomic, nonnull) NSCache *memCache;
//磁盘缓存路径
@property (strong, nonatomic, nonnull) NSString *diskCachePath;
//保存缓存路径的数组
@property (strong, nonatomic, nullable) NSMutableArray<NSString *> *customPaths;
//执行处理输入输出的队列
@property (SDDispatchQueueSetterSementics, nonatomic, nullable) dispatch_queue_t ioQueue;

@end


@implementation SDImageCache {
    NSFileManager *_fileManager;
}

#pragma mark - Singleton, init, dealloc
//生成单例SDImageCache
+ (nonnull instancetype)sharedImageCache {
    static dispatch_once_t once;
    static id instance;
    dispatch_once(&once, ^{
        instance = [self new];
    });
    return instance;
}

- (instancetype)init {
    return [self initWithNamespace:@"default"];
}

- (nonnull instancetype)initWithNamespace:(nonnull NSString *)ns {
    NSString *path = [self makeDiskCachePath:ns];
    return [self initWithNamespace:ns diskCacheDirectory:path];
}
//初始化磁盘缓存路径和内存缓存name
- (nonnull instancetype)initWithNamespace:(nonnull NSString *)ns
                       diskCacheDirectory:(nonnull NSString *)directory {
    if ((self = [super init])) {
        NSString *fullNamespace = [@"com.hackemist.SDWebImageCache." stringByAppendingString:ns];

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

        _config = [[SDImageCacheConfig alloc] init];

        // Init the memory cache
        _memCache = [[AutoPurgeCache alloc] init];
        _memCache.name = fullNamespace;

        // Init the disk cache
        if (directory != nil) {
            _diskCachePath = [directory stringByAppendingPathComponent:fullNamespace];
        } else {
            NSString *path = [self makeDiskCachePath:ns];
            _diskCachePath = path;
        }

        dispatch_sync(_ioQueue, ^{
            _fileManager = [NSFileManager new];
        });

#if SD_UIKIT
        // Subscribe to app events
        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(clearMemory)
                                                     name:UIApplicationDidReceiveMemoryWarningNotification
                                                   object:nil];

        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(deleteOldFiles)
                                                     name:UIApplicationWillTerminateNotification
                                                   object:nil];

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

    return self;
}

- (void)dealloc {
    [[NSNotificationCenter defaultCenter] removeObserver:self];
    SDDispatchQueueRelease(_ioQueue);
}

- (void)checkIfQueueIsIOQueue {
    //dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL)获取当前队列的名字
    //dispatch_queue_get_label获取队列的名字,如果队列没有名字,返回NULL
    const char *currentQueueLabel = dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL);
    const char *ioQueueLabel = dispatch_queue_get_label(self.ioQueue);
    if (strcmp(currentQueueLabel, ioQueueLabel) != 0) {
        NSLog(@"This method should be called from the ioQueue");
    }
}

#pragma mark - Cache paths
////添加一个只读的缓存路径
- (void)addReadOnlyCachePath:(nonnull NSString *)path {
    if (!self.customPaths) {
        self.customPaths = [NSMutableArray new];
    }

    if (![self.customPaths containsObject:path]) {
        [self.customPaths addObject:path];
    }
}
//获取给定key的缓存路径 需要一个根缓存路径
- (nullable NSString *)cachePathForKey:(nullable NSString *)key inPath:(nonnull NSString *)path {
    NSString *filename = [self cachedFileNameForKey:key];
    return [path stringByAppendingPathComponent:filename];
}
//获取给定key的默认缓存路径
- (nullable NSString *)defaultCachePathForKey:(nullable NSString *)key {
    return [self cachePathForKey:key inPath:self.diskCachePath];
}
//根据key值生成文件名:采用MD5
- (nullable NSString *)cachedFileNameForKey:(nullable NSString *)key {
    const char *str = key.UTF8String;
    if (str == NULL) {
        str = "";
    }
    unsigned char r[CC_MD5_DIGEST_LENGTH];
    CC_MD5(str, (CC_LONG)strlen(str), r);
    NSURL *keyURL = [NSURL URLWithString:key];
    NSString *ext = keyURL ? keyURL.pathExtension : key.pathExtension;
    NSString *filename = [NSString stringWithFormat:@"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%@",
                          r[0], r[1], r[2], r[3], r[4], r[5], r[6], r[7], r[8], r[9], r[10],
                          r[11], r[12], r[13], r[14], r[15], ext.length == 0 ? @"" : [NSString stringWithFormat:@".%@", ext]];
    return filename;
}
//生成磁盘路径
- (nullable NSString *)makeDiskCachePath:(nonnull NSString*)fullNamespace {
    NSArray<NSString *> *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
    return [paths[0] stringByAppendingPathComponent:fullNamespace];
}

#pragma mark - Store Ops

- (void)storeImage:(nullable UIImage *)image
            forKey:(nullable NSString *)key
        completion:(nullable SDWebImageNoParamsBlock)completionBlock {
    [self storeImage:image imageData:nil forKey:key toDisk:YES completion:completionBlock];
}

- (void)storeImage:(nullable UIImage *)image
            forKey:(nullable NSString *)key
            toDisk:(BOOL)toDisk
        completion:(nullable SDWebImageNoParamsBlock)completionBlock {
    [self storeImage:image imageData:nil forKey:key toDisk:toDisk completion:completionBlock];
}

- (void)storeImage:(nullable UIImage *)image
         imageData:(nullable NSData *)imageData
            forKey:(nullable NSString *)key
            toDisk:(BOOL)toDisk
        completion:(nullable SDWebImageNoParamsBlock)completionBlock {
    if (!image || !key) {
        if (completionBlock) {
            completionBlock();
        }
        return;
    }
    // if memory cache is enabled
    //缓存到内存
    if (self.config.shouldCacheImagesInMemory) {
        NSUInteger cost = SDCacheCostForImage(image);
        [self.memCache setObject:image forKey:key cost:cost];
    }
    //缓存到磁盘,采用异步操作
    if (toDisk) {
        dispatch_async(self.ioQueue, ^{
            @autoreleasepool {
                NSData *data = imageData;
                if (!data && image) {
                    // If we do not have any data to detect image format, use PNG format
                    //如果没有data则采用png的格式进行format
                    data = [[SDWebImageCodersManager sharedInstance] encodedDataWithImage:image format:SDImageFormatPNG];
                }
                [self storeImageDataToDisk:data forKey:key];
            }

            if (completionBlock) {
                dispatch_async(dispatch_get_main_queue(), ^{
                    completionBlock();
                });
            }
        });
    } else {
        if (completionBlock) {
            completionBlock();
        }
    }
}
//利用key进行缓存data
- (void)storeImageDataToDisk:(nullable NSData *)imageData forKey:(nullable NSString *)key {
    if (!imageData || !key) {
        return;
    }

    [self checkIfQueueIsIOQueue];
    //如果文件中不存在磁盘缓存路径 则创建
    if (![_fileManager fileExistsAtPath:_diskCachePath]) {
        [_fileManager createDirectoryAtPath:_diskCachePath withIntermediateDirectories:YES attributes:nil error:NULL];
    }

    // get cache Path for image key  得到该key的缓存路径
    NSString *cachePathForKey = [self defaultCachePathForKey:key];
    // transform to NSUrl  将缓存路径转化为url
    NSURL *fileURL = [NSURL fileURLWithPath:cachePathForKey];
    //将imageData存储起来
    [_fileManager createFileAtPath:cachePathForKey contents:imageData attributes:nil];

    // disable iCloud backup  如果调用者关闭icloud 关闭iCloud备份
    if (self.config.shouldDisableiCloud) {
        [fileURL setResourceValue:@YES forKey:NSURLIsExcludedFromBackupKey error:nil];
    }
}

#pragma mark - Query and Retrieve Ops
//异步检查图片是否缓存在磁盘中
- (void)diskImageExistsWithKey:(nullable NSString *)key completion:(nullable SDWebImageCheckCacheCompletionBlock)completionBlock {
    dispatch_async(_ioQueue, ^{
        BOOL exists = [_fileManager fileExistsAtPath:[self defaultCachePathForKey:key]];

        // fallback because of https://github.com/rs/SDWebImage/pull/976 that added the extension to the disk file name
        // checking the key with and without the extension
        if (!exists) {
            exists = [_fileManager fileExistsAtPath:[self defaultCachePathForKey:key].stringByDeletingPathExtension];
        }

        if (completionBlock) {
            dispatch_async(dispatch_get_main_queue(), ^{
                completionBlock(exists);
            });
        }
    });
}
//在内存缓存中查询对应key的图片
- (nullable UIImage *)imageFromMemoryCacheForKey:(nullable NSString *)key {
    return [self.memCache objectForKey:key];
}
//在磁盘缓存中查询对应key的图片 并且如果允许内存缓存则在内存中缓存
- (nullable UIImage *)imageFromDiskCacheForKey:(nullable NSString *)key {
    UIImage *diskImage = [self diskImageForKey:key];
    if (diskImage && self.config.shouldCacheImagesInMemory) {
        NSUInteger cost = SDCacheCostForImage(diskImage);
        [self.memCache setObject:diskImage forKey:key cost:cost];
    }

    return diskImage;
}
//在缓存中查询对应key的图片
- (nullable UIImage *)imageFromCacheForKey:(nullable NSString *)key {
    // First check the in-memory cache...
    UIImage *image = [self imageFromMemoryCacheForKey:key];
    if (image) {
        return image;
    }

    // Second check the disk cache...
    image = [self imageFromDiskCacheForKey:key];
    return image;
}
//根据key在磁盘缓存中搜索图片data
- (nullable NSData *)diskImageDataBySearchingAllPathsForKey:(nullable NSString *)key {
    NSString *defaultPath = [self defaultCachePathForKey:key];
    NSData *data = [NSData dataWithContentsOfFile:defaultPath options:self.config.diskCacheReadingOptions error:nil];
    if (data) {
        return data;
    }

    // fallback because of https://github.com/rs/SDWebImage/pull/976 that added the extension to the disk file name
    // checking the key with and without the extension
    data = [NSData dataWithContentsOfFile:defaultPath.stringByDeletingPathExtension options:self.config.diskCacheReadingOptions error:nil];
    if (data) {
        return data;
    }

    NSArray<NSString *> *customPaths = [self.customPaths copy];
    for (NSString *path in customPaths) {
        NSString *filePath = [self cachePathForKey:key inPath:path];
        NSData *imageData = [NSData dataWithContentsOfFile:filePath options:self.config.diskCacheReadingOptions error:nil];
        if (imageData) {
            return imageData;
        }

        // fallback because of https://github.com/rs/SDWebImage/pull/976 that added the extension to the disk file name
        // checking the key with and without the extension
        imageData = [NSData dataWithContentsOfFile:filePath.stringByDeletingPathExtension options:self.config.diskCacheReadingOptions error:nil];
        if (imageData) {
            return imageData;
        }
    }

    return nil;
}
//根据key在磁盘缓存中搜索图片dimage
- (nullable UIImage *)diskImageForKey:(nullable NSString *)key {
    NSData *data = [self diskImageDataBySearchingAllPathsForKey:key];
    if (data) {
        UIImage *image = [[SDWebImageCodersManager sharedInstance] decodedImageWithData:data];
        //根据图片的scale或图片中的图片组 重新计算返回一张新图片
        image = [self scaledImageForKey:key image:image];
        if (self.config.shouldDecompressImages) {
            image = [[SDWebImageCodersManager sharedInstance] decompressedImageWithImage:image data:&data options:@{SDWebImageCoderScaleDownLargeImagesKey: @(NO)}];
        }
        return image;
    } else {
        return nil;
    }
}

- (nullable UIImage *)scaledImageForKey:(nullable NSString *)key image:(nullable UIImage *)image {
    return SDScaledImageForKey(key, image);
}
//在缓存中查询对应key的图片信息 包含image,diskData以及缓存的类型
- (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key done:(nullable SDCacheQueryCompletedBlock)doneBlock {
    if (!key) {
        if (doneBlock) {
            doneBlock(nil, nil, SDImageCacheTypeNone);
        }
        return nil;
    }

    // First check the in-memory cache...如果内存缓存包含图片数据则回调结束
    UIImage *image = [self imageFromMemoryCacheForKey:key];
    if (image) {
        NSData *diskData = nil;
        if (image.images) {//imageData都存储在磁盘
            diskData = [self diskImageDataBySearchingAllPathsForKey:key];
        }
        if (doneBlock) {
            doneBlock(image, diskData, SDImageCacheTypeMemory);
        }
        return nil;
    }

    NSOperation *operation = [NSOperation new];
    dispatch_async(self.ioQueue, ^{
        if (operation.isCancelled) {
            // do not call the completion if cancelled
            return;
        }

        @autoreleasepool {
            //到这里说明已经来到磁盘缓存区获取 然后回调结束
            NSData *diskData = [self diskImageDataBySearchingAllPathsForKey:key];
            UIImage *diskImage = [self diskImageForKey:key];
            if (diskImage && self.config.shouldCacheImagesInMemory) {
                NSUInteger cost = SDCacheCostForImage(diskImage);
                [self.memCache setObject:diskImage forKey:key cost:cost];
            }

            if (doneBlock) {
                dispatch_async(dispatch_get_main_queue(), ^{
                    doneBlock(diskImage, diskData, SDImageCacheTypeDisk);
                });
            }
        }
    });

    return operation;
}

#pragma mark - Remove Ops
//删除缓存中指定key的图片
- (void)removeImageForKey:(nullable NSString *)key withCompletion:(nullable SDWebImageNoParamsBlock)completion {
    [self removeImageForKey:key fromDisk:YES withCompletion:completion];
}
//删除缓存中指定key的图片 磁盘是可选项
- (void)removeImageForKey:(nullable NSString *)key fromDisk:(BOOL)fromDisk withCompletion:(nullable SDWebImageNoParamsBlock)completion {
    if (key == nil) {
        return;
    }
    //如果内存也缓存了,则删除内存缓存
    if (self.config.shouldCacheImagesInMemory) {
        [self.memCache removeObjectForKey:key];
    }

    if (fromDisk) {
        dispatch_async(self.ioQueue, ^{
            [_fileManager removeItemAtPath:[self defaultCachePathForKey:key] error:nil];

            if (completion) {
                dispatch_async(dispatch_get_main_queue(), ^{
                    completion();
                });
            }
        });
    } else if (completion){
        completion();
    }

}

# pragma mark - Mem Cache settings

- (void)setMaxMemoryCost:(NSUInteger)maxMemoryCost {
    self.memCache.totalCostLimit = maxMemoryCost;
}

- (NSUInteger)maxMemoryCost {
    return self.memCache.totalCostLimit;
}

- (NSUInteger)maxMemoryCountLimit {
    return self.memCache.countLimit;
}

- (void)setMaxMemoryCountLimit:(NSUInteger)maxCountLimit {
    self.memCache.countLimit = maxCountLimit;
}

#pragma mark - Cache clean Ops
//清空所有的内存缓存
- (void)clearMemory {
    [self.memCache removeAllObjects];
}
//异步清除所有的磁盘缓存
- (void)clearDiskOnCompletion:(nullable SDWebImageNoParamsBlock)completion {
    dispatch_async(self.ioQueue, ^{
        [_fileManager removeItemAtPath:self.diskCachePath error:nil];
        [_fileManager createDirectoryAtPath:self.diskCachePath
                withIntermediateDirectories:YES
                                 attributes:nil
                                      error:NULL];

        if (completion) {
            dispatch_async(dispatch_get_main_queue(), ^{
                completion();
            });
        }
    });
}

- (void)deleteOldFiles {
    [self deleteOldFilesWithCompletionBlock:nil];
}
//异步清除所有失效的缓存图片-因为可以设定缓存时间,超过则失效
- (void)deleteOldFilesWithCompletionBlock:(nullable SDWebImageNoParamsBlock)completionBlock {
    dispatch_async(self.ioQueue, ^{
        NSURL *diskCacheURL = [NSURL fileURLWithPath:self.diskCachePath isDirectory:YES];
        //resourceKeys数组包含遍历文件的属性,NSURLIsDirectoryKey判断遍历到的URL所指对象是否是目录,
        //NSURLContentModificationDateKey判断遍历返回的URL所指项目的最后修改时间,NSURLTotalFileAllocatedSizeKey判断URL目录中所分配的空间大小
        NSArray<NSString *> *resourceKeys = @[NSURLIsDirectoryKey, NSURLContentModificationDateKey, NSURLTotalFileAllocatedSizeKey];

        // This enumerator prefetches useful properties for our cache files.
        //利用目录枚举器遍历指定磁盘缓存路径目录下的文件,从而我们活的文件大小,缓存时间等信息
        NSDirectoryEnumerator *fileEnumerator = [_fileManager enumeratorAtURL:diskCacheURL
                                                   includingPropertiesForKeys:resourceKeys
                                                                      options:NSDirectoryEnumerationSkipsHiddenFiles
                                                                 errorHandler:NULL];
        //计算过期时间,默认1周以前的缓存文件是过期失效
        NSDate *expirationDate = [NSDate dateWithTimeIntervalSinceNow:-self.config.maxCacheAge];
        //保存遍历的文件url
        NSMutableDictionary<NSURL *, NSDictionary<NSString *, id> *> *cacheFiles = [NSMutableDictionary dictionary];
        //保存当前缓存的大小
        NSUInteger currentCacheSize = 0;

        // Enumerate all of the files in the cache directory.  This loop has two purposes:
        //
        //  1. Removing files that are older than the expiration date.
        //  2. Storing file attributes for the size-based cleanup pass.
        //保存删除的文件url
        NSMutableArray<NSURL *> *urlsToDelete = [[NSMutableArray alloc] init];
        //遍历目录枚举器,目的1删除过期文件 2纪录文件大小,以便于之后删除使用
        for (NSURL *fileURL in fileEnumerator) {
            NSError *error;
            //获取指定url对应文件的指定三种属性的key和value
            NSDictionary<NSString *, id> *resourceValues = [fileURL resourceValuesForKeys:resourceKeys error:&error];

            // Skip directories and errors.
            //如果是文件夹则返回
            if (error || !resourceValues || [resourceValues[NSURLIsDirectoryKey] boolValue]) {
                continue;
            }

            // Remove files that are older than the expiration date;
            //获取指定url文件对应的修改日期
            NSDate *modificationDate = resourceValues[NSURLContentModificationDateKey];
            //如果修改日期大于指定日期,则加入要移除的数组里
            if ([[modificationDate laterDate:expirationDate] isEqualToDate:expirationDate]) {
                [urlsToDelete addObject:fileURL];
                continue;
            }

            // Store a reference to this file and account for its total size.
            //获取指定的url对应的文件的大小,并且把url与对应大小存入一个字典中
            NSNumber *totalAllocatedSize = resourceValues[NSURLTotalFileAllocatedSizeKey];
            currentCacheSize += totalAllocatedSize.unsignedIntegerValue;
            cacheFiles[fileURL] = resourceValues;
        }
        //删除所有最后修改日期大于指定日期的所有文件
        for (NSURL *fileURL in urlsToDelete) {
            [_fileManager removeItemAtURL:fileURL error:nil];
        }

        // If our remaining disk cache exceeds a configured maximum size, perform a second
        // size-based cleanup pass.  We delete the oldest files first.
        //如果当前缓存的大小超过了默认大小,则按照日期删除,直到缓存大小<默认大小的一半
        if (self.config.maxCacheSize > 0 && currentCacheSize > self.config.maxCacheSize) {
            // Target half of our maximum cache size for this cleanup pass.
            const NSUInteger desiredCacheSize = self.config.maxCacheSize / 2;

            // Sort the remaining cache files by their last modification time (oldest first).
            //根据文件创建的时间排序
            NSArray<NSURL *> *sortedFiles = [cacheFiles keysSortedByValueWithOptions:NSSortConcurrent
                                                                     usingComparator:^NSComparisonResult(id obj1, id obj2) {
                                                                         return [obj1[NSURLContentModificationDateKey] compare:obj2[NSURLContentModificationDateKey]];
                                                                     }];

            // Delete files until we fall below our desired cache size.
            //迭代删除缓存,直到缓存大小是默认缓存大小的一半
            for (NSURL *fileURL in sortedFiles) {
                if ([_fileManager removeItemAtURL:fileURL error:nil]) {
                    NSDictionary<NSString *, id> *resourceValues = cacheFiles[fileURL];
                    NSNumber *totalAllocatedSize = resourceValues[NSURLTotalFileAllocatedSizeKey];
                    currentCacheSize -= totalAllocatedSize.unsignedIntegerValue;

                    if (currentCacheSize < desiredCacheSize) {
                        break;
                    }
                }
            }
        }
        //在主线程中回调
        if (completionBlock) {
            dispatch_async(dispatch_get_main_queue(), ^{
                completionBlock();
            });
        }
    });
}

#if SD_UIKIT
//应用进入后台的时候,调用这个方法 然后清除过期图片
- (void)backgroundDeleteOldFiles {
    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 deleteOldFilesWithCompletionBlock:^{
        [application endBackgroundTask:bgTask];
        bgTask = UIBackgroundTaskInvalid;
    }];
}
#endif

#pragma mark - Cache Info
//得到磁盘缓存的大小size
- (NSUInteger)getSize {
    __block NSUInteger size = 0;
    dispatch_sync(self.ioQueue, ^{
        NSDirectoryEnumerator *fileEnumerator = [_fileManager enumeratorAtPath:self.diskCachePath];
        for (NSString *fileName in fileEnumerator) {
            NSString *filePath = [self.diskCachePath stringByAppendingPathComponent:fileName];
            NSDictionary<NSString *, id> *attrs = [[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:nil];
            size += [attrs fileSize];
        }
    });
    return size;
}
//得到在磁盘缓存中图片的数量
- (NSUInteger)getDiskCount {
    __block NSUInteger count = 0;
    dispatch_sync(self.ioQueue, ^{
        NSDirectoryEnumerator *fileEnumerator = [_fileManager enumeratorAtPath:self.diskCachePath];
        count = fileEnumerator.allObjects.count;
    });
    return count;
}
//异步计算磁盘缓存的大小
- (void)calculateSizeWithCompletionBlock:(nullable SDWebImageCalculateSizeBlock)completionBlock {
    NSURL *diskCacheURL = [NSURL fileURLWithPath:self.diskCachePath isDirectory:YES];

    dispatch_async(self.ioQueue, ^{
        NSUInteger fileCount = 0;
        NSUInteger totalSize = 0;

        NSDirectoryEnumerator *fileEnumerator = [_fileManager enumeratorAtURL:diskCacheURL
                                                   includingPropertiesForKeys:@[NSFileSize]
                                                                      options:NSDirectoryEnumerationSkipsHiddenFiles
                                                                 errorHandler:NULL];

        for (NSURL *fileURL in fileEnumerator) {
            NSNumber *fileSize;
            [fileURL getResourceValue:&fileSize forKey:NSURLFileSizeKey error:NULL];
            totalSize += fileSize.unsignedIntegerValue;
            fileCount += 1;
        }

        if (completionBlock) {
            dispatch_async(dispatch_get_main_queue(), ^{
                completionBlock(fileCount, totalSize);
            });
        }
    });
}

@end

下图是在SDWebImageManager中调用SDCacheImage查找缓存的严重性流程图:

此篇著作讲解的是UIImageView+WebCache/UIView+WebCache,SDWebImageManager和SDImageCache两个关键类,有成千上万很值得我们去读书的点,例如:
1.善用接口分离原则-设计更好的对外调用API。
2.适作为很是处理体制-这多少个特别处理可以防止消耗不必要的资源仍然特别暴发。例如SDWebImageManager中

 if (!url) { return @"";}
 if ([url isKindOfClass:NSString.class]) {
     url = [NSURL URLWithString:(NSString *)url];
 }

3.日增互斥锁-起到线程的爱抚功能。

@synchronized (self.failedURLs) {
     isFailedUrl = [self.failedURLs containsObject:url];
 }

4.多运用inline函数-提升程序的实行成效。

FOUNDATION_STATIC_INLINE NSUInteger SDCacheCostForImage(UIImage *image) {
#if SD_MAC
    return image.size.height * image.size.width;
#elif SD_UIKIT || SD_WATCH
    return image.size.height * image.size.width * image.scale * image.scale;
#endif
}

5.全优运用封装思想和支行概念,写出更为平台化的组件-例如我们常见行使SD大多都是使用UIImageView+WebCache的API,
其实SDWebImageManager是全然可以抽出来单独行使,不会因为跳过了UIImageView+WebCache没了借助而望洋兴叹利用。假设项目是急需两人跨多机构合作,如若客人或另外机构索要调用你写的一个很牛逼的效劳,这这多少个平台化就很要紧了。
SDWebImage还有许多值得我们借鉴和读书的地点,需要我们细细研读。下一篇会讲解SDWebImageDownloader和SDWebImageDownloaderOperation。

从何证实

尚未理想的假使、战略、白板上的图谋分析花招,只有对消费者实在需求的追逐,以及调整产品和战略性去迎合那多少个要求的各个困顿工作。我们的工作就是要让商家愿景和买主接受度匹配。重点并不在于我们付出了有点东西,而介于我们的大力换到多少经求证的体会。每拿到一些体会,就象征又有新的实验要做,因而渐渐把衡量目标一步步促进既定目标。

零的愚勇

在零收入、零顾客数、零拓展的图景下,更便于筹集到成本或任何资源。因为零让人有遐想空间,而的收入则令人质疑,不亮堂高收入能否实现。这种意况激发了一个非理性观点:推迟数据收集,直到确认能拿到成功。这种延误导致的消极效用是大气做事的浪费,首要报告音讯的压缩,公司支付出无人想要的出品风险骤增。

IMVU之外的启迪

精益创业不是各样技术的合成,而是新产品开发的法门原理。需要支付这个产品呢?围绕这一多级的成品和劳动,大家能建立一项可不止的事务呢?新创公司要做的每件事,包括每种产品、每项意义、每趟营销活动都被视为五遍尝试,用来收获经求证的体会。

第四章 实验

从点金术到正确

新创集团的实验由其愿景为指引,每个实验的对象都是为着要白手起家一项围绕愿景的可不止工作。第一步要做的事把大愿景分解成一个个独自的片段。我把创业者最根本的多少个比方称为价值倘使和提升假诺。价值倘诺衡量的是当消费者使用某种产品或劳务时,它是不是真的实现了其价值。增长假设是用来测试新买主怎么发现一种产品或劳务的。

试验也是产品

尝试可以解决实际的题材,对应当付出什么产品提供详实的口径表明。开发在此以前提议以下4个问题:

1.买主肯定你正在解决的问题就是他俩面对的题材吗?

2.一旦有缓解问题的章程,顾客会为之买单呢?

3.他们会向我们购买呢?

4.我们可以开发出解决问题的方法呢?

政党部门中的精益创业

毫无把信心寄托在考察完备的计划上,要领悟,计划这种管理工具只在有着长期稳定的营业记录的情事下才有效。

第二篇 驾驭

顾客和产品竞相时提供了申报和数量,那一个反映既是定性的(如喜欢咋样/不喜欢咋样),也是定量(比如有微微人利用了出品并以为可行)开发—测量—认知的反馈循环是新创公司情势的要旨内容。很多创业者重视把精力放在各个务实上,比如最好的出品概念、最佳设计的先前时期产品等,这一个移动我并不是最关键的,我们要做的是集中精力把举报循环流程的总时间缩减到最短。

第五章 飞跃

那一个失利的互联网商家和中路商差不多,实际上就是花钱购买消费者关注度,然后再把那么些关怀卖给其旁人(广告商)

战略基于假使

各类商业计划都是从一文山会海即便开端的。在默认这个借使的根基上,指出一项战略,并演讲怎么样贯彻集团愿景。但只要未经证实,而且在实际中频繁是错的。因而,新创集团初期努力的目的,应该是急忙评释假若。

幸亏因为商家的打响寄托在假诺上,所以这一个假如被喻为信念飞跃。就算对了,无数机遇尽在面前;假设错了,企业将危险。类比:人们会不会在公共场地使用动圈耳机听音乐?索尼的身上听就是一连串比的点子。反证:人们不愿为听音乐付钱,音乐服务供应商是一个反证。iPod业务中,信念飞跃之一就是假如人们会花钱买进音乐。

现地现物

尊敬把战略决策建立在对消费者平昔的知道上。除非亲自考察,你不能够确定自己是不是真的了解任何生意问题中的任何部分,想当然和依赖别人的报告都是不可以被接受的。举行持续革新的店堂了解他们的客户是何人、在哪儿,他们会用现地现物的措施考察顾客想要什么。

走出办公大楼

B2B情势中,记得每个业务单位是由个人组成的这点,会对你大有裨益。所有成功的行销情势,都要倚重把齐足并驱的个人从其构成的完好社团中表达出来。与最初顾客接触的目的并非要寻找适合答案,而是要大致确认大家领会潜在顾客以及她们的题目。有了这么些了然咱们可以创制一个消费者典型,它是一个显然的公文,意在将对象顾客具体化。这些优良是产品开发的显要指南,确保每个产品开发团队平常工作先行顺序的核定与信用社愿意抓住的消费者相符。顾客典型是一种假若,而非事实。大家必须透过经证实的认知,表明大家可以用可不断的情势服务此类顾客,否则所谓顾客形象就是暂时的。

剖析瘫痪症

进而感觉走的创业派总是等不及要最先,不想花时间分析他们的战略。由于消费者并不知道自己实在要如何,结果令这一个创业者容易自以为走在科学的征程上。分析瘫痪症创业派无终止的调整计划。他们计划中的问题并不是因为尚未基于成熟的战略性原则,而是立足的事实点就错了。

第六章 测试

最小化可行产品(MVP)用最快的办法,以最少生气完成支付—测量—认知的反馈循环。MVP并非用于应对产品设计或技术上边的题材,而是以申明基本的经贸假使为目的。

为啥第一个产品不完善

新产品在推进公众事先,会先销售给早期使用者。他们承受甚至更愿意接受一个只完成了八成的创作,你不需要一个周密的缓解方案去俘获他们的兴趣。第一代摩托罗拉紧缺一些基本效能,但早起技术迷们仍旧趋之若鹜。Google最初的查找引擎能应对一些专程问题,它离把大地消息公司起来的日子尚有好几年,可这并不影响早期使用者对其赞不绝口。

早期使用者会用自己的设想来补充产品的不足部分。他们注意的是变成第一个应用新产品的人。在企业产品市场里,愿意铤而走险使用新产品,则是为着争取竞争优势。早起使用者对太过精致的事物反而心存芥蒂:假诺那种产品怎么着人都能用,那么作为早期用户又有如何利益?由此,任何超出早期使用者需要的附加效能或修饰,都是资源和时间上的荒废。

事例:以赠送一个月免费试用的不二法门来销售,顾客必须注册试用。这项业务格局的一个眼看假诺就是,当消费者对这项劳动有肯定了解后就会登记试用。要考虑的关键问题是,顾客是否真的会为了局部承诺的效益(价值如若)而注册试用。信念飞跃问题:顾客看到免费使用后注册的百分比。到底要开销多少效能来吸引早期使用者?MVP的经验教训在于,不管某项工作在霎时看起来何等紧要,只要在开启认知流程所需之外的,都是荒废。

录像式最小化可行产品

把MVP做成一段关于产品技术和操作的视频,通过把视频宣布到网上获取公测版等候名单,证实了信心飞跃的比方,即顾客实在需要这款产品。

贵宾式最小化可行产品

开拓者团队经过上门拜访和劳动一位顾客,渐渐服务多位顾客,当无暇再接过新买主时。他们起首向自动化方面投入,每便MVP的再一次利用,得以让他们挤出更多一些日子再多服务一些买主。不久从此,产品开发团队始终着眼于把有效的功力升级壮大,而不是想着发明一些未来才可能用上的东西。

别在意这五个幕后人士

透过人工后台回答顾客的题目,顾客相信她们是和确实的产品竞相。那种措施很没用,但验证了第一问题:假使我们能迎刃而解这厮工智能产品背后的技巧问题,人们会使用啊?

质地和筹划在最小化可行产品中的角色

如果我们不通晓何人是消费者,大家也不了解哪些是质料。集团需要了然怎么样产品特质在消费者眼中是有价值的。摒弃对你需要的认知没有一贯用处的整整功效、流程或用力。

开发最小化可行产品中的减速路障

专利多有着防御目标,作为一种威慑力制约竞争对手。创业者应该寻求法律咨询,确保自身充足了然所有风险。一旦创意为人所知,而竞争对手能比新创集团更好的实践这多少个新意,这这家新创集团反正没戏唱了。之所以要白手起家集体去落实这多少个构想,是因为你相信在支付—测量—认知的申报循环中,你可以比任什么人推进的更快。唯一的常胜之道是比任何人学的更快。

葡京国际平台,从最小化可行产品到更新核算

MVP只是学习认知过程中的第一步。在这条路上经历反复反复后,你恐怕会认拿到成品或战略中有一部分瑕疵,然后到了决定改变的时候(转型),用另一种不同的章程实现您的不错。

第七章 衡量

一家新创集团的工作是:严刻测量公司目前的状况,正视评估中发表现实的实质。设计实验,从而精通怎么让诚实数据向商业计划中的理想目的靠得再近些。

为啥看似枯燥的核算将改变您的生存

改进核算提议一些假若:将来政工成功时会是什么样样子?对创立公司来说,公司增长率重要取决于:单一客户获利率、得到新消费者的本金以及现有顾客的重新购买率。但平台型集团则有不同的增强格局,它的增长率取决于来到该网站的新买主的兴趣度。

如何履行革新核算—三大认知阶段性目的

1.使用MVP确定公司目前所处阶段的诚实数据。用MVP验证如果并创立基准线目标。

2.把提升引擎从基准线逐步调至理想状态。每一次产品开发、营销或其他活动,都应当以升级增长模式中的某个驱动因素为对象。如花时间改进产品设计,让新主顾容易采纳。此做法的前提假若是:新买主的激活率是增进的驱动因素,而且它的基准线低于集团愿意。假诺那些只要要变为经证实的体会,产品设计的精益求精就必须能加强消费者的激活率。反之,新的规划就是没戏的。好的统筹是能改进顾客行为的统筹。

3.转型如故坚韧不拔?不可以推动商业情势中的驱动因素,就不会拿到提高。它成为一个显然的提拔,表明已经到了转型时刻。

IMVU的翻新核算

天天花五比索进步产品。追踪漏斗式衡量目的作为:从消费者注册、下载应用程序、试用、重复试用到买入行为。每日拿五日币购得Google重点字广告,带来天天100个点击。每一天都精益求精产品,每日都是一个实验,每日的买主都是独立于往年消费者之外的。

同期群分析。它看的不是综合收入和总顾客数量,而是分级接触产品的每组顾客的显现。每一组被称之为一个同期群。每个商家都是以一名目繁多的买主行为(称为流向)作为立身之本,顾客流向决定了消费者和公司产品的互动关系。

优化VS认知

新创集团必须以高标准来衡量其展开情形,即它能围绕产品或劳务建立起一项可不断工作的证据。唯有与当新创集团落实做出清晰、实际的展望,才能对这么些标准举行评估。大家把消费者要求的要紧意义进入产品,似乎拿到了正确的效益。但消费者没有提议和违反解答的则是有的地下问题:公司有没有一个实用的增高引擎?早起的中标和脚下产品开发团队的平时工作相关吗?

小心虚荣目标

光看总顾客数量和总付费顾客数量可能形势一片大好,但增长引擎已开行,调整引擎的竭力却丢失时效,每个新顾客群暴发的低收入没有进步。这多少个衡量目的让团队感到自己在提高,但实际却未曾取得进展。

可实施目标VS虚荣目的

从关爱总数目的转向以同期群为基础的对象。从事后找因果关系改为把各类产品的宣布作为四次真正的对峙统一测试(在同一时间向顾客提供不同版本的产品,通过观望两组人的作为变化,对两样版本的影响力得出判断)

看板

即对生产量的控制。用户故事会按四种开发阶段分类:尚在成品列表中、正在开发、完成以及处于验证的过程中。看板规则只同意一定数额的用户故事存在于自由三个等级中,随着故事从一个品级进入另一个品级,它被填入下一个阶段的方框中。一旦方框填满就不可能接受任何故事了。唯有当一个故事通过了求证,才能从看板上移除。假使证实战败,则和它相关的功用就会从产品中除去。

第八章 转型仍旧坚持不渝

转型需要胆量

好强指数会让创业者形成错误结论。如果创业者没有明晰的前提倘使,这她就不会失败进而做出转型改变。很多创业者恐惧认同失利。

转型列表

1.放大转型。在此以前被视为产品中独立的一个效能特色,成为产品的任何。

2.压缩转型。把原本一切产品转化为一个更大型产品的一项单独功能特色。

3.客户细分市场转型。产品解决了消费者的急需但消费者并非产品原打算服务的顾客。

4.客户要求转型。目标客户的要求和产品臆度的急需不均等。

5.平台转型。从利用产品转为平台产品,或反方向转向。

6.买卖架构转型。高盈利低产量或低利润高产量相互转换。

7.价值获取转型/渠道转型/技术转型。

转型是一个战略性假若

把转型作为一种新的战略性假如,需要用新的最小化可行产品来证实。转型是一种有团体有系统的改观,用以测试一个关于产品、商业形式和增长引擎的新的底蕴假若。它是精益创业的基本所在:假若大家转错了弯,我们有必不可少的工具来发现错误,并能神速找到另一条路。

第三篇 加速

任何精益转变的要害问题是:哪些活动创办价值,哪些造成浪费?

第九章 批量

创业活动中的小批量

大批量艺术,我们要直接到接近流程终点才能窥见问题。而用小批量的话,我们几乎能顿时意识问题。小批量情势得以让新创集团把这些最后可能被浪费的时间、金钱和生机降到最小。

大批量的身故螺旋

大批量的数量很容易随着工作时间延长而增长,每一回要把批量前行推进往往会造成额外的工作、返工、延误和烦扰,所以每个人都想以更大批量来行事,试图把直接成本降到最低。批量或许会极其增长,既然已经花了那么长日子来开发,为何不再多修复一个漏洞?

要带动,不要推动

成立产品的目标是为着拓展试验,从而帮助我们学到怎样建立一项可不断的事务。精益创业中产品开发的流程是以举行考试的需要来带动,从而做出响应。只要大家定下想要测试的如若,产品开发团队就应尽可能神速的出手规划并进行这项考试,使用最小的批次数量把任务成功。先找出需要领会如何,再倒回去看为了得到这么些体会,要用什么产品举办考试。因而,不是主顾,而是大家对消费者的只要,拉动了成品及其它职能特色开发的工作。除此之外的工作都是荒废。

第十章 成长

加强来自何方

1.口碑传说。

2.成品应用带来的衍生效应。

3.有资金来源的广告。

4.再一次购买或采取。

两种提升引擎

黏着式增长引擎

亟待有较高的顾客保留率。公司要过细追踪顾客流失率,即自由一段时间内,没有持续利用集团出品的那部分买主占顾客总数的比率。假若得到新买主的比值抢先流失率,产品会提升。增长的进度取决于复合率。想要找到增长点,就要关注现有顾客,令产品能更加吸引他们。

病毒式增长引擎

富有病毒式增长特质的成品依靠人和人以内的传递,是例行使用产品的必然结果。只要消费者使用产品,就自然带来了加强。病毒式传播无刻不在。病毒式增长引擎由量化的举报循环提供动力,这种循环称为病毒循环,其速度取决于病毒系数。周全越高,传播越快。如周到0.1则100主顾将牵动10主顾,10主顾又将带来1买主。集团必须关注怎么着增强病毒周到,很多病毒式产品不直接向顾客收费,而是依靠广告这样的直接收入来自。因为病毒式产品在收获新主顾和招募他们的情人过程中无法有一丝一毫挡住。

付费式增长引擎

或者提高来自每位顾客的低收入,要么降低获取新买主的血本。每位顾客在其生命周期内为产品开发一定花费,扣除可变成本之后,剩下的片段通常被誉为顾客的生命周期价值(L电视机)这项收入可用以进货广告,作为成人的投资。

发动机停滞之时

每架增长引擎都凭借一定的一群顾客及她们的连带习惯、偏好、广告渠道和相互的关联。到了某一点,那一个消费者群会被充足利用。按照集团所处的本行和机遇,那多少个过程可长可短。

第十一章 适应

创建自适应社团

当大家依靠学得的事物开发出顾客想要的成品,我们相会临提升减速的问题,低质料产品的症结阻碍顾客感受产品优势,以及指出有关汇报,制约了大家尤其得到认知。我们扩充的成品效果特色越多,就越难再添新的效应,因为存在新的效应烦扰现有效能的风险。

四个为何的聪明

当遇到题目标时候你有没有问五遍为何?比如机械截至运转了:为何会停机?为啥会过分?为何不够润滑?为啥无法使得压轴?为什么会毁掉?这样翻来覆去问五遍会帮你找到问题的根本原因,制止问题再一次发生。按比例投入:按题目的多个级次,不断向每顶级按百分比投入决绝方案。

五大罪状之魔咒

浅析根本原因时让每个受问题影响的人齐聚一堂。需要一个互相互相信任和权利下放的环境。第一次错误要忍受。不容许同一的一无是处爆发两次。学相会对令人难受的真面目。从下凹处做起,尽量具体,症状越具体就越容易让我们了然。

第十二章 改进

新创公司成长之际,创业者可建立一个团社团,学习怎样在满足现有顾客要求足与追寻新消费者之间求得平衡,管理现有产品线,开发新的业务情势,并同时进行所有这个任务。

哪些培养颠覆式革新

两种集体架构特征:

1.稀少但平稳的资源。新创集团索要的老本总量小,但这么些资金必须断然安全,制止受其他因素的震慑。

2.独立的开发权。新创公司索要完全的自主权,在他们先行
的办事范围内开发并推销新产品。他们计划和施行相关考试不需要过多的认同流程。

3.与绩效挂钩的个人利益。新创集团一般会采取优先认股权或此外格局的工本所有权作为奖励。而在必得接纳奖金类其它景色下,那么最高奖金就应该和长久表现最好的翻新项目持续。

第十三章 尾声:杜绝浪费

只关心智能的频率让我们忽视了革新的着实指标:学习前所未知的东西。建立量化的靶子并非关键,而是要找到达成那个目标的形式。

社团的超能力

千古,人是率先位的;将来,体系亟须是第一位的。任何好体制的首先对象必须是挖潜一流人才,并在系统管理之下,使顶级人才能比往日更有把握更快捷的提拔到领导岗位来。我们可以付出多种MVP,一次两回去化解同一类题目,从而量化分析哪一类产品能生出最好的消费者转化率。我们也足以挑选复杂程度不同的支付平台及分销渠道,来改变循环周期时间,测试那个要素对团队生产力的震慑。最关键的是,我们需要制定明确的章程让集体对经求证的咀嚼负责。

总结

总得把具备假使清楚表明出来,并对其严俊测试,这是出于真心想找出每个序列愿景的基本真理所在。我们会加快测试愿景,设法消除浪费,不在半空中搭建漂亮城堡,而是以急速的技巧开发高质地产品。大家会绕过这个不暴发学习认知的结余工作,从而加飞快度。最重点的事,我们要杜绝浪费人们的岁月。

第十四章 参与精益创业活动

http://leanstarupcircle.com

必修读物

《顿悟的四步》《创业者的客户开发指南》《改进者的泥坑》《改进者的解答》《跨越中断期》《旋风期》《产品开发流程标准化:第二代精益产品开发》《精益思想》