SDWebImage源码分析

@[TOC](SDWebImage源码分析

  • 基本框架
    • UIKit层
    • 工具层
      • SDWebImageManager
      • SDImageCache
      • SDWebImageDownloader
      • downloadImageWithURL

基本框架

SDWebImage作为一个著名的iOS图像加载库,其源码主要包括以下几个核心部分:

  • 图片下载管理:SDWebImageManager 是整个库的核心管理类,负责协调图片下载、缓存和处理。
  • 缓存模块:SDImageCache 提供了内存缓存和磁盘缓存功能。
  • 图片解码与处理:图片下载后,SDWebImage会对图片采取渐进式解码的方式加载图片。
  • UIImageView分类:UIImageView (WebCache) 提供了一系列便捷的接口,如sd_setImageWithURL
  • 多线程与异步处理:SDWebImage内部大量运用了GCD(Grand Central Dispatch)来处理并发任务,确保图片下载、缓存读写以及图像处理都是在后台线程执行,避免阻塞主线程影响UI流畅度。

通过网上的资料我简单的了解到该框架主要源码可以分为两部分:UIKit层和工具层
UIKit层的** UIImageView+WebCache和UIView+WebCache**为外部提供了下载图片的接口 ;
工具层的SDWImageManager是该库的核心类;它通过协调SDWebImageCache(负责缓存方面的工作),SDWebImageDownLoader(负责下载方面的工作) 实现任务的管理 ;

UIKit层(负责接收下载参数)和工具层(负责下载操作和缓存)

UIKit层

这部分源码主要包括UIImageView+WebCache和UIView+WebCache,由于UIImageView+WebCache中的接口方法的实现依赖于UIView+WebCache提供的接口 ,我们先从UIimageView+WebCahce来了解一下接口的功能和实现 ;

UIImageView+WebCache:

@interface UIImageView (WebCache)


- (void)sd_setImageWithURL:(nullable NSURL *)url NS_REFINED_FOR_SWIFT;


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


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


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


- (void)sd_setImageWithURL:(nullable NSURL *)url
                 completed:(nullable SDExternalCompletionBlock)completedBlock;


- (void)sd_setImageWithURL:(nullable NSURL *)url
          placeholderImage:(nullable UIImage *)placeholder
                 completed:(nullable SDExternalCompletionBlock)completedBlock NS_REFINED_FOR_SWIFT;


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


- (void)sd_setImageWithURL:(nullable NSURL *)url
          placeholderImage:(nullable UIImage *)placeholder
                   options:(SDWebImageOptions)options
                  progress:(nullable SDImageLoaderProgressBlock)progressBlock
                 completed:(nullable SDExternalCompletionBlock)completedBlock;

/**
 * 通过指定的URL设置imageView的`image`属性,同时支持占位图、自定义下载选项以及上下文参数。
 *
 * 图片下载过程为异步,并且会进行缓存。
 *
 * @param url            图片的URL地址。
 * @param placeholder    图片请求完成前要设置的初始占位图片。
 * @param options        下载图片时使用的选项。有关所有可能的选项值,请参见`SDWebImageOptions`。
 * @param context        一个包含不同选项以执行特定改变或过程的上下文对象,参考`SDWebImageContextOption`。此上下文用于持有`options`枚举无法容纳的额外对象。
 * @param progressBlock  图片正在下载时调用的进度回调块。
 *                       @note 进度块在后台队列中执行。
 * @param completedBlock 操作完成时调用的回调块。此块没有返回值,其第一个参数为请求的UIImage对象;若发生错误,该参数为nil,第二个参数可能包含NSError对象;第三个参数是一个布尔值,表示图片是从本地缓存还是网络获取的;第四个参数是原始的图片URL。
 */
- (void)sd_setImageWithURL:(nullable NSURL *)url
          placeholderImage:(nullable UIImage *)placeholder
                   options:(SDWebImageOptions)options
                   context:(nullable SDWebImageContext *)context
                  progress:(nullable SDImageLoaderProgressBlock)progressBlock
                 completed:(nullable SDExternalCompletionBlock)completedBlock;

通过观察ImageView+Cache.m中各个方法的实现,发现前面所有的接口都调用了

  • (void)sd_setImageWithURL:(nullable NSURL *)url
    placeholderImage:(nullable UIImage *)placeholder
    options:(SDWebImageOptions)options
    context:(nullable SDWebImageContext *)context
    progress:(nullable SDImageLoaderProgressBlock)progressBlock
    completed:(nullable SDExternalCompletionBlock)completedBlock;

这个方法的实现又依赖于UIView+Cache中的

  • (nullable id)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

该方法的实现如下:

- (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 {
    // 确保context为不可变对象,并为缺失的context提供默认值
    context = context ? [context copy] : [NSDictionary dictionary];
    // 设置唯一操作键用于追踪操作
    NSString *validOperationKey = context[SDWebImageContextSetImageOperationKey] ?: NSStringFromClass([self class]);
    self.sd_latestOperationKey = validOperationKey;
    // 取消当前视图上与该键关联的任何现有加载操作
    [self sd_cancelImageLoadOperationWithKey:validOperationKey];
    self.sd_imageURL = url;

    // 确定SDWebImageManager实例,优先使用自定义管理器或共享管理器
    SDWebImageManager *manager = context[SDWebImageContextCustomManager] ?: [SDWebImageManager sharedManager];
    // 避免循环引用
    if (context[SDWebImageContextCustomManager]) {
        SDWebImageMutableContext *mutableContext = [context mutableCopy];
        mutableContext[SDWebImageContextCustomManager] = nil;
        context = [mutableContext copy];
    }

    // 弱引用缓存逻辑,根据配置决定是否触发
    BOOL shouldUseWeakCache = manager.imageCache isKindOfClass:[SDImageCache class]] ? ((SDImageCache *)manager.imageCache).config.shouldUseWeakMemoryCache : NO;
    // 如果不需要延迟占位图,且使用弱引用缓存,则预先触发一次内存缓存查询以确保同步逻辑
    if (!(options & SDWebImageDelayPlaceholder) && shouldUseWeakCache) {
        NSString *key = [manager cacheKeyForURL:url context:context];
        [((SDImageCache *)manager.imageCache) imageFromMemoryCacheForKey:key];
        // 立即显示占位图
        dispatch_main_async_safe(^{
            [self sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock cacheType:SDImageCacheTypeNone imageURL:url];
        });
    }

    // 初始化加载操作
    id <SDWebImageOperation> operation = nil;
    if (url) {
        // 重置进度条
        NSProgress *imageProgress = objc_getAssociatedObject(self, @selector(sd_imageProgress));
        if (imageProgress) {
            imageProgress.totalUnitCount = 0;
            imageProgress.completedUnitCount = 0;
        }
        
        // UI相关的指示器逻辑
        #if SD_UIKIT || SD_MAC
        [self sd_startImageIndicator];
        id<SDWebImageIndicator> imageIndicator = self.sd_imageIndicator;
        #endif
        
        // 合并进度回调,包含UI更新和用户自定义的进度回调
        SDImageLoaderProgressBlock combinedProgressBlock = ^(...){ ... };

        // 使用SDWebImageManager加载图片
        operation = [manager loadImageWithURL:url options:options context:context progress:combinedProgressBlock completed:^(UIImage *image, NSData *data, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
            // 一系列逻辑处理,包括进度更新、指示器停止、图片设置、完成回调
            // ...
            // 根据条件设置图片,调用自定义设置图片block,处理过渡效果等
            // ...
            // 调用完成回调
            callCompletedBlockClosure();
        }];

        // 记录当前操作
        [self sd_setImageLoadOperation:operation forKey:validOperationKey];
    } else {
        // 处理URL为空的情况,停止指示器,调用完成回调
        #if SD_UIKIT || SD_MAC
        [self sd_stopImageIndicator];
        #endif
        if (completedBlock) {
            NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorInvalidURL userInfo:@{NSLocalizedDescriptionKey : @"Image url is nil"}];
            dispatch_main_async_safe(^{
                completedBlock(nil, nil, error, SDImageCacheTypeNone, YES, url);
            });
        }
    }

    return operation;
}

