常用第三方框架

1、AFNetworking

要使用常规的AFN网络访问 

 1. AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager]; 

    所有的网络请求,均有manager发起 

 2. 需要注意的是,默认提交请求的数据是二进制的,返回格式是JSON 

    1> 如果提交数据是JSON的,需要将请求格式设置为AFJSONRequestSerializer 
    2> 如果返回格式不是JSON的, 

 3. 请求格式 

     AFHTTPRequestSerializer            二进制格式 
     AFJSONRequestSerializer            JSON 
     AFPropertyListRequestSerializer    PList(是一种特殊的XML,解析起来相对容易) 

 4. 返回格式 

     AFHTTPResponseSerializer           二进制格式 
     AFJSONResponseSerializer           JSON 
     AFXMLParserResponseSerializer      XML,只能返回XMLParser,还需要自己通过代理方法解析 
     AFXMLDocumentResponseSerializer (Mac OS X) 
     AFPropertyListResponseSerializer   PList 
     AFImageResponseSerializer          Image 
     AFCompoundResponseSerializer       组合

AFNetworking 主要分为如下5个功能模块:

  • 网络通讯模块(AFURLSessionManager、AFHTTPSessionManager)
  • 网络网络通讯信息序列化/反序列化(Serialization)
  • 网络状态监听模块(Reachability)
  • 网络通讯安全策略模块(Security)
  • 对 iOS UIKit 库的扩展(UIKit)

AF3.x是基于NSURLSession来封装的。所以这个类围绕着NSURLSession做了一系列的封装。而其余的四个模块,均是为了配合网络通信或对已有UIKit的一个扩展工具包

1. 其中AFHTTPSessionManager是继承于NSURLSessionManager的,我们一般做网络请求都是用AFHTTPSessionManager,但是它本身是没有做实事的,只是把一些请求逻辑分发给父类AFURLSessionManager或者其它类去做

2. 父类AFURLSessionManager的初始化方法

  • self.operationQueue.maxConcurrentOperationCount = 1;这个operationQueue就是我们代理回调的queue。这里把代理回调的线程并发数设置为1了
  • 初始化了一些属性,其中包括self.mutableTaskDelegatesKeyedByTaskIdentifier,这个是用来让每一个请求task和我们自定义的AF代理来建立映射用的,其实AF对task的代理进行了一个封装,并且转发代理到AF自定义的代理

  • [self.session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) {

    }];异步的获取当前session的所有未完成的task,猜测可能是当我们重复去初始化session的时候(当然我们实际也不会这么做),会有新的session指向旧的有未完成task的session。为了排除这种不确定性因素,所以在初始化的时候把里面绑定的代理之类的东西都置为nil了。或许这就是防御性编程思想的一种体现吧

- (instancetype)init {
    return [self initWithSessionConfiguration:nil];
}
- (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration {
    self = [super init];
    if (!self) {
        return nil;
    }
    if (!configuration) {
        configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
    }
    self.sessionConfiguration = configuration;
    self.operationQueue = [[NSOperationQueue alloc] init];
    //queue并发线程数设置为1
    self.operationQueue.maxConcurrentOperationCount = 1;
    //注意代理,代理的继承,实际上NSURLSession去判断了,你实现了哪个方法会去调用,包括子代理的方法!
    self.session = [NSURLSession sessionWithConfiguration:self.sessionConfiguration delegate:self delegateQueue:self.operationQueue];
    //各种响应转码
    self.responseSerializer = [AFJSONResponseSerializer serializer];
    //设置默认安全策略
    self.securityPolicy = [AFSecurityPolicy defaultPolicy];
#if !TARGET_OS_WATCH
    self.reachabilityManager = [AFNetworkReachabilityManager sharedManager];
#endif
    // 设置存储NSURL task与AFURLSessionManagerTaskDelegate的词典(重点,在AFNet中,每一个task都会被匹配一个AFURLSessionManagerTaskDelegate 来做task的delegate事件处理) ===============
    self.mutableTaskDelegatesKeyedByTaskIdentifier = [[NSMutableDictionary alloc] init];
    //  设置AFURLSessionManagerTaskDelegate 词典的锁,确保词典在多线程访问时的线程安全
    self.lock = [[NSLock alloc] init];
    self.lock.name = AFURLSessionManagerLockName;
    // 置空task关联的代理
    [self.session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) {        
        for (NSURLSessionDataTask *task in dataTasks) {
            [self addDelegateForDataTask:task uploadProgress:nil downloadProgress:nil completionHandler:nil];
        }
        for (NSURLSessionUploadTask *uploadTask in uploadTasks) {
            [self addDelegateForUploadTask:uploadTask progress:nil completionHandler:nil];
        }
        for (NSURLSessionDownloadTask *downloadTask in downloadTasks) {
            [self addDelegateForDownloadTask:downloadTask progress:nil destination:nil completionHandler:nil];
        }
    }];
    return self;
}
3. 网络请求
  • 用self.requestSerializer和各种参数去获取了一个我们最终请求网络需要的NSMutableURLRequest实例。
  • 调用另外一个方法dataTaskWithRequest去拿到我们最终需要的NSURLSessionDataTask实例,并且在完成的回调里,调用我们传过来的成功和失败的回调
