iOS
中是否存在野指標的情況?
野指標
野指標指向一個已刪除的物件或未申請訪問受限記憶體區域的指標。特別要指出的是野指標不是空指標。
Block
一提到 Block
大家肯定都知道要說的是迴圈引用。在 ARC
中,如果兩個物件相互持有對方,就會造成迴圈引用,導致記憶體無法釋放。在 Block
中,最常用的場景則是,self
持有 block
, block
中又持有了 self
。例如下方一段程式碼:
@property (nonatmaic, copy) Block dataChanged;
- (void)setUpModel{
XYModel *model = [XYModel new];
model.dataChanged = ^(NSString *title) {
self.titleLabel.text = title;
};
self.model = model;
}
複製程式碼
上面的這段程式碼就會造成迴圈引用。那我們怎麼破除呢?通常的做法都是使用 weakSelf
來處理,即:
- (void)setUpModel {
XYModel *model = [XYModel new];
__weak typeof(self) weakSelf = self;
model.dataChanged = ^(NSString *title) {
weakSelf.titleLabel.text = title;
};
self.model = model;
}
複製程式碼
或許你還看到另外一種不是很一樣的版本:
- (void)setUpModel {
XYModel *model = [XYModel new];
__weak typeof(self) weakSelf = self;
model.dataChanged = ^(NSString *title) {
__strong typeof(self) strongSelf = weakSelf;
strongSelf.titleLabel.text = title;
};
self.model = model;
}
複製程式碼
對比一下,多了一個 strongSelf
。那為什麼又要多加一個 strongSelf
呢?
考慮一下下面的程式碼,
__weak __typeof__(self) weakSelf = self;
dispatch_group_async(_operationsGroup, _operationsQueue, ^{
[weakSelf doSomething];
[weakSelf doSomethingElse];
});
複製程式碼
在 doSomething
時, weakSelf
不會被釋放,但是在 doSomethingElse
時,weakSelf
有可能被釋放。
這個時候就遇到了野指標問題,回答了一開始的題目。
在這裡就需要用到 strongSelf
,使用 __strong
確保在 Block
內, strongSelf
不會被釋放。
小結
-
在使用
Block
時,如遇到迴圈引用問題,可以使用__weak
來破除迴圈引用。 -
如果在
Block
內需要多次訪問__weak
變數,則需要使用__strong
來保持變數不會被釋放。
SDWebImage
中為什麼要解碼圖片
要說明這麼問題我們需要先了解一下在 iOS
中,圖片顯示的流程。
概括來說,從磁碟中載入一張圖片,並將它顯示到螢幕上,中間的主要工作流如下:
假設我們使用
imageWithContentsOfFile:
方法從磁碟中載入一張圖片,這個時候的圖片並沒有解壓縮;然後將生成的
UIImage
賦值給UIImageView
;接著一個隱式的
CATransaction
捕獲到了UIImageView
圖層樹的變化;在主執行緒的下一個
run loop
到來時,Core Animation
提交了這個隱式的transaction
,這個過程可能會對圖片進行copy
操作,而受圖片是否位元組對齊等因素的影響,這個copy
操作可能會涉及以下部分或全部步驟:
- 分配記憶體緩衝區用於管理檔案
IO
和解壓縮操作;- 將檔案資料從磁碟讀到記憶體中;
- 將壓縮的圖片資料解碼成未壓縮的點陣圖形式,這是一個非常耗時的
CPU
操作;- 最後
Core Animation
使用未壓縮的點陣圖資料渲染UIImageView
的圖層。在上面的步驟中,我們提到了圖片的解壓縮是一個非常耗時的
CPU
操作,並且它預設是在主執行緒中執行的。那麼當需要載入的圖片比較多時,就會對我們應用的響應性造成嚴重的影響,尤其是在快速滑動的列表上,這個問題會表現得更加突出。
這裡順便提一下 imageNamed:
和 imageWithContentsOfFile:
的區別,這兩個 API
都需要解碼,並且工作流程都是一致的。不過imageNamed:
會做快取處理,在下一次用到相同的資源時,就會從快取裡面讀取。而 imageWithContentsOfFile:
則不會。所以網上大多文章都會告訴你,多次使用的小圖片使用 imageNamed:
載入,一次性使用的大圖片使用 imageWithContentsOfFile:
載入。
對於上面引用的流程中最後提到,當有大量圖片滑動時就會造成主執行緒的卡頓,原因就是解碼圖片在主執行緒中操作的。那有什麼辦法避免呢? 我在查詢關於這個問題的相關資料時,發現有些部落格給出了2種方案:
我們不使用
imageNamed:
載入圖片,使用其他的方法,比如imageWithContentsOfFile:
我們自己解碼圖片,可以把這個解碼過程放到子執行緒
其實第一種方式沒法避免卡頓。這就引出了為什麼 SDWebImage
中需要自己解碼圖片。
在我們使用
UIImage
的時候,建立的圖片通常不會直接載入到記憶體,而是在渲染的時候再進行解壓並載入到記憶體。這就會導致UIImage
在渲染的時候效率上不是那麼高效。為了提高效率通過decodedImageWithImage
方法把圖片提前解壓載入到記憶體,這樣這張新圖片就不再需要重複解壓了,提高了渲染效率。這是一種空間換時間的做法。
參考文章:
- 到底什麼時候才需要在ObjC的Block中使用weakSelf/strongSelf 浮生獵趣
- 談談 iOS 中圖片的解壓縮 雷純鋒的技術部落格
- SDWebImage原始碼解析(三)——SDWebImage圖片解碼/壓縮模組 SHY圓圓圈圈圓圓
你也可以關注我的公眾號,獲取更多文章。