從SDWebImage原始碼中學到的

weixin_33866037發表於2017-04-24

親,我的簡書已不再維護和更新了,所有文章都遷移到了我的個人部落格:https://mikefighting.github.io/,歡迎交流。

保證一段程式碼在主執行緒中執行,怎麼做更好?

可以使用一個巨集來替代,這樣程式碼更加整潔,如

#define dispatch_main_sync_safe(block)\
if ([NSThread isMainThread]) {\
    block();\
} else {\
    dispatch_sync(dispatch_get_main_queue(), block);\
}

#define dispatch_main_async_safe(block)\
if ([NSThread isMainThread]) {\
    block();\
} else {\
    dispatch_async(dispatch_get_main_queue(), block);\
}
Block除了常見的回撥,還有什麼應用場景?

在具體的處理方式需要客戶端傳入時。(在模板方法設計模式中,其呼叫的是子類的實現)。
如:

typedef NSString *(^SDWebImageCacheKeyFilterBlock)(NSURL *url);
@property (nonatomic, copy) SDWebImageCacheKeyFilterBlock cacheKeyFilter;

該處就是利用Block將處理CacheKey的方法開放給了客戶端。通過block的返回值獲取。其實這裡用代理也可以實現,但是代理相對來說程式碼量會更多,並且程式碼較為分散。

我們在加鎖的時候一直用@synchronized (self)合理嗎?

不合理,@synchronized (objc),只要這個objc是同一個物件,那麼就會獲得同一把鎖。如果訪問的是兩種不同的資源,那麼就需要使用兩種不同的objc,比如:

   @synchronized (self.runningOperations) {
    [self.runningOperations addObject:operation];
}
  @synchronized (self.failedURLs) {
    isFailedUrl = [self.failedURLs containsObject:url];
}

這裡分別是對runningOperationsfailedURLs同步,那麼就需要使用兩種不同的objc,當然都用self不能算錯,但是將不需要同步的程式碼同步了,就降低了系統的效能。另外使用self,還容易引起死鎖,比如下程式碼:

 //class A
    @synchronized (self) {
    [_sharedLock lock];
    NSLog(@"code in class A");
    [_sharedLock unlock];
 }
 
  //class B
   [_sharedLock lock];
    @synchronized (objectA) {
        NSLog(@"code in class B");
    }
    [_sharedLock unlock];

如果要讓陣列中的每個物件都呼叫某個方法怎麼做?

- (void)makeObjectsPerformSelector:(SEL)aSelector`
- (void)makeObjectsPerformSelector:(SEL)aSelector withObject:(nullable id)argument`

以上兩個方法可以實現,而不需要分別遍歷每個物件,然後分別呼叫performSelector:

記憶體快取為啥要用NSCache

NSCache和NSDictionary極其相似,他的方法如下:

