從上圖能夠看到,棧裏面存放的是值類型,堆裏面存放的是對象類型。對象的引用計數是在堆內存中操做的。下面咱們講講堆和棧怎麼存放和操做數據, 還有MRC
和ARC
怎麼管理引用計數。git
引自維基百科堆(英語:Heap)是計算機科學中一類特殊的數據結構的統稱。堆一般是一個能夠被看作一棵樹的數組對象。在隊列中,調度程序反覆提取隊列中第一個做業並運行,由於實際狀況中某些時間較短的任務將等待很長時間才能結束,或者某些不短小,但具備重要性的做業,一樣應當具備優先權。堆即爲解決此類問題設計的一種數據結構。程序員
堆(Heap)又被爲優先隊列(priority queue)。儘管名爲優先隊列,但堆並非隊列。回憶一下,在隊列中,咱們能夠進行的限定操做是dequeue和enqueue。dequeue是按照進入隊列的前後順序來取出元素。而在堆中,咱們不是按照元素進入隊列的前後順序取出元素的,而是按照元素的優先級取出元素。github
這就好像候機的時候,不管誰先到達候機廳,老是頭等艙的乘客先登機,而後是商務艙的乘客,最後是經濟艙的乘客。每一個乘客都有頭等艙、商務艙、經濟艙三種個鍵值(key)中的一個。頭等艙->商務艙->經濟艙依次享有從高到低的優先級。算法
總的來講,堆是一種數據結構,數據的插入和刪除是根據優先級定的,他有幾個特性:編程
舉個例子,就像疊羅漢,體重大(優先級低、值大)的站在最下面,體重小的站在最上面(優先級高,值小)。 爲了讓堆穩固,咱們每次都讓最上面的參與者退出堆,也就是每次取出優先級最高的元素。數組
引自維基百科棧是計算機科學中一種特殊的串列形式的抽象資料型別,其特殊之處在於只能容許在連接串列或陣列的一端(稱爲堆疊頂端指標,英語:top)進行加入數據(英語:push)和輸出數據(英語:pop)的運算。另外棧也能夠用一維數組或連結串列的形式來完成。堆疊的另一個相對的操做方式稱爲佇列。 因爲堆疊數據結構只容許在一端進行操做,於是按照後進先出(LIFO, Last In First Out)的原理運做。緩存
舉個例子,一把54式手槍的子彈夾,你往裏面裝子彈,最早射擊出來的子彈確定是最後裝進去的那一個。 這就是棧的結構,後進先出。安全
棧中的每一個元素稱爲一個frame
。而最上層元素稱爲top frame
。棧只支持三個操做, pop
, top
, push
。bash
棧不支持其餘操做。若是想取出元素12, 必須進行3次pop
操做。微信
堆棧空間分配
棧(操做系統):由操做系統自動分配釋放 ,存放函數的參數值,局部變量的值等。其操做方式相似於數據結構中的棧。 堆(操做系統): 通常由程序員分配釋放, 若程序員不釋放,程序結束時可能由OS回收,分配方式卻是相似於鏈表。
堆棧緩存方式
棧使用的是一級緩存, 他們一般都是被調用時處於存儲空間中,調用完畢當即釋放。 堆則是存放在二級緩存中,生命週期由虛擬機的垃圾回收算法來決定(並非一旦成爲孤兒對象就能被回收)。因此調用這些對象的速度要相對來得低一些。
通常狀況下程序存放在Rom
(只讀內存,好比硬盤)或Flash
中,運行時須要拷到RAM
(隨機存儲器RAM)中執行,RAM
會分別存儲不一樣的信息,以下圖所示:
內存中的棧區處於相對較高的地址以地址的增加方向爲上的話,棧地址是向下增加的。
棧中分配局部變量空間,堆區是向上增加的用於分配程序員申請的內存空間。另外還有靜態區是分配靜態變量,全局變量空間的;只讀區是分配常量和程序代碼空間的;以及其餘一些分區。
也就是說,在iOS
中,咱們的值類型是放在棧空間的,內存分配和回收不須要咱們關係,系統會幫我處理。在堆空間的對象類型就要有程序員本身分配,本身釋放了。
引自維基百科引用計數是計算機編程語言中的一種內存管理技術,是指將資源(能夠是對象、內存或磁盤空間等等)的被引用次數保存起來,當被引用次數變爲零時就將其釋放的過程。使用引用計數技術能夠實現自動資源管理的目的。同時引用計數還能夠指使用引用計數技術回收未使用資源的垃圾回收算法。 當建立一個對象的實例並在堆上申請內存時,對象的引用計數就爲1,在其餘對象中須要持有這個對象時,就須要把該對象的引用計數加1,須要釋放一個對象時,就將該對象的引用計數減1,直至對象的引用計數爲0,對象的內存會被馬上釋放。
正常狀況下,當一段代碼須要訪問某個對象時,該對象的引用的計數加1;當這段代碼再也不訪問該對象時,該對象的引用計數減1,表示這段代碼再也不訪問該對象;當對象的引用計數爲0時,代表程序已經再也不須要該對象,系統就會回收該對象所佔用的內存。
alloc
、new
、copy
、mutableCopy
開頭的方法來建立對象時,該對象的引用計數加1
。retain
方法時,該對象的引用計數加1
。release
方法時,該對象的引用計數減1
。NSObject
中提供了有關引用計數的以下方法:
retain
:將該對象的引用計數器加1
。release
:將該對象的引用計數器減1
。autorelease
:不改變該對象的引用計數器的值,只是將對象添加到自動釋放池中。retainCount
:返回該對象的引用計數的值。看到「引用計數」這個名稱,咱們便會不自覺地聯想到「某處有某物多少多少」而將注意力放到計數上。但其實,更加客觀、正確的思考方式:
引用計數式內存管理的思考方式僅此而已。按照這個思路,徹底沒必要考慮引用計數。 上文出現了「生成」、「持有」、「釋放」三個詞。而在Objective-C
內存管理中還要加上「廢棄」一詞。各個詞標書的Objective-C
方法以下表。
對象操做 | Objective-C方法 |
---|---|
生成並持有對象 | alloc/new/copy/mutableCopy等方法 |
持有對象 | retain方法 |
釋放對象 | release方法 |
廢棄對象 | dealloc方法 |
這些有關Objective-C
內存管理的方法,實際上不包括在該語言中,而是包含在Cocoa框架中用於macOS
、iOS
應用開發。Cocoa
框架中Foundation
框架類庫的NSObject
類擔負內存管理的職責。Objective-C
內存管理中的alloc/retain/release/dealloc
方法分別指代NSObject
類的alloc
類方法、retain
實例方法、release
實例方法和dealloc
實例方法。
Cocoa框架、Foundation框架和NSObject類的關係
顧名思義,MRC
就是調用Objective-C
的方法(alloc/new/copy/mutableCopy/retain/release
等)實現引用計數的增長和減小。
下面經過Objective-C
的方法實現內存管理的思考方式。
使用如下名稱開頭的方法名意味着本身生成的對象只有本身持有:
// 本身生成並持有對象
id obj = [[NSObject alloc] init];
複製代碼
使用NSObject
類的alloc
方法就能本身生成並持有對象。指向生成並持有對象的指針被賦給變量obj
。
// 本身生成並持有對象
id obj = [NSObject new];
複製代碼
[NSObject new]
與[[NSObject alloc] init]
是徹底一致的。
copy
方法利用基於NSCopying
方法約定,由各種實現的copyWithZone:
方法生成並持有對象的副本。
#import "ViewController.h"
@interface Person: NSObject<NSCopying>
@property (nonatomic, strong) NSString *name;
@end
@implementation Person
- (id)copyWithZone:(NSZone *)zone {
Person *obj = [[[self class] allocWithZone:zone] init];
obj.name = self.name;
return obj;
}
@end
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
//alloc生成並持有對象
Person *p = [[Person alloc] init];
p.name = @"testname";
//copy生成並持有對象
id obj = [p copy];
//打印對象
NSLog(@"p對象%@", p);
NSLog(@"obj對象%@", obj);
}
@end
複製代碼
打印結果: 2018-03-28 23:01:32.321661+0800 ocram[4466:1696414] p對象<Person: 0x1c0003320> 2018-03-28 23:01:32.321778+0800 ocram[4466:1696414] obj對象<Person: 0x1c0003370>
從打印能夠看到obj
是p
對象的副本。二者的引用計數都是1
。
說明:在
- (id)copyWithZone:(NSZone *)zone
方法中,必定要經過[self class]
方法返回的對象調用allocWithZone:
方法。由於指針可能實際指向的是Person
的子類。這種狀況下,經過調用[self class]
,就能夠返回正確的類的類型對象。
與copy
方法相似,mutableCopy
方法利用基於NSMutableCopying
方法約定,由各種實現的mutableCopyWithZone:
方法生成並持有對象的副本。
#import "ViewController.h"
@interface Person: NSObject<NSMutableCopying>
@property (nonatomic, strong) NSString *name;
@end
@implementation Person
- (id)mutableCopyWithZone:(NSZone *)zone {
Person *obj = [[[self class] allocWithZone:zone] init];
obj.name = self.name;
return obj;
}
@end
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
//alloc生成並持有對象
Person *p = [[Person alloc] init];
p.name = @"testname";
//copy生成並持有對象
id obj = [p mutableCopy];
//打印對象
NSLog(@"p對象%@", p);
NSLog(@"obj對象%@", obj);
}
@end
複製代碼
打印結果: 2018-03-28 23:08:17.382538+0800 ocram[4476:1699096] p對象<Person: 0x1c4003c20> 2018-03-28 23:08:17.382592+0800 ocram[4476:1699096] obj對象<Person: 0x1c4003d70>
從打印能夠看到obj
是p
對象的副本。二者的引用計數都是1
。
copy
和mutableCopy
的區別在於,copy
方法生成不可變動的對象,而mutableCopy
方法生成可變動的對象。
既然講到copy
和mutableCopy
,那就要談一下深拷貝和淺拷貝的概念和實踐。
簡單理解就是,淺拷貝是拷貝了指向對象的指針, 深拷貝不但拷貝了對象的指針,還在系統中再分配一塊內存,存放拷貝對象的內容。
淺拷貝:拷貝對象自己,返回一個對象,指向相同的內存地址。 深層複製:拷貝對象自己,返回一個對象,指向不一樣的內存地址。
深淺拷貝取決於拷貝後的對象的是否是和被拷貝對象的地址相同,若是不一樣,則產生了新的對象,則執行的是深拷貝,若是相同,則只是指針拷貝,至關於retain一次原對象, 執行的是淺拷貝。
深拷貝和淺拷貝的判斷要注意兩點:
copy
仍是mutableCopy
id obj = [NSArray array];
id obj1 = [obj copy];
NSLog(@"obj是%p", obj);
NSLog(@"obj1是%p", obj1);
複製代碼
打印結果: 2018-03-29 20:48:56.087197+0800 ocram[5261:2021415] obj是0x1c0003920 2018-03-29 20:48:56.087250+0800 ocram[5261:2021415] obj1是0x1c0003920
指針同樣obj
是淺拷貝。
id obj = [NSArray array];
id obj1 = [obj mutableCopy];
NSLog(@"obj是%p", obj);
NSLog(@"obj1是%p", obj1);
複製代碼
打印結果: 2018-03-29 20:42:16.508134+0800 ocram[5244:2018710] obj是0x1c00027d0 2018-03-29 20:42:16.508181+0800 ocram[5244:2018710] obj1是0x1c0453bf0
指針不同obj
是深拷貝。
id obj = [NSMutableArray array];
id obj1 = [obj copy];
NSLog(@"obj是%p", obj);
NSLog(@"obj1是%p", obj1);
複製代碼
打印結果: 2018-03-29 20:50:36.936054+0800 ocram[5265:2022249] obj是0x1c0443f90 2018-03-29 20:50:36.936097+0800 ocram[5265:2022249] obj1是0x1c0018580
指針不同obj
是深拷貝。
id obj = [NSMutableArray array];
id obj1 = [obj mutableCopy];
NSLog(@"obj是%p", obj);
NSLog(@"obj1是%p", obj1);
複製代碼
打印結果: 2018-03-29 20:52:30.057542+0800 ocram[5268:2023155] obj是0x1c425e6f0 2018-03-29 20:52:30.057633+0800 ocram[5268:2023155] obj1是0x1c425e180
指針不同obj
是深拷貝。
id obj = [NSMutableArray arrayWithObject:@"test"];
id obj1 = [obj mutableCopy];
NSLog(@"obj是%p", obj);
NSLog(@"obj內容是%p", obj[0]);
NSLog(@"obj1是%p", obj1);
NSLog(@"obj1內容是%p", obj1[0]);
複製代碼
打印結果: 2018-03-29 20:55:18.196597+0800 ocram[5279:2025743] obj是0x1c0255120 2018-03-29 20:55:18.196647+0800 ocram[5279:2025743] obj內容是0x1c02551e0 2018-03-29 20:55:18.196665+0800 ocram[5279:2025743] obj1是0x1c0255210 2018-03-29 20:55:18.196682+0800 ocram[5279:2025743] obj1內容是0x1c02551e0
能夠看到obj
和obj1
雖然指針是不同的(深拷貝),可是他們的元素的指針是同樣的,因此數組裏的元素依然是淺拷貝。
使用上述使用一下名稱開頭的方法,下面名稱也意味着本身生成並持有對象。
使用駝峯拼寫法來命名。
#import "ViewController.h"
@interface Person: NSObject
@property (nonatomic, strong) NSString *name;
+ (id)allocObject;
@end
@implementation Person
+ (id)allocObject {
//本身生成並持有對象
id obj = [[Person alloc] init];
return obj;
}
@end
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
//取得非本身生成並持有的對象
Person *p = [Person allocObject];
p.name = @"testname";
NSLog(@"p對象%@", p);
}
@end
複製代碼
打印結果: 2018-03-28 23:33:37.044327+0800 ocram[4500:1706677] p對象<Person: 0x1c0013770>
allocObject
名稱符合上面的命名規則,所以它與用alloc
方法生成並持有對象的狀況徹底相同,因此使用allocObject
方法也意味着「本身生成並持有對象」。
//非本身生成的對象,暫時沒有持有
id obj = [NSMutableArray array];
//經過retain持有對象
[obj retain];
複製代碼
上述代碼中NSMutableArray
經過類方法array
生成了一個對象賦給變量obj
,但變量obj
本身並不持有該對象。使用retain
方法能夠持有對象。
本身持有的對象,一旦再也不須要,持有者有義務釋放該對象。釋放使用release
方法。
// 本身生成並持有對象
id obj = [[NSObject alloc] init];
//釋放對象
[obj release];
複製代碼
如此,用alloc
方法由本身生成並持有的對象就經過realse
方法釋放了。本身生成而非本身所持有的對象,若用retain
方法變爲本身持有,也一樣能夠用realse
方法釋放。
//非本身生成的對象,暫時沒有持有
id obj = [NSMutableArray array];
//經過retain持有對象
[obj retain];
//釋放對象
[obj release];
複製代碼
像調用[NSMutableArray array]
方法使取得的對象存在,但本身並不持有對象,是如何實現的呢?
+ (id)array {
//生成並持有對象
id obj = [[NSMutableArray alloc] init];
//使用autorelease不持有對象
[obj autorelease];
//返回對象
return obj;
}
複製代碼
上例中,咱們使用了autorelease
方法。用該方法,可使取得的對象存在,但本身不持有對象。autorelease
提供這樣的功能,使對象在超出指定的生存範圍時可以自動並正確的釋放(調用release
方法)。
在後面會對autorelease
作更爲詳細的介紹。使用NSMutableArray
類的array
類方法等能夠取得誰都不持有的對象,這些方法都是經過autorelease
實現的。根據上文的命名規則,這些用來取得誰都不持有的對象的方法名不能以alloc/new/copy/mutableCopy
開頭,這點須要注意。
對於用alloc/new/copy/mutableCopy
方法生成並持有的對象,或是用retain
方法持有的對象,因爲持有者是本身,因此在不須要該對象時須要將其釋放。而由此之外所獲得的對象絕對不能釋放。假若在程序中釋放了非本身所持有的對象就會形成崩潰。
// 本身生成並持有對象
id obj = [[NSObject alloc] init];
//釋放對象
[obj release];
//再次釋放已經非本身持有的對象,應用程序崩潰
[obj release];
複製代碼
釋放了非本身持有的對象,確定會致使應用崩潰。所以絕對不要去釋放非本身持有的對象。
說到Objective-C內存管理,就不能不提autorelease。 顧名思義,autorelease就是自動釋放。這看上去很像ARC,單實際上它更相似於C語言中自動變量(局部變量)的特性。
在C語言中,程序程序執行時,若局部變量超出其做用域,該局部變量將被自動廢棄。
{
int a;
}
//由於超出變量做用域,代碼執行到這裏,自動變量`a`被廢棄,不可再訪問。
複製代碼
autorelease
會像C
語言的局部變量那樣來對待對象實例。當其超出做用域時,對象實例的release
實例方法被調用。另外,同C
語言的局部變量不一樣的是,編程人員能夠設置變量的做用域。
autorelease
的具體使用方法以下:
NSAutoreleasePool
對象。autorelease
實例方法。NSAutoreleasePool
對象。NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
id obj = [[NSObject alloc] init];
[obj autorelease];
[pool drain];
複製代碼
上述代碼中最後一行的[pool drain]
等同於[obj release]
。
調用NSObject
類的autorelease
實例方法。
[obj autorelease];
複製代碼
調用autorelease
方法的內部實現
- (id) autorelease {
[NSAutoreleasePool addObject: self];
}
複製代碼
autorelease
實例方法的本質就是調用NSAutoreleasePool
對象的addObject
類方法。
autorelease
是NSObject
的實例方法,NSAutoreleasePool
也是繼承NSObject
的類。那能不能調用autorelease
呢?
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
[pool release];
複製代碼
運行結果發生崩潰。一般在使用Objective-C
,也就是Foundation
框架時,不管調用哪個對象的autorelease
實例方法,實現上是調用的都是NSObject
類的autorelease
實例方法。可是對於NSAutoreleasePool
類,autorelease
實例方法已被該類重載,所以運行時就會出錯。
上面講了「引用計數內存管理的思考方式」的本質部分在ARC中並無改變。就像「自動引用計數」這個名稱表示的那樣,ARC只是自動地幫助咱們處理「引用計數」的相關部分。
在編譯單位上,可設置ARC
有效或無效,即設置特定類是否啓用ARC
。 在Project
裏面找到Build Phases
-Compile Sources
,這裏是全部你的編譯文件。指定編譯器屬性爲-fobjc-arc
即爲該文件使用ARC
,指定編譯器屬性爲-fno-objc-arc
即爲該文件不使用ARC
,以下圖所示。
編譯器在編譯時會幫咱們自動插入,包括 retain
、release
、copy
、autorelease
、autoreleasepool
Objective-C編程中爲了處理對象,可將變量類型定義爲id類型或各類對象類型。 ARC中,id類型和對象類其類型必須附加全部權修飾符。
__strong
修飾符是id
類型和對象類型默認的全部權修飾符。也就是說,不寫修飾符的話,默認對象前面被附加了__strong
全部權修飾符。
id obj = [[NSObject alloc] init];
等同於
id __strong obj = [[NSObject alloc] init];
複製代碼
__strong
修飾符的變量obj
在超出其變量做用域時,即在該變量被廢棄時,會釋放其被賦予的對象。 __strong
修飾符表示對對象的「強引用」。持有強引用的變量在超出其做用域時被廢棄,隨着強引用的失效,引用的對象會隨之釋放。
固然,__strong
修飾符也能夠用在Objective-C類成員變量和方法參數上。
@interface Test: NSObject
{
id __strong obj_;
}
- (void)setObject:(id __strong)obj;
@end
@implementation Test
- (instancetype)init {
self = [super init];
return self;
}
- (void)setObject:(id __strong)obj {
obj_ = obj
}
@end
複製代碼
無需額外的工做即可以使用於類成員變量和方法參數中。__strong
修飾符和後面要講的__weak
修飾符和__autoreleasing
修飾符一塊兒,能夠保證將附有這些修飾符的自動變量初始化爲nil
。
正如蘋果宣稱的那樣,經過__strong
修飾符再鍵入retain
和release
,完美地知足了「引用計數式內存管理的思考方式」。
經過__strong
修飾符並不能完美的進行內存管理,這裏會發生「循環引用」的問題。
經過上面的例子代碼實現循環引用。
{
id test0 = [[Test alloc] init];
id test1 = [[Test alloc] init];
[test0 setObject:test1];
[test1 setObject:test0];
}
複製代碼
能夠看到test0
和tets1
互相持有對方,誰也釋放不了誰。
循環引用容易發生內存泄露。所謂內存泄露就是應當廢棄的對象在超出其生命週期後繼續存在。
__weak
修飾符能夠避免循環引用,與__strong
修飾符相反,提供弱引用。弱引用不能持有對象實例,因此在超出其變量做用域時,對象即被釋放。像下面這樣將以前的代碼修改,就能夠避免循環引用了。
@interface Test: NSObject
{
id __weak obj_;
}
- (void)setObject:(id __strong)obj;
複製代碼
使用__weak
修飾符還有另一個優勢。在持有某對象的弱引用時,若該對象被廢棄,則此弱引用將自動失效且處於nil賦值的狀態(空弱引用)。
id __weak obj1 = nil;
{
id __strong obj0 = [[NSObject alloc] init];
obj1 = obj0;
NSLog(@"%@", obj1);
}
NSLog(@"%@", obj1);
複製代碼
打印結果: 2018-03-30 21:47:50.603814+0800 ocram[51624:22048320] <NSObject: 0x60400001ac10> 2018-03-30 21:47:50.604038+0800 ocram[51624:22048320] (null)
能夠看到由於obj0
超出做用域就被釋放了,弱引用也被至爲nil
狀態。
__unsafe_unretained
修飾符是不安全的修飾符,儘管ARC
式的內存管理是編譯器的工做,但附有__unsafe_unretained
修飾符的變量不屬於編譯器的內存管理對象。__unsafe_unretained
和__weak
同樣不能持有對象。
id __unsafe_unretained obj1 = nil;
{
id __strong obj0 = [[NSObject alloc] init];
obj1 = obj0;
NSLog(@"%@", obj1);
}
NSLog(@"%@", obj1);
複製代碼
打印結果: 2018-03-30 21:58:28.033250+0800 ocram[51804:22062885] <NSObject: 0x604000018e80>
能夠看到最後一個打印沒有打印出來,程序崩潰了。這是由於超出了做用域,obj1
已經變成了一個野指針,而後咱們去操做野指針的時候會發生崩潰。
因此在使用__unsafe_unretained
修飾符時,賦值給__strong
修飾符的變量時有必要確保被賦值的對象確實存在。
在ARC
中,我也可使用autorelease
功能。指定「@autoreleasepool
塊」來代替「NSAutoreleasePool
類對象生成、持有以及廢棄這一範圍,使用附有__autoreleasing
修飾符的變量替代autorelease
方法。
其實咱們不用顯示的附加 __autoreleasing
修飾符,這是因爲編譯器會檢查方法名是否以alloc/new/copy/mutableCopy
開始,若是不是則自動將返回值的對象註冊到autoreleasepool
。
有時候__autoreleasing
修飾符要和__weak
修飾符配合使用。
id __weak obj1 = obj0;
id __autoreleasing tmp = obj1;
複製代碼
爲何訪問附有__weak
修飾符的變量時必須訪問註冊到autoreleasepool
的對象呢?這是由於__weak
修飾符只持有對象的弱引用,而在訪問引用對象的過程當中,該對象有可能被廢棄。若是把訪問的對象註冊到autoreleasepool
中,那麼在@autoreleasepool
塊結束以前都能確保該對象存在。
以上各類屬性賦值給指定的屬性中就至關於賦值給附加各屬性對應的全部權修飾符的變量中。只有copy
不是簡單的賦值,它賦值的是經過NSCopying
接口的copyWithZone:
方法複製賦值源所生成的對象。
在ARC
有效的狀況下編譯源代碼,必須遵照必定的規則。
retain/release/retainCount/autorelease
ARC
有效時,實現retain/release/retainCount/autorelease
會引發編譯錯誤。代碼會標紅,編譯不經過。
NSAllocateObject/NSDeallocateObject
alloc
,new
,copy
,mutableCopy
,init
以init
開始的方法的規則要比alloc
,new
,copy
,mutableCopy
更嚴格。該方法必須是實例方法,而且要返回對象。返回的對象應爲id
類型或方法聲明類的對象類型,抑或是該類的超類型或子類型。該返回對象並不註冊到autoreleasepool
上。基本上只是對alloc
方法返回值的對象進行初始化處理並返回該對象。
//符合命名規則
- (id) initWithObject;
//不符合命名規則
- (void) initThisObject;
複製代碼
dealloc
當對象的引用計數爲0
,全部者不持有該對象時,該對象會被廢棄,同時調用對象的dealloc
方法。ARC
會自動對此進行處理,所以沒必要書寫[super dealloc]
。
@autoreleasepool
塊替代NSAutoreleasePool
C語言結構體(struct、union)的成員中,若是存在Objective-C對象型變量,便會引發編譯錯誤。
struct Data {
NSMutableArray *array;
};
複製代碼
顯示警告: ARC forbids Objective-C objects in struct
在C
語言的規約上沒有方法來管理結構體成員的生命週期。由於ARC
把內存管理的工資分配給編譯器,因此編譯器必須可以知道並管理對象的生命週期。例如C
語言的局部變量可以使用該變量的做用域管理對象。可是對於C
語言的結構體成員來講,這在標準上就是不可實現的。
要把對象類型添加到結構體成員中,能夠強制轉換爲void *
或是附加__unsafe_unretained
修飾符。
struct Data {
NSMutableArray __unsafe_unretained *array;
};
複製代碼
__unsafe_unretained
修飾符的變量不屬於編譯器的內存管理對象。若是管理時不注意賦值對象的全部者,即可能遭遇內存泄露或者程序崩潰。
id
和void *
在MRC時,將id
變量強制轉換void *
變量是能夠的。
id obj = [[NSObject alloc] init];
void *p = obj;
id o = p;
[o release];
複製代碼
可是在ARC時就會編譯報錯,id
型或對象型變量賦值給void *
或者逆向賦值時都須要進行特定的轉換。若是隻想單純的賦值,則可使用「__bridge
轉換」
__bridge轉換中還有另外兩種轉換,分部是「__bridge_retained
」和「__bridge_transfer
轉換」 __bridge_retained
轉換與retain
相似,__bridge_transfer
轉換與release相似。
void *p = (__bridge_retained void *)[[NSObject alloc] init];
NSLog(@"class = %@", [(__bridge id)p class]);
(void)(__bridge_transfer id)p;
複製代碼
struct Data {
NSMutableArray __unsafe_unretained *array;
};
複製代碼
__unsafe_unretained
修飾符的變量不屬於編譯器的內存管理對象。若是管理時不注意賦值對象的全部者,即可能遭遇內存泄露或者程序崩潰。
循環引用常見有三種現象:
@interface Test: NSObject
{
id __weak obj_;
}
- (void)setObject:(id __strong)obj;
複製代碼
__weak __typeof(self) wself = self;
obj.block = ^{
__strong __typeof(wself) sself = wself;
[sself updateSomeThing];
}
複製代碼
NSTimer會形成循環引用,timer會強引用target即self,通常self又會持有timer做爲屬性,這樣就形成了循環引用。 那麼,若是timer只做爲局部變量,不把timer做爲屬性呢?一樣釋放不了,由於在加入runloop的操做中,timer被強引用。而timer做爲局部變量,是沒法執行invalidate的,因此在timer被invalidate以前,self也就不會被釋放。
嚴格來講這個不算是內存泄露,主要就是咱們在單例裏面設置一個對象的屬性,由於單例是不會釋放的,因此單例會有一直持有這個對象的引用。
[Instanse shared].obj = self;
複製代碼
能夠看到單例持有了當前對象self
,這個self
就不會釋放了。
打開Xcode8自帶的Instruments
或者
或者:長按運行按鈕,而後出現如圖所示列表,點擊Profile.
按上面操做,build成功後跳出Instruments工具,選擇Leaks選項
選擇以後界面以下圖:
到這裏以後,咱們前期的準備工做作完啦,下面開始正式的測試!
(有一個注意的點,最好選擇真機進行測試,模擬器是運行在mac上的,mac跟手機仍是有區別的嘛。)
1.選中Xcode先把程序(command + R)運行起來(若是Xcode左上角已是instrument的圖標就不用執行這一步了)
2.再選中Xcode,按快捷鍵(command + control + i)運行起來,此時Leaks已經跑起來了
3.因爲Leaks是動態監測,因此咱們須要手動操做APP,一邊操做,一邊觀察Leaks的變化,當出現紅色叉時,就監測到了內存泄露,點擊左上角的第二個,進行暫停檢測(也可繼續檢測).如圖所示:
4.下面就是定位修改了,此時選中有紅色柱子的Leaks,下面有個"田"字方格,點開,選中Call Tree
顯示以下圖界面
5.下面就是最關鍵的一步,在這個界面的右下角有若干選框,選中Invert Call Tree 和Hide System Libraries,(紅圈範圍內)顯示以下:
若是右下角找不到此設置窗口,能夠在底部點擊Call Tree,顯示以下:
到這裏就算基本完成啦,這裏顯示的就是內存泄露代碼部分,那麼如今還差一步:定位!
6.選中顯示的若干條中的一條,雙擊,會自動跳到內存泄露代碼處,如圖所示
在選擇call tree後,可能你會發現查看不到源碼從而沒法定位內存泄漏的位置,只是顯示16進制的數據。此時須要你在Xcode中檢查是否有dSYM File生成,以下圖所示選擇第二項DWARF with dSYM File.
咱們生成的對象,在即將釋放的時候,會調用dealloc
方法。因此咱們能夠在dealloc
打印當前對象已經釋放的消息。若是沒有釋放,對象的dealloc
方法就永遠不會執行,此時咱們知道發生了內存泄露。
經過這個思路,我寫了一個小工具用來檢查當前controller
沒有釋放的,而後打印出來。
歡迎關注公衆號:jackyshan,技術乾貨首發微信,第一時間推送。