YYWebImageOperation 源码解析(伪代码,关键流

2019-11-02  本文已影响0人  好有魔力

YYWebImageOperation是支持并发的自定义NSOperation,并且支持线程安全.

重载系统的方法

对于自定义NSOpertion来说,有两个关键父类的方法要重载, 他们是- (void)start-(void)cancel,要在这两个方法中处理任务的开始和取消逻辑,并生成相应状态的KVO通知. 剩下就是处理好自定义任务的逻辑,生成相应状态的KVO通知.

- (void)start

- (void)start {
    //加锁
    [_lock lock];
    //检测是否已经被取消
    if ([self isCanceld]) {
        //在工作线程执行真正的取消逻辑
        [ self _cancelOperationOnWorkThread];
        //生成KVO通知
        self.canceld = YES;
    }
    //检测是否正在执行
    else if ([self isReady] ||
             [self executing] ||
             ![self isFinished]) {
        //执行真正的 操作逻辑
        [self _startOperation];
        
        //生成KVO通知
        self.isExcuting = YES;
    }
    //
    [_lock unlock];
}
   

- (void)cancel

- (void)cancel {
  [_lock lock];
   //判断是否被取消过
   if (![self isCanceled]) {
   //调用父类NSOperation的cancel方法
     [super cancel];
     
     //判断是否是正在执行中
     if (self.isExecuting) {
        self.isExecuting = NO;
        在_workThread上执行和真正的取消逻辑     
     }
         //触发取消KVO通知
     self.isCanceled = YES;
   }
   
   //判断是否开始过
   if ([self isStarted]) {
   //触发结束的KVO通知
    self.isFinished = YES;
   }
  [_lock unlock];
}

真正的开始逻辑

//伪代码
- (void)_startOperation {
//如果已取消直接return;
if ([self isCanceled]) return;
      
检测缓存选项 {
    从内存缓存中加载图片
        //再次判断是否取消
    if (!self.isCanceled){
        
        if (如果从内存中加载到图片) {
        加锁
        调用completion闭包返回给外界
        //生成KVO通知
        self.isFinished = YES;
        self.isExecuting = NO;
        解锁
           return;
        }
      }
      
      缓存选项如果不是忽略磁盘缓存 {
        在_imageQueue中执行 {
          //再次判断是否取消
          if (self.isCanceled) return;
          让cache从磁盘中取出图片
          如果图片存在 {
            把图片缓存到内存中
            
            再次判断是否取消,取消则直接返回
            加锁
            调用completion闭包返回给外界
            //生成KVO通知
            self.isFinished = YES;
            self.isExecuting = NO;
            解锁
          } 
          图片不存在 {
            再次判断是否取消,取消则直接返回
            在子线程下载图片
          }        
        }
      }
}

对于支持并发的自定义任务,在进行真正的操作或者返回数据之前,要注意是否被取消(大神的代码处处体现着严谨).

真正的取消逻辑

- (void)_cancelOperation {
   if _connection  {
      if 操作选项是展示菊花 {
         隐藏掉菊花
      }
      [_connection cancel];
      _connection = nil;
      结束后台任务
      //注意这里没有加锁,是因为在调用_cancelOperation之前已经加了锁
      调用_completion闭包给外界传递nil图片
   }
}

图片下载逻辑

发起请求

- (void)startRequest {
 if 取消直接return
 在自动释放池中执行 {
   if 操作选项是忽略下载失败的URL 
    并且 这个URL在黑名单(下载失败的url列表)中 {
      加锁
      调用completion闭包返回给外界 空图片
      //生成KVO通知
      self.isExecuting = NO;
      self.isFinished = YES;
      解锁
      return;
    }
    
   加锁
   if 图片URL是FileUrl {
      从fileUrl中取出图片的大小,并引用
   }
   if 没被取消 {
      使用NSURLConnection 进行图片下载
      <解析操作选项,决定是否要在状态栏上显示菊花>
   }
   解锁  
 }
}

下载过程中

//开始接受到响应
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
    在autorelease中执行 {
       解析response中的状态码,
       if 状态码>=400 || 状态码 == 304 {
          创建NSError
       }
       if 有错误 {
         调用 _connection 取消方法
         调用 _connection 代理收到错误的方法
       } else {
         拿到 reponse中数据的大小
         更新已经数据大小
         用数据大小初始化_data
         加锁
         if 没有取消 {
           调用_progress闭包相外界报告图片下载进度 此时是0
         }
         解锁
       }
    }
}

//图片数据传输过程中
//代码比较长
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
  在autoReleasePool中执行 {
     加锁
     if 已经取消 {return}
     解锁
     将获取到的数据放到_data中,
     if 没有取消 {
       加锁
       调用_progress闭包报告进度
       解锁
     }
     获取进度解码器
     让进度解码器解码_data 
     if 已经取消 {return}
      
      if 解析图片的类型是
      YYImageTypeUnkwon or
      YYWebImageTypeWebP or 
      YYWebImgeTypeOther {
        将YYImageDecoder 置nil;
        标记 __progressiveIgnored 为 YES;
        return;
      }
      
      if 操作选项是 YYWebImageOptionProgressiveBlur {
         if 图片类型不是PNG or JPEG {
            将YYImageDecoder 置nil;
            标记 __progressiveIgnored 为 YES;
            return;

         }
      }
      
      if 解码器解码的帧数是0 {return}
       
       if  操作选项不是 YYWebImageOptionProgressiveBlur {
            从YYWebImageCode中取出第0帧 YYImageFrame
            if (帧的图片的图片不为nil) {
                  加锁
                  if 没有取消 {
                    调用_completion闭包传递给外界显示
                  }
                  解锁
            }
            return;
       } else {
          if 图片类型是JPEG  {
             if <!_progressiveDetected 进度没有检测过> {
                从解码器中取出第0帧的属性字典
                //这里没有仔细研究ㄟ( ▔, ▔ )ㄏ
                取出 kCGImagePropertyJFIFDictionary 属性字典
                从 kCGImagePropertyJFIFDictionary 字典中取出 kCGImagePropertyJFIFIsProgressive 
                if  不支持kCGImagePropertyJFIFIsProgressive  {
                   将YYImageDecoder 置nil;
                   标记 __progressiveIgnored 为 YES;
                   return;
                }
                标记已经检测过 __progressiveDetected = YES
             }
             从_data中找到 0xFF,0xDA 的范围 markedRange
             设置 _progressiveScanedLength = _data.length 
             if markedRange无效 {return}
             if 已经取消 {return}
          } else if 图片类型是PNG {
            if <!_progressiveDetected 进度没有检测过> {
                从解码器中取出第0帧的属性字典
                //这里没有仔细研究ㄟ( ▔, ▔ )ㄏ
                从第0帧属性字典中取出 kCGImagePropertyPNGDictionary 属性字典
                  kCGImagePropertyPNGDictionary 属性字典 中取出 kCGImagePropertyPNGInterlaceType 
                  if 不支持kCGImagePropertyPNGInterlaceType {
                       将YYImageDecoder 置nil;
                       标记 __progressiveIgnored 为 YES;
                       return;
                  }
                标记为已经检测 _progressiveDetected = YES;
            }
            从解码器中取出第0帧 YYImageFrame
            if 第0帧中的image为空 {return}
            if 已经取消 {return}
            计算模糊半径 radius
            用计算出来的模糊半径重新生成图片
            if 用计算出来的模糊半径重新生成的图片不是空 {
                //又是熟悉的操作
                加锁
                if 没有取消 {
                调用_completion闭包发送给外界显示
                记录时间戳 _lastProgressiveDecodeTimestamp
                }
                解锁
            }
          }
       }
  }
}

