当前位置: 首页 > news >正文

【iOS】——SDWebImage源码学习

文章目录

  • 一、SDWebIamge简介
  • 二、SDWebImage的调用流程
  • SDWebImage源码分析
    • 1.UIImageView+WebCache层
    • 2.UIView+WebCache层
    • 3.SDWebManager层
    • 4.SDWebCache层
    • 5.SDWebImageDownloader层


一、SDWebIamge简介

SDWebImage是iOS中提供图片加载的第三方库,可以给UIKit框架中的控件比如UIImageView和UIButton提供从网络上下载和缓存的图片。它的接口十分简洁,如果给UIImageView控件添加图片可以使用如下代码

[imageView sd_setImageWithURL:imageUrl placeholderImage:nil];//第一个参数是图片的URL第二个参数是占位图片加载失败时显示

如果给UIButton添加图片可以使用如下代码

[button sd_setImageWithURL:imageUrl forState:UIControlStateNormal placeholderImage:nil];//第一个参数是图片的URL,第二个参数是按钮状态,第三个参数是占位图片,加载失败时显示

SDWebImage有下面一些常见的功能:

  • 通过异步方式加载图片
  • 可以自动缓存到内存和磁盘中,并且可以自动清理过期的缓存
  • 支持多种的图片格式包括jpg、jepg、png等,同时还支持多种动图格式包括GIF、APNG等
  • 同一图片的URL不会重复下载
  • 对失效的图片URL不会重复尝试下载
  • 在子线程中进行操作,确保不会阻塞主线程

二、SDWebImage的调用流程

在这里插入图片描述
当使用[imageView sd_setImageWithURL:imageUrl placeholderImage:nil];方法时,会执行UIImageView+WebCache类中的相应方法,当使用[button sd_setImageWithURL:imageUrl forState:UIControlStateNormal placeholderImage:nil];方法时会执行UIBUtton+WebCache类中的相应方法,但是最后都会调用UIView+WebCache类中的- (nullable id<SDWebImageOperation>)sd_internalSetImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder options:(SDWebImageOptions)options context:(nullable SDWebImageContext *)context setImageBlock:(nullable SDSetImageBlock)setImageBlock progress:(nullable SDImageLoaderProgressBlock)progressBlock completed:(nullable SDInternalCompletionBlock)completedBlock {};方法。接着根据URL,通过SDWebImageManager的loadImageWithURL:options:context:progress:completed:方法加载图片,接着通过sd_setImageLoadOperation方法将operation加入到SDOperationsDictionary中。然后调用queryCacheOperationForKey方法进行查询图片缓存,通过查询内存和磁盘中是否有缓存,如果有则通过回调函数显示照片,如果没有则调用downloadImageWithURL:options:context:progress: completed:方法进行图片下载和缓存,最后显示图片。

SDWebImage源码分析

1.UIImageView+WebCache层

- (void)sd_setImageWithURL:(nullable NSURL *)url {[self sd_setImageWithURL:url placeholderImage:nil options:0 progress:nil completed:nil];
}- (void)sd_setImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder {[self sd_setImageWithURL:url placeholderImage:placeholder options:0 progress:nil completed:nil];
}- (void)sd_setImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder options:(SDWebImageOptions)options {[self sd_setImageWithURL:url placeholderImage:placeholder options:options progress:nil completed:nil];
}- (void)sd_setImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder options:(SDWebImageOptions)options context:(nullable SDWebImageContext *)context {[self sd_setImageWithURL:url placeholderImage:placeholder options:options context:context progress:nil completed:nil];
}- (void)sd_setImageWithURL:(nullable NSURL *)url completed:(nullable SDExternalCompletionBlock)completedBlock {[self sd_setImageWithURL:url placeholderImage:nil options:0 progress:nil completed:completedBlock];
}- (void)sd_setImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder completed:(nullable SDExternalCompletionBlock)completedBlock {[self sd_setImageWithURL:url placeholderImage:placeholder options:0 progress:nil completed:completedBlock];
}- (void)sd_setImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder options:(SDWebImageOptions)options completed:(nullable SDExternalCompletionBlock)completedBlock {[self sd_setImageWithURL:url placeholderImage:placeholder options:options progress:nil completed:completedBlock];
}- (void)sd_setImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder options:(SDWebImageOptions)options progress:(nullable SDImageLoaderProgressBlock)progressBlock completed:(nullable SDExternalCompletionBlock)completedBlock {[self sd_setImageWithURL:url placeholderImage:placeholder options:options context:nil progress:progressBlock completed:completedBlock];
}- (void)sd_setImageWithURL:(nullable NSURL *)urlplaceholderImage:(nullable UIImage *)placeholderoptions:(SDWebImageOptions)optionscontext:(nullable SDWebImageContext *)contextprogress:(nullable SDImageLoaderProgressBlock)progressBlockcompleted:(nullable SDExternalCompletionBlock)completedBlock {[self sd_internalSetImageWithURL:urlplaceholderImage:placeholderoptions:optionscontext:contextsetImageBlock:nilprogress:progressBlockcompleted:^(UIImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, SDImageCacheType cacheType, BOOL finished, NSURL * _Nullable imageURL) {if (completedBlock) {completedBlock(image, error, cacheType, imageURL);}}];
}

不难发现上面的方法最后都会调用到下面这个方法,也就是基类方法。

//UIImageView+WebCache
- (void)sd_setImageWithURL:(nullable NSURL *)urlplaceholderImage:(nullable UIImage *)placeholderoptions:(SDWebImageOptions)optionsprogress:(nullable SDWebImageDownloaderProgressBlock)progressBlockcompleted:(nullable SDExternalCompletionBlock)completedBlock;

2.UIView+WebCache层

接着在上面的方法中又会调用到UIView+WebCache层的下面这个方法