在这个方法中总体做了下面这几件事:
1.根据key取消当前的操作

[self sd_cancelImageLoadOperationWithKey:validOperationKey];

从SDOperationsDictionary中根据key获取到operation执行cancle,并从SDOperationsDictionary中remove这个key对应的operation。

2.将url作为属性绑定到UIView上

objc_setAssociatedObject(self, &imageURLKey, url, OBJC_ASSOCIATION_RETAIN_NONATOMIC);

3.根据URL,通过SDWebImageManager的loadImageWithURL方法加载图片

id <SDWebImageOperation> operation = [manager loadImageWithURL:url options:options progress:combinedProgressBlock completed:^(UIImage *image, NSData *data, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
	...
}

4.将上一步得到的operation,存入SDOperationsDictionary中

[self sd_setImageLoadOperation:operation forKey:validOperationKey];

下面看几个我比较在意的点:

    // 弱引用缓存逻辑,根据配置决定是否触发
    BOOL shouldUseWeakCache = manager.imageCache isKindOfClass:[SDImageCache class]] ? ((SDImageCache *)manager.imageCache).config.shouldUseWeakMemoryCache : NO;

这里的弱引用缓存逻辑通过网络查询大概知道什么意思,但还是直接用别人的话讲了:

弱引用缓存逻辑是指在SDWebImage库中处理图片缓存的一种策略,特别涉及到内存缓存部分。通常,内存缓存在iOS开发中用于临时存储最近或频繁访问的数据,以便快速访问,提高应用性能。内存缓存中的对象会被强引用,这意味着只要缓存中的对象被引用,它们就不会被自动释放,即使在低内存情况下也可能占用大量内存资源。
而弱引用缓存逻辑是一种优化措施,它允许缓存中的图片对象被弱引用(weak reference)而不是强引用。这意味着一旦图片不在其他地方被强引用(例如,不再显示在UI上),即使它们还在缓存中,也可以被系统自动回收以释放内存。这在内存管理上更加灵活和高效,尤其适用于内存紧张的场景,能减少内存泄漏的风险,并帮助应用避免因内存压力过大而被系统终止。
在SDWebImage中,通过判断SDImageCache实例的配置属性shouldUseWeakMemoryCache来决定是否启用弱引用缓存模式。如果启用,即使图片被缓存,当系统需要回收内存时,这些图片对象可以被适时释放,从而保证应用的内存使用更加合理和高效。不过,这也意味着被弱引用的图片在系统进行内存回收时可能更早消失,下次访问时可能需要重新从磁盘或网络加载。

    // 如果不需要延迟占位图,且使用弱引用缓存,则预先触发一次内存缓存查询以确保同步逻辑
    if (!(options & SDWebImageDelayPlaceholder) && shouldUseWeakCache) {
        NSString *key = [manager cacheKeyForURL:url context:context];
        [((SDImageCache *)manager.imageCache) imageFromMemoryCacheForKey:key];
        // 立即显示占位图
        dispatch_main_async_safe(^{
            [self sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock cacheType:SDImageCacheTypeNone imageURL:url];
        });
    }

