MRC — 手動管理記憶體 1.1 記憶體引用平衡原則 1) 如果使用alloc,new開頭,或者是copy(複製一個對象)來創建一個對象,意味著你擁有這個對象的所有權。這個對象的引用計數器初始值為1(也有可能>1)。 2) 如果你擁有這個對象的所有權,在不使用此對象時,就有責任向對象發送release ...
MRC — 手動管理記憶體
1.1 記憶體引用平衡原則
1) 如果使用alloc,new開頭,或者是copy(複製一個對象)來創建一個對象,意味著你擁有這個對象的所有權。這個對象的引用計數器初始值為1(也有可能>1)。
2) 如果你擁有這個對象的所有權,在不使用此對象時,就有責任向對象發送release消息。(誰創建了對象,誰就有責任release這個對象)
3) 如果並不擁有一個對象的所有權,而想要使用這個對象,為了防止你在使用此對象期間,對象被別人釋放掉,需要向對象發送retain消息,以保持對象。此時可以認為,你也擁有了這個對象所有權。
4) 當你使用完retain過的對象後,有責任release一下這個對象。
(誰retain了一個對象,誰就有責任release這個對象)
配對出現:(+1、-1 ==>平衡)
我們創建的對象不用了,就release;我們retain的對象不用了,就release。
記憶體管理的原則就是有加就有減。也就是說, 一次alloc(new)對應一次release, 一次retain對應一次release。
1.2 自動釋放池(autoreleasepool)
通過自動釋放池來管理對象,只需要一個自動釋放池,可以管理很多對象,當自動釋放池結束的時候,會自動向池中的每個對象都發送release消息。
1) 如果一個對象創建後,不能馬上釋放它,但又不得不盡到釋放對象的責任,此時可以將對象放入自動釋放池,延遲對象的釋放時機。比如絕大部分工廠方法都是如此。工廠方法中的對象是方法中創建的,按理來說應該由工廠方法內部釋放,但工廠方法的功能決定了這個對象不能馬上釋放,此時應該將對象放入自動釋放池。
2) 當自動釋放池結束時,會向池中的所有對象發送release消息,如果此時,池中的對象的引用計數器是1,那麼,對象會被釋放掉。
3) 如何開始和結束一個自動釋放池呢?
//自動釋放池,用於回收對象的存儲空間。 @autoreleasepool{ //開始(創建一個自動釋放池) …… …… } //結束(自動釋放池銷毀了, 給自動釋放池中所有的對象發送一條release消息) |
還有一種性能低下,被淘汰的自動釋放池創建方式(瞭解)
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc]init];//開始一個自動釋放池 …… …… [pool drain];//結束一個自動釋放池 |
4) 無論一個對象是否在自動釋放池,只要這個對象是由別人創建的,你要使用此對象,就得retain,用完此對象,就得release,即使對象在自動釋放池,也依然如此。
5) 實際開發中,合理使用自動釋放池來避免記憶體使用出現峰值。
如果App出現的記憶體使用的峰值,此時才考慮是否是由於大量使用工廠方法造成的,是否需要使用自動釋放池解決問題。要合理使用自動釋放池,大量使用會消耗系統資源。
6) autorelease方法:在自動釋放池中使用,目的是將對象添加到自動釋放池中。自動釋放池銷毀時會對池中對象作release操作。(延遲release)
@autoreleasepool { Person *p = [[[Person alloc] init] autorelease];
} |
- autorelease方法與release方法的區別:這兩個方法都能把對象的引用計數器減1。
- release是一個精確的減1,對對象的操作只能在release之前進行,如果是在之後,就會出現野指針錯誤;
- autorelease是一個不精確的引用計數器減1,當給對象發送autorelease消息時,對象就會被放到自動釋放池中,自動釋放池銷毀時會給池中的所有對象發送release消息,使得所有對象的計數器減1,所以本質上autorelease還是會調用release。
- 常見錯誤:
// 銷毀自動釋放池的時候 要對person再執行release操作的話 會報野指針錯誤 @autoreleasepool { Person *person = [[[Person alloc] init] autorelease]; [person release]; } |
// 對象執行兩次autorelease意味著自動釋放池銷毀的時候 對象會執行兩次release操作 會報野指針錯誤 @autoreleasepool { Person *person = [[[[Person alloc] init] autorelease] autorelease]; } |
1.3 setter方法的記憶體管理(應用場景:兩個類是聚合關係)
當一個方法傳入一個對象後,如果需要將這個對象用實例變數等手段保存起來持續使用時,需要做以下事:
1) 先將此對象的引用計數器加1(retain)
2) 再將原來實例變數指向的對象的引用計數器減1(release)
3) 最後將傳入的對象地址保存到實例變數中
4) 被retain的對象通常需要在dealloc方法中release.
只要一個對象想使用房間,就需要對這個房間的引用計數器+1
只要一個對象不想使用房間,就需要對這個房間的引用技術器-1
經常會被問到的問題:
1) 前面3個順序可否顛倒?
能不能先release原來的對象,再賦值,最後retain新對象? 一般可以,但不建議
2) dealloc方法中,可不可以使用self.屬性 = nil;的方式釋放屬性所指向的對象? ok的
可以的,self.屬性 相當於調用上面的setter方法(該方法中有向對象發release)
Demo:setter方法的記憶體管理
1.4 property修飾符
1) nonatomic 非原子性操作 (安全性低,但效率高。iOS實際開發中99%用這個,以確保性能)
atomic 原子性操作 (安全性高,但很耗費資源)
註意,預設是原子性的(atomic),所以在定義屬性時一定要寫上nonatomic。
2) assign, retain, copy
assign 是預設值,僅做賦值。不會解決記憶體問題(即不會retain,也不會release)。在MRC中可用於對象類型和非對象類型。
retain 只能用於對象類型的屬性,會解決記憶體問題(生成的setter方法會自動加入retain,release等記憶體操作代碼)
copy 一些特殊對象類型,如果不希望和別人共用一個對象,用copy會自動創建一個新的對象。
有些屬性在傳入後,需要拷貝一份,以防止傳入的對象內容被修改後,影響到我的對象。
(並不是所有的對象都能拷貝,只有遵守<NSCopying>協議,實現了協議中CopyWithZone方法的這種對象才擁有拷貝的功能)
3) readonly/readwrite
readwrite 是預設的,編譯器生成getter和setter方法
readonly 只讀,編譯器只生成getter方法
屬性訪問器的類型:
註:聲明屬性預設情況下,並沒有解決記憶體問題
當使用 @property(retain)引用數據類型…,幫我們解決了setter使用中的記憶體問題,但dealloc中的release操作,我們自己來做。
1.5 MRC中的迴圈引用(兩個對象互相引用(即互相包含)時,要一強一弱!)
如果A對象要擁有B對象, 而B對象又要擁有A對象, 此時會形成迴圈retain
如何解決這個問題:讓其中一方不要做retain操作即可!
Demo:
ARC — 自動管理記憶體
1.1 概念
Automatic Reference Counting 自動引用計數
基於MRC, 在MRC下,對象的引用計數是由程式員負責的。在ARC下,對象的引用計數工作由編譯器來完成。
1.2 ARC的工作原理
在ARC下,堆記憶體空間的管理依然使用引用計數器的方式。只是ARC下的引用計數器的加減工作不再由程式員來做,而是由編譯器來做。
編譯器在編譯程式期間,會在程式恰當的位置自動加入對對象的retain,release和autorelease的操作。
註意:ARC是 編譯期語法或編譯器特性(即可以理解為是Xcode的一個功能),而不是運行時特性。
1.3 怎麼用
程式員不要對對象的引用計數器進行操作,編譯器會幫我們做:
1) 在ARC下,不要在程式中調用retain, release, autorelease方法。
2) 在ARC下,不要重寫retain, release, autorelease方法。
3) 在ARC下,不要在dealloc方法中調用父類的dealloc方法。實際上,ARC下,dealloc方法基本沒有用了。
總之,一切與記憶體操作相關的東西都由ARC自動完成。
1.4 ARC的判斷原則:(即系統怎麼判斷對象是否要釋放)
- 自動管理記憶體的判斷原則:只要還有一個強指針變數指向對象,對象就會保持在記憶體中。
- 強指針(強引用):
- 預設情況下所有的指針變數都是強指針
- 被__strong修飾的指針
- 例如:
TRPerson *p1 = [[TRPerson alloc]init]; __strong TRPerson *p2 = [[TRPerson alloc]init]; |
- 弱指針(弱引用)
- 被__weak修飾的指針
- 例如:
__weak TRPerson *p = [[TRPerson alloc]init]; |
- 舉例:
//ARC的判斷準則:只要沒有強指針指向對象,對象就會釋放 { TRPerson *p1 = [[TRPerson alloc]init]; __strong TRPerson *p2 = [[TRPerson alloc]init]; __weak TRPerson *p3 = p2; p2 = nil;//p2改變了指向,此時就沒有強指針指向對象,對象就會釋放 }//出了大括弧,局部變數p1就釋放,此時就沒有強指針指向對象,對象就會釋放 |
單個對象的記憶體管理:如果一個對象不再使用了,就把指向對象的強指針置為nil,對象就會釋放。
- 註意:
- 當使用ARC的時候,暫時忘記“引用計數器”,因為判斷標準變了。
- 在實際開發中,千萬不要使用一個弱指針來保存一個剛剛創建的對象。
{ //p是弱指針,對象會被立即釋放 __weak TRPerson *p = [[TRPerson alloc]init];//剛創建就被釋放! } |
1.5 ARC中多個對象的記憶體管理:
- ARC和MRC一樣,想擁有某個對象必須用強指針保存對象,但是不需要在dealloc方法中release
@class TRDog; @interface TRPerson : NSObject
//MRC下寫法 @property (nonatomic, retain) TRDog *dog; //ARC下寫法 @property (nonatomic, strong) TRDog *dog;
@end |
MRC
A對象想使用B對象, 需要對B對象進行一次retain
A對象不用B對象了, 需要對B對象進行一次release
即,property的時候進行retain, dealloc的時候進行release
ARC
A對象想使用B對象, 那麼就需要用一個強指針指向B對象(即用strong,表示強引用)
// 在ARC中保存一個對象用strong, 相當於MRC中的retain
@property (nonatomic, strong) Dog *dog;
A對象不用B對象了, 什麼都不需要做, 編譯器會自動幫我們做
1.6 ARC下迴圈引用問題
迴圈引用:指兩個對象相互強引用了對方,即retain了對方,從而導致誰也釋放不了誰的記憶體泄露問題。
- ARC和MRC一樣,如果A擁有B,B也擁有A,那麼必須一方使用弱指針
- 兩個強指針互相引用,兩個空間就會永不釋放!所以必須要一強一弱。
@class TRDog; @interface TRPerson : NSObject //MRC寫法 //@property (nonatomic, retain) TRDog *dog; //ARC寫法 @property (nonatomic, strong) TRDog *dog; @end
@interface TRDog : NSObject //錯誤寫法,迴圈引用會導致記憶體泄露 //@property(nonatomic, strong)TRPerson *owner;
//正確寫法,當如果保存的是對象類型建議使用weak //@property(nonatomic, assign)TRPerson *owner; @property (nonatomic, weak) TRPerson *owner; @end |
- ARC下迴圈引用的案例:
1.7 如果在ARC下定義屬性的記憶體特質(attribute)
- 在MRC下, 與記憶體相關的屬性特質有:assign, retain, copy
- 在ARC下, 與記憶體相關的屬性特質有:
(1)strong 強引用
類似於retain,引用時候會引用計數+1。
strong 案例: |
@property (nonatomic, strong) NSString *str1; @property (nonatomic, strong) NSString *str2; |
self.str1 = @"Hello World"; self.str2 = self.str1; self.str1 = nil; NSLog(@"str2 = %@", self.str2); |
結果是:str2 = Hello World |
(2)weak 弱引用
- 類似於assign,不會改變引用計數,只做簡單的賦值。
- weak與assign的區別:
- assign 僅做賦值(預設值),不會解決記憶體問題。在MRC中可用於對象類型和非對象類型。
- weak只用於ARC下的對象類型(即帶*號的指針類型)。weak比assign更完全,會自動把野指針置空。
- assign一般在ARC下用於基本數據類型(即不帶*號的類型,包括id類型)
- weak 的特點:
- 聲明為weak的指針,指針指向的地址一旦被釋放,這些指針都將被賦值為nil。這樣的好處能有效的防止野指針。
- 特定情況下,如果記憶體的釋放會出現問題,經常使用weak來解決,比如,最常見的問題是"記憶體迴圈引用"