/*
imageURL: NSURL 类型,指定了要加载图片的远程URL。这是图片请求的核心依据。
options: SDWebImageOptions 枚举类型,包含了多个可选标志位,用于控制图片加载的行为,如是否只从内存缓存加载、是否同步查询缓存、是否允许重定向、是否使用渐进式加载等。
progressBlock: SDWebImageDownloaderProgressBlock 类型,一个进度回调块,当图片下载过程中更新进度时会被调用,传递已下载数据量和总数据量。
completedBlock: SDWebImageCompletionBlock 类型,一个完成回调块,当图片加载成功、失败或被取消时会被调用。它接收以下参数:
image: 加载成功的UIImage对象,或在加载失败时为nil。
data: 图片对应的原始NSData对象,可能用于进一步处理或存储。
error: 如果加载失败,包含错误信息的NSError对象;否则为nil。
cacheType: 表示图片来源于哪种缓存类型的枚举值(内存、磁盘或无缓存)。
finished: 标记此次加载是否真正完成,即使加载失败,也可能因为有占位图而标记为YES。
imageURL: 当前请求的URL,与函数参数中的imageURL相同,提供上下文信息。
*/
- (void)sd_internalSetImageWithURL:(nullable NSURL *)urlplaceholderImage:(nullable UIImage *)placeholderoptions:(SDWebImageOptions)optionsoperationKey:(nullable NSString *)operationKeysetImageBlock:(nullable SDSetImageBlock)setImageBlockprogress:(nullable SDWebImageDownloaderProgressBlock)progressBlockcompleted:(nullable SDExternalCompletionBlock)completedBlock;

上面这个函数主要目的是为控件如UIImageView、UIButton设置图片,处理从指定URL加载图片的相关逻辑,包括异步下载、缓存检查、下载操作、过渡动画应用以及完成回调的触发。

函数里有个[self sd_cancelImageLoadOperationWithKey:validOperationKey];方法,是根据key取消当前操作,针对于比如cell中的UIImageView被复用的时候,首先需要根据key取消当前imageView上的下载或者缓存操作

3.SDWebManager层

在上面的方法中又会调用到SDWebmanager层的loadImageWithURL: options: context: progress: completed:方法,其具体实现细节如下:

/*** 加载指定URL的图像,支持多种选项、上下文以及进度和完成回调。** @param url 图像URL,可为`nil`或无效。如果传入的是`NSString`类型,会自动转换为`NSURL`。若非`NSURL`类型,则置为`nil`。* @param options 加载选项,如缓存策略、重试失败图片等。* @param context 上下文信息字典,包含如回调队列、下载器、解码器等自定义设置。* @param progressBlock 图像加载进度回调,返回已加载的数据长度和总长度。* @param completedBlock 图像加载完成回调,返回加载结果(成功或失败)、图像、数据、缓存类型、URL等信息。* @return 返回一个`SDWebImageCombinedOperation`对象,可用于取消或查询加载状态。*/
- (SDWebImageCombinedOperation *)loadImageWithURL:(nullable NSURL *)urloptions:(SDWebImageOptions)optionscontext:(nullable SDWebImageContext *)contextprogress:(nullable SDImageLoaderProgressBlock)progressBlockcompleted:(nonnull SDInternalCompletionBlock)completedBlock {// 确保已完成回调块不为空,否则调用此方法无意义NSAssert(completedBlock != nil, @"若要预取图像,请使用-[SDWebImagePrefetcher prefetchURLs]方法");// 处理URL类型,允许传入NSString并转换为NSURLif ([url isKindOfClass:NSString.class]) {url = [NSURL URLWithString:(NSString *)url];}// 防止传入非NSURL类型的无效URL(如NSNull),将其置为nilif (![url isKindOfClass:NSURL.class]) {url = nil;}// 创建一个新的图像加载操作对象SDWebImageCombinedOperation *operation = [SDWebImageCombinedOperation new];operation.manager = self;// 检查URL是否在失败列表中(已标记为失败的URL)BOOL isFailedUrl = NO;if (url) {SD_LOCK(_failedURLsLock);isFailedUrl = [self.failedURLs containsObject:url];SD_UNLOCK(_failedURLsLock);}// 根据URL、选项和上下文预处理并生成最终加载结果SDWebImageOptionsResult *result = [self processedResultForURL:url options:options context:context];// 若URL无效或已标记为失败且不开启重试选项,直接调用完成回调并返回操作对象if (url.absoluteString.length == 0 || (!(options & SDWebImageRetryFailed) && isFailedUrl)) {NSString *description = isFailedUrl ? @"Image url is blacklisted" : @"Image url is nil";NSInteger code = isFailedUrl ? SDWebImageErrorBlackListed : SDWebImageErrorInvalidURL;[self callCompletionBlockForOperation:operation completion:completedBlock error:[NSError errorWithDomain:SDWebImageErrorDomain code:code userInfo:@{NSLocalizedDescriptionKey : description}] queue:result.context[SDWebImageContextCallbackQueue] url:url];return operation;}// 将当前操作添加到正在运行的操作列表中SD_LOCK(_runningOperationsLock);[self.runningOperations addObject:operation];SD_UNLOCK(_runningOperationsLock);// 开始从缓存加载图像,后续步骤如下:// 1. 查询缓存中的图像(可能涉及原始图像和经过变换的图像,取决于是否有变换器)// 2. 缓存未命中时,下载数据和图像// 3. 存储图像到缓存(可能同时存储原始图像和经过变换的图像)// 4. 对图像进行CPU变换(若有变换器)[self callCacheProcessForOperation:operation url:url options:result.options context:result.context progress:progressBlock completed:completedBlock];return operation;
}

上面方法的主要功能就是异步加载指定URL的图像资源
1.首先确保completedBlock不为空,以便在加载完成后执行回调。同时,对传入的url参数进行类型检查和转换,确保其为有效的NSURL对象。
2.接着实例化一个SDWebImageCombinedOperation对象,用于管理整个图片加载过程,并将其与当前SDWebImageManager实例关联。
3.如果URL有效,检查其是否在失败URL列表中(即之前加载该URL时失败且未被重试)。这一步有助于避免重复尝试已知失败的请求。根据URL、选项和上下文,生成一个SDWebImageOptionsResult对象,其中包含了实际应用的加载选项和处理后的上下文信息。
4.如果URL为空、无效或者已标记为失败且未开启SDWebImageRetryFailed选项,立即调用完成回调,报告错误并返回操作对象。将当前加载操作加入到正在运行的操作列表中,便于全局管理所有正在进行的加载任务。
5.最后调用callCacheProcessForOperation方法,开始从缓存查找图像,如果缓存未命中,则启动网络下载,并在下载完成后存储图像到缓存。在整个过程中,可能会根据上下文中的变换器对图像进行CPU处理。同时,如果提供了progressBlock,会在加载过程中定期回调更新加载进度。

