Objective-C高級編程-iOS與OS+X多線程和內(nèi)存管理
第一章:自動引用計(jì)數(shù)
- 自己生成的對象婶希,自己所持有。
- 非自己生成的對象,自己也能持有
- 不再需要自己持有的對象時釋放
- 非自己持有的對象無法釋放
對象操作 | Objective-C方法 |
---|---|
生成并持有對象 | alloc/new/copy/mutableCopy等 |
持有對象 | retain |
釋放對象 | release |
廢棄對象 | dealloc |
Cocoa
框架中 Foundation
框架類庫的 NSObject
類擔(dān)負(fù)內(nèi)存管理的職責(zé)。
自己生成的對象涯贞,自己所持有
使用以下名稱開頭的方法名意味著自己生成的對象只有自己持有:
alloc
new
copy
mutableCopy
使用 NSObject
類的 alloc
類方法就能自己生成并持有對象。另外危喉,使用 new
類方法也能生成并持有對象宋渔。[NSObject new]
與 [[NSObject alloc] init]
是完全一致的
非自己生成的對象,自己也能持有
用上述方法之外的方法取得的對象即用 alloc/new/copy/mutableCopy
以外的方法取得的對象辜限,因?yàn)榉亲约荷刹⒊钟谢始穑宰约翰皇窃搶ο蟮某钟姓摺@?NSArray
類的 array
類方法薄嫡。通過 retain
方法审磁,非自己生成的對象成為了自己所持有的。
不再需要自己持有的對象時釋放
自己持有的對象岂座,一旦不再需要态蒂,持有者有義務(wù)釋放該對象,釋放使用的是 release
方法费什。
用 alloc/new/copy/mutableCopy
方法生成并持有的對象钾恢,或者用 retain
方法持有的對象,一旦不再需要鸳址,務(wù)必要用 release
方法進(jìn)行釋放
autorelease
提供這樣的功能瘩蚪,使對象再超出指定的生存范圍時能夠自動并正確地釋放(調(diào)用release方法)
無法釋放非自己持有的對象
對于用 alloc/new/copy/mutableCopy
方法生成并持有的對象,或者用 retain
方法持有的對象稿黍,由于持有者是自己疹瘦,所以在不需要該對象時需要將其釋放。而由此以外所得到的對象絕對不能釋放巡球。倘若在應(yīng)用程序中釋放了非自己所持有的對象就會造成崩潰言沐。
- 在
Objective-C
的對象中存有引用技術(shù)這一整數(shù)值邓嘹。 - 調(diào)用
alloc
或是retain
方法后,引用計(jì)數(shù)值加 1. - 調(diào)用
release
后险胰,引用技術(shù)減 1. - 引用計(jì)數(shù)值為 0 時汹押,調(diào)用
dealloc
方法廢棄對象。
蘋果對引用計(jì)數(shù)大概是采用 散列表(引用計(jì)數(shù)表)來管理引用計(jì)數(shù)
GNUstep
將引用計(jì)數(shù)保存在對象占用內(nèi)存塊頭部的變量中起便,而蘋果則是保存在引用計(jì)數(shù)表的記錄中棚贾。
通過內(nèi)存塊頭部管理引用計(jì)數(shù)的好處:
- 少量代碼即可完成
- 能夠統(tǒng)一管理引用計(jì)數(shù)用內(nèi)存塊與對象用內(nèi)存塊
通過引用計(jì)數(shù)表管理引用計(jì)數(shù)的好處:
- 對象用內(nèi)存塊的分配無序考慮內(nèi)存塊頭部
- 引用計(jì)數(shù)表各記錄中存有內(nèi)存塊地址,可從各個記錄追溯到各對象的內(nèi)存塊
在利用工具檢測內(nèi)存泄露時榆综,引用計(jì)數(shù)表的各記錄也有助于檢測各對象的持有者是否存在妙痹。
autorelease
autorelease
就是自動釋放,這看上去很像 ARC 鼻疮,實(shí)際上更類似于 C 語言中自動變量(局部)變量的特性怯伊。
C 語言的自動變量特性:程序執(zhí)行時,若某自動變量超出其作用域陋守,該自動變量將被自動廢棄。
與 C 語言的自動變量不同的是利赋,編程人員可以設(shè)定變量的作用域水评。
autorelease
的具體使用方法如下:
- 生成并持有
NSAutoreleasePool
對象 - 調(diào)用已分配對象的
autorelease
實(shí)例方法 - 廢棄
NSAutoreleasePool
對象
NSAutoreleasePool
對象的生存周期相當(dāng)于 C 語言變量的作用域。對于所有調(diào)用過 autorelease
實(shí)例方法的對象媚送,在廢棄NSAutoreleasePool
對象時中燥,都將調(diào)用 release
實(shí)例方法。
在大量產(chǎn)生 autorelease
對象時塘偎,需要在適當(dāng)?shù)牡胤缴闪粕妗⒊钟谢驈U棄 NSAutoreleasePool
對象。
調(diào)用 NSObject
類的 autorelease
實(shí)例方法吟秩,該對象將被追加到正在使用的 NSAutoreleasePool
對象中的數(shù)組里咱扣。即 本質(zhì)就是調(diào)用 NSAutoreleasePool
對象的 addObject
類方法。
ARC 規(guī)則
循環(huán)引用容易發(fā)生內(nèi)存泄露涵防,所謂內(nèi)存泄露就是應(yīng)當(dāng)廢棄的對象在超出其生存周期后繼續(xù)存在闹伪。使用__weak
修飾符可避免循環(huán)引用,__weak
修飾符與__strong
修飾符 相反壮池,提供弱引用偏瓤。弱引用不能持有對象實(shí)例,因?yàn)?code>__weak修飾符的變量不吃又對象椰憋,所以在超出其變量作用域時厅克,對象即被釋放。
__weak
修飾符還有另一優(yōu)點(diǎn):在持有對象的弱引用時橙依,若該對象被被廢棄证舟,則此弱引用將自動失效且處于 nil 被賦值的狀態(tài)硕旗。
在 ARC 有效的情況下編譯源代碼需遵守如下規(guī)則:
- 不能使用
retain/release/retainCount/autorelease
- 不能使用
NSAllocateObject / NSDeallocateObject
- 須遵守內(nèi)存管理的方法命名規(guī)則
- 不要顯式調(diào)用
dealloc
- 使用
@autoreleasepoll
塊替代NSAutoreleasePool
- 不能使用區(qū)域 (NSZone)
- 對象型變量不能作為 C 語言結(jié)構(gòu)體(struct/union)的成員
- 顯式轉(zhuǎn)換
id
和void *
, 通過(__bridge void *)
或(__bridge id)
進(jìn)行橋接
__weak
修飾符
weak 表與引用計(jì)數(shù)表相同褪储,作為散列表被實(shí)現(xiàn)卵渴。如果使用 weak 表,將廢棄對象的變量的地址作為鍵值進(jìn)行檢索鲤竹,就能高速地獲取對應(yīng)的附有 __weak 修飾符的變量的地址浪读。另外,由于一個對象可同時賦值給多個附有 __weak 修飾符的變量中辛藻,所以對于一個鍵值碘橘,可注冊多個變量的地址。
如果附有 __weak 修飾符的變量所引用的對象被廢棄吱肌,則將 nil 賦值給該變量的步驟:
- 從 weak 表中獲取廢棄對象的地址為鍵值的記錄
- 將包含在記錄中的所有附有 __weak 修飾符的變量的地址痘拆,賦值為 nil.
- 從 weak 表中刪除該記錄
- 從引用計(jì)數(shù)表中刪除廢棄對象的地址為鍵值的記錄
如果大量使用附有 __weak 修飾符的變量,會消耗相應(yīng)的 CPU 資源氮墨,良策是只在需要避免循環(huán)引用時使用 __weak 修飾符纺蛆。
第二章:Blocks
Blocks 是 C語言的擴(kuò)充功能,用一句話表示:帶有自動變量(局部變量)的匿名函數(shù)规揪。
Block 語法:
^ 返回值類型 參數(shù)列表 表達(dá)式
使用附有 __block
說明符修飾的自動變量可在 Block 中賦值桥氏,該變量成為 __block
變量
在Block中,截獲自動變量的方法并沒有實(shí)現(xiàn)對C語言數(shù)組的截獲猛铅,這時使用指針可以解決問題字支。
Blocks 的實(shí)現(xiàn)
Block 本質(zhì)是一個 OC 對象,它內(nèi)部也有一個 isa 指針奸忽。是封裝了函數(shù)調(diào)用和函數(shù)調(diào)用環(huán)境的 OC 對象堕伪。
iOS 類的本質(zhì)即 class_t
結(jié)構(gòu)體:
struct class_t {
struct class_t *isa;
struct class_t *superclass;
Cache cache;
IMP *vtable;
uintptr_t data_NEVER_USE;
}
該實(shí)例名稱持有聲明的成員變量、方法的名稱栗菜、方法的實(shí)現(xiàn)(即函數(shù)指針)欠雌、屬性以及父類的指針。
Block 即為 Objective-C 的對象疙筹。
Block 的三種類型
類 | 設(shè)置對象的存儲域 | 備注 |
---|---|---|
_NSConcreteStackBlock | 棧 | 捕獲局部變量 |
_NSConcreteGlobalBlock | 程序的數(shù)據(jù)區(qū)域(.data 區(qū)) | 不捕獲自動變量 |
_NSConcreteMallocBlock | 堆 | 捕獲成員變量 |
Block 何時會復(fù)制到堆
- 調(diào)用 Block 的 copy 實(shí)例方法時
- Block 作為函數(shù)返回值返回時
- 將 Block 賦值給附有 __strong 修飾符 id 類型的類或 Block 類型成員變量時
- 在方法名中含有 usingBlock 的 Cocoa 框架方法或 GCD 的 API 中傳遞 Block 時桨昙。
第三章:Grand Central Dispatch
Grand Central Dispatch (GCD) 是異步執(zhí)行任務(wù)的技術(shù)之一,一般將應(yīng)用程序中記述的線程管理用的代碼在系統(tǒng)級中實(shí)現(xiàn)腌歉,開發(fā)者只需要定義想執(zhí)行的任務(wù)并追加到適當(dāng)?shù)?Dispatch Queue 中蛙酪,GCD 就能生成必要的線程并計(jì)劃執(zhí)行任務(wù)。
多線程編程可能會出現(xiàn)的問題:
- 多個線程更新相同的資源會導(dǎo)致數(shù)據(jù)的不一致(數(shù)據(jù)競爭)- 解決:使用 Serial Dispatch Queue (串行隊(duì)列)
- 停止等待事件的線程會導(dǎo)致多個線程相互持續(xù)等待(死鎖)
- 使用太多線程會消耗大量內(nèi)存
Dispatch Queue 按照追加的順序(先進(jìn)先出 FIFO)執(zhí)行處理翘盖,另在執(zhí)行處理時存在兩種 Dispatch Queue :一種是等待現(xiàn)在執(zhí)行中處理的 Serial Dispatch Queue桂塞,另一種是不等待現(xiàn)在執(zhí)行中處理的 Concurrent Dispatch Queue。
GCD API
可使用 dispatch_set_target_queue
API 設(shè)置 Dispatch Queue 的優(yōu)先級馍驯,同時也可以使多個本應(yīng)并行執(zhí)行的多個 Serial Dispatch Queue阁危,在目標(biāo) Serial Dispatch Queue 上串行執(zhí)行玛痊。
dispatch_after
函數(shù)并不準(zhǔn)時
因?yàn)?Main Dispatch Queue 在主線程的 Runloop 中執(zhí)行,所以在比如每隔 1/60 秒自行的 Runloop 中狂打,Block 最快3秒后自行擂煞,最慢在3秒 + 1/60 秒后執(zhí)行,并且在 Main Dispatch Queue 有大量處理追加或主線程的處理本身有延遲時趴乡,這個時間會更長对省。
Dispatch Group
在追加到 Dispatch Queue 中的多個處理全部結(jié)束后想執(zhí)行結(jié)束處理可使用 Dispatch Group 實(shí)現(xiàn)。
dispatch_sync
如同簡易版的 dispatch_group_wait 函數(shù)晾捏,會在指定隊(duì)列中同步執(zhí)行任務(wù)蒿涎,在任務(wù)執(zhí)行結(jié)束之前不會返回。如果在主線程同步執(zhí)行 Block 就會出現(xiàn)死鎖惦辛。
dispatch_apply
函數(shù)可進(jìn)行快速遍歷劳秋。由于 dispatch_apply
函數(shù)與 dispatch_sync
函數(shù)相同,會等待處理執(zhí)行結(jié)束胖齐,因此推薦在 dispatch_async
函數(shù)中非同步地執(zhí)行 dispatch_apply
函數(shù)玻淑。
dispatch_suspend
函數(shù)可暫時掛起指定的 Dispatch Queue。
dispatch_resume
函數(shù)可恢復(fù)指定的 Dispatch Queue呀伙。
dispatch_semaphore
函數(shù)可對操作進(jìn)行更細(xì)粒度的排他控制补履。
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER)
dispatch_semaphore_wait
函數(shù)等待 Dispatch Semaphore 的計(jì)數(shù)值達(dá)到大于或等于1. 該處理結(jié)束是使用 dispatch_semaphore_signal
函數(shù)將 Dispatch Semaphore 的計(jì)數(shù)值加1.
dispatch_once
函數(shù)是保證再應(yīng)用程序執(zhí)行中只執(zhí)行一次指定處理的 API。
dispatch_IO
函數(shù)可多線程并發(fā)處理大文件区匠,以提高文件讀取速度干像。
GCD 實(shí)現(xiàn)
Block 并不是直接加入 FIFO 隊(duì)列中帅腌,而是先加入 Dispatch Continuation 這一 dispatch_continuation_t
類型結(jié)構(gòu)體中驰弄,然后再加入 FIFO 隊(duì)列。用于記憶 Block 所屬的 Dispatch Group 喝其他一些信息速客,通常叫上下文戚篙。
。溺职。岔擂。