用DeepSeek学源码 - SDWebImage 中是怎么运用单例模式的?有哪些关键点?对我们日常开发有哪些启示?

349 阅读3分钟

SDWebImage 中的单例模式应用

1. 核心单例类

SDWebImage 使用单例模式的 典型场景

  • SDImageCache: 全局管理内存与磁盘缓存
  • SDWebImageManager: 协调图片加载流程(下载器+缓存+解码器)
  • SDWebImageDownloader: 网络下载任务调度

2. 单例实现关键代码

SDImageCache 为例

SDImageCache.h

@interface SDImageCache : NSObject

// 单例访问类方法
+ (nonnull instancetype)sharedImageCache;

@end

SDImageCache.m

@implementation SDImageCache {
    NSCache *_memoryCache;         // 内存缓存
    NSString *_diskCachePath;      // 磁盘缓存路径
    NSFileManager *_fileManager;   // 文件操作
}

// 单例内核实现
+ (nonnull instancetype)sharedImageCache {
    static SDImageCache *instance;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        instance = [[self alloc] init];
    });
    return instance;
}

// 初始化资源
- (instancetype)init {
    self = [super init];
    if (self) {
        // 初始化内存缓存(容量可配置)
        _memoryCache = [[NSCache alloc] init];
        _memoryCache.name = @"com.sdwebimage.cache";
      
        // 初始化磁盘缓存目录
        _diskCachePath = [self defaultDiskCachePath];
        _fileManager = [NSFileManager new];
    }
    return self;
}
@end

单例模式核心关键点

1. 线程安全初始化

  • 使用 dispatch_once 确保单例在 多线程环境下唯一初始化
  • 避免 @synchronized 的性能损耗
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{ /* 初始化代码 */ });

2. 全局访问入口

  • 提供 +sharedInstance 类方法作为唯一访问点
  • 禁止用户通过 alloc/new/copy 创建新实例
//SDImageCache 没有这么实现,不过可以借鉴用来实现绝对单例
+ (instancetype)allocWithZone:(struct _NSZone *)zone {
    return [self sharedImageCache]; // 或断言禁止
}

3. 资源封装

  • 内存缓存:使用 NSCache(自动管理内存)
  • 磁盘缓存:指定沙盒目录存储(独立线程管理 IO)

4. 配置灵活性

  • 允许自定义初始化参数
@interface SDImageCache : NSObject

// 带命名空间的自定义初始化(允许创建多个单例)
+ (nonnull instancetype)sharedImageCacheWithNamespace:(NSString *)ns;

@end

对日常开发的启示

1. 单例适用场景

  • 全局唯一资源控制:如缓存管理、网络监控、日志系统
  • 高频访问的服务:如位置管理、音频播放控制器

2. 单例设计建议

关键点正确做法错误做法
线程安全使用 dispatch_once直接 if (!instance)
内存管理单例生命周期绑定整个应用手动过早释放
可测试性允许传递协议对象(依赖注入)硬编码依赖
可扩展性允许通过派生类或配置修改行为完全封闭内部实现

3. 单例优化示例:销毁重建

解决 多用户切换需重置缓存 的场景:

// 扩展单例支持销毁
@interface SDImageCache (Destroyable)

+ (void)destroySharedInstance;

@end

@implementation SDImageCache (Destroyable)

+ (void)destroySharedInstance {
    onceToken = 0; // 重置 token
    instance = nil; // 释放资源
}

@end

4. 替代方案:依赖注入

避免滥用单例,对于需要灵活替换的模块,采用协议 + 依赖注入:

// 定义缓存协议
@protocol ImageCacheProtocol <NSObject>
- (void)storeImage:(UIImage *)image forKey:(NSString *)key;
@end

// 通过属性配置而非全局访问
@interface MyViewController : UIViewController
@property (nonatomic, strong) id<ImageCacheProtocol> imageCache;
@end

总结

SDWebImage 单例技术的收益

  • 资源统一管控:集中管理内存和磁盘缓存
  • 性能高效:避免重复创建/销毁高频访问对象
  • 接口统一:简化调用复杂度

开发注意事项

  1. 单例不应持有过多外部对象(防止循环引用)
  2. 升级到 Swift 时优先考虑 static let + final class
  3. 合理划分职责(避免「上帝单例」)

(ps: 以上大部分内容使用 DeepSeek R 生成,作者有部分内容调整,如有任何不正确之处欢迎指正)