上面代码最后调用到callCacheProcessForOperation:方法,这个方法的具体实现如下:

- (void)callCacheProcessForOperation:(nonnull SDWebImageCombinedOperation *)operationurl:(nonnull NSURL *)urloptions:(SDWebImageOptions)optionscontext:(nullable SDWebImageContext *)contextprogress:(nullable SDImageLoaderProgressBlock)progressBlockcompleted:(nullable SDInternalCompletionBlock)completedBlock {// 获取要使用的图像缓存实例id<SDImageCache> imageCache = context[SDWebImageContextImageCache];if (!imageCache) {imageCache = self.imageCache; // 如果上下文中没有指定缓存,则使用默认缓存}// 获取查询缓存类型SDImageCacheType queryCacheType = SDImageCacheTypeAll; // 默认查询所有缓存类型if (context[SDWebImageContextQueryCacheType]) {queryCacheType = [context[SDWebImageContextQueryCacheType] integerValue]; // 如果上下文中指定了查询缓存类型,则使用指定值}// 判断是否需要查询缓存(根据options判断)BOOL shouldQueryCache = !SD_OPTIONS_CONTAINS(options, SDWebImageFromLoaderOnly); // 如果选项中不包含仅从加载器加载,则需要查询缓存if (shouldQueryCache) {// 计算转换后的缓存键NSString *key = [self cacheKeyForURL:url context:context];// 为避免循环引用,对operation进行弱引用@weakify(operation);// 向缓存发起查询请求operation.cacheOperation = [imageCache queryImageForKey:key options:options context:context cacheType:queryCacheType completion:^(UIImage * _Nullable cachedImage, NSData * _Nullable cachedData, SDImageCacheType cacheType) {// 恢复对operation的强引用@strongify(operation);// 检查操作是否已被取消或不存在if (!operation || operation.isCancelled) {// 用户取消了操作,调用完成回调并移除运行中的操作[self callCompletionBlockForOperation:operation completion:completedBlock error:[NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorCancelled userInfo:@{NSLocalizedDescriptionKey : @"Operation cancelled by user during querying the cache"}] queue:context[SDWebImageContextCallbackQueue] url:url];[self safelyRemoveOperationFromRunning:operation];return;} else if (!cachedImage) {// 缓存中未找到图像// 获取原始缓存键NSString *originKey = [self originalCacheKeyForURL:url context:context];// 判断是否有可能在原始缓存中找到图像(未经过转换)BOOL mayInOriginalCache = ![key isEqualToString:originKey];if (mayInOriginalCache) {// 有可能在原始缓存中找到图像,尝试查询原始缓存[self callOriginalCacheProcessForOperation:operation url:url options:options context:context progress:progressBlock completed:completedBlock];return;}}// 缓存查询成功或无原始缓存查询必要,继续执行下载过程[self callDownloadProcessForOperation:operation url:url options:options context:context cachedImage:cachedImage cachedData:cachedData cacheType:cacheType progress:progressBlock completed:completedBlock];}];} else {// 用户选择不查询缓存,直接跳转至下载过程[self callDownloadProcessForOperation:operation url:url options:options context:context cachedImage:nil cachedData:nil cacheType:SDImageCacheTypeNone progress:progressBlock completed:completedBlock];}
}

这里主要是判断任务是否该走缓存查询,或者直接下载。如果是缓存查询,就进入SDImageCache里面进行缓存查询,且在此处理缓存结果的回调。否则就调用callDownloadProcessForOperation进入下一步判断。
继续往下说loadImageWithURL中的工作流程,下面一个重要的方法就是queryCacheOperationForKey(),在SDImageCache里查询是否存在缓存的图片

4.SDWebCache层

queryCacheOperationForKey()方法的实现细节如下:

@param key           缓存键值,用于标识特定的图片资源。如果为nil,则直接返回nil并调用完成回调。* @param options       查询选项,如是否仅解码第一帧、是否检查动画图片类匹配等。* @param context       上下文信息,包含如回调队列、期望的动画图片类、存储缓存类型等。* @param queryCacheType 待查询的缓存类型(内存、磁盘或两者皆查)。* @param doneBlock     完成回调,传递查询结果(图片、数据、缓存类型)。** @return SDImageCacheToken对象,表示正在进行的查询操作。可用于取消查询。*/
-(nullable SDImageCacheToken *)queryCacheOperationForKey:(nullable NSString *)keyoptions:(SDImageCacheOptions)optionscontext:(nullable SDWebImageContext *)contextcacheType:(SDImageCacheType)queryCacheTypedone:(nullable SDImageCacheQueryCompletionBlock)doneBlock {// 如果键值为空,则立即返回nil并调用完成回调if (!key) {if (doneBlock) {doneBlock(nil, nil, SDImageCacheTypeNone);}return nil;}// 非法缓存类型,直接返回nil并调用完成回调if (queryCacheType == SDImageCacheTypeNone) {if (doneBlock) {doneBlock(nil, nil, SDImageCacheTypeNone);}return nil;}// 首先检查内存缓存...UIImage *image;if (queryCacheType != SDImageCacheTypeDisk) {image = [self imageFromMemoryCacheForKey:key]; // 获取内存缓存中的图片}// 若内存缓存命中,则根据选项进一步处理图片if (image) {if (options & SDImageCacheDecodeFirstFrameOnly) { // 仅解码第一帧选项// 确保静态图片(即非动画图片)if (image.sd_imageFrameCount > 1) {#if SD_MACimage = [[NSImage alloc] initWithCGImage:image.CGImage scale:image.scale orientation:kCGImagePropertyOrientationUp];#elseimage = [[UIImage alloc] initWithCGImage:image.CGImage scale:image.scale orientation:image.imageOrientation];#endif}} else if (options & SDImageCacheMatchAnimatedImageClass) { // 检查动画图片类匹配选项// 根据上下文中的期望动画图片类进行检查Class animatedImageClass = image.class;Class desiredImageClass = context[SDWebImageContextAnimatedImageClass];if (desiredImageClass && ![animatedImageClass isSubclassOfClass:desiredImageClass]) {image = nil; // 不匹配则清空图片}}}// 如果仅查询内存缓存或已从内存缓存获取到图片且不需要查询内存数据,则直接返回结果并调用完成回调BOOL shouldQueryMemoryOnly = (queryCacheType == SDImageCacheTypeMemory) || (image && !(options & SDImageCacheQueryMemoryData));if (shouldQueryMemoryOnly) {if (doneBlock) {doneBlock(image, nil, SDImageCacheTypeMemory);}return nil;}// 初始化查询操作令牌并设置相关属性SDCallbackQueue *queue = context[SDWebImageContextCallbackQueue];SDImageCacheToken *operation = [[SDImageCacheToken alloc] initWithDoneBlock:doneBlock];operation.key = key;operation.callbackQueue = queue;// 判断是否需要同步查询磁盘缓存// 1. 内存缓存命中且要求同步查询内存数据// 2. 内存缓存未命中且要求同步查询磁盘数据BOOL shouldQueryDiskSync = ((image && options & SDImageCacheQueryMemoryDataSync) ||(!image && options & SDImageCacheQueryDiskDataSync));// 定义磁盘数据查询闭包NSData* (^queryDiskDataBlock)(void) = ^NSData* {@synchronized (operation) {if (operation.isCancelled) {return nil;}}return [self diskImageDataBySearchingAllPathsForKey:key]; // 从磁盘缓存中获取图片数据};// 定义磁盘图片解析闭包,根据磁盘数据生成UIImage对象UIImage* (^queryDiskImageBlock)(NSData*) = ^UIImage*(NSData* diskData) {@synchronized (operation) {if (operation.isCancelled) {return nil;}}UIImage *diskImage;if (image) { // 图片已从内存缓存获取,仅需根据数据生成UIImagediskImage = image;} else if (diskData) { // 从磁盘缓存获取到数据,需解析为UIImageBOOL shouldCacheToMomery = YES;if (context[SDWebImageContextStoreCacheType]) {SDImageCacheType cacheType = [context[SDWebImageContextStoreCacheType] integerValue];shouldCacheToMomery = (cacheType == SDImageCacheTypeAll || cacheType == SDImageCacheTypeMemory);}CGSize thumbnailSize = CGSizeZero;NSValue *thumbnailSizeValue = context[SDWebImageContextImageThumbnailPixelSize];if (thumbnailSizeValue != nil) {#if SD_MACthumbnailSize = thumbnailSizeValue.sizeValue;#elsethumbnailSize = thumbnailSizeValue.CGSizeValue;#endif}if (thumbnailSize.width > 0 && thumbnailSize.height > 0) {// 查询生成缩略图的全尺寸缓存键时,不应将缩略图写回全尺寸内存缓存shouldCacheToMomery = NO;}// 特殊情况:当用户针对同一URL在列表中查询图片时,为了避免多次解码和写入相同图像对象到磁盘缓存,这里再次检查内存缓存if (shouldCacheToMomery && self.config.shouldCacheImagesInMemory) {diskImage = [self.memoryCache objectForKey:key];}// 如果内存缓存未命中,才进行解码if (!diskImage) {diskImage = [self diskImageForKey:key data:diskData options:options context:context];if (shouldCacheToMomery && diskImage && self.config.shouldCacheImagesInMemory) {NSUInteger cost = diskImage.sd_memoryCost;[self.memoryCache setObject:diskImage forKey:key cost:cost]; // 将解析后的图片写入内存缓存}}}return diskImage;};// 根据是否同步查询磁盘缓存执行相应操作if (shouldQueryDiskSync) {__block NSData* diskData;__block UIImage* diskImage;dispatch_sync(self.ioQueue, ^{diskData = queryDiskDataBlock();diskImage = queryDiskImageBlock(diskData);});// 同步查询时,直接在当前线程调用完成回调if (doneBlock) {doneBlock(diskImage, diskData, SDImageCacheTypeDisk);}} else {dispatch_async(self.ioQueue, ^{NSData* diskData = queryDiskDataBlock();UIImage* diskImage = queryDiskImageBlock(diskData);@synchronized (operation) {if (operation.isCancelled) {return;}}// 异步查询时,在指定回调队列或主线程异步调用完成回调if (doneBlock) {[(queue ?: SDCallbackQueue.mainQueue) async:^{// 在从IO队列切换至主线程的过程中可能被取消,因此在此处再次检查是否已取消@synchronized (operation) {if (operation.isCancelled) {return;}}doneBlock(diskImage, diskData, SDImageCacheTypeDisk);}];}});}return operation;
}

上面的代码很长,但总结下来就是做了三件事:
1.先检查键值是否为空并且图片类型是否合法,如果不为空并且合法的情况再执行下面的操作,否则直接执行回调
2.在内存中查找缓存,如果仅查询内存缓存或已从内存缓存获取到图片且不需要查询内存数据直接执行回调函数,如果没有查到的话接着执行下面的操作
3.在磁盘中查找缓存,这里分两种情况,第一种是在内存中已经查到缓存但是还接着要在磁盘中继续查找,第二种是在内存中没有查到缓存,在磁盘中尝试寻找。如果在磁盘中找到缓存的话并且内存中也有缓存的话则直接解析图片,如果在内存中没有的话则将数据传给内存再解析图片。如果磁盘中也没找到缓存的话则先回调再根据operation的属性值决定是否执行下载任务,如果执行下载操作的话则调用SDWebManager层callDownloadProcessForOperation:方法进行下载前的一些配置,其实现细节如下:

// 定义一个方法,用于调用图片下载过程。参数包括当前的图片组合操作(SDWebImageCombinedOperation)、图片URL、加载选项、上下文信息、已缓存的图片、已缓存的数据、缓存类型、进度回调和完成回调。
- (void)callDownloadProcessForOperation:(nonnull SDWebImageCombinedOperation *)operationurl:(nonnull NSURL *)urloptions:(SDWebImageOptions)optionscontext:(SDWebImageContext *)contextcachedImage:(nullable UIImage *)cachedImagecachedData:(nullable NSData *)cachedDatacacheType:(SDImageCacheType)cacheTypeprogress:(nullable SDImageLoaderProgressBlock)progressBlockcompleted:(nullable SDInternalCompletionBlock)completedBlock {// 标记缓存操作结束@synchronized (operation) {operation.cacheOperation = nil; // 清空当前操作的缓存操作引用}// 获取图片加载器,优先使用上下文提供的,否则使用默认的imageLoader属性id<SDImageLoader> imageLoader = context[SDWebImageContextImageLoader];if (!imageLoader) {imageLoader = self.imageLoader;}// 判断是否应该从网络下载图片BOOL shouldDownload = !SD_OPTIONS_CONTAINS(options, SDWebImageFromCacheOnly); // 不仅限于缓存加载shouldDownload &= (!cachedImage || options & SDWebImageRefreshCached); // 缓存图片不存在或要求刷新缓存时shouldDownload &= (![self.delegate respondsToSelector:@selector(imageManager:shouldDownloadImageForURL:)] || [self.delegate imageManager:self shouldDownloadImageForURL:url]); // 委托方法允许下载shouldDownload &= ([imageLoader respondsToSelector:@selector(canRequestImageForURL:options:context:)] ? [imageLoader canRequestImageForURL:url options:options context:context] : [imageLoader canRequestImageForURL:url]); // 图片加载器支持该请求if (shouldDownload) { // 需要下载图片的情况if (cachedImage && options & SDWebImageRefreshCached) { // 缓存存在且要求刷新缓存// 通知已找到缓存图片并尝试重新下载以更新缓存[self callCompletionBlockForOperation:operation completion:completedBlock image:cachedImage data:cachedData error:nil cacheType:cacheType finished:YES queue:context[SDWebImageContextCallbackQueue] url:url];// 将缓存图片传递给图片加载器,以便比较远程图片是否与缓存图片一致SDWebImageMutableContext *mutableContext;if (context) {mutableContext = [context mutableCopy];} else {mutableContext = [NSMutableDictionary dictionary];}mutableContext[SDWebImageContextLoaderCachedImage] = cachedImage;context = [mutableContext copy]; // 更新上下文}// 弱引用operation,防止循环引用@weakify(operation);// 发起图片加载请求,传入URL、选项、上下文、进度回调和完成回调operation.loaderOperation = [imageLoader requestImageWithURL:url options:options context:context progress:progressBlock completed:^(UIImage *downloadedImage, NSData *downloadedData, NSError *error, BOOL finished) {@strongify(operation); // 强引用恢复operationif (!operation || operation.isCancelled) { // 操作已被用户取消// 调用完成回调,报告操作取消[self callCompletionBlockForOperation:operation completion:completedBlock error:[NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorCancelled userInfo:@{NSLocalizedDescriptionKey : @"Operation cancelled by user during sending the request"}] queue:context[SDWebImageContextCallbackQueue] url:url];} else if (cachedImage && options & SDWebImageRefreshCached && [error.domain isEqualToString:SDWebImageErrorDomain] && error.code == SDWebImageErrorCacheNotModified) { // 图片刷新命中NSURLCache,无需调用完成回调// Do nothing} else if ([error.domain isEqualToString:SDWebImageErrorDomain] && error.code == SDWebImageErrorCancelled) { // 下载操作被用户取消// 调用完成回调,报告操作取消[self callCompletionBlockForOperation:operation completion:completedBlock error:error queue:context[SDWebImageContextCallbackQueue] url:url];} else if (error) { // 下载过程中出现错误// 调用完成回调,报告错误[self callCompletionBlockForOperation:operation completion:completedBlock error:error queue:context[SDWebImageContextCallbackQueue] url:url];// 根据条件决定是否将失败的URL加入黑名单BOOL shouldBlockFailedURL = [self shouldBlockFailedURLWithURL:url error:error options:options context:context];if (shouldBlockFailedURL) {SD_LOCK(self->_failedURLsLock);[self.failedURLs addObject:url];SD_UNLOCK(self->_failedURLsLock);}} else { // 图片下载成功// 如果允许重试失败,移除失败URLif (options & SDWebImageRetryFailed) {SD_LOCK(self->_failedURLsLock);[self.failedURLs removeObject:url];SD_UNLOCK(self->_failedURLsLock);}// 调用图片转换过程,处理下载成功的图片[self callTransformProcessForOperation:operation url:url options:options context:context originalImage:downloadedImage originalData:downloadedData cacheType:SDImageCacheTypeNone finished:finished completed:completedBlock];}if (finished) { // 图片加载(无论成功或失败)完成[self safelyRemoveOperationFromRunning:operation]; // 从运行中的操作列表中移除当前操作}}];} else if (cachedImage) { // 仅使用缓存图片的情况// 调用完成回调,报告使用缓存图片[self callCompletionBlockForOperation:operation completion:completedBlock image:cachedImage data:cachedData error:nil cacheType:cacheType finished:YES queue:context[SDWebImageContextCallbackQueue] url:url];// 从运行中的操作列表中移除当前操作[self safelyRemoveOperationFromRunning:operation];} else { // 未找到缓存图片且不允许下载的情况// 调用完成回调,报告未找到图片[self callCompletionBlockForOperation:operation completion:completedBlock image:nil data:nil error:nil cacheType:SDImageCacheTypeNone finished:YES queue:context[SDWebImageContextCallbackQueue] url:url];// 从运行中的操作列表中移除当前操作[self safelyRemoveOperationFromRunning:operation];}
}

这个函数首先根据传入的options参数判断是否需要下载图片,如果存在缓存图片并且请求要求刷新缓存,先通知客户端已找到缓存图片并开始重新下载以更新缓存,然后将缓存图片信息添加到上下文中,以便图片加载器在下载过程中进行比较。接着发起图片下载请求,并将返回值存到operation.loaderOperation中以便进行后续的取消操作。

5.SDWebImageDownloader层

对于下载图片的部分,会用到downloadImageWithURL:方法,其具体实现细节如下:

@param url               图片资源的URL。作为回调字典的键,不能为nil。若为nil,则立即调用完成回调并返回nil。* @param options           下载选项,如重试次数、超时时间、HTTP头处理等。* @param context           上下文信息,包含如解码选项、缓存键过滤器、代理等。* @param progressBlock     下载进度回调,传递已下载数据大小和总大小。* @param completedBlock    下载完成回调,传递图片、数据、错误信息以及是否从缓存加载。** @return SDWebImageDownloadToken对象,表示正在进行的下载任务。可用于取消下载。*/
- (nullable SDWebImageDownloadToken *)downloadImageWithURL:(nullable NSURL *)urloptions:(SDWebImageDownloaderOptions)optionscontext:(nullable SDWebImageContext *)contextprogress:(nullable SDWebImageDownloaderProgressBlock)progressBlockcompleted:(nullable SDWebImageDownloaderCompletedBlock)completedBlock {// 如果URL为nil,立即调用完成回调并返回nilif (url == nil) {if (completedBlock) {NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorInvalidURL userInfo:@{NSLocalizedDescriptionKey : @"Image url is nil"}];completedBlock(nil, nil, error, YES);}return nil;}// 初始化下载操作取消令牌(用于取消关联的下载操作)id downloadOperationCancelToken;// 根据上下文中的缓存键过滤器生成缓存键(用于唯一标识图片资源)id<SDWebImageCacheKeyFilter> cacheKeyFilter = context[SDWebImageContextCacheKeyFilter];NSString *cacheKey;if (cacheKeyFilter) {cacheKey = [cacheKeyFilter cacheKeyForURL:url];} else {cacheKey = url.absoluteString;}// 根据上下文和下载选项生成解码选项SDImageCoderOptions *decodeOptions = SDGetDecodeOptionsFromContext(context, [self.class imageOptionsFromDownloaderOptions:options], cacheKey);// 加锁保护操作字典SD_LOCK(_operationsLock);// 从操作字典中获取与URL关联的下载操作(如果存在)NSOperation<SDWebImageDownloaderOperation> *operation = [self.URLOperations objectForKey:url];// 检查是否可以复用现有下载操作(未完成且未取消)BOOL shouldNotReuseOperation;if (operation) {@synchronized (operation) {shouldNotReuseOperation = operation.isFinished || operation.isCancelled;}} else {shouldNotReuseOperation = YES;}if (shouldNotReuseOperation) {// 创建新的下载操作operation = [self createDownloaderOperationWithUrl:url options:options context:context];if (!operation) {SD_UNLOCK(_operationsLock);if (completedBlock) {NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorInvalidDownloadOperation userInfo:@{NSLocalizedDescriptionKey : @"Downloader operation is nil"}];completedBlock(nil, nil, error, YES);}return nil;}// 设置操作完成时从操作字典移除该操作@weakify(self);operation.completionBlock = ^{@strongify(self);if (!self) {return;}SD_LOCK(self->_operationsLock);[self.URLOperations removeObjectForKey:url];SD_UNLOCK(self->_operationsLock);};// 将新创建的下载操作添加到操作字典[self.URLOperations setObject:operation forKey:url];// 在提交到操作队列之前添加进度和完成回调,避免操作完成前回调未设置导致的问题downloadOperationCancelToken = [operation addHandlersForProgress:progressBlock completed:completedBlock decodeOptions:decodeOptions];// 将下载操作添加到下载队列[self.downloadQueue addOperation:operation];} else {// 复用已存在的下载操作并附加新的回调@synchronized (operation) {downloadOperationCancelToken = [operation addHandlersForProgress:progressBlock completed:completedBlock decodeOptions:decodeOptions];}}SD_UNLOCK(_operationsLock);// 创建并初始化下载任务令牌,关联下载操作、URL、请求及取消令牌SDWebImageDownloadToken *token = [[SDWebImageDownloadToken alloc] initWithDownloadOperation:operation];token.url = url;token.request = operation.request;token.downloadOperationCancelToken = downloadOperationCancelToken;return token;
}

downloadImageWithURL方法返回的是一个SDWebImageDownloadToken类型的token,这么做的目的是可以在取消的回调中及时取消下载操作。上面代码中的关键是 operation = [self createDownloaderOperationWithUrl:url options:options context:context];这行代码中的createDownloaderOperationWithUrl:方法执行的是真正执行网络请求的下载操作,在执行完成后返回一个operation通过operation进行后面的操作最后返回一个token。

相关文章:

【iOS】——SDWebImage源码学习

文章目录 一、SDWebIamge简介二、SDWebImage的调用流程SDWebImage源码分析1.UIImageViewWebCache层2.UIViewWebCache层3.SDWebManager层4.SDWebCache层5.SDWebImageDownloader层 一、SDWebIamge简介 SDWebImage是iOS中提供图片加载的第三方库&#xff0c;可以给UIKit框架中的控…...

树和二叉树(一)

一、树 非线性数据结构&#xff0c;在实际场景中&#xff0c;存在一对多&#xff0c;多对多的情况。 树( tree&#xff09;是n (n>0&#xff09;个节点的有限集。当n0时&#xff0c;称为空树。 在任意一个非空树中&#xff0c;有如下特点。 1.有且仅有一个特定的称为根的节点…...

RAID 磁盘阵列及RAID配置实战

目录 一.RAID磁盘阵列介绍 二.常用的RAID磁盘阵列的介绍 1.RAID 0 &#xff08;条带化存储&#xff09; 2.RAID 1&#xff08;镜像存储&#xff09; 3.RAID 5 4.RAID 6 5.RAID 10&#xff08;先做镜像&#xff0c;再做条带&#xff09; 6.RAID 01 &#xff08;先做条带…...

listpack

目录 为什么有listpack? listpack结构 listpack的节点entry 长度length encoding编码方式 listpack的API 1.创建listpack 2.遍历操作 正向遍历 反向遍历 3.查找元素 4.插入/替换/删除元素 总结 为什么有listpack? ziplist是存储在连续内存空间&#xff0c;节省…...

Web3与社会契约:去中心化治理的新模式

在数字化时代&#xff0c;技术不断为我们提供新的可能性&#xff0c;而Web3技术作为一种基于区块链的创新&#xff0c;正在引领着互联网的下一波变革。它不仅改变了我们的经济模式和商业逻辑&#xff0c;还对社会契约和权力结构提出了全新的挑战和思考。本文将深入探讨Web3的基…...

实体类List重复校验

如果实体类有多个属性&#xff0c;并且你希望根据所有属性的组合来进行重复校验&#xff0c;你可以考虑以下几种方法&#xff1a; 使用集合存储已经出现过的实体对象&#xff1a; 将每个实体对象放入一个 Set 中进行重复校验。在 Set 中元素的比较可以使用自定义的 equals 方法…...