图片下载结束

- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
  @autoreleasePool {
    加锁
    置空_connection 
    if 没有取消 {
      _imageQueue 异步执行 {
        if 操作选项不是 YYWebImageOptionIgnoreAnimatedImage {
           //没有忽略动图
           使用_data创建 YYImage 判断是不是动图,
        } else {
           //忽略动图
           直接用YYImageDecoder 解码_data获取第0帧图片
        }
        解析图片类型:{
             如果是WebP,PNG,GIF,JPEG {
                if 没有动画 并且格式是GIF,WebP{
                    清空_data //稍后重新编码到磁盘和内存缓存
                }
             }
             其他情况 {
              清空_data
             }
        }
        if 已经取消 return
        if transform闭包和前面解码的图片不是空 {
           调用 transform 闭包 获取新的图片newImage
           if newImage != image {
             清空_data
           }
           引用newImage
           if 已经取消 return
           在 _newThread 执行 _didReceiveImageFromWeb方法传递image
        }    
        if url是fileURL 并且 操作选项是显示菊花 {
           减少菊花数
        }
      }
    }
    
    
    解锁
  }
}

- (void)_didReceiveImageFromWeb:(UIImage *)image {
 @autorealsePool {
   加锁
   if 没有取消 {
     if 缓存对象存在 {
        if (image || 缓存操作的选项中包含刷新缓存(YYWebImageOptionRefreshImageCache)) {
          解析缓存操作的类型,如果设置了忽略磁盘缓存,将缓存类型设置为 内存缓存,否则设置为双缓存
          调用_cache的图片缓存方法,传递缓存类型
        }
       置空_data
       if 图片为空 {
          创建NSError对象,一会传递出去
          if 操作选项是忽略失败的url {
            if url 已经包含在 黑名单中 {
              更新NSError对象中的错误信息
            }else {
              将url添加到 黑名单中
            }
          } 
       }
       if _completion闭包不是空 {
          调用_completion闭包,把图片传递出去
       }
       //生成KVO通知
       self.isExecuting = NO;
       self.isFinished = YES;
     }
   }
   解锁
 }
}

图片下载错误

- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
  @autoreleasePool {
    加锁
    if 没有取消 {
      if _completion闭包不是空 {
        调用_completion闭包 nil image, YYWebImageFromNone,  
      }
      if url是fileURL 并且 操作选项是显示菊花 {
           减少菊花数
      }
     清空_connection
     清空_data
     
     //生成KVO通知
    self.isExecuting = NO;
    self.isFinished = YES;

     
     if (error.code != NSURLErrorNotConnectedToInternet &&
                    error.code != NSURLErrorCancelled &&
                    error.code != NSURLErrorTimedOut &&
                    error.code != NSURLErrorUserCancelledAuthentication &&
                    error.code != NSURLErrorNetworkConnectionLost) {
                    将图片url添加到黑名单中
                }  
    }
    解锁
  }
}

以上就是YYWebImageOperation的开始,取消,和图片下载流程的伪代码,这几个流程看了一遍下来,相信其它部分的代码再看就更容易了.

上一篇 下一篇

猜你喜欢

热点阅读