- (NSURLSessionDataTask *)dataTaskWithHTTPMethod:(NSString *)method
                                       URLString:(NSString *)URLString
                                      parameters:(id)parameters
                                  uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgress
                                downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgress
                                         success:(void (^)(NSURLSessionDataTask *, id))success
                                         failure:(void (^)(NSURLSessionDataTask *, NSError *))failure
{
    NSError *serializationError = nil;
    //把参数,还有各种东西转化为一个request
    NSMutableURLRequest *request = [self.requestSerializer requestWithMethod:method URLString:[[NSURL URLWithString:URLString relativeToURL:self.baseURL] absoluteString] parameters:parameters error:&serializationError];
    if (serializationError) {
        if (failure) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu"
            //如果解析错误,直接返回
            dispatch_async(self.completionQueue ?: dispatch_get_main_queue(), ^{
                failure(nil, serializationError);
            });
#pragma clang diagnostic pop
        }
        return nil;
    }
    __block NSURLSessionDataTask *dataTask = nil;
    dataTask = [self dataTaskWithRequest:request
                          uploadProgress:uploadProgress
                        downloadProgress:downloadProgress
                       completionHandler:^(NSURLResponse * __unused response, id responseObject, NSError *error) {
        if (error) {
            if (failure) {
                failure(dataTask, error);
            }
        } else {
            if (success) {
                success(dataTask, responseObject);
            }
        }
    }];
    return dataTask;
}
4. AFURLRequestSerialization类
  • 设置request的请求类型,get,post,put...等
  • 往request里添加一些参数设置,其中AFHTTPRequestSerializerObservedKeyPaths()是一个c函数,返回一个数组
  • 把需要传递的参数进行编码,并且设置到request中去
- (NSMutableURLRequest *)requestWithMethod:(NSString *)method
                                 URLString:(NSString *)URLString
                                parameters:(id)parameters
                                     error:(NSError *__autoreleasing *)error
{
    //断言,debug模式下,如果缺少改参数,crash
    NSParameterAssert(method);
    NSParameterAssert(URLString);
    NSURL *url = [NSURL URLWithString:URLString];
    NSParameterAssert(url);
    NSMutableURLRequest *mutableRequest = [[NSMutableURLRequest alloc] initWithURL:url];
    mutableRequest.HTTPMethod = method;
    //将request的各种属性循环遍历
    for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) {
        //如果自己观察到的发生变化的属性,在这些方法里
        if ([self.mutableObservedChangedKeyPaths containsObject:keyPath]) {
           //把给自己设置的属性给request设置
            [mutableRequest setValue:[self valueForKeyPath:keyPath] forKey:keyPath];
        }
    }
    //将传入的parameters进行编码,并添加到request中
    mutableRequest = [[self requestBySerializingRequest:mutableRequest withParameters:parameters error:error] mutableCopy];
    return mutableRequest;
}
下面函数做的事情:
  • 从self.HTTPRequestHeaders中拿到设置的参数,赋值要请求的request里去
  • 把请求网络的参数,从array dic set这些容器类型转换为字符串,具体转码方式,我们可以使用自定义的方式,也可以用AF默认的转码方式