- (nullable ObjectType)objectForKey:(KeyType)key;
- (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;
@property NSUInteger totalCostLimit;    // limits are imprecise/not strict
@property NSUInteger countLimit;    // limits are imprecise/not strict
@property BOOL evictsObjectsWithDiscardedContent;

從中可以看到,他和NSDictioary非常相似,但是為啥在做記憶體快取時要用它呢?原因在於:

  1. 當系統資源將要耗盡時,它可以自動刪減快取。如果採用普通的字典,那麼就要自己編寫掛鉤,在系統發出“低記憶體”通知時手工刪減快取。
  2. NSCache還是執行緒安全的。
  3. NSDictionary中的key必須實現NSCopying協議,NSCache中的key不必實現copy因為它是"保留"鍵的(強引用),而不是"拷貝"鍵的。
  4. 如果快取設定超過了設定的最大值,則會清除舊的資料,保留最新快取的資料。

FOUNDATION_STATIC_INLINE放在方法名前有何作用?

 FOUNDATION_STATIC_INLINE NSUInteger SDCacheCostForImage(UIImage *image) {
return image.size.height * image.size.width * image.scale * image.scale;

}

行內函數的程式碼會被直接嵌入在它被呼叫的地方,呼叫幾次就嵌入幾次,沒有使用call指令。這樣省去了函式呼叫時的一些額外開銷,比如儲存和恢復函式返回地址等,可以加快速度。不過呼叫次數多的話,會使可執行檔案變大,這樣會降低速度。相比起巨集來說,核心開發者一般更喜歡使用行內函數。因為行內函數沒有長度限制,格式限制。編譯器還可以檢查函式呼叫方式,以防止其被誤用。

inline(行內函數)在什麼時候使用?

SDWebIamgeCompat中使用了inline UIImage *SDScaledImageForKey(NSString *key, UIImage *image),這是個行內函數(函式程式碼被放入符號表中,在使用時進行替換,比呼叫一般的函式更加高效),那麼我們在什麼時候使用行內函數呢?經過查詢相關資料,總結下inline的使用場合:
使用inline的場合:

  1. 想要使用inline替換#define時。
  2. 函式很短。(如果函式的程式碼較長,使用內聯將消耗過多記憶體)
  3. 函式呼叫很頻繁。
    不應使用inline的場合:
  4. 很大的函式。
  5. 和I/O相關的函式。
  6. 建構函式和解構函式。
  7. 開放框架時候,使用inline可能會破壞框架的相容性。

獲取某個目錄下檔案的個數:

 NSDirectoryEnumerator *fileEnumerator = [_fileManager enumeratorAtPath:self.diskCachePath];
 count = [[fileEnumerator allObjects] count];

如何讓某個屬性只在固定版本的時候才會有?

 #if TARGET_OS_IPHONE && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_4_0
 @property (assign, nonatomic) UIBackgroundTaskIdentifier backgroundTaskId;
 #endif

上面這段程式碼就可以讓backgroundTaskId在iphone的版本在4.0以後才會有。

Notification的方法呼叫所在的執行緒是根據Post時候所線上程決定的,

 dispatch_async(dispatch_get_main_queue(), ^{
        [[NSNotificationCenter defaultCenter]   postNotificationName:SDWebImageDownloadStopNotification object:self];
    });  

這樣註冊該通知的物件就可以在主執行緒中呼叫響應的方法了。

NSRunLoop,為了保持後臺下載圖片的執行緒一直存在,就使用了RunLoop

CFRunLoopRun()CFRunLoopStop(CFRunLoopGetCurrent())分別用來開始和結束一個RunLoop

分類中需要填加屬性怎麼辦?

如果分類中的屬性只是分類內部使用,那麼其實可以直接使用關聯,而不必非要顯式建立一個屬性,這樣也可以直接使用.語法,這時沒有屬性,所以.語法的無論是在=左邊,還是在=右邊最終都會呼叫這個方法,例如:

static char imageURLStorageKey;
  - (NSMutableDictionary *)imageURLStorage {
NSMutableDictionary *storage = objc_getAssociatedObject(self, &imageURLStorageKey);
if (!storage)
{
    storage = [NSMutableDictionary dictionary];
    objc_setAssociatedObject(self, &imageURLStorageKey, storage, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
return storage;
}

如何讓一個id型別的物件呼叫某個具體的方法?

讓這個物件遵守某項協議就可以呼叫,如下

 for (id <SDWebImageOperation> operation in operations) {
 if (operation) {
     [operation cancel];
 }

同理,如果想讓某個方法的返回值具有某個方法,也可以讓這個返回值遵守某協議,如:

- (id <SDWebImageOperation>)downloadImageWithURL:(NSURL *)url
                                         options:(SDWebImageOptions)options
                                        progress:  (SDWebImageDownloaderProgressBlock)progressBlock
                                       completed:(SDWebImageCompletionWithFinishedBlock)completedBlock;

設計介面時,要儘量考慮使用者的習慣,並對常見錯誤進行處理

在需要傳入URL引數的地方,使用者很可能不小心傳入了字串,這個時候要麼在方法中丟擲異常,要麼就在內部判斷型別並替使用者做相應的轉換,如:

- (id <SDWebImageOperation>)downloadImageWithURL:(NSURL *)url {
     if ([url isKindOfClass:NSString.class]) {
    url = [NSURL URLWithString:(NSString *)url];
}
}

未完,待續

相關文章