技 術 文 章 / 超 人
2019-03-20 補充@ dynamic與@ synthesize內容
個人覺得要更加深入直觀了解MRC與ARC的區別建議先從內存分析開始所以文章開始會從內存說起
文章目錄
- 1.五大內存區域
- 1.1 棧區
- 1.2 堆區
- 1.3 全局區
- 1.4 常量區
- 1.5 代碼區
- 1.6 static靜態變量
- 1.7 extern全局變量
- 1.8 const常量
- 2.屬性標識符
- 2.1 @property、@synthesize、@dynamic
- 2.2 nonatomic與atomic
- 2.3 strong、weak、retain、assgin、copy、unsafe_unretained
- 2.4 readOnly、readWrite、getter=、setter=
- 2.5 __unsafe_unretained、__weak、__strong
- 3.MRC與ARC區別
- 3.1 MRC手動內存管理
- 3.2 ARC自動內存管理
- 3.3 autoreleasepool自動釋放池
- 4.NSString單獨說
1.五大內存區域
棧區,堆區,全局區,常量區,代碼區
-
1.1棧區:
創建臨時變量時由編譯器自動分配,在不需要的時候自動清除的變量的存儲區
。
里面的變量通常是局部變量
、函數參數
等。在一個進程中,位于用戶虛擬地址空間頂部的是用戶棧,編譯器用它來實現函數的調用。和堆一樣,用戶棧在程序執行期間可以動態地擴展和收縮。
@interface TestObject()
@end
@implementation TestObject
- (void)testMethodWithName:(NSString *)name
{
//方法參數name是一個指針,指向傳入的參數指針所指向的對象內存地址。name是在棧中
//通過打印地址可以看出來,傳入參數的對象內存地址與方法參數的對象內存地址是一樣的。但是指針地址不一樣。
NSLog(@"name指針地址:%p,name指針指向的對象內存地址:%p",&name,name);
//*person 是指針變量,在棧中, [Person new]是創建的對象,放在堆中。
//person指針指向了[Person new]所創建的對象。
//那么[Person new]所創建的對象的引用計數器就被+1了,此時[Person new]對象的retainCount為1
Person *person = [Person new];
}
-
1.2堆區:
就是那些由 new alloc 創建的對象所分配的內存塊,它們的釋放系統不會主動去管,由我們的開發者去告訴系統什么時候釋放這塊內存(一個對象引用計數為0時系統就會回銷毀該內存區域對象)。一般一個 new 就要對應一個 release。在ARC下編譯器會自動在合適位置為OC對象添加release操作。會在當前線程Runloop退出或休眠時銷毀這些對象,MRC則需程序員手動釋放。
堆可以動態地擴展和收縮。
//alloc是為Person對象分配內存,init是初始化Person對象。本質上跟[Person new]一樣。
Person *person = [[Person alloc] init];
1.3全局/靜態存儲區,
全局變量
和靜態變量
被分配到同一塊內存中,在以前的 C 語言中,全局變量又分為初始化
的和未初始化
的(初始化的全局變量和靜態變量在一塊區域,
未初始化的全局變量與靜態變量在相鄰的另一塊區域,
同時未被初始化的對象存儲區可以通過 void* 來訪問和操縱,
程序結束后由系統自行釋放),在 C++ 里面沒有這個區分了,
他們共同占用同一塊內存區。1.4常量存儲區
這是一塊比較特殊的存儲區,他們里面存放的是常量,不允許修改。一般值都是放在這個地方的1.5代碼區
存放函數的二進制代碼
NSString *string1;//string1 這個NSString 類型的指針,未初始化存在于<全局區>的<BBS區>
NSString *string2 = @"1234";//string2 這個NSString類型的指針,已初始化存在與<全局區>的<data數據區>,@“1234”存在與堆區,因為@代表了對象。
static NSString *string3;//string3 這個NSString 類型的指針存在于<全局區>的<BBS區>
static NSString *string4 = @"1234";//string4 這個NSString類型的指針存在與<全局區>的<data數據區>,@“1234”存在與堆區,因為@代表了對象。stiring2和string4的值地址是一樣的
static const NSString *string5 = @"654321";//const 修飾后 string5不能修改值。 其他的與string4一樣
- (void)test
{
int a;//a這個int類型的變量 是存在與<棧區>的
a = 10;//10這個值是存在與 <常量區>的
NSStirng *str;//str這個NSString類型的指針 存在于<棧區>
str = @“1234”;//@“1234”這個@對象存在于 <堆區>
static NSString *str1;//str1這個NSString類型的指針 存在于<全局區>的<BBS區>
static NSString *str2 = @"4321';//str2這個NSString類型的指針 存在于<全局區>的<data區>
NSString *str3;//str3這個NSString類型的指針 存在于<棧區>
str3 = [[NSString alloc]initWithString:@"1234"];//[[NSString alloc]initWithString:@"1234"]這個對象 存在于<堆區>
}
1.6 static靜態變量
靜態變量有兩種
- 全局靜態變量
優點
:不管對象方法還是類方法都可以訪問和修改全局靜態變量,并且外部類無法調用靜態變量,定義后只會指向固定的指針地址,供所有對象使用,節省空間。
缺點
:存在的生命周期長,從定義直到程序結束。
建議
:從內存優化和程序編譯的角度來說,盡量少用全局靜態變量,因為存在的聲明周期長,一直占用空間。程序運行時會單獨加載一次全局靜態變量,過多的全局靜態變量會造成程序啟動慢,當然就目前的手機處理器性能,幾十幾百個估計也影響不大吧。 - 局部靜態變量
優點
:定義后只會存在一份值,每次調用都是使用的同一個對象內存地址的值,并沒有重新創建,節省空間,只能在該局部代碼塊中使用。
缺點
:存在的生命周期長,從定義直到程序結束,只能在該局部代碼塊中使用。
建議
:局部和全局靜態變量從本根意義上沒有什么區別,只是作用域不同而已。如果值僅一個類中的對象和類方法使用并且值可變,可以定義全局靜態變量,如果是多個類使用并可變,建議值定義在model作為成員變量使用。如果是不可變值,建議使用宏定義
static NSString *name;
1.7 extern全局變量
全局變量有兩種
- 對內的全局變量:沒有用extern在.h中修飾的變量,僅定義在.m中讓該變量只能在該類使用
優點
:不管對象方法還是類方法都可以訪問和修改全局靜態變量,并且外部類無法調用靜態變量,定義后只會存一份值,供所有對象使用,節省空間。跟全局靜態變量一樣,只是少了static修飾少了static特性
缺點
:存在的生命周期長,從定義直到程序結束
建議
:跟全局靜態變量都一樣了,還需要用對內的全局變量嗎?不用extern修飾就少了extern的特性,還不如用全局靜態變量,至少能明確的知道static是對內使用的 - 外部全局變量:除了該類,其他文件也可以訪問該變量
優點
:除了該類,其他文件也可以訪問該變量
缺點
:存在的生命周期長,從定義直到程序結束。并且外部可以修改其值,出現錯誤不容易定位
建議
:使用全局變量的原因就在于其對外的特性,但是其使用的方便性沒有使用model的屬性或宏來得方便。程序啟動的時候會單獨加載全局變量,同理與全局靜態變量,少使用。
.m中要定義
NSString *name;
.h中同時要定義
extern NSString *name;
全局靜態變量與全局變量 其實本質上是沒有區別的,只是存在修飾區別,一個static讓其只能內部使用,一個extern讓其可以外部使用
1.8 const常量
不同于變量,常量的值是固定不可變的,一般用于只讀值。
優點
:只可以讀取值,不能修改。一般用于接口或者文字顯示這種固定值。添加extern可以對外全局常量,任意位置都可以訪問
缺點
:存在的生命周期長,從定義直到程序結束。需要在.h .m中分別定義代碼較多
建議
:良好的編碼習慣而言,少使用宏,多使用常量。因為常量聲明是有明確類型的,而宏只是替換并不能進行類型判斷。不夠嚴謹。
.h中定義extern
extern NSString *const name;
.m中定義值
NSString *const name = @"123";
//const聲明部位不同,意義也不同。const定義的是其右邊整體不可變。
//*const name1定義的是 name1 不可變。 name1是指針。
//因此 不能通過修改name1而指向其他值。常規的const使用這個方法定義不可修改的值
NSString *const name1 = @"456";
//const * name定義的是 * name不可變, 而*name指向的是@"123",
//也就是說@"123"這個內存地址的值不可變為其他值。
NSString const * name2 = @"789";
//但name指針可以指向其他值,所以該定義方式無法保證值的唯一性。
NSString *newName = @"222";
name2 = newName;
2.屬性標識符
- 2.1 @property、@synthesize、@dynamic
@synthesize:作用于在@implementation內部,用@synthesiz聲明的屬性在編譯的時候會自動為該屬性按照固有規則生成相應的getter setter方法。如果有手動生成getter setter方法也不會報錯。
//array是聲明的屬性名稱,_array是編譯器自動生成的成員變量。
@synthesize array = _array;
@dynamic:作用于在@implementation內部,與@synthesize不同,使用@dynamic聲明時相當于告訴編譯器getter setter方法由用戶自己生成。如果聲明為@dynamic而沒有手動生成getter setter方法編譯的時候不報錯,但是在運行時如果使用.語法
去調用該屬性時會崩潰。之所以在運行時才會發生崩潰是因為OC具有動態綁定特性。只有在運行時才會去確認具體的調用方法。
@interface TestObject()
{
NSMutableDictionary *_dic;
}
@property (nonatomic, strong) NSMutableDictionary *dic;
@end
//不同于@ synthesize, @dynamic只需要聲明屬性名稱即可,不需要dic = _dic把值賦值過去。但是需要自己在@interface中聲明成員變量。
@dynamic dic;
@property:相對于@dynamic 和 @synthesize ,@property聲明的作用區域在@interface內部。 它會告訴編譯器自動生成getter setter方法。也允許用戶手動生成getter setter中的一個方法,用@property聲明的屬性不能手動同時寫getter setter方法,否則編譯器會報錯。@property更好的聲明屬性變量。因為訪問方法的命名約定,可以很清晰的看出getter和setter的用處,會傳遞一些額外信息,后面會跟相應的各種信息例如:@property (nonatomic, strong,onlyread) NSString *name;大多數時候都用的@property聲明
@interface TestObject()
{
NSMutableDictionary *_dic;
}
//因為使用了@property聲明,編譯器會自動生成相應getter setter方法。
//使用@property不能手動同時生成getter setter方法,編譯器會報錯
//nonatomic表示屬性是非原子操作,strong表示強引用,
//readonly表示該屬性權限為僅讀,那么編譯器只會生成getter方法,不會生成setter方法
@property (nonatomic, strong,readonly) NSString *name;
@property (nonatomic, strong) NSMutableArray *array;
@property (nonatomic, strong) NSMutableDictionary *dic;
@end
@implementation TestObject
//如果上面的array使用的@property聲明,而用戶又要手動同時生成getter setter方法
//可以使用@synthesize 告訴編譯器 該屬性getter setter方法如果沒有手動聲明就自動創建,有就不自動生成。
@synthesize array = _array;
//如果dic用@property聲明過了,會自動生成getter setter方法。但是又不希望它自動生成getter setter方法。
//可以用@dynamic 聲明。告訴編譯器 該屬性的getter setter方法不自動生成
//但如果要自己生成getter setter必須在@ interface內部聲明對應的成員變量
@dynamic dic;
- (void)setArray:(NSMutableArray *)array
{
_array = array;
}
- (NSMutableArray *)array
{
if (!_array) {
_array = [NSMutableArray new];
}
return _array;
}
- (void)setArray:(NSMutableDictionary *)dic
{
_dic = dic;
}
- (NSMutableDictionary *)dic
{
if (!_dic) {
_dic = [NSMutableDictionary new];
}
return _dic;
}
@end
- 2.2 nonatomic與atomic
nonatomic(非原子性):在調用用nonatomic聲明的對象屬性時是非線程安全性的。最為直觀的就是NSMutableArray的使用。當同時在子線程去增刪數組元素,在主線程中去遍歷數組元素就會出現數組越界或者數組沒有遍歷完。因為采用的nonatomic,不同操作可以同時執行,而不需要等前面的操作完成后在進行下一步操作。所以稱之為非線程安全。非原子性的執行效率更高不會阻塞線程
atomic(原子性):相反與非原子性,atomic是具有線程安全性的。當屬性聲明為atomic時,調用該屬性的getter/setter方法,會加上同步鎖,保證同一時刻只能有一個線程調用屬性的getter/setter方法。但是atomic只能保證線程的讀和寫過程是可靠的。但并不能保證數據一定是可靠的。例如:有線程A和線程B的情況下,從調度順序來說,線程A先調用了setter方法修改了屬性值,然后線程B也調用了setter方法再次修改了屬性值,最后線程A調用getter方法獲取屬性值時,獲取到的結果值可能是線程A修改的值,也可能是線程B修改的值,因為A、B是兩個線程異步的,最后A調用getter獲取的值,取決于B線程是已經設置成功還是B線程還沒有執行setter方法。
- 2.3 strong、weak、retain、assgin、copy、unsafe_unretained
retain:釋放舊對象,提高輸入對象的引用計數+1,將輸入對象的值賦值于舊對象,只能用戶聲明OC對象
@property (nonatomic, retain) Room *room;
- (void)setRoom:(Room *)room // room = r
{
// 只有房間不同才需用release和retain
if (_room != room) {
// 將以前的房間釋放掉 -1,將舊對象釋放
[_room release];
// MRC中需要手動對房間的引用計數器+1
[room retain];
_room = room;
}
}
strong:強引用,它是ARC特有
。在MRC時代沒有,相當于retain。由于MRC時代是靠引用計數器來管理對象什么時候被銷毀所以用retain,而ARC時代管理對象的銷毀是有系統自動判斷,判斷的依據就是該對象是否有強引用對象。如果對象沒有被任何地方強引用就會被銷毀。所以在ARC時代基本都用的strong來聲明代替了retain。只能用于聲明OC對象(ARC特有)
蘋果官網對strong的解釋代碼:
Precondition:object is a valid pointer to a __strong object which is adequately aligned for a pointer. value is null or a pointer to a valid object.
Performs the complete sequence for assigning to a __strong object of non-block type [[*]]. Equivalent to the following code:
void objc_storeStrong(id *object, id value) {
id oldValue = *object;
value = [value retain];
*object = value;
[oldValue release];
}
assgin:簡單的賦值操作,不會更改引用計數,用于基本的數據類型聲明。
weak:弱引用,表示該屬性是一種“非擁有關系”。為這種屬性設置新值時既不會保留新值也不會釋放舊值,類似于assgin。 然而在對象被摧毀時,屬性也會被清空(nil out)。這樣可以有效的防止崩潰(因為OC中給沒有對象地址的指針發送消息不會崩潰,而給有內存地址但地址中是空對象的指針發消息會崩潰,野指針),該聲明必須作用于OC對象。
對于 weak 對象會放入一個 hash 表中, 用 weak 指向的對象內存地址作為 key,當此對象的引用計數為0的時候會調用 dealloc, 使用key在weak 表中搜索,將找到的所有對象設置為 nil。(ARC特有),strong 和 weak的指針,根本區別在于,strong執行了retain操作,而weak沒有。
runtime 如何實現 weak 變量的自動置 nil?
runtime會對注冊的類進行內存布局,對于 weak 修飾的對象會放入一個 hash 表中。
1.初始化時:runtime 會調用 objc_initWeak 函數,初始化一個新的 weak 指針指向對象的地址。
2.添加引用時:objc_initWeak 函數會調用 objc_storeWeak() 函數, objc_storeWeak()的作用是更新指 針指向,創建對應的弱引用表。
3.釋放時,調用 clearDeallocating 函數。clearDeallocating 函數首先根據對象地址獲取所有 weak 指針 地址的數組,然后遍歷這個數組把其中的數據設為 nil,最后把這個 entry 從 weak 表中刪除,最后清理對象的記錄。
蘋果官網對weak的說明
id objc_storeWeak(id *object, id value);
Precondition: object is a valid pointer which either contains a null pointer or has been registered as a __weak >object. value is null or a pointer to a valid object.If value is a null pointer or the object to which it points has begun deallocation, object is assigned null and >unregistered as a __weak object. Otherwise, object is registered as a __weak object or has its registration >updated to point to value.
Returns the value of object after the call.
copy:不同于其他聲明,copy會根據聲明的屬性是否是可變類型而進行不同操作。如果對象是一個不可變對象,例如NSArray NSString 等,那么copy等同于retain、strong。如果對象是一個可變對象,例如:NSMutableArray,NSMutableString等,它會在內存中重新開辟了一個新的內存空間,用來 存儲新的對象,和原來的對象是兩個不同的地址,引用計數分別為1. 這就是所謂的深拷貝淺拷貝,淺拷貝只是copy了對象的內存地址,而深拷貝是重新在內存中開辟新空間,新空間對象值與拷貝對象的值一樣。但是是完全不同的2個內存地址。 例如copy修飾的類型為 NSString不可變對象時,copy可以保護其封裝性,當賦值對象是一個 NSMutableString 類時(NSMutableString是 NSString 的子類,表示一種可修改其值的字符串),此時若是不用copy修飾拷貝字符串,那么賦值該對象之后,賦值對象字符串的值就可能會在其他地方被修改,修改后賦值后對象也會改變,造成值不對。所以,這時就要拷貝一份“不可變” (immutable)的字符串,確保對象中的字符串值不會無意間變動。只要實現屬性所用的對象是“可變的” (mutable),就應該在設置新屬性值時拷貝一份。
1.當不可變對象進行copy的時候,本質上只是創建了一個新的指針,指向了copy對象的內存空間。所以新指針與原指針所指向的地址相同
2.當可變對象進行copy操作的時候,由于copy方法返回的對象都是不可變對象,所以對可變對象進行copy操作相當于單層深拷貝,創建了一個新指針,并重新再內存中分配了一個新的對象,但是這個對象是不可變對象,而非原對象的可變對象。所以這是一種單層的深拷貝。
3.不可變對象進行mutableCopy的時候,本質上是進行了深拷貝,創建了新指針,并深拷貝重新開辟了一個新的內存空間放置拷貝的對象。所以指針和指針所指向的內存地址與原對象都不一樣。并且新的內存中實際對象是一個可變對象。
4.可變對象進行mutableCopy與不可變對象進行mutableCopy本質是一樣的。
5.copy一個可變對象并賦值給一個不可變對象,由于copy方法返回的是不可變對象,因此會對原可變對象進行深拷貝,新的內存地址,新的指針。并且copy的結果是一個不可變對象
6.copy一個不可變對象并復制給一個可變對象,同理與上面5的解答,雖然最終賦值給一個可變的指針,但是內存地址中實際是一個不可變對象。
7.mutableCopy一個不可變對象,并賦值給一個可變對象。對原對象進行深拷貝,由于mutableCopy方法返回的是一個可變對。所以實際內存中是一個可變對象
unsafe_unretained:和weak 差不多,唯一的區別便是,對象即使被銷毀,指針也不會自動置空, 對象被銷毀后指針指向的是一個無用的內存地址(野地址)。如果對象銷毀后后還使用此指針,程序會拋出 BAD_ACCESS 的異常。 所以一般不使用unsafe_unretained。目前我還未在實際項目中使用過該聲明。(ARC特有)
- 2.4 readOnly、readWrite、getter=、setter=
readOnly表示屬性僅能讀不能設置其值。告訴編譯器只生成getter方法不生成setter方法。
readWrite默認值,表示屬性可讀可寫。編譯器會自動生成getter setter方法
getter=指定屬性gettter方法的方法名
@property (nonatomic, strong, getter=getMyDic) NSMutableDictionary *dic;
setter=指定屬性setter方法的方法名
@property (nonatomic, strong,setter=myArray:) NSMutableArray *arr;
2.5__unsafe_unretained、__weak、__strong
__unsafe_unretained
NSMutableArray __unsafe_unretained *array = [[NSMutableArray alloc]init];
[array addObject:@"123"];
使用__unsafe_unretained修飾符的變量與使用__weak修飾符的變量一樣,因為自己生成并持有的對象不能繼續為自己持有,所以生成的對象會立即被釋放。也就是說在執行完init方法以后,對象指針所指向的內存就已經釋放掉了,但因為用的__unsafe_unretained修飾指針并沒不像__weak的指針那樣,將指針自動置為nil,它依然指向原來的地址,可是這塊地址的內存已經被系統回收了,再訪問就是非法的,也就是野指針,再執行后面的addObject方法自然會出錯了。
__weak
主要用于解決循環引用,用__weak修飾的變量 當對象釋放后,指針自動設置為nil,當后面繼續使用該指針變量的時候不會造成crash,更不會造成強引用使該釋放的對象無法釋放,造成內存泄露。
__weak typeof(self) weakSelf = self;
__strong
相反與__weak,主要用于當使用某個對象時,希望它沒有提前被釋放。強引用該對象使其無法釋放。例如在block內部,希望block調用時該對象不會被提前釋放造成錯誤??梢允褂脧娨?。
TestAlertView *alertView = [TestAlertView new];
alertView = ^()
{
//當block內部需要使用本身這個局部對象時,需要用強引用方式,讓alertView在傳遞完block后不會被釋放依然可以執行setTitle操作
__strong typeof(alertView) strongAlertView = alertView;
[strongAlertView setTitle:@"1234"];
}
[alertView show];
3 MRC與ARC區別
3.1 MRC手動內存管理
引用計數器:在MRC時代,系統判定一個對象是否銷毀是根據這個對象的引用計數器來判斷的。
1.每個對象被創建時引用計數都為1
2.每當對象被其他指針引用時,需要手動使用[obj retain];讓該對象引用計數+1。
3.當指針變量不在使用這個對象的時候,需要手動釋放release這個對象。 讓其的引用計數-1.
4.當一個對象的引用計數為0的時候,系統就會銷毀這個對象。
NSMutableArray *array = [NSMutableArray array];//[NSMutableArray array]創建后引用計數器為1
NSLog(@"array的對象地址:%p,array的retainCount:%zd",array,[array retainCount]);
[array release];//調用release后[NSMutableArray array]創建的對象引用計數-1.
//當程序執行到[array addObject:@"1234"];這里是就會崩潰。因為此時array指針指向的內存地址中沒有任何對象,該指針是一個野指針。
//因為release后[NSMutableArray array]創建的對象引用計數變為了0.系統就會銷毀這個內存地址的對象。
[array addObject:@"1234"];
NSLog(@"array的對象地址:%p,array的retainCount:%zd",array,[array retainCount]);
NSLog(@"%@",array);
在MRC模式下必須遵循誰創建,誰釋放,誰引用,誰管理
在MRC下使用ARC
在Build Phases的Compile Sources中選擇需要使用MRC方式的.m文件,然后雙擊該文件在彈出的會話框中輸入 -fobjc-arc
3.2 ARC自動內存管理
WWDC2011和iOS5所引入自動管理機制——自動引用計數(ARC),它不是垃圾回收機制而是編譯器的一種特性。ARC管理機制與MRC手動機制差不多,只是不再需要手動調用retain、release、autorelease;當你使用ARC時,編譯器會在在適當位置插入release和autorelease;ARC時代引入了strong強引用來帶代替retain,引入了weak弱引用。
在ARC下使用MRC方法
在ARC工程中如果要使用MRC的需要在工程的Build Phases的Compile Sources中選擇需要使用MRC方式的.m文件,然后雙擊該文件在彈出的會話框中輸入 -fno-objc-arc
在非MRC文件中無法使用retain release retainCount 方法,無法再dealloc方法中調用[super dealloc];方法
3.3 autoreleasepool自動釋放池
自動釋放池始于MRC時代,主要是用于 自動 對 釋放池內 對象 進行引用計數-1的操作,即自動執行release方法。
autorelease、autoreleasepool、release
autorelease:本質是將對象放入當前的自動釋放池中
autoreleasepool:本質是當前runloop銷毀自動釋放池時會自動對自動釋放池內的對象調用release
release:是對當前對象的引用計數進行減1的操作,如果對象引用計算為0,則會釋放對象的內存空間。autoreleasepool的原理
autoreleasepool本質上是一個__AtAutoreleasePool的結構體,通過__AtAutoreleasePool的構造函數和析構函數去實現對池內的對象進行自動釋放。1.當對象調用autorelease方法時,會將對象加入autoreleasepool的棧中,而autoreleasepool會在__AtAutoreleasePool結構體的構造函數中調用autoreleasepoolPage::push方法將自動釋放池中的對象加入到雙向鏈表中,每個autoreleasepoolpage大小都是4096個字節。所以如果autoreleasepool中對象超過4096個字節。那么就會再創建一個autoreleasepoolpage。因此autoreleasepoolpage有多個。
2.當__AtAutoreleasePool的結構體被銷毀時,會自動調用析構函數,通過析構函數調用autoreleasepoolPage::pop方法會向棧中的對象發送release消息
在MRC中使用autoreleasepool必須在代碼塊內部手動為對象調用autorelease把對象加入到的自動釋放池,系統會自動在代碼塊結束后,對加入自動釋放池中的對象發送一個release消息。無需手動調用release
int main(int argc, const char * argv[])
{
Person *father = [[Person alloc] init];//引用計數為1
@autoreleasepool {//這里創建自動釋放池
int a = 10; // a在棧,10在常量區
int b = 20; //b在棧,20在常量區
// p : 棧
// [[Person alloc] init]創建的對象(引用計數器==1) : 在堆
Person *p = [[Person alloc] init];
//MRC下需要手動讓對象加入自動釋放池
[p autorelease];
Person *pFather = father;
[father retain];//father指針指向的對象被pFather引用,在MRC下需要手動讓被引用對象引用計數+1
NSLog(@"pFather對象內存地址:%p,pFather的引用計數:%zd",pFather,[pFather retainCount]);
NSLog(@"father對象內存地址:%p,father的引用計數:%zd",father,[father retainCount]);
}//這里釋放 自動釋放池
// 當autoreleasepool內部代碼塊執行完畢后上面代碼塊后, 棧里面的變量a、b、p 都會被回收
// 但是堆里面的Person對象還會留在內存中,因為它是計數器依然是1。當autoreleasepool代碼塊執行完畢后,會對釋放池內部的所有對象執行一個release消息。如果發送release消息后,對象引用計數為0了,那么就會被系統回收。
NSLog(@"father對象內存地址:%p,father的引用計數:%zd",father,[father retainCount]);
return 0;
}
在ARC中對@autoreleasepool的使用相比MRC不太多。主要用于一些大內存消耗對象的重復創建時,保證內存處于比較優越的狀態。常用于創建對象較多的for循環中。在ARC下不要手動的為@autoreleasepool代碼塊內部對象添加autorelease,ARC下自動的把@autoreleasepool代碼塊中創建的對象加入了自動釋放池中。
for (int i = 0; i < 10000000; i++)
{
@autoreleasepool{
NSMutableArray *array = [NSMutableArray new];
NSMutableDictionary *dic = [NSMutableDictionary new];
NSMutableArray *array1 = [NSMutableArray new];
NSMutableDictionary *dic1 = [NSMutableDictionary new];
NSMutableArray *array2 = [NSMutableArray new];
NSMutableDictionary *dic2 = [NSMutableDictionary new];
NSData *data = UIImageJPEGRepresentation([UIImage imageNamed:@"testimage"], 1);
NSError *error;
NSURL *url = [NSURL URLWithString:@"www.baidu.com"];
NSString *fileContents = [NSString stringWithContentsOfURL:url
encoding:NSUTF8StringEncoding
error:&error];
}
}
可以從上圖看出,使用autoreleasepool時,內存一直保持穩定狀態,上次幾乎沒浮動
因為沒有使用 autoreleasepool,隨著for循環一直執行下去,內存一直在上升。
autorelease對象釋放的時機
1.系統自行創建的@autoreleasepool釋放時機
線程與Runloop是一對一關系,主線程中會自動創建Runloop,而子線程需要自行調用Runloop來讓子線程自動創建Runloop。當@autoreleasepool加入到某個線程時,該線程的Runloop會
借用runloop的Autorelease對象釋放的背后的解釋(ARC環境下)
圖中第1步 Observer 監視的事件是 Entry(即將進入Loop),其回調內會調用 _objc_autoreleasePoolPush() 創建自動釋放池。其 order 是-2147483647,優先級最高,保證創建釋放池發生在其他所有回調之前
圖中第6步 Observer 監視了兩個事件: BeforeWaiting(準備進入休眠) 時調用_objc_autoreleasePoolPop() 和 _objc_autoreleasePoolPush() 釋放舊的池并創建新池;Exit(即將退出Loop) 時調用 _objc_autoreleasePoolPop() 來釋放自動釋放池。這個 Observer 的 order 是 2147483647,優先級最低,保證其釋放池子發生在其他所有回調之后。
圖中第10 Observer 監視事件是exit(即講退出runloop),其回調內會調用 _objc_autoreleasePoolpop() 釋放自動釋放池。
從上面就能看出,Runloop中系統自動創建的@autoreleasepool是在準備進入休眠狀態才被銷毀的。所以在ARC下,在線程中的臨時對象是在當前線程的Runloop進入休眠或者退出loop或者退出線程時被執行release的。
2.自己創建的@autoreleasepool
@autoreleasepool
{//這個{開始創建的自動釋放池,這里開始內部的對象自動加入autorelease
}//這個}開始,不必再對 對象加入autorelease,自動釋放池被銷毀。
從上面就能看出,自行創建的@autoreleasepool,是在}后被釋放的,而其中的autorelease對象,也是在這個時候自動執行的release操作。
從 1
, 2
兩點可以看出,雖然autorelease對象釋放的時機并不都是在代碼塊結束后就釋放。但是他們有一個共同特性,那就是必定是在@autoreleasepool被銷毀時,釋放的。 所以要清楚autorelease對象什么時候被釋放,只需要搞清楚@autoreleasepool什么時候被銷毀即可
主線程中既有系統創建的@autoreleasepool也有開發者自行創建的@autoreleasepool。那么他的釋放順序是怎樣的呢?
因為@autoreleasepool是以棧的形式存儲的,按照先進后出的規則釋放棧中每個@autoreleasepool。主線程的@autoreleasepool是在Runloop一開始就創建了所以,它必然是棧最里面的,而自行創建的@autoreleasepool是在Runloop運行中創建的,所以在棧上面一點。按照棧的規則,@autoreleasepool是先釋放自行創建的@autoreleasepool,在釋放系統創建的。
NSString 單獨說
為什么要單獨說NSString呢,因為NSString在內存上與其他類型存在很大的不同
//該代碼是在MRC環境下測試用
NSString *str1 = @"123456789";//用@""方法創建一個 固定長度為9的字符串
NSString *str2 = @"1234567890";//用@""方法創建一個 固定長度為10的字符串
NSString *str3 = [NSString stringWithFormat:@"234567890"];//用stringWithFormat方法創建一個 固定長度為9的字符串
NSString *str4 = [NSString stringWithFormat:@"2345678901"];//用stringWithFormat方法創建一個 固定長度為10的字符串
NSString *str5 = [[NSString alloc] initWithString:@"345678901"];//用initWithString方法創建一個 固定長度為9的字符串
NSString *str6 = [[NSString alloc] initWithString:@"3456789012"];//用initWithString方法創建一個 固定長度為9的字符串
NSString *str7 = [[NSString alloc] initWithFormat:@"456789012"];//用initWithFormat方法創建一個 固定長度為9的字符串
NSString *str8 = [[NSString alloc] initWithFormat:@"4567890123"];//用initWithFormat方法創建一個 固定長度為9的字符串
NSString *str9 = [NSString stringWithFormat:@"1234567890"];//用stringWithFormat方法創建一個 固定長度為10的字符串并與str2字符串一樣的字符串
NSString *str10 = [[NSString alloc] initWithString:@"1234567890"];//用initWithString方法創建一個 固定長度為10的字符串并與str2字符串一樣的字符串
NSLog(@"str1 用@"" 的retainCount為:%ld \n 對象內地地址:%p",[str1 retainCount],str1);
NSLog(@"str2 用@"" 的retainCount為:%ld \n 對象內地地址:%p",[str2 retainCount],str2);
NSLog(@"-----------------------------------------------------------------");
NSLog(@"str3 用stringWithFormat 的retainCount為:%ld \n 對象內地地址:%p",[str3 retainCount],str3);
NSLog(@"str4 用stringWithFormat 的retainCount為:%ld \n 對象內地地址:%p",[str4 retainCount],str4);
NSLog(@"-----------------------------------------------------------------");
NSLog(@"str5 用initWithString 的retainCount為:%ld \n 對象內地地址:%p",[str5 retainCount],str5);
NSLog(@"str6 用initWithString 的retainCount為:%ld \n 對象內地地址:%p",[str6 retainCount],str6);
NSLog(@"-----------------------------------------------------------------");
NSLog(@"str7 用initWithFormat 的retainCount為:%ld \n 對象內地地址:%p",[str7 retainCount],str7);
NSLog(@"str8 用initWithFormat 的retainCount為:%ld \n 對象內地地址:%p",[str8 retainCount],str8);
NSLog(@"-----------------------------------------------------------------");
NSLog(@"使用lu看一下 str1 的retainCount為:%lu \n 對象內地地址:%p",[str1 retainCount],str1);
NSLog(@"使用lu看一下 str4 的retainCount為:%lu \n 對象內地地址:%p",[str4 retainCount],str4);
NSLog(@"-----------------------------------------------------------------");
NSLog(@"str9 字符串與str2一樣 的retainCount為:%lu \n 對象內地地址:%p",[str9 retainCount],str9);
NSLog(@"str10 字符串與str2一樣 的retainCount為:%lu \n 對象內地地址:%p",[str10 retainCount],str10);
輸出結果
1.為什么用 @""
, stringWithFormat
, initWithString
, initWithFormat
四種方式。
因為方式不同,創建的對象所存在內存區域不同。你會發現str2 與 str9 字符串內容一樣。為什么對象地址一個是0x1053674f8
另一個是0x604000033580
。正常情況下,字符串內容一樣,應該取的是同一個內存地址。就像str2與str10一樣。雖然創建的方法不一樣,但是字符串一樣,內存地址就是一樣的。 這就是區別。 @“”與initWithString方法創建的字符串于stringWithFormat、initWithFormat創建的字符串所在的內存區域不同。
2.為什么要區分長度9 和 長度10的字符串?
因為字符串長度不一樣,字符串所在內存區域不同,retainCount也不同。從上面結果中可以看出,str3和str4是同一種方式創建的字符串,但一個內存是0xa287dcaecc2ac5d9
一個是0x6040000339a0
。因為前者是在五大區域之外的內存區,而后者在堆中。
由上面連2點結合可知,由initWithString和stringWithString創建的NSString對象,不管字符串的內容和長度怎么變化,該字符串對象始終是存儲在常量區的,引用計數為-1;從用%lu打印來看initWithString和stringWithString創建的字符串retainCount是無符號長整型的最大值。所以可以說他們沒有引用計數這個概念
而由initWithFormat和stringWithFormat創建的對象,如果字符串內容是非漢字的,那么當字符串長度小于10個時,該字符串存儲區域在五大區域之外,且隨著字符串長度的變化,存儲地址會有很大變化。當字符串長度超過10個以后,該字符串在堆中,與正常的OC對象一樣。這里為什么要說非漢字呢,因為如果字符串內容是漢字,不管字符串的內容和長度怎么變化,該字符串都是在堆中,與正常OC對象一樣。
App占用手機內存最大限制記錄
型號 | 最大限制 |
---|---|
iPad1 | 127MB/256MB/49% |
iPad2 | 275MB/512MB/53% |
iPad3 | 645MB/1024MB/62% |
iPad4 | 585MB/1024MB/57% (iOS 8.1) |
iPad Mini 1st Generation | 297MB/512MB/58% |
iPad Mini retina | 696MB/1024MB/68% (iOS 7.1) |
iPad Air | 697MB/1024MB/68% |
iPad Air 2 | 1383MB/2048MB/68% (iOS 10.2.1) |
iPad Pro 9.7" | 1395MB/1971MB/71% (iOS 10.0.2 (14A456)) |
iPad Pro 10.5” | 3057/4000/76% (iOS 11 beta4) |
iPad Pro 12.9” (2015) | 3058/3999/76% (iOS 11.2.1) |
iPad Pro 12.9” (2017) | 3057/3974/77% (iOS 11 beta4) |
iPad Pro 11.0” (2018) | 2858/3769/76% (iOS 12.1) |
iPad Pro 12.9” (2018) | 4598/5650/81% (iOS 12.1) |
iPod touch 4th gen | 130MB/256MB/51% (iOS 6.1.1) |
iPod touch 5th gen | 286MB/512MB/56% (iOS 7.0) |
iPhone4 | 325MB/512MB/63% |
iPhone4s | 286MB/512MB/56% |
iPhone5 | 645MB/1024MB/62% |
iPhone5s | 646MB/1024MB/63% |
iPhone6 | 645MB/1024MB/62% (iOS 8.x) |
iPhone6+ | 645MB/1024MB/62% (iOS 8.x) |
iPhone6s | 1396MB/2048MB/68% (iOS 9.2) |
iPhone6s+ | 1392MB/2048MB/68% (iOS 10.2.1) |
iPhoneSE | 1395MB/2048MB/69% (iOS 9.3) |
iPhone7 | 1395/2048MB/68% (iOS 10.2) |
iPhone7+ | 2040MB/3072MB/66% (iOS 10.2.1) |
iPhone X | 1392/2785/50% (iOS 11.2.1) |
iPhone XS Max | 2039/3735/55% (iOS 12.1) |