天天看點

Objective-C 記憶體管理和多線程

本文是閱讀

《Objective-C進階程式設計》

過程中的一些心得。

1、ARC

1、記憶體管理

OC中根據自動計數來進行記憶體空間的管理,針對一個對象的操作經曆以下幾個階段:

  • 生成并持有對象:

    alloc、new、copy、mutableCopy

    等方法,持有對象才能對該對象做出操作;
  • 持有對象:

    retain

    ,持有會使該對象的引用計數加一,可調用

    retainCount

    檢視引用計數大小;
  • 釋放對象:

    release

    ,釋放會使該對象的引用計數減一,前提是已經持有該對象;
  • 廢棄對象:

    dealloc

    ,對象的引用計數為0時會清理對象的存儲空間。

還有一種操作

autorelease

,使用

NSRunloop

NSAutoreleasePool

管理引用持有的對象,直到其運作生命周期結束才會釋放所有持有的對象,相當于執行了對象的

release

方法,有點類似局部變量。

==注意:以上方法在

ARC

開啟時不可用。

2、引用修飾符

_ _strong

_ _strong

修飾符是id類型和對象類型的預設所有權修飾符,表示對對象的強引用,持有對象,該變量在超出其作用域時被廢棄,其引用的對象也會随之釋放。

強引用帶來的問題是循環引用,造成記憶體洩漏,看下面這個例子:

@interface Test:NSObject
{
	id _strong obj;
}
- (void)setObject:(id _strong)obj;
@end
//循環引用
{
id test0 = [Test new];	//對象A
id test1 = [Test new];	//對象B
[test0 setObject:test1];
[test1 setObject:test0];
}
           

當test0要釋放對象A時,發現test1的變量對A有強引用,要test1先釋放B,同理要test0先釋放A。誰也無法釋放,導緻記憶體洩漏。

_ _weak

_ _weak

代表弱引用,不持有對象,當引用的對象被釋放時自動指向

nil

。前面的例子隻要把

Test

中的

obj

改為弱引用即可。

_ _unsafe_unretained

_ _weak

不同的是當引用的對象釋放時使用該修飾符修飾的變量時程式會崩潰。

_ _autoreleasing

相當于代替調用

autorelease

方法,一般不顯示附加該關鍵詞,取而代之的是

@autoreleasepool{ }

。在代碼塊中,編譯器會檢查方法名是否以

alloc、new、copy、mutableCopy

開始,如果不是則将傳回值的對象注冊到

autoreleasepool

。常用在循環中,可以減少記憶體占用率。

将對象注冊到

autoreleasepool

還有幾種隐示的方法(不加

_ _autorelease

關鍵字):

  • + (id) array {return [[NSMutableArray alloc] init]};

    生成的對象超出函數範圍應該被自動釋放,但該對象同時又是函數的傳回值,會自動被注冊到

    autoreleasepool

  • 通路

    _ _weak

    修飾的變量時,實際上必定通路注冊到

    autoreleasepool

    的對象,因為通路過程中對象可能被廢棄,如果注冊的話保證在

    @autoreleasepool

    塊結束之前對象存在;
  • id和對象的指針會預設加上

    _ _autoreleasing

    修飾符。

3、屬性

屬性聲明的屬性與所有權修飾符的對應關系如下:

Objective-C 記憶體管理和多線程

對于

copy

關鍵詞,一般用于不可變類型,如

NSString、NSArray

等。相當于在

setter

方法中做了一次深拷貝,防止淺拷貝導緻該屬性随着所指派的改變而改變。而

strong

相當于在

setter

方法中進行淺拷貝,與所指派的指針共享對象。

2、BLOCK

1、文法

block可以了解為匿名函數,寫法為

^ 傳回值類型 (參數清單) {表達式}

,參數清單和傳回值都可省略,即便有傳回值,也可以通過

return

推測出來。當block作為變量時可以這樣寫:

傳回值類型 (^變量名稱) (參數清單)

,跟一般的變量不同的是block變量被複制後,可以用

變量名稱(參數清單)

,相當于函數調用。比較常見的方式是利用

typedef

聲明block變量,例如

typedef int (^blk_t) (int);

,之後就可以利用

blk_t blk

之類的方式聲明變量,非常友善。

block可截獲自動變量值,比如在block之前定義了一個變量,block裡面要利用此變量的值,之後修改了該變量的值,再次調用block的話此變量的值仍是最初捕獲的值。當在block中修改變量的值時會産生編譯錯誤,此時可在變量前加上

_ _block

修飾符。

2、BLOCK循環引用

當對象中含有block屬性,且block屬性中代碼塊中又利用到

self

或其它屬性的值,一般用

id _ _wak tmp = self;

來代替block中用到的

self

3、多線程

1、Dispatch Queue

将block加入不同的隊列執行順序也不一樣:

  • Serial

    :按添加順序先後執行block,隻有一個線程
  • Concurrent

    :有多個線程,不同線程并行,同一個線程串行

通過

dispatch_queue_create("<reverse DNS id>", DISPATCH_QUEUE_SERIAL/DISPATCH_QUEUE_CONCURRENT)

建立一個

dispatch_queue_t

對象,隊列中的每個blcok都持有該對象,除非執行完畢,否則不釋放。

API 作用

dispatch_get_main_queue()

擷取主線程中執行的隊列,在RunLoop中執行,屬于Serial

dispatch_get_gloabl_queue(<priority>, 0)

擷取全局的Concurrent隊列,優先級可以設定

dispatch_time(DISPATCH_TIME_NOW, <n>ull * NSEC_PER_SEC)

擷取n秒之後的

dispatch_time_t

對象

dispatch_after(<dispatch_time_t>, <queue>, block)

n秒後将block加入到queue中

2、dispatch_async和dispatch_sync

  • dispatch_async

    表示異步,将block加入queue的隊尾,串/并執行取決于queue的類型,并馬上傳回
  • dispatch_sync

    表示同步,将block加入queue的隊尾,串/并執行取決于queue的類型,但是會阻塞調用該函數的queue,直到block執行完畢才會傳回,使用時需要避免死鎖的情況:
//主線程執行下面這條語句會出現死鎖
dispatch_async(dispatch_get_main_queue(), ^{
	dispatch_sync(dispatch_get_main_queue(), block);
});
           

3、Dispatch Group

  • diapactch_group_create()

    建立

    dispatch_group_t

    對象
  • dispatch_group_async(group, queue, block)

    添加block到queue
  • dispatch_group_notify(group, queue, block)

    監視block的執行情況,全部執行完畢會調用剛方法中的block

類似的還有

dispatch_apply

,均可實作等待所有block執行完畢:

dispatch_apply(counts, queue, ^(size_t index){ })

,将counts個block加入到queue中,全部執行完畢後再進行其它處理。

4、Dispatch Semaphore

利用信号量對資料的更新進行控制:

API 作用

dispatch_semaphore_create(<n>)

擷取

dispatch_semaphore_t

對象,計數為n

dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER)

一直等待,直到計數值>=1

dispatch_semaphore_signal(semaphore)

計數值+1

5、dispatch_once

單例模式:

dispatch_once(static修飾的dispatch_once_t對象, block進行資料初始化)