loadash常用的函数方法

Lodash是一个JavaScript实用工具库&#xff0c;提供了很多常用的函数方法来简化开发过程。以下是一些常用的Lodash函数方法&#xff1a; _.map(array, iteratee)&#xff1a;对数组中的每个元素应用一个函数&#xff0c;并返回结果数组。_.filter(collection, predicate)&…...

【零基础入门TypeScript】模块

目录 内部模块 内部模块语法&#xff08;旧&#xff09; 命名空间语法&#xff08;新&#xff09; 两种情况下生成的 JavaScript 是相同的 外部模块 选择模块加载器 定义外部模块 句法 例子 文件&#xff1a;IShape.js 文件&#xff1a;Circle.js 文件&#xff1a;…...

Scala 之数组

可变数组与不可变数组 import scala.collection.mutable.ArrayBuffer// 不可变数组。 长度不可变&#xff0c;但是元素的值可变 object Demo1 {def main(args: Array[String]): Unit {// 不可变数组定义方式// 未初始化有默认值 Int > 0val arr1 : Array[Int] new Arr…...

【Phytium】飞腾D2000 UEFI/EDK2 适配 RTC(IIC SD3077)

文章目录 0. env1. 软件2. 硬件 10. 需求1. 硬件2. 软件 20. DatasheetCPURTC 30. 调试步骤1. 硬件环境搭建2. UEFI 开发环境搭建3. 修改步骤1. UEFI 中使能RTC驱动、配置RTC信息等1.1 使能RTC驱动1.2 修改RTC对应的IIC配置信息1.3 解决驱动冲突1.4 验证波形 2. 修改对应RTC驱动…...

如何利用纯前端技术,实现一个网页版视频编辑器?

纯网页版视频编辑器 一、前言二、功能实现三、所需技术四、部分功能实现4.1 素材预设4.2 多轨道剪辑 一、前言 介绍&#xff1a;本篇文章打算利用纯前端的技术&#xff0c;来实现一个网页版的视频编辑器。为什么突然想做一个这么项目来呢&#xff0c;主要是最近一直在利用手机…...

stm32实现hid键盘

前面的cubelmx项目配置参考 stm32实现hid鼠标-CSDN博客https://blog.csdn.net/anlog/article/details/137814494?spm1001.2014.3001.5502两个项目的配置完全相同。 代码 引用 键盘代码&#xff1a; 替换hid设备描述符 先屏蔽鼠标设备描述符 替换为键盘设备描述符 修改宏定…...

【单例模式】饿汉式、懒汉式、静态内部类--简单例子

单例模式是⼀个单例类在任何情况下都只存在⼀个实例&#xff0c;构造⽅法必须是私有的、由⾃⼰创建⼀个静态变量存储实例&#xff0c;对外提供⼀个静态公有⽅法获取实例。 目录 一、单例模式 饿汉式 静态内部类 懒汉式 反射可以破坏单例 道高一尺魔高一丈 枚举 一、单例…...

windows关闭Windows Search功能

我发现windows最恶心的功能就是自动更新和搜索。自动更新就是个毒瘤&#xff0c;得到了全世界的人讨厌。 而搜索功能难用、慢和造成卡死&#xff0c;根本没有存在的必要。并且他的windows search filter服务会在每次移动大量文件后建立索引&#xff0c;持续的占用cpu和硬盘的资…...

政安晨:【深度学习神经网络基础】(九)—— 在深度学习神经网络反向传播训练中理解梯度

目录 简述 理解梯度 什么是梯度 计算梯度 政安晨的个人主页&#xff1a;政安晨 欢迎 &#x1f44d;点赞✍评论⭐收藏 收录专栏: 政安晨的机器学习笔记 希望政安晨的博客能够对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出指正&#xff01; 简述 在深度…...

免费的 ChatGPT、GPTs、AI绘画(国内版)

&#x1f525;博客主页&#xff1a;白云如幻❤️感谢大家点赞&#x1f44d;收藏⭐评论✍️ ChatGPT3.5、GPT4.0、GPTs、AI绘画相信对大家应该不感到陌生吧&#xff1f;简单来说&#xff0c;GPT-4技术比之前的GPT-3.5相对来说更加智能&#xff0c;会根据用户的要求生成多种内容甚…...

UniApp 微信小程序:在 onLaunch 中等待异步方法执行完成后,再调用页面中的接口

最近遇到了一个问题&#xff1a;在 App.vue 中的 onLaunch 中调用登录接口时&#xff0c;由于异步登录尚未完成就调用了 index 页面的接口&#xff0c;导致 token 异常。如何确保页面在 App 中的 onLaunch 执行完毕后再继续执行呢&#xff1f; 在网上查阅了一些资料&#xff0c…...

【招贤纳士】长期有效

【招贤纳士】长期有效&#xff0c;有意者联系 一、SLAM算法工程师工作内容&#xff1a;任职资格&#xff1a; 二、规划算法工程师工作内容&#xff1a;任职资格&#xff1a; 三、感知算法工程师岗位职责&#xff1a;任职要求&#xff1a;加分项&#xff1a; 四、传感器系统工程…...

华为配置静态ARP示例

华为配置静态ARP示例 组网图形 图1 配置静态ARP组网图 静态ARP简介配置注意事项组网需求配置思路操作步骤配置文件相关信息 静态ARP简介 静态ARP表项是指网络管理员手工建立IP地址和MAC地址之间固定的映射关系。 正常情况下网络中设备可以通过ARP协议进行ARP表项的动态学习&…...

LRTimelapse for Mac:专业延时摄影视频制作利器

LRTimelapse for Mac是一款专为Mac用户设计的延时摄影视频制作软件&#xff0c;它以其出色的性能和丰富的功能&#xff0c;成为摄影爱好者和专业摄影师的得力助手。 LRTimelapse for Mac v6.5.4中文激活版下载 这款软件提供了直观易用的界面&#xff0c;用户可以轻松上手&#…...

Java复习第十九天学习笔记(Cookie、Session登录),附有道云笔记链接

【有道云笔记】十九 4.7 Cookie、Session登录 https://note.youdao.com/s/VwpxfEim 一、会话技术简介 生活中会话 我&#xff1a; 小张&#xff0c;你会跳小苹果码&#xff1f; 小张&#xff1a; 会&#xff0c;怎么了&#xff1f; 我&#xff1a; 公司年会上要表演节目&a…...

HBase的数据模型与架构

官方文档&#xff1a;Apache HBase – Apache HBase™ Homehttps://hbase.apache.org/ 一、HBase概述 1.概述 HBase的技术源自Google的BigTable论文&#xff0c;HBase建立在Hadoop之上&#xff0c;是一个高可靠性、高性能、面向列、可伸缩的分布式存储系统&#xff0c;用于…...

卷积神经网络的结构组成与解释(详细介绍)

文章目录 前言 1、卷积层 2、激活层 3、BN层 4、池化层 5、FC层&#xff08;全连接层&#xff09; 6、损失层 7、Dropout层 8、优化器 9、学习率 10、卷积神经网络的常见结构 前言 卷积神经网络是以卷积层为主的深层网络结构&#xff0c;网络结构包括有卷积层、激活层、BN层、…...

使用ansible的连通性检查的关键参数

使用ansible进行ping命令的时候发现有些不通 ansible cba -m ping 10.1.1.1 | FAILED! > {"msg": "Using a SSH password instead of a key is not possible because Host Key checking is enabled and sshpass does not support this. Please add this h…...

Jenkins用maven风格build报错解决过程记录

1、Jenkins2.453新建项目&#xff0c;构建风格选的maven 2、自由风格构建部署没有任何问题&#xff0c;但是maven风格build一直失败&#xff0c;报错如下图 3、解决方案&#xff1a;在系统管理–系统配置–Maven项目配置&#xff0c;删除全局MAVEN_OPT的路径信息&#xff0c;…...

Web3.0与AI的交融:开启智能互联网新时代

目前有140 多个 Web3 AI 概念项目&#xff0c;覆盖了基础设施、数据、预测市场、计算与算力、教育、DeFi & 跨链、安全、NFT & 游戏 & 元宇宙、搜索引擎、社交 & 创作者经济、AI 聊天机器人、DID & 消息传递、治理、医疗、交易机器人等诸多方向。持续关注…...

自动化_Ansible学习笔记

文章目录 Ansible 介绍配置文件主配置文件优先级 常用命令ansible-playbook ad-hocinventory 主机清单Playbook 剧本YAML格式 ansible 模块介绍模块对应功能Commands modules(命令模块)command (命令)shell (外壳) 官方帮助文档 模块索引playbook 开头示例系统类setup (收集远程…...

用于密集视觉冲击的紧凑三维高斯散射Compact 3D Gaussian Splatting For Dense Visual SLAM

Compact 3D Gaussian Splatting For Dense Visual SLAM 用于密集视觉冲击的紧凑三维高斯散射 Tianchen Deng 邓天辰11Yaohui Chen 陈耀辉11Leyan Zhang 张乐妍11Jianfei Yang 杨健飞22Shenghai Yuan 圣海元22Danwei Wang 王丹伟22Weidong Chen 陈卫东11 Abstract 摘要 …...

ChatGPT揭秘:高效论文写作的秘籍

ChatGPT无限次数:点击直达 ChatGPT揭秘&#xff1a;高效论文写作的秘籍 引言 在当今信息爆炸的时代&#xff0c;高效撰写论文对于研究者和学术工作者至关重要。随着人工智能技术的不断发展&#xff0c;ChatGPT等自然语言处理工具的出现为论文写作提供了全新的思路和工具。本文…...

电脑不能上网,宽带调制解调器出现问题如何处理

目录 一、问题说明 二、解决方案 一、问题说明 内网的设备能互联&#xff0c;内网的各个设备无法连外网。 电脑在检测网络时&#xff0c;出现以下提示&#xff1a; 二、解决方案 首先重启光猫&#xff08;我们是电信宽带&#xff09;。 如果还是有问题&#xff0c;再重启…...

旅游网站自己怎么做/免费建站哪个比较好

FAT32数据恢复原理结构&#xff1a;MBR-DBR-DBR2-FAT1-FAT2-DIR-DATA重点&#xff1a;FAT32数据恢复原理由于FAT与FDT对数据的链式存储管理的&#xff0c;可以通过链式搜索查找。只有FAT表和FDT配合使用&#xff0c;才能可以统一管理整个磁盘文件,也就可以准确定位被删除的文件…...

做文字头像的网站/郑州专业seo哪家好

本文主要内容包括以下几个方面&#xff1a; ● 唯品会实时平台现状&#xff1b;● Flink在唯品会的实践&#xff1b;● Flink On K8S&#xff1b;● 最新项目进展。 一、唯品会实时平台现状 目前在唯品会&#xff0c;实时平台并不是一个统一的计算框架&#xff0c;而是包括…...

做内衣模特接广告网站/seo研究中心骗局

欢迎关注个人公众号摸鱼范式 目录STA基本概念PrimeTimeSTA概念时序弧单元延迟建立时间和保持时间时序路径时钟域操作条件参考书目网络课程《数字集成电路静态时序分析基础》的笔记 地址&#xff1a;https://www.iccollege.cn/portal/courseDetail/193.mooc STA基本概念 PrimeT…...

o2o网站建设渠道/10条重大新闻

神图压镇&#xff01;​ExpandableListActivity1.生成一个新的activity要继承ExpandableListActivitypublic class MainActivity extends ExpandableListActivity2.为一级列表和二级列表&#xff0c;一级主activity分别创建布局文件。2.1main的布局文件中主要是要声明总的acivi…...

信息推广网站点不开的那种怎么做/郑州seo优化顾问热狗

站立会议信息:今天组内的成员报告了自己的进度&#xff0c;每个人都汇报了自己所完成的情况&#xff0c;例如系统的登录界面的整天模板、进入登录界面后用户选择的界面以及程序大体数据库的连接&#xff0c;每个人都在努力完成自己那一部分的工作。 站立会议照片&#xff1a; 任…...

企业自助网站建设/seo顾问服务 品达优化

人们有时好像喜欢故意使C语言的术语难以理解。比如说new操作符&#xff08;new operator&#xff09;和new操作&#xff08;operator new&#xff09;的区别。 当你写这样的代码&#xff1a; string *ps new string("Memory Management"); 你使用的new是new操作符…...