- (NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request
                             withParameters:(id)parameters
                                      error:(NSError *__autoreleasing *)error
{
  NSParameterAssert(request);
  NSMutableURLRequest *mutableRequest = [request mutableCopy];
  //从自己的head里去遍历,如果有值则设置给request的head
  [self.HTTPRequestHeaders enumerateKeysAndObjectsUsingBlock:^(id field, id value, BOOL * __unused stop) {
      if (![request valueForHTTPHeaderField:field]) {
          [mutableRequest setValue:value forHTTPHeaderField:field];
      }
  }];
  //来把各种类型的参数,array dic set转化成字符串,给request
  NSString *query = nil;
  if (parameters) {
      //自定义的解析方式
      if (self.queryStringSerialization) {
          NSError *serializationError;
          query = self.queryStringSerialization(request, parameters, &serializationError);
          if (serializationError) {
              if (error) {
                  *error = serializationError;
              }
              return nil;
          }
      } else {
          //默认解析方式
          switch (self.queryStringSerializationStyle) {
              case AFHTTPRequestQueryStringDefaultStyle:
                  query = AFQueryStringFromParameters(parameters);
                  break;
          }
      }
  }
  //最后判断该request中是否包含了GET、HEAD、DELETE(都包含在HTTPMethodsEncodingParametersInURI)。因为这几个method的quey是拼接到url后面的。而POST、PUT是把query拼接到http body中的。
  if ([self.HTTPMethodsEncodingParametersInURI containsObject:[[request HTTPMethod] uppercaseString]]) {
      if (query && query.length > 0) {
          mutableRequest.URL = [NSURL URLWithString:[[mutableRequest.URL absoluteString] stringByAppendingFormat:mutableRequest.URL.query ? @"&%@" : @"?%@", query]];
      }
  } else {
      //post put请求
      // #2864: an empty string is a valid x-www-form-urlencoded payload
      if (!query) {
          query = @"";
      }
      if (![mutableRequest valueForHTTPHeaderField:@"Content-Type"]) {
          [mutableRequest setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
      }
      //设置请求体
      [mutableRequest setHTTPBody:[query dataUsingEncoding:self.stringEncoding]];
  }
  return mutableRequest;
}
默认解析

在递归调用AFQueryStringPairsFromKeyAndValue。判断vaLue是什么类型的,然后去递归调用自己,直到解析的是除了array dic set以外的元素,然后把得到的参数数组返回

NSString * AFQueryStringFromParameters(NSDictionary *parameters) {
  NSMutableArray *mutablePairs = [NSMutableArray array];
  //把参数给AFQueryStringPairsFromDictionary,拿到AF的一个类型的数据就一个key,value对象,在URLEncodedStringValue拼接keyValue,一个加到数组里
  for (AFQueryStringPair *pair in AFQueryStringPairsFromDictionary(parameters)) {
      [mutablePairs addObject:[pair URLEncodedStringValue]];
  }
  //拆分数组返回参数字符串
  return [mutablePairs componentsJoinedByString:@"&"];
}
NSArray * AFQueryStringPairsFromDictionary(NSDictionary *dictionary) {
  //往下调用
  return AFQueryStringPairsFromKeyAndValue(nil, dictionary);
}
NSArray * AFQueryStringPairsFromKeyAndValue(NSString *key, id value) {
  NSMutableArray *mutableQueryStringComponents = [NSMutableArray array];
  // 根据需要排列的对象的description来进行升序排列,并且selector使用的是compare:
  // 因为对象的description返回的是NSString,所以此处compare:使用的是NSString的compare函数
  // 即@[@"foo", @"bar", @"bae"] ----> @[@"bae", @"bar",@"foo"]
  NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"description" ascending:YES selector:@selector(compare:)];
  //判断vaLue是什么类型的,然后去递归调用自己,直到解析的是除了array dic set以外的元素,然后把得到的参数数组返回。
  if ([value isKindOfClass:[NSDictionary class]]) {
      NSDictionary *dictionary = value;
      // Sort dictionary keys to ensure consistent ordering in query string, which is important when deserializing potentially ambiguous sequences, such as an array of dictionaries
      //拿到
      for (id nestedKey in [dictionary.allKeys sortedArrayUsingDescriptors:@[ sortDescriptor ]]) {
          id nestedValue = dictionary[nestedKey];
          if (nestedValue) {
              [mutableQueryStringComponents addObjectsFromArray:AFQueryStringPairsFromKeyAndValue((key ? [NSString stringWithFormat:@"%@[%@]", key, nestedKey] : nestedKey), nestedValue)];
          }
      }
  } else if ([value isKindOfClass:[NSArray class]]) {
      NSArray *array = value;
      for (id nestedValue in array) {
          [mutableQueryStringComponents addObjectsFromArray:AFQueryStringPairsFromKeyAndValue([NSString stringWithFormat:@"%@[]", key], nestedValue)];
      }
  } else if ([value isKindOfClass:[NSSet class]]) {
      NSSet *set = value;
      for (id obj in [set sortedArrayUsingDescriptors:@[ sortDescriptor ]]) {
          [mutableQueryStringComponents addObjectsFromArray:AFQueryStringPairsFromKeyAndValue(key, obj)];
      }
  } else {
      [mutableQueryStringComponents addObject:[[AFQueryStringPair alloc] initWithField:key value:value]];
  }
  return mutableQueryStringComponents;
}

根据该request中请求类型,来判断参数字符串应该如何设置到request中去。如果是GET、HEAD、DELETE,则把参数quey是拼接到url后面的。而POST、PUT是把query拼接到http body中的:

if ([self.HTTPMethodsEncodingParametersInURI containsObject:[[request HTTPMethod] uppercaseString]]) {
  if (query && query.length > 0) {
      mutableRequest.URL = [NSURL URLWithString:[[mutableRequest.URL absoluteString] stringByAppendingFormat:mutableRequest.URL.query ? @"&%@" : @"?%@", query]];
  }
} else {
  //post put请求
  // #2864: an empty string is a valid x-www-form-urlencoded payload
  if (!query) {
      query = @"";
  }
  if (![mutableRequest valueForHTTPHeaderField:@"Content-Type"]) {
      [mutableRequest setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
  }
  //设置请求体
  [mutableRequest setHTTPBody:[query dataUsingEncoding:self.stringEncoding]];
}
NSURLSession内部去生成task的时候是用多线程并发去执行的
- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request
                               uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgressBlock
                             downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgressBlock
                            completionHandler:(nullable void (^)(NSURLResponse *response, id _Nullable responseObject,  NSError * _Nullable error))completionHandler {
    __block NSURLSessionDataTask *dataTask = nil;
    //第一件事,创建NSURLSessionDataTask,里面适配了Ios8以下taskIdentifiers,函数创建task对象。
    //其实现应该是因为iOS 8.0以下版本中会并发地创建多个task对象,而同步有没有做好,导致taskIdentifiers 不唯一…这边做了一个串行处理
    url_session_manager_create_task_safely(^{
        dataTask = [self.session dataTaskWithRequest:request];
    });
    [self addDelegateForDataTask:dataTask uploadProgress:uploadProgressBlock downloadProgress:downloadProgressBlock completionHandler:completionHandler];
    return dataTask;
}

static void url_session_manager_create_task_safely(dispatch_block_t block) {
  if (NSFoundationVersionNumber < NSFoundationVersionNumber_With_Fixed_5871104061079552_bug) {
      // Fix of bug
      // Open Radar:http://openradar.appspot.com/radar?id=5871104061079552 (status: Fixed in iOS8)
      // Issue about:https://github.com/AFNetworking/AFNetworking/issues/2093
    //理解下,第一为什么用sync,因为是想要主线程等在这,等执行完,在返回,因为必须执行完dataTask才有数据,传值才有意义。
    //第二,为什么要用串行队列,因为这块是为了防止ios8以下内部的dataTaskWithRequest是并发创建的,
    //这样会导致taskIdentifiers这个属性值不唯一,因为后续要用taskIdentifiers来作为Key对应delegate。
      dispatch_sync(url_session_manager_creation_queue(), block);
  } else {
      block();
  }
}
static dispatch_queue_t url_session_manager_creation_queue() {
  static dispatch_queue_t af_url_session_manager_creation_queue;
  static dispatch_once_t onceToken;
  //保证了即使是在多线程的环境下,也不会创建其他队列
  dispatch_once(&onceToken, ^{
      af_url_session_manager_creation_queue = dispatch_queue_create("com.alamofire.networking.session.manager.creation", DISPATCH_QUEUE_SERIAL);
  });
  return af_url_session_manager_creation_queue;
}

为什么我们不直接去调用dataTask = [self.session dataTaskWithRequest:request];非要绕这么一圈,我们点进去bug日志里看看,原来这是为了适配iOS8的以下,创建session的时候,偶发的情况会出现session的属性taskIdentifier这个值不唯一,而这个taskIdentifier是我们后面来映射delegate的key,所以它必须是唯一的

下面是方法:[self addDelegateForDataTask:dataTask uploadProgress:uploadProgressBlock downloadProgress:downloadProgressBlock completionHandler:completionHandler];的调用

  • 这个方法,生成了一个AFURLSessionManagerTaskDelegate,这个其实就是AF的自定义代理。我们请求传来的参数,都赋值给这个AF的代理了。
  • delegate.manager = self;代理把AFURLSessionManager这个类作为属性了,我们可以看到:@property (nonatomic, weak) AFURLSessionManager *manager;这个属性是弱引用的,所以不会存在循环引用的问题。
  • 我们调用了[self setDelegate:delegate forTask:dataTask];
  • - (void)addDelegateForDataTask:(NSURLSessionDataTask *)dataTask
                    uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgressBlock
                  downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgressBlock
                 completionHandler:(void (^)(NSURLResponse *response, id responseObject, NSError *error))completionHandler
    {
        AFURLSessionManagerTaskDelegate *delegate = [[AFURLSessionManagerTaskDelegate alloc] init];
        // AFURLSessionManagerTaskDelegate与AFURLSessionManager建立相互关系
        delegate.manager = self;
        delegate.completionHandler = completionHandler;
        //这个taskDescriptionForSessionTasks用来发送开始和挂起通知的时候会用到,就是用这个值来Post通知,来两者对应
        dataTask.taskDescription = self.taskDescriptionForSessionTasks;
        // ***** 将AF delegate对象与 dataTask建立关系
        [self setDelegate:delegate forTask:dataTask];
        // 设置AF delegate的上传进度,下载进度块。
        delegate.uploadProgressBlock = uploadProgressBlock;
        delegate.downloadProgressBlock = downloadProgressBlock;
    }
    
    // 这个方法主要就是把AF代理和task建立映射,存在了一个我们事先声明好的字典里。而要加锁的原因是因为本身我们这个字典属性是mutable的,是线程不安全的
    - (void)setDelegate:(AFURLSessionManagerTaskDelegate *)delegate
                forTask:(NSURLSessionTask *)task
    {
        //断言,如果没有这个参数,debug下crash在这
        NSParameterAssert(task);
        NSParameterAssert(delegate);
        //加锁保证字典线程安全
        [self.lock lock];
        // 将AF delegate放入以taskIdentifier标记的词典中(同一个NSURLSession中的taskIdentifier是唯一的)
        self.mutableTaskDelegatesKeyedByTaskIdentifier[@(task.taskIdentifier)] = delegate;
        // 为AF delegate 设置task 的progress监听
        [delegate setupProgressForTask:task];
        //添加task开始和暂停的通知
        [self addNotificationObserverForTask:task];
        [self.lock unlock];
    }
    

NSURLSessionDelegate的响应

因为AFURLSessionmanager所管理的AFURLSession的delegate指向其自身,因此所有的

NSURLSessiondelegate的回调地址都是AFURLSessionmanager,而AFURLSessionmanager又会根据是否需要具体处理会将AF delegate所响应的delegate,传递到对应的AF delegate去

http://www.cocoachina.com/ios/20161209/18277.html

2. SDWebImage

SDWebImage底层实现有沙盒缓存机制,主要由三块组成

  • 1、内存图片缓存
  • 2、内存操作缓存
  • 3、磁盘沙盒缓存

  • 在显示图片之前,先检查内存缓存中时候有该图片

  • 如果内存缓存中有图片,那么就直接使用,不下载
  • 如果内存缓存中无图片,那么再检查是否有磁盘缓存
  • 如果磁盘缓存中有图片,那么直接使用,还需要保存一份到内存缓存中(方便下一次使用)
  • 如果磁盘缓存中无图片,那么再去下载,并且把下载完的图片保存到内存缓存和磁盘缓存

使用:

除了带options选项的方法,其他的方法都是综合存储,也就是内存缓存和磁盘缓存结合的方式,如果你只需要内存缓存,那么在options这里选择SDWebImageCacheMemoryOnly就可以

options所有选项:
//失败后重试
         SDWebImageRetryFailed = 1 << 0,

         //UI交互期间开始下载,导致延迟下载比如UIScrollView减速。
         SDWebImageLowPriority = 1 << 1,

         //只进行内存缓存
         SDWebImageCacheMemoryOnly = 1 << 2,

         //这个标志可以渐进式下载,显示的图像是逐步在下载
         SDWebImageProgressiveDownload = 1 << 3,

         //刷新缓存
         SDWebImageRefreshCached = 1 << 4,

         //后台下载
         SDWebImageContinueInBackground = 1 << 5,

         //NSMutableURLRequest.HTTPShouldHandleCookies = YES;

         SDWebImageHandleCookies = 1 << 6,

         //允许使用无效的SSL证书
         //SDWebImageAllowInvalidSSLCertificates = 1 << 7,        
         //优先下载
         SDWebImageHighPriority = 1 << 8,

         //延迟占位符
         SDWebImageDelayPlaceholder = 1 << 9,

         //改变动画形象
         SDWebImageTransformAnimatedImage = 1 << 10

SDWebImage内部实现过程

  • 入口 setImageWithURL:placeholderImage:options: 会先把 placeholderImage 显示,然后 SDWebImageManager 根据 URL 开始处理图片。
  • 进入 SDWebImageManager-downloadWithURL:delegate:options:userInfo:,交给 SDImageCache 从缓存查找图片是否已经下载 queryDiskCacheForKey:delegate:userInfo:.
  • 先从内存图片缓存查找是否有图片,如果内存中已经有图片缓存,SDImageCacheDelegate 回调 imageCache:didFindImage:forKey:userInfo: 到 SDWebImageManager。
  • SDWebImageManagerDelegate 回调 webImageManager:didFinishWithImage: 到 UIImageView+WebCache 等前端展示图片。
  • 如果内存缓存中没有,生成 NSInvocationOperation 添加到队列开始从硬盘查找图片是否已经缓存。
  • 根据 URLKey 在硬盘缓存目录下尝试读取图片文件。这一步是在 NSOperation 进行的操作,所以回主线程进行结果回调 notifyDelegate:。
  • 如果上一操作从硬盘读取到了图片,将图片添加到内存缓存中(如果空闲内存过小,会先清空内存缓存)。SDImageCacheDelegate 回调 imageCache:didFindImage:forKey:userInfo:。进而回调展示图片。
  • 如果从硬盘缓存目录读取不到图片,说明所有缓存都不存在该图片,需要下载图片,回调 imageCache:didNotFindImageForKey:userInfo:。
  • 共享或重新生成一个下载器 SDWebImageDownloader 开始下载图片。
  • 图片下载由 NSURLConnection 来做,实现相关 delegate 来判断图片下载中、下载完成和下载失败。
  • connection:didReceiveData: 中利用 ImageIO 做了按图片下载进度加载效果。
  • connectionDidFinishLoading: 数据下载完成后交给 SDWebImageDecoder 做图片解码处理。
  • 图片解码处理在一个 NSOperationQueue 完成,不会拖慢主线程 UI。如果有需要对下载的图片进行二次处理,最好也在这里完成,效率会好很多。
  • 在主线程 notifyDelegateOnMainThreadWithInfo: 宣告解码完成,imageDecoder:didFinishDecodingImage:userInfo: 回调给 SDWebImageDownloader。
  • imageDownloader:didFinishWithImage: 回调给 SDWebImageManager 告知图片下载完成。
  • 通知所有的 downloadDelegates 下载完成,回调给需要的地方展示图片。
  • 将图片保存到 SDImageCache 中,内存缓存和硬盘缓存同时保存。写文件到硬盘也在以单独 NSInvocationOperation 完成,避免拖慢主线程。
  • SDImageCache 在初始化的时候会注册一些消息通知,在内存警告或退到后台的时候清理内存图片缓存,应用结束的时候清理过期图片。
  • SDWI 也提供了 UIButton+WebCache 和 MKAnnotationView+WebCache,方便使用。
  • SDWebImagePrefetcher 可以预先下载图片,方便后续使用

https://www.cnblogs.com/muzichenyu/p/6129933.html

results matching ""

    No results matching ""