预先触发一次内存缓存查询以确保同步逻辑,主要是为了优化用户体验并提高效率。但这里的作用更重要的是确认同步逻辑,更详细点就是

在某些情况下,特别是当使用弱引用缓存策略时,直接查询内存缓存可以帮助确保缓存状态与当前加载策略同步。弱引用缓存意味着对缓存对象的持有不保证其生命周期,因此即时查询可以确认预期的图片是否已经存在于缓存中,这对于后续的加载决策(比如是否需要从磁盘或网络加载)至关重要。

 // 使用SDWebImageManager加载图片
        operation = [manager loadImageWithURL:url options:options context:context progress:combinedProgressBlock completed:^(UIImage *image, NSData *data, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
            // 一系列逻辑处理,包括进度更新、指示器停止、图片设置、完成回调
            // ...
            // 根据条件设置图片,调用自定义设置图片block,处理过渡效果等
            // ...
            // 调用完成回调
            callCompletedBlockClosure();
        }];

这段代码内部细节没有详细描述,知道功能就行 ;

工具层

SDWebImageManager同时管理着SDIMageCache和SDWebImageDownloader两个类,SDWebImageManager首先会通过SDImagerCache类来查询缓存,如果缓存中没有找到对应的图片就会通过SDWebImageloader来下载图片,下载下来的图片会存入缓存,然后显示。在分析这三个类的前提下,我们先了解一下这个管理类 ;

SDWebImageManager

首先我们要先知道该类的属性:

@interface SDWebImageManager ()

// 使用SD_LOCK_DECLARE宏定义了一个锁,用于保护_failedURLs集合的并发访问,确保多线程环境下的数据一致性。
// 当需要修改或访问_failedURLs时,会先获取这个锁,操作完成后释放锁,以此来防止数据竞争问题。
SD_LOCK_DECLARE(_failedURLsLock); 

// 同样,这是另一个锁,用于保护_runningOperations集合的并发访问。
// _runningOperations集合用于跟踪当前所有正在执行的下载或缓存操作,确保对它的操作也是线程安全的。
SD_LOCK_DECLARE(_runningOperationsLock); 

// 属性声明:
// _imageCache:这是一个强引用的SDImageCache对象,负责图片的磁盘缓存和内存缓存。
//              SDImageCache是SDWebImage中用于存储和检索图片的核心组件,支持内存和磁盘两级缓存策略。
@property (strong, nonatomic, readwrite, nonnull) SDImageCache *imageCache;

// _imageLoader:一个遵循SDImageLoader协议的对象,负责实际的图片加载任务。
//               这个协议定义了如何从网络或其他源加载图片的方法,SDWebImage内部提供了几个默认实现,如SDWebImageDownloader。
@property (strong, nonatomic, readwrite, nonnull) id<SDImageLoader> imageLoader;

// _failedURLs:这是一个可变的NSMutableSet,用于记录所有下载失败的图片URL。
//              当下载操作因错误而失败时,对应的URL会被加入此集合,这有助于实现重试逻辑或错误处理。
@property (strong, nonatomic, nonnull) NSMutableSet<NSURL *> *failedURLs;

// _runningOperations:包含所有正在进行的SDWebImageCombinedOperation对象的集合。
//                     SDWebImageCombinedOperation是一个复合操作,结合了从缓存读取和网络下载两个子操作,
//                     用于协调图片加载的整体流程。维护这个集合可以用来取消所有操作(如视图即将被销毁时)或监控当前的加载状态。
@property (strong, nonatomic, nonnull) NSMutableSet<SDWebImageCombinedOperation *> *runningOperations;

@end

简单的了解一下上面各个属性所负责的功能 ,之后的两个类都会用到 ;

这个类的方法很多,这里具体讲一个和功能实现相关的方法,也是从url网络下载图片的方法,同上面的- (void)sd_setImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder
options:(SDWebImageOptions)options
context:(nullable SDWebImageContext *)context
progress:(nullable SDImageLoaderProgressBlock)progressBlock
completed:(nullable SDExternalCompletionBlock)completedBlock;
其他的许多方法都依赖于这个方法 ;

url网络下载图片的方法:

- (SDWebImageCombinedOperation *)loadImageWithURL:(nullable NSURL *)url
                                          options:(SDWebImageOptions)options
                                          context:(nullable SDWebImageContext *)context
                                         progress:(nullable SDImageLoaderProgressBlock)progressBlock
                                        completed:(nonnull SDInternalCompletionBlock)completedBlock {
    
    // 确保completedBlock不为空,因为没有完成回调意味着无法处理加载结果
    NSAssert(completedBlock != nil, @"If you mean to prefetch the image, use -[SDWebImagePrefetcher prefetchURLs] instead");
    
    // 将可能传入的NSString类型的URL转换为NSURL,以防类型错误
    if ([url isKindOfClass:NSString.class]) {
        url = [NSURL URLWithString:(NSString *)url];
    }
    
    // 如果URL不是NSURL类型,则设为nil以避免后续错误
    if (![url isKindOfClass:NSURL.class]) {
        url = nil;
    }

    // 创建一个新的SDWebImageCombinedOperation实例,该操作组合了缓存查询和网络下载
    SDWebImageCombinedOperation *operation = [SDWebImageCombinedOperation new];
    operation.manager = self; // 关联操作与当前manager

    // 检查URL是否在失败列表中,使用锁保证线程安全
    BOOL isFailedUrl = NO;
    if (url) {
        SD_LOCK(_failedURLsLock);
        isFailedUrl = [self.failedURLs containsObject:url];
        SD_UNLOCK(_failedURLsLock);
    }

    // 处理options和context,生成最终的处理结果
    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);

    // 开始加载图片的流程,首先尝试从缓存中获取
    // 此步骤会根据是否有transformer(图片处理器)选择不同的加载路径
    [self callCacheProcessForOperation:operation url:url options:result.options context:result.context progress:progressBlock completed:completedBlock];

    // 返回操作实例,外部可以通过此操作取消加载等
    return operation;
}

