SDWebImage 框架中使用的知識(shí)點(diǎn)整理
<1>.NSCache
蘋果官方文檔中介紹NSCache是一種可變集合,用于臨時(shí)存儲(chǔ)在資源不足時(shí)可能被釋放的鍵值對(duì)。
和其他可變集合不同之處有:
1.NSCache在系統(tǒng)內(nèi)存很低時(shí),會(huì)自動(dòng)釋放一些對(duì)象(而且是沒(méi)有順序的,所以SDWebImage中還使用了NSMapTable作為緩存的備份,
當(dāng)在NSCache找不到時(shí),再去NSMapTable中查找)。
2.NSCache是線程安全的,所以SDWebImage中NSCache做增刪操作沒(méi)有加鎖。
3.與NSMutableDictionary對(duì)象不同,緩存不會(huì)復(fù)制放入其中的鍵對(duì)象。
在SDWebImage源碼文件SDImageCache.m 中,使用NSCache做內(nèi)存緩存。
// A memory cache which auto purge the cache on memory warning and support weak cache.
@interface SDMemoryCache <KeyType, ObjectType> : NSCache <KeyType, ObjectType>
@end
// Private
@interface SDMemoryCache <KeyType, ObjectType> ()
@property (nonatomic, strong, nonnull) SDImageCacheConfig *config;
@property (nonatomic, strong, nonnull) NSMapTable<KeyType, ObjectType> *weakCache; // strong-weak cache
@property (nonatomic, strong, nonnull) dispatch_semaphore_t weakCacheLock; // a lock to keep the access to `weakCache` thread-safe
- (instancetype)init NS_UNAVAILABLE;
- (instancetype)initWithConfig:(nonnull SDImageCacheConfig *)config;
@end
其中weakCache就是上文說(shuō)的用來(lái)做補(bǔ)充的緩存集合
通過(guò)重寫這四個(gè)方法,同步對(duì)weakCache做增刪操作
- (void)setObject:(ObjectType)obj forKey:(KeyType)key; // 0 cost
- (void)setObject:(ObjectType)obj forKey:(KeyType)key cost:(NSUInteger)g;
- (void)removeObjectForKey:(KeyType)key;
- (void)removeAllObjects;
為什么用NSMapTable做備份的原因上面已經(jīng)說(shuō)過(guò)了,可為什么不直接使用NSMapTable,而要使用NSCache?除了上面說(shuō)的線程安全,內(nèi)存緊張時(shí)會(huì)自動(dòng)釋放存儲(chǔ)對(duì)象,NSCache還有totalCostLimit和countLimit兩個(gè)屬性控制消耗限制和數(shù)量限制。
<2>. @autoreleasepool
自動(dòng)釋放池塊提供了一種機(jī)制,你放棄對(duì)象的所有權(quán)的同時(shí),可以避免立即釋放它(例如從方法返回對(duì)象時(shí))。Application Kit在事件循環(huán)的每個(gè)循環(huán)開始時(shí)在主線程上創(chuàng)建一個(gè)自動(dòng)釋放池,并在最后將其排出,從而釋放處理事件時(shí)生成的任何自動(dòng)釋放的對(duì)象。每個(gè)線程(包括主線程)都維護(hù)自己的NSAutoreleasePool對(duì)象堆棧。在創(chuàng)建新池時(shí),它們會(huì)添加到堆棧頂部。當(dāng)池被釋放時(shí),它們將從堆棧中刪除。自動(dòng)釋放的對(duì)象放置在當(dāng)前線程的頂部自動(dòng)釋放池中。當(dāng)線程終止時(shí),它會(huì)自動(dòng)排出與自身關(guān)聯(lián)的所有自動(dòng)釋放池。通常情況下你不需要?jiǎng)?chuàng)建自己的釋放池,除非應(yīng)用程序在事件循環(huán)中創(chuàng)建了大量臨時(shí)自動(dòng)釋放的對(duì)象,則創(chuàng)建“本地”自動(dòng)釋放池以幫助最小化峰值內(nèi)存占用量。
蘋果開發(fā)文檔中實(shí)例
NSArray *urls = <# An array of file URLs #>;
for (NSURL *url in urls) {
@autoreleasepool {
NSError *error;
NSString *fileContents = [NSString stringWithContentsOfURL:url
encoding:NSUTF8StringEncoding error:&error];
/* Process the string, creating and autoreleasing more objects. */
}
}
如果必須使用自動(dòng)釋放池塊之外的臨時(shí)對(duì)象,則可以通過(guò)向塊內(nèi)的對(duì)象發(fā)送保留消息,然后在塊之后將其發(fā)送自動(dòng)釋放,如此示例所示:
– (id)findMatchingObject:(id)anObject {
id match;
while (match == nil) {
@autoreleasepool {
/* Do a search that creates a lot of temporary objects. */
match = [self expensiveSearchForObject:anObject];
if (match != nil) {
[match retain]; /* Keep match around. */
}
}
}
return [match autorelease]; /* Let match go and return it. */
}
發(fā)送retain以在自動(dòng)釋放池中匹配并在自動(dòng)釋放池塊延長(zhǎng)匹配的生命周期后向其發(fā)送自動(dòng)釋放,并允許它在循環(huán)外接收消息并返回到findMatchingObject:的調(diào)用者。
實(shí)例:
源碼 SDWebImageCoderHelper.m
for (size_t i = 0; i < frameCount; i++) {
@autoreleasepool {
SDWebImageFrame *frame = frames[i];
float frameDuration = frame.duration;
CGImageRef frameImageRef = frame.image.CGImage;
NSDictionary *frameProperties = @{(__bridge NSString *)kCGImagePropertyGIFDictionary : @{(__bridge NSString *)kCGImagePropertyGIFDelayTime : @(frameDuration)}};
CGImageDestinationAddImage(imageDestination, frameImageRef, (__bridge CFDictionaryRef)frameProperties);
}
}
使用Clang將main.m轉(zhuǎn)換為main.cpp文件
終端輸入 clang -rewrite-objc main.m ,得到__AtAutoreleasePool的結(jié)構(gòu)
extern "C" __declspec(dllimport) void * objc_autoreleasePoolPush(void);
extern "C" __declspec(dllimport) void objc_autoreleasePoolPop(void *);
struct __AtAutoreleasePool {
__AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}
~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}
void * atautoreleasepoolobj;
};
從 objc_autoreleasePoolPush、objc_autoreleasePoolPop可以看出來(lái)是堆棧進(jìn)棧出棧的操作。高頻率的創(chuàng)建變量后,加入自動(dòng)釋放池,可以避免短時(shí)間之內(nèi)存暴漲
@autoreleasepool {
//進(jìn)棧
__AtAutoreleasePool __autoreleasepool = objc_autoreleasePoolPush();
// 新建對(duì)象加入自動(dòng)釋放池
//出棧
objc_autoreleasePoolPop(__autoreleasepool)
}
<3>.CGImageSourceRef
<4>.NSOperation,NSOperationQueue
<5>.dispatch_semaphore_t
源碼 SDWebImageDownloader.m
#define LOCK(lock) dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);
#define UNLOCK(lock) dispatch_semaphore_signal(lock);
@property (strong, nonatomic, nonnull) dispatch_semaphore_t operationsLock; // a lock to keep the access to `URLOperations` thread-safe
@property (strong, nonatomic, nonnull) dispatch_semaphore_t headersLock; // a lock to keep the access to `HTTPHeaders` thread-safe
_operationsLock = dispatch_semaphore_create(1);
_headersLock = dispatch_semaphore_create(1);
__weak typeof(self) wself = self;
operation.completionBlock = ^{
__strong typeof(wself) sself = wself;
if (!sself) {
return;
}
LOCK(sself.operationsLock);
[sself.URLOperations removeObjectForKey:url];
UNLOCK(sself.operationsLock);
};
- (void)cancel {
@synchronized(self) {
self.cancelled = YES;
if (self.cacheOperation) {
[self.cacheOperation cancel];
self.cacheOperation = nil;
}
if (self.downloadToken) {
[self.manager.imageDownloader cancel:self.downloadToken];
}
[self.manager safelyRemoveOperationFromRunning:self];
}
}
<6>.dispatch_main_async_safe
#ifndef dispatch_queue_async_safe
#define dispatch_queue_async_safe(queue, block)\
if (dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL) == dispatch_queue_get_label(queue)) {\
block();\
} else {\
dispatch_async(queue, block);\
}
#endif
#ifndef dispatch_main_async_safe
#define dispatch_main_async_safe(block) dispatch_queue_async_safe(dispatch_get_main_queue(), block)
#endif
<7>.
__weak typeof(self)weakSelf = self;
__strong typeof(weakSelf)strongSelf = weakSelf;
if (!strongSelf) {
return;
}
<8>.Objective-C Associated Objects
Associated Objects 主要用來(lái)給分類增加屬性
objc_setAssociatedObject(id _Nonnull object, const void * _Nonnull key, id _Nullable value, objc_AssociationPolicy policy)
objc_getAssociatedObject(id _Nonnull object, const void * _Nonnull key);
從名字可以看出兩個(gè)方法分別用于實(shí)現(xiàn)屬性的set和get。
objc_setAssociatedObject 用于給對(duì)象添加屬性,傳入 nil 可以移除已添加的屬性;
objc_getAssociatedObject 用于獲取屬性;
分類 UIImage + FLAnimatedImage 中源碼
- (FLAnimatedImage *)sd_FLAnimatedImage {
return objc_getAssociatedObject(self, @selector(sd_FLAnimatedImage));
}
- (void)setSd_FLAnimatedImage:(FLAnimatedImage *)sd_FLAnimatedImage {
objc_setAssociatedObject(self, @selector(sd_FLAnimatedImage), sd_FLAnimatedImage, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
其中@selector(sd_FLAnimatedImage) 就是方法參數(shù)中的key,SDWebImage 有些也使用類似 static char sd_FLAnimatedImage,兩種方式都可行,但是還是推薦使用@selector(),這樣不用另外在其他申明參數(shù),保持代碼命名的一致性。(其中objc_setAssociatedObject ()還可以使用_cmd作為key)
其中OBJC_ASSOCIATION_RETAIN_NONATOMIC 就是方法參數(shù)中的objc_AssociationPolicy policy
| Behavior | @property Equivalent | Description |
|---|---|---|
| OBJC_ASSOCIATION_ASSIGN | @property (assign) or @property (unsafe_unretained) | Specifies a weak reference to the associated |
| OBJC_ASSOCIATION_RETAIN_NONATOMIC | @property (nonatomic, strong) | Specifies a strong reference to the associated object. |
| OBJC_ASSOCIATION_COPY_NONATOMIC | @property (nonatomic, copy) | Specifies that the associated object is copied. |
| OBJC_ASSOCIATION_RETAIN | @property (atomic, strong) | pecifies a strong reference to the associated object. |
| OBJC_ASSOCIATION_COPY | @property (atomic, copy) | Specifies that the associated object is copied. |
iOS 動(dòng)態(tài)庫(kù)與靜態(tài)庫(kù)的區(qū)別 framework
dispatch_group_enter和dispatch_group_leave
FOUNDATION_EXPORT NSString * _Nonnull const SDWebImageDownloadStartNotification;
FOUNDATION_EXPORT NSString * _Nonnull const SDWebImageDownloadReceiveResponseNotification;
FOUNDATION_EXPORT NSString * _Nonnull const SDWebImageDownloadStopNotification;
FOUNDATION_EXPORT NSString * _Nonnull const SDWebImageDownloadFinishNotification;
NSString *const SDWebImageDownloadStartNotification = @"SDWebImageDownloadStartNotification";
NSString *const SDWebImageDownloadReceiveResponseNotification = @"SDWebImageDownloadReceiveResponseNotification";
NSString *const SDWebImageDownloadStopNotification = @"SDWebImageDownloadStopNotification";
NSString *const SDWebImageDownloadFinishNotification = @"SDWebImageDownloadFinishNotification";
static inline