这段方法首先进行了一系列的预检查和初始化,然后根据图片URL是否有效、是否需要重试失败的URL等条件,决定是否直接调用完成回调还是继续执行图片加载流程。它通过SDWebImageCombinedOperation来封装了缓存查询和网络下载的复合操作,并利用锁机制维护了failedURLs和runningOperations集合的线程安全性,确保了整个加载过程的高效和稳定。

这里用到了两个新方法:

// 处理options和context,生成最终的处理结果
   SDWebImageOptionsResult *result = [self processedResultForURL:url options:options context:context];
// 此步骤会根据是否有transformer(图片处理器)选择不同的加载路径
    [self callCacheProcessForOperation:operation url:url options:result.options context:result.context progress:progressBlock completed:completedBlock];

第一个方法负责返回url处理后的结果,第二个方法就真正进入了图片的加载流程了 ;

首先看第一个方法(返回已处理的操作结果,包括指定图像URL的操作和content)的实现部分:

- (SDWebImageOptionsResult *)processedResultForURL:(NSURL *)url options:(SDWebImageOptions)options context:(SDWebImageContext *)context {
    SDWebImageOptionsResult *result; // 初始化结果对象,用于存放处理后的选项和上下文
    
    // 创建一个可变的上下文字典,用于临时存储可能添加的默认处理器信息
    SDWebImageMutableContext *mutableContext = [SDWebImageMutableContext dictionary];
    
    // 检查并设置默认的图片转换器(Transformer),如果用户没有自定义的话
    if (!context[SDWebImageContextImageTransformer]) {
        id<SDImageTransformer> transformer = self.transformer; // 从manager中获取默认的图片转换器
        [mutableContext setValue:transformer forKey:SDWebImageContextImageTransformer]; // 添加到mutableContext中
    }
    
    // 类似地,检查并设置默认的缓存键过滤器(Cache Key Filter)
    if (!context[SDWebImageContextCacheKeyFilter]) {
        id<SDWebImageCacheKeyFilter> cacheKeyFilter = self.cacheKeyFilter; // 获取默认的缓存键过滤器
        [mutableContext setValue:cacheKeyFilter forKey:SDWebImageContextCacheKeyFilter]; // 添加到mutableContext中
    }
    
    // 检查并设置默认的缓存序列化器(Cache Serializer)
    if (!context[SDWebImageContextCacheSerializer]) {
        id<SDWebImageCacheSerializer> cacheSerializer = self.cacheSerializer; // 获取默认的缓存序列化器
        [mutableContext setValue:cacheSerializer forKey:SDWebImageContextCacheSerializer]; // 添加到mutableContext中
    }
    
    // 如果mutableContext中有新增的内容(即有默认处理器被添加)
    if (mutableContext.count > 0) {
        // 把用户传入的context也合并到mutableContext中,这样就包含了用户自定义和默认的所有处理器
        if (context) {
            [mutableContext addEntriesFromDictionary:context];
        }
        // 然后把mutableContext拷贝给context,确保后续操作不影响原始的context
        context = [mutableContext copy];
    }
    
    // 应用自定义的选项处理器(如果有的话),这个处理器可以进一步修改加载图片的选项和上下文
    if (self.optionsProcessor) {
        result = [self.optionsProcessor processedResultForURL:url options:options context:context];
    }
    
    // 如果没有自定义处理器,或者处理器没有返回结果,使用默认的选项和上下文创建结果对象
    if (!result) {
        result = [[SDWebImageOptionsResult alloc] initWithOptions:options context:context];
    }
    
    // 最终返回处理后的选项和上下文结果
    return result;
}

这个方法的主要目的是确保图片加载请求具有所有必需的处理逻辑(如图片转换、缓存键过滤、缓存序列化等),同时允许用户自定义覆盖这些默认行为。通过这种方式,SDWebImage提供了高度灵活和可定制的图片加载机制。

第二个方法(查询普通缓存进程)的实现:

// 查询正常缓存流程的方法
- (void)callCacheProcessForOperation:(nonnull SDWebImageCombinedOperation *)operation
                                 url:(nonnull NSURL *)url
                             options:(SDWebImageOptions)options
                             context:(nullable SDWebImageContext *)context
                            progress:(nullable SDImageLoaderProgressBlock)progressBlock
                           completed:(nullable SDInternalCompletionBlock)completedBlock {
    
    // 获取用于查询的图片缓存,默认使用manager的imageCache,但如果context中指定了则优先使用那个
    id<SDImageCache> imageCache = context[SDWebImageContextImageCache] ?: self.imageCache;
    
    // 获取查询缓存的类型,默认为全部缓存(内存+磁盘),但如果context中指定了则使用指定类型
    SDImageCacheType queryCacheType = context[SDWebImageContextQueryCacheType] ? [context[SDWebImageContextQueryCacheType] integerValue] : SDImageCacheTypeAll;
    
    // 判断是否需要查询缓存,如果options中没有指定仅从loader加载,则需要查询缓存
    BOOL shouldQueryCache = !(options & SDWebImageFromLoaderOnly);
    
    if (shouldQueryCache) {
        // 计算转换后的缓存键,用于查询缓存
        NSString *key = [self cacheKeyForURL:url context:context];
        
        // 使用弱引用避免循环引用,确保operation在block执行完后可以被正确释放
        @weakify(operation);
        
        // 查询缓存
        operation.cacheOperation = [imageCache queryImageForKey:key options:options context:context cacheType:queryCacheType completion:^(UIImage * _Nullable cachedImage, NSData * _Nullable cachedData, SDImageCacheType cacheType) {
            @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 && ![key isEqualToString:[self originalCacheKeyForURL:url context:context]]) {
                [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];
    }
}

// 使用弱引用避免循环引用,确保operation在block执行完后可以被正确释放
@weakify(operation);
这个地方的循环引用,从operation.manager = self; // 关联操作与当前manager可以明白 ;

// 继续下载流程,无论图片是否从缓存中获取到
[self callDownloadProcessForOperation:operation url:url options:options context:context cachedImage:cachedImage cachedData:cachedData cacheType:cacheType progress:progressBlock completed:completedBlock];
这个地方的逻辑是先从缓存中寻找,但即使寻找到,也要通过网络下载更新图片或者后面再进行判断 ;

从第二个方法再深入一下,我们可以看到上面只是对缓存进行了查找,直到最后调用的函数才开始了url下载 :- (void)callDownloadProcessForOperation:(nonnull SDWebImageCombinedOperation *)operation
url:(nonnull NSURL *)url
options:(SDWebImageOptions)options
context:(SDWebImageContext *)context
cachedImage:(nullable UIImage *)cachedImage
cachedData:(nullable NSData *)cachedData
cacheType:(SDImageCacheType)cacheType
progress:(nullable SDImageLoaderProgressBlock)progressBlock
completed:(nullable SDInternalCompletionBlock)completedBlock

方法实现:

- (void)callDownloadProcessForOperation:(nonnull SDWebImageCombinedOperation *)operation
                                    url:(nonnull NSURL *)url
                                options:(SDWebImageOptions)options
                                context:(SDWebImageContext *)context
                            cachedImage:(nullable UIImage *)cachedImage
                             cachedData:(nullable NSData *)cachedData
                              cacheType:(SDImageCacheType)cacheType
                               progress:(nullable SDImageLoaderProgressBlock)progressBlock
                              completed:(nullable SDInternalCompletionBlock)completedBlock {

    // 标记缓存操作结束,解除可能存在的循环引用
    @synchronized (operation) {
        operation.cacheOperation = nil;
    }
    
    // 获取图片加载器,优先使用context中的,否则使用manager的默认加载器
    id<SDImageLoader> imageLoader = context[SDWebImageContextImageLoader] ?: self.imageLoader;
    
    // 判断是否应该从网络下载图片
    BOOL shouldDownload = !(options & SDWebImageFromCacheOnly);
    shouldDownload &= (!cachedImage || options & SDWebImageRefreshCached);
    shouldDownload &= ([self.delegate respondsToSelector:@selector(imageManager:shouldDownloadImageForURL:)] ? [self.delegate imageManager:self shouldDownloadImageForURL:url] : YES);
    if ([imageLoader respondsToSelector:@selector(canRequestImageForURL:options:context:)]) {
        shouldDownload &= [imageLoader canRequestImageForURL:url options:options context:context];
    } else {
        shouldDownload &= [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 = context ? [context mutableCopy] : [NSMutableDictionary dictionary];
            mutableContext[SDWebImageContextLoaderCachedImage] = cachedImage;
            context = [mutableContext copy];
        }
        
        // 使用弱引用避免block内的循环引用
        @weakify(operation);
        operation.loaderOperation = [imageLoader requestImageWithURL:url options:options context:context progress:progressBlock completed:^(UIImage *downloadedImage, NSData *downloadedData, NSError *error, BOOL finished) {
            @strongify(operation);
            // 根据操作状态、错误情况等条件,调用相应的完成回调
            // 包括操作取消、图片刷新命中、错误处理、下载成功后的转换流程等
            ...
            
            // 如果下载完成(无论成功还是失败),安全移除操作
            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];
        } else {
            [self callCompletionBlockForOperation:operation completion:completedBlock image:nil data:nil error:nil cacheType:SDImageCacheTypeNone finished:YES queue:context[SDWebImageContextCallbackQueue] url:url];
        }
        [self safelyRemoveOperationFromRunning:operation];
    }
}

此方法首先确保了缓存操作的引用被正确清理,然后基于传入的选项和上下文判断是否应当从网络下载图片。如果决定下载,它会调用图片加载器发起下载请求,并通过一系列逻辑处理下载过程中的各种情况,包括缓存刷新、错误处理、完成后的图片转换流程等。如果决定不下载(例如因为设置了仅从缓存加载或者代理方法不允许下载),则直接根据是否有缓存图片调用完成回调。整个方法通过弱引用和强引用的转换有效避免了潜在的循环引用问题。

SDImageCache

同理,先了解该类的属性:

这段代码是SDImageCache类的一个类别(Category)定义,用于声明一些私有的属性。下面是这段代码的详细注释:

```objective-c
// 静态字符串,用于存储默认的磁盘缓存目录路径。这是一个类级别的变量,所有SDImageCache实例共享。
static NSString * _defaultDiskCacheDirectory;

// 开始定义SDImageCache类的一个匿名类别,主要用于声明私有属性
@interface SDImageCache ()

#pragma mark - Properties

// 强引用类型,遵循SDMemoryCache协议的对象,用于存储内存中的图片缓存。
@property (nonatomic, strong, readwrite, nonnull) id<SDMemoryCache> memoryCache;

// 强引用类型,遵循SDDiskCache协议的对象,用于存储磁盘上的图片缓存。
@property (nonatomic, strong, readwrite, nonnull) id<SDDiskCache> diskCache;

// 拷贝属性,存储SDImageCache的配置对象,包含了诸如缓存大小限制、缓存路径等配置信息。
@property (nonatomic, copy, readwrite, nonnull) SDImageCacheConfig *config;

// 拷贝属性,存储磁盘缓存的具体路径。
@property (nonatomic, copy, readwrite, nonnull) NSString *diskCachePath;

// 强引用类型,一个GCD的队列,用于执行I/O相关的操作,如磁盘读写,确保这些操作在后台线程执行,不阻塞主线程。
@property (nonatomic, strong, nonnull) dispatch_queue_t ioQueue;

@end

其实实现部分最重要的一个函数- (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key options:(SDImageCacheOptions)options context:(nullable SDWebImageContext *)context cacheType:(SDImageCacheType)queryCacheType done:(nullable SDImageCacheQueryCompletionBlock)doneBlock
用与查询缓存:

```objective-c
// 定义查询缓存操作的方法,接受键、查询选项、上下文和完成回调作为参数,可能返回一个缓存查询令牌
- (nullable SDImageCacheToken *)queryCacheOperationForKey:(nullable NSString *)key 
                                                   options:(SDImageCacheOptions)options 
                                                  context:(nullable SDWebImageContext *)context 
                                             cacheType:(SDImageCacheType)queryCacheType 
                                                done:(nullable SDImageCacheQueryCompletionBlock)doneBlock {
    // 参数有效性检查:如果键为空,则直接回调失败并返回nil
    if (!key) {
        if (doneBlock) {
            doneBlock(nil, nil, SDImageCacheTypeNone); // 回调无结果,缓存类型为None
        }
        return nil;
    }
    
    // 如果查询的缓存类型无效(即None),也直接回调失败并返回nil
    if (queryCacheType == SDImageCacheTypeNone) {
        if (doneBlock) {
            doneBlock(nil, nil, SDImageCacheTypeNone);
        }
        return nil;
    }
    
    // 内存缓存查询
    UIImage *image;
    if (queryCacheType != SDImageCacheTypeDisk) { // 允许从内存查询时尝试获取图片
        image = [self imageFromMemoryCacheForKey:key];
    }
    
    // 图片处理逻辑:根据查询选项调整图片(如仅解码第一帧、匹配动画图类)
    // ...

    // 确定是否仅查询内存
    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;
    
    // 定义查询磁盘数据和图片的块
    NSData* (^queryDiskDataBlock)(void) = ^NSData* { ... }; // 查询磁盘数据
    UIImage* (^queryDiskImageBlock)(NSData*) = ^UIImage*(NSData* diskData) { ... }; // 根据数据生成图片
    
    // 异步或同步执行磁盘查询逻辑
    if (shouldQueryDiskSync) { // 同步查询
        // 执行同步查询逻辑
        ...
    } else { // 异步查询
        // 使用I/O队列异步执行查询,确保磁盘操作不阻塞主线程
        dispatch_async(self.ioQueue, ^{
            // 查询磁盘数据和生成图片的逻辑
            ...
            // 安全回调,确保操作未被取消
            if (doneBlock) {
                [(queue ?: SDCallbackQueue.mainQueue) async:^{
                    if (!operation.isCancelled) {
                        doneBlock(diskImage, diskData, SDImageCacheTypeDisk);
                    }
                }];
            }
        });
    }
    
    // 返回操作令牌,允许外部控制查询操作
    return operation;
}

SDWebImageDownloader

这个类负责图片的下载 ;
先了解属性:

```objc
@interface SDWebImageDownloader () <NSURLSessionTaskDelegate, NSURLSessionDataDelegate>

// 创建一个操作队列,用于管理下载任务的并发执行
@property (strong, nonatomic, nonnull) NSOperationQueue *downloadQueue;

// 用于存储下载操作的字典,键为图片的URL,值为对应的下载操作对象。
// 这样做可以方便地追踪和管理每个URL的下载过程。
@property (strong, nonatomic, nonnull) NSMutableDictionary<NSURL *, NSOperation<SDWebImageDownloaderOperation> *> *URLOperations;

// 存储HTTP头信息的字典,键是头部字段名,值是对应的字段值。
// 这些头部信息可能会在发起网络请求时用到,以便定制化请求或处理服务器认证等。
@property (strong, nonatomic, nullable) NSMutableDictionary<NSString *, NSString *> *HTTPHeaders;

// 定义一个NSURLSession实例,所有的数据下载任务都在这个会话中执行。
// NSURLSession提供了高级的网络通信功能,如后台传输服务、上传下载进度跟踪等。
@property (strong, nonatomic) NSURLSession *session;

@end

这段代码是关于SDWebImageDownloader类的一个匿名分类,用于声明一些私有属性和遵循的协议。它主要涉及到网络图片下载的相关配置和管理,包括任务队列、操作追踪、HTTP头信息及网络会话的管理。

首先我们先来看到下面这个方法

downloadImageWithURL

downloadImageWithURL方法返回的是一个SDWebImageDownloadToken类型的token,这么做的目的是,可以在取消的回调中及时取消下载操作 ;

这段代码是SDWebImage库中`SDWebImageDownloader`类的一个方法实现,用于根据指定的URL异步下载图片,并支持进度监控和完成回调。以下是详细的注释:

```objc
- (nullable SDWebImageDownloadToken *)downloadImageWithURL:(nullable NSURL *)url
                                                   options:(SDWebImageDownloaderOptions)options
                                                   context:(nullable SDWebImageContext *)context
                                                  progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
                                                 completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock {

    // 参数验证:URL不能为空,否则直接调用完成回调并传入错误信息
    if (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<SDWebImageCacheKeyFilter> cacheKeyFilter = context[SDWebImageContextCacheKeyFilter];
    NSString *cacheKey = cacheKeyFilter ? [cacheKeyFilter cacheKeyForURL:url] : url.absoluteString;
    SDImageCoderOptions *decodeOptions = SDGetDecodeOptionsFromContext(context, [self.class imageOptionsFromDownloaderOptions:options], cacheKey);
    
    // 线程安全:加锁处理当前下载操作字典
    SD_LOCK(_operationsLock);
    NSOperation<SDWebImageDownloaderOperation> *operation = [self.URLOperations objectForKey:url];
    
    // 判断是否应该复用或创建新的下载操作
    BOOL shouldNotReuseOperation = operation ? (@synchronized (operation) { operation.isFinished || operation.isCancelled || SDWebImageDownloaderOperationGetCompleted(operation) }) : 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) {
                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);
    
    // 创建并返回下载令牌,包含操作详情供外部控制或追踪
    SDWebImageDownloadToken *token = [[SDWebImageDownloadToken alloc] initWithDownloadOperation:operation];
    token.url = url;
    token.request = operation.request;
    token.downloadOperationCancelToken = downloadOperationCancelToken;
    return token;
}

这段方法主要实现了以下几个功能点:

  1. 参数校验:确保URL不为空,否则立即回调错误信息。
  2. 操作复用与创建:根据URL查找现有的下载操作,如果不存在或不可复用,则创建一个新的下载操作。
  3. 回调处理:为下载操作添加进度和完成的回调处理,确保新添加的回调能在操作执行过程中正确触发。
  4. 线程安全与锁机制:使用锁来保护操作字典和操作内部的状态更改,防止多线程下的数据竞争问题。
  5. 下载令牌:创建并返回一个SDWebImageDownloadToken对象,便于外部管理或取消下载操作。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/593437.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

C语言实战项目--贪吃蛇

贪吃蛇是久负盛名的游戏之一&#xff0c;它也和俄罗斯⽅块&#xff0c;扫雷等游戏位列经典游戏的行列。在编程语言的教学中&#xff0c;我们以贪吃蛇为例&#xff0c;从设计到代码实现来提升大家的编程能⼒和逻辑能⼒。 在本篇讲解中&#xff0c;我们会看到很多陌生的知识&…

牛角源码 | 【独立版】商城盲盒源码带uniapp(H5+小程序+APP三端)全开源

前端uniapp开源代码&#xff0c;可用HBuilder工具无限发行H5、小程序和打包app&#xff0c;后端PHP开源源码&#xff0c;支持二开。 内有安装搭建教程&#xff0c;轻松部署&#xff0c;搭建即可运营&#xff0c;内置永久免费更新地址&#xff0c;后续无忧升级。 下载地址&…

window 安装ai 基础环境(yolo8,训练推理等)

安装步骤: 1. python sdk 3.9以上&#xff1a;选择 3.9.13, 不知道为什么 3.9.0-0a等安装pytorch 不行。 2. 显卡驱动 可以使用驱动精灵 直接安装N 卡推荐 3. 安装机器学习套件CUDA cuda 安装在PyTorch 需要根 PyTorch版本一致&#xff0c;我的 win-srv 最高支持 12.1 …

专业渗透测试 Phpsploit-Framework(PSF)框架软件小白入门教程(五)

本系列课程&#xff0c;将重点讲解Phpsploit-Framework框架软件的基础使用&#xff01; 本文章仅提供学习&#xff0c;切勿将其用于不法手段&#xff01; 继续接上一篇文章内容&#xff0c;讲述如何进行Phpsploit-Framework软件的基础使用和二次开发。 在下面的图片中&#…

星辰考古:TiDB v1.0 再回首

“ 1.0 版本只是个开始&#xff0c;是新的起点&#xff0c;愿我们一路相扶&#xff0c;不负远途。 前言 TiDB 是 PingCAP 公司自主设计、研发的开源分布式关系型数据库。 近日&#xff0c;TiDB v8.0.0 DMR 发布&#xff0c;详细发版说明戳这里&#xff1a; https://docs.pingca…

2024年Q1季度防晒霜数据分析:个性化与差异化成为破局关键

五一出游期间&#xff0c;防晒必不可少&#xff0c;尤其是随着“颜值经济”的崛起&#xff0c;防晒霜成为了许多消费者出游时的必备选择。但随着“物理防晒”、“硬防晒”等概念的推出&#xff0c;防晒霜作为“化学防晒”的代表&#xff0c;在今年Q1季度线上市场表现受到影响。…

ICode国际青少年编程竞赛- Python-1级训练场-变量入门

ICode国际青少年编程竞赛- Python-1级训练场-变量入门 1、 a 4 Dev.turnRight() Dev.step(a)2、 a 4 Spaceship.step(a) Dev.step(a)3、 a 4 Dev.step(a) Dev.turnLeft() Dev.step(a)4、 a 5 Dev.step(a) Spaceship.step(a) Dev.step(a)5、 a 3 Dev.step(a) Dev.tur…

轨道交通巡检机器人的应用范围

在现代轨道交通系统的庞大网络中&#xff0c;无数的轨道、设备和设施交织在一起&#xff0c;如同一个精密的机器在高效运转。而在这背后&#xff0c;轨道交通巡检机器人正悄然登场&#xff0c;它们如同一个个智能的守护者&#xff0c;穿梭于各个场景之中。那么&#xff0c;这些…

【LeetCode:1235. 规划兼职工作 + 动态规划 + 二分查找】

&#x1f680; 算法题 &#x1f680; &#x1f332; 算法刷题专栏 | 面试必备算法 | 面试高频算法 &#x1f340; &#x1f332; 越难的东西,越要努力坚持&#xff0c;因为它具有很高的价值&#xff0c;算法就是这样✨ &#x1f332; 作者简介&#xff1a;硕风和炜&#xff0c;…

win10安装DHCP服务--用于2台机器之间搭建简易网络来进入目标机器修改配置

前言&#xff1a; 客户多了&#xff0c;往往会出现各种突发情况。 比如一个客户现场没有DHCP&#xff0c;没有显示器&#xff0c;键盘。 你只有一台笔记本的情况下要配置目标机器的网络。要如何配置&#xff1f;&#xff1f; 这时候就可以使用这篇博客提供的方式了。 Windows…

Android使用kts发布aar到JitPack仓库

Android使用kts发布aar到JitPack 之前做过sdk开发&#xff0c;需要将仓库上传到maven、JitPack或JCenter,但是JCenter已停止维护&#xff0c;本文是讲解上传到JitPack的方式,使用KTS语法&#xff0c;记录使用过程中遇到的一些坑.相信Groovy的方式是大家经常使用的&#xff0c;…

Codeforces Round 738 (Div. 2) D2. Mocha and Diana (Hard Version)

题目 思路&#xff1a; 性质1&#xff1a;能在结点u&#xff0c;v添加边的充要条件是u&#xff0c;v在第一个图和第二个图都不连通 性质2&#xff1a;可以添加的边数等于 n - 1 - max(m1, m2)&#xff0c;并且添加边的顺序不会影响结果&#xff08;即 边&#xff08;u&#x…

U.S. Student Information Center——全球学历认证

权威机构 中国留服中心认证&#xff0c;全称是中国教育部留学服务中心国(境)外学历学位认证。国&#xff08;境&#xff09;外学历学位认证工作旨在落实中华人民共和国的留学政策&#xff0c;即中国教育部留学服务中心根据归国留学生提出的申请&#xff0c;鉴别国(境)外学历的…

C语言——文件相关操作

2.什么是文件 3.文件的打开和关闭 4.文件的顺序读写 5.文件的随机读写 6.文本文件和二进制文件 7.文件读取结束的判定 8.文件缓冲区 一、文件相关介绍 1、为什么使用文件 文件用于永久存储数据。通过使用文件&#xff0c;我们可以在程序关闭后保存数据&#xff0c;以便将来…

XBoot:基于Spring Boot 2.x的一站式前后端分离快速开发平台

XBoot&#xff1a;基于Spring Boot 2.x的一站式前后端分离快速开发平台 摘要 随着信息技术的迅速发展&#xff0c;快速构建高质量、高可靠性的企业级应用成为了迫切需求。XBoot&#xff0c;作为一个基于Spring Boot 2.x的一站式前后端分离快速开发平台&#xff0c;通过整合微信…

AI-数学-高中56-成对数据统计-线性回归方程

原作者视频&#xff1a;【成对数据统计】【一数辞典】1线性回归方程_哔哩哔哩_bilibili 注意&#xff1a;高中只学线性回归。 最小二乘法&#xff08;残差和平方最小的直线、方差最小>拟合程度最好&#xff09;&#xff1a;

滑动验证码登陆测试编程示例

一、背景及原理 处理登录时的滑动验证码有两个难点&#xff0c;第一个是找到滑块需要移动的距离&#xff0c;第二个是模拟人手工拖动的轨迹。模拟轨迹在要求不是很严的情况下可以用先加速再减速拖动的方法&#xff0c;即路程的前半段加速度为正值&#xff0c;后半段为负值去模…

微搭低代码入门03页面管理

目录 1 创建页面2 页面布局3 页面跳转总结 上一篇我们介绍了应用的基本操作&#xff0c;掌握了应用的概念后接着我们需要掌握页面的常见操作。 1 创建页面 打开应用的编辑器&#xff0c;在顶部导航条点击创建页面图标 在创建页面的时候可以从空白新建&#xff0c;也可以使用模…

docker-本地私有仓库、harbor私有仓库部署与管理

一、本地私有仓库&#xff1a; 1、本地私有仓库简介&#xff1a; docker本地仓库&#xff0c;存放镜像&#xff0c;本地的机器上传和下载&#xff0c;pull/push。 使用私有仓库有许多优点&#xff1a; 节省网络带宽&#xff0c;针对于每个镜像不用每个人都去中央仓库上面去下…

JavaEE >> Spring Boot 日志

日志的作用以及什么是日志 日志就是为了当程序出错的时候&#xff0c;程序员们可以通过日志看到是哪部分出现错误了&#xff0c;为了发现和定位问题。当然&#xff0c;我们还可以通过日志实现一些功能&#xff0c;如下&#xff1a; 记录系统的操作⽇志&#xff0c;⽅便数据恢…
最新文章