#C語言內存分配
Objective-C從名字來看就可以知道是一門超C語言背亥,所以了解C語言的內存模型對于理解Objective-C的內存管理有很大的幫助秒际。C語言內存模型圖如下:
從圖中可以看出內存被分成了5個區(qū),每個區(qū)存儲的內容如下:
棧區(qū)(stack):存放函數的參數值狡汉、局部變量的值等娄徊,由編譯器自動分配釋放,通常在函數執(zhí)行結束后就釋放了盾戴,其操作方式類似數據結構中的棧寄锐。棧內存分配運算內置于處理器的指令集,效率很高,但是分配的內存容量有限橄仆,比如iOS中棧區(qū)的大小是2M剩膘。
堆區(qū)(heap):就是通過new、malloc沿癞、realloc分配的內存塊援雇,它們的釋放編譯器不去管,由我們的應用程序去釋放椎扬。如果應用程序沒有釋放掉惫搏,操作系統(tǒng)會自動回收。分配方式類似于鏈表蚕涤。
靜態(tài)區(qū):全局變量和靜態(tài)變量的存儲是放在一塊的筐赔,初始化的全局變量和靜態(tài)變量在一塊區(qū)域,未初始化的全局變量和未初始化的靜態(tài)變量在相鄰的另一塊區(qū)域揖铜。程序結束后茴丰,由系統(tǒng)釋放。
常量區(qū):常量存儲在這里天吓,不允許修改的贿肩。
代碼區(qū):存放函數體的二進制代碼。
棧區(qū)在什么時候釋放內存呢龄寞?我們通過下面的一個例子來說明下:
汰规、、物邑、
- (void)print {
int i = 10;
int j = 20;
NSLog(@"i+j = %d", (i+j));
}
溜哮、、色解、
在上面的代碼中當程序執(zhí)行到 } 的時候茂嗓,變量i和j的作用域已經結束了,編譯器就會自動釋放掉i和j所占的內存科阎,所以理解好作用域就理解了棧區(qū)的內存分配述吸。
棧區(qū)和堆區(qū)的區(qū)別主要為以下幾點:
對于棧來說,內存管理由編譯器自動分配釋放锣笨;對于堆來說蝌矛,釋放工作由程序員控制。
棧的空間大小比堆小許多票唆。
棧是機器系統(tǒng)提供的數據結構,計算機會在底層對棧提供支持屹徘,所以分配效率比堆高走趋。
棧中存儲的變量出了作用域就無效了,而堆由于是由程序員進行控制釋放的噪伊,變量的生命周期可以延長簿煌。
參考文章:
#block
聲明block屬性的時候為什么用copy呢氮唯?
在說明為什么要用copy前,先思考下block是存儲在棧區(qū)還是堆區(qū)呢姨伟?其實block有3種類型:
全局塊(_NSConcreteGlobalBlock)
棧塊(_NSConcreteStackBlock)
堆塊(_NSConcreteMallocBlock)
全局塊存儲在靜態(tài)區(qū)(也叫全局區(qū))惩琉,相當于Objective-C中的單例;棧塊存儲在棧區(qū)夺荒,超出作用域則馬上被銷毀瞒渠。堆塊存儲在堆區(qū)中,是一個帶引用計數的對象技扼,需要自行管理其內存伍玖。
怎么判斷一個block所在的存儲位置呢?
block不訪問外界變量(包括棧中和堆中的變量)
block既不在棧中也不在堆中剿吻,此時就為全局塊窍箍,ARC和MRC下都是如此。
block訪問外界變量
MRC環(huán)境下:訪問外界變量的block默認存儲在棧區(qū)丽旅。
ARC環(huán)境下:訪問外界變量的block默認存放在堆中椰棘,實際上是先放在棧區(qū),在ARC情況下自動又拷貝到堆區(qū)榄笙,自動釋放邪狞。
使用copy修飾符的作用就是將block從棧區(qū)拷貝到堆區(qū),為什么要這么做呢办斑?我們看下Apple官方文檔給出的答案:
通過官方文檔可以看出外恕,復制到堆區(qū)的主要目的就是保存block的狀態(tài),延長其生命周期乡翅。因為block如果在棧上的話鳞疲,其所屬的變量作用域結束,該block就被釋放掉蠕蚜,block中的__block變量也同時被釋放掉尚洽。為了解決棧塊在其變量作用域結束之后被釋放掉的問題,我們就需要把block復制到堆中靶累。
不同類型的block使用copy方法的效果也不一樣腺毫,如下所示:
block的類型 存儲區(qū)域 復制效果
_NSConcreteStackBlock 棧 從棧復制到堆
_NSConcreteGlobalBlock 靜態(tài)區(qū)(全局區(qū)) 什么也不做
_NSConcreteMallocBlock 堆 引用計數增加
加上__block之后為什么就可以修改block外面的變量了?
我們先看下例子1:
挣柬、潮酒、、
- (void)testMethod {
int anInteger = 42;
void (^testBlock)(void) = ^{
NSLog(@"Integer is : %i", anInteger);
};
anInteger = 50;
testBlock();
}
邪蛔、急黎、、
運行后輸出的結果如下:
、Integer is : 42勃教、
為什么不是50呢淤击?這個問題稍后做解釋。我們在看加入__block的情況故源,例子2如下:
污抬、、绳军、
- (void)testMethod {
__block int anInteger = 42;
void (^testBlock)(void) = ^{
NSLog(@"Integer is : %i", anInteger);
};
anInteger = 50;
testBlock();
}
印机、、删铃、
運行后輸出的結果如下:
耳贬、Integer is : 50、
兩次運行結果不一樣猎唁,中間發(fā)生了什么呢咒劲?我們接下來來具體分析下。
在例子1中诫隅,block會把anInteger變量復制為自己私有的const變量腐魂,也就是說block會捕獲棧上的變量(或指針),將其復制為自己私有的const變量逐纬。在例子1中蛔屹,在進行anInteger = 50的操作的時候,block已經將其復制為自己的私有變量豁生,所以這里的修改對block里面的anInteger不會造成任何影響兔毒。
在例子2中,anInteger是一個局部變量甸箱,存儲在棧區(qū)的育叁。給anInteger加入__block修飾符所起到的作用就是只要觀察到該變量被block所持有,就將該變量在棧中的內存地址放到堆中芍殖,此時不管block外部還是內部anInterger的內存地址都是一樣的豪嗽,進而不管在block外部還是內部都可以修改anInterger變量的值,所以anInteger = 50之后豌骏,在block輸出的值就是50了龟梦。可以通過一個圖簡單來描述一下:
#block中循環(huán)引用的問題窃躲?使用系統(tǒng)的block api是否也考慮循環(huán)引用的問題计贰?weak與strong之間的區(qū)別?
在使用block的時候蒂窒,我們要特別注意循環(huán)引用的問題躁倒,先來看一個循環(huán)引用的例子:
赎婚、、樱溉、
@interface XYZBlockKeeper : NSObject
@property (nonatomic, copy) void (^block)(void);
@end
@implementation XYZBlockKeeper
- (void)configureBlock {
self.block = ^{
[self doSomething];
};
}
@end
、纬凤、福贞、
在上面的代碼中我們聲明了一個block屬性,所以self對block有一個強引用停士。而在block內部又對self進行了一次強引用挖帘,這樣就形成了一個封閉的環(huán)乞巧,也就是我們經常說的強引用循環(huán)道逗。引用關系如圖:
在這種情況下,由于其相互引用匕坯,內存不能夠進行釋放蜻底,就造成了內存泄漏的問題骄崩。怎么解決循環(huán)引用的問題呢?我們經常通過聲明一個weakSelf來解決循環(huán)引用的問題薄辅,更改后的代碼如下:
要拂、、站楚、
- (void)configureBlock {
__weak typeof(self) weakSelf = self;
self.block = ^{
[weakSelf doSomething];
};
}
脱惰、、窿春、
加入weakSelf之后拉一,block對self就由強引用關系變成了弱引用關系,這樣在屬性所指的對象遭到摧毀時旧乞,屬性值也會被清空蔚润,就打破了block捕獲的作用域帶來的循環(huán)引用。這跟設置self.block = nil是同樣的道理良蛮。
在使用系統(tǒng)提供的block api需要考慮循環(huán)引用的問題嗎抽碌?比如:
、决瞳、货徙、
[UIView animateWithDuration:0.5 animations:^{
[self doSomething];
}];
、皮胡、痴颊、
在這種情況下是不需要考慮循環(huán)引用的,因為這里只有block對self進行了一次強引用屡贺,屬于單向的強引用蠢棱,沒有形成循環(huán)引用锌杀。
weak與strong有什么區(qū)別呢?先看一段代碼:
泻仙、糕再、、
- (void)configureBlock {
__weak typeof(self) weakSelf = self;
self.block = ^{
[weakSelf doSomething]; // weakSelf != nil
// preemption(搶占) weakSelf turned nil
[weakSelf doAnotherThing];
};
}
玉转、突想、、
這段代碼看起來很正常呀究抓,但是在并發(fā)執(zhí)行的時候猾担,block的執(zhí)行是可以搶占的,而且對weakSelf指針的調用時序不同可以導致不同的結果刺下,比如在一個特定的時序下weakSelf可能會變成nil绑嘹,這個時候在執(zhí)行doAnotherThing就會造成程序的崩潰。為了避免出現這樣的問題橘茉,采用__strong的方式來進行避免工腋,更改后的代碼如下:
、畅卓、夷蚊、
- (void)configureBlock {
__weak typeof(self) weakSelf = self;
self.block = ^{
__strong typeof(weakSelf) strongSelf = weakSelf;
[strongSelf doSomething]; // strongSelf != nil
// 在搶占的時候,strongSelf還是非nil的髓介。
[strongSelf doAnotherThing];
};
}
惕鼓、、唐础、
從代碼中可以看出加入__strong所起的作用就是在搶占的時候strongSelf還是非nil的箱歧,避免出現nil的情況。
總結:
在block不是作為一個property的時候一膨,可以在block里面直接使用self呀邢,比如UIView的animation動畫block。
當block被聲明為一個property的時候豹绪,需要在block里面使用weakSelf价淌,來解決循環(huán)引用的問題。
當和并發(fā)執(zhí)行相關的時候瞒津,當涉及異步的服務的時候蝉衣,block可以在之后被執(zhí)行,并且不會發(fā)生關于self是否存在的問題巷蚪。
參考文章:
#property
##@synthesize和@dynamic分別有什么作用病毡?
在說兩者分別有什么作用前,我們先看下@property的本質是什么:
屁柏、@property = ivar + getter + setter;啦膜、
從上面可以看出@property的本質就是ivar(實例變量)加存取方法(getter + setter)有送。在我們屬性定義完成后,編譯器會自動生成該屬性的getter和setter方法僧家,這個過程就叫做自動合成雀摘。除了生成getter與setter方法,編譯器還要自動向類中添加適當類型的實例變量八拱,并且在屬性名前面加下劃線届宠,以此做實例變量的名字。
##@synthesize的作用就是如果你沒有手動實現getter與setter方法乘粒,那么編譯器就會自動為你加上這兩個方法。
##@dynamic的作用就是告訴編譯器伤塌,getter與setter方法由用戶自己實現灯萍,不自動生成。當然對于readonly的屬性只需要提供getter即可每聪。
如果都沒有寫@synthesize和@dynamic旦棉,那么默認的就是@synthesize var = _var;
為了加深對@synthesize和@dynamic的理解,我們來看幾個具體的例子药薯,例子1代碼如下:
绑洛、、童本、
@interface ViewController ()
@property (nonatomic, copy) NSString *name;
@end
@implementation ViewController
@dynamic name;
- (void)viewDidLoad {
[super viewDidLoad];
self.name = @"國士梅花";
NSLog(@"name is : %@", self.name);
}
@end
真屯、、穷娱、
我們在進行編譯的時候沒有問題绑蔫,但是運行的時候就會發(fā)生崩潰,崩潰的原因如下:
泵额、'NSInvalidArgumentException', reason: '-[ViewController setName:]: unrecognized selector sent to instance 0x7fd28dd06000'配深、
崩潰的原因是不識別setName方法,這也驗證了如果加入了@dynamic的話嫁盲,編譯系統(tǒng)就不會自己生成getter和setter方法了篓叶,需要我們自己來實現。
我們在來看下@synthesize合成實例變量的規(guī)則是什么羞秤?例子2代碼如下:
缸托、、瘾蛋、
@interface ViewController ()
@property (nonatomic, copy) NSString *name;
@end
@implementation ViewController
@synthesize name = _myName;
- (void)viewDidLoad {
[super viewDidLoad];
self.name = @"國士梅花";
NSLog(@"name is : %@", _myName);
}
@end
嗦董、、瘦黑、
從代碼中可以看出京革,1奇唤、當我們指定了成員變量的名稱(指定為帶下劃線的myName),就會生成指定的成員變量匹摇。如果代碼中存在帶下劃線的name咬扇,就不會在生成了。2廊勃、如果是@synthesize name;還會生成一個名稱為帶下劃線的name成員變量懈贺,也就是說如果沒有指定成員變量的名稱會自動生成一個屬性同名的成員變量。3坡垫、如果是@synthesize name = _name; 就不會生成成員變量了梭灿。
在有了自動合成屬性實例變量之后,@synthesize還有哪些使用場景呢冰悠?先搞清楚一個問題堡妒,什么時候不會使用自動合成?
1.同時重寫了setter和getter時溉卓。
2.重寫了只讀屬性的getter時皮迟。
3.使用了@dynamic時。
4.在@protocol中定義的所有屬性桑寨。
5.在category中定義的所有屬性伏尼。
6.重載的屬性。
#注意點:
在category中使用@property也是只會生成getter和setter方法的聲明尉尾,如果真的需要給category增加屬性的實現爆阶,需要借助于運行時的兩個函數:objc_setAssociatedObject和objc_getAssociatedObject。
在protocol中使用property只會生成setter和getter方法聲明沙咏,使用屬性的目的是希望遵守我協議的對象能夠實現該屬性扰她。
weak、copy芭碍、strong徒役、assgin分別用在什么地方?
#什么情況下會使用weak關鍵字窖壕?
在ARC中忧勿,出現循環(huán)引用的時候,會使用weak關鍵字瞻讽。
自身已經對它進行了一次強引用鸳吸,沒有必要再強調引用一次速勇。
assgin適用于基本的數據類型晌砾,比如NSInteger、BOOL等烦磁。
NSString养匈、NSArray哼勇、NSDictionary等經常使用copy關鍵字,是因為他們有對應的可變類型:NSMutableString呕乎、NSMutableArray积担、NSMutableDictionary;
除了上面的三種情況猬仁,剩下的就使用strong來進行修飾帝璧。
為什么NSString、NSDictionary湿刽、NSArray要使用copy修飾符呢的烁?
要搞清楚這個問題,我們先來弄明白深拷貝與淺拷貝的區(qū)別诈闺,以非集合類與集合類兩種情況來進行說明下渴庆,先看非集合類的情況,代碼如下:
买雾、、杨帽、
NSString *name = @"國士梅花";
NSString *newName = [name copy];
NSLog(@"name memory address: %p newName memory address: %p", name, newName);
漓穿、、注盈、
運行之后晃危,輸出的信息如下:
、name memory address: 0x10159f758 newName memory address: 0x10159f758老客、
可以看出復制過后僚饭,內存地址是一樣的,沒有發(fā)生變化胧砰,這就是淺復制鳍鸵,只是把指針地址復制了一份。我們改下代碼改成[name mutableCopy]尉间,此時日志的輸出信息如下:
偿乖、name memory address: 0x101b72758 newName memory address: 0x608000263240、
我們看到內存地址發(fā)生了變化哲嘲,并且newName的內存地址的偏移量比name的內存地址要大許多贪薪,由此可見經過mutableCopy操作之后,復制到堆區(qū)了眠副,這就是深復制了画切,深復制就是內容也就進行了拷貝。
上面的都是不可變對象囱怕,在看下可變對象的情況霍弹,代碼如下:
毫别、、庞萍、
NSMutableString *name = [[NSMutableString alloc] initWithString:@"國士梅花"];
NSMutableString *newName = [name copy];
NSLog(@"name memory address: %p newName memory address: %p", name, newName);
拧烦、、钝计、
運行之后日志輸出信息如下:
恋博、name memory address: 0x600000076e40 newName memory address: 0x6000000295e0、
從上面可以看出copy之后私恬,內存地址不一樣债沮,且都存儲在堆區(qū)了,這是深復制本鸣,內容也就進行拷貝疫衩。在把代碼改成[name mutableCopy],此時日志的輸出信息如下:
荣德、name memory address: 0x600000077380 newName memory address: 0x6000000776c0闷煤、
可以看出可變對象copy與mutableCopy的效果是一樣的,都是深拷貝涮瞻。
##總結:對于非集合類對象的copy操作如下:
[immutableObject copy]; //淺復制
[immutableObject mutableCopy]; //深復制
[mutableObject copy]; //深復制
[mutableObject mutableCopy]; //深復制
##采用同樣的方法可以驗證集合類對象的copy操作如下:
[immutableObject copy]; //淺復制
[immutableObject mutableCopy]; //單層深復制
[mutableObject copy]; //深復制
[mutableObject mutableCopy]; //深復制
對于NSString鲤拿、NSDictionary、NSArray等經常使用copy關鍵字署咽,是因為它們有對應的可變類型:NSMutableString近顷、NSMutableDictionary、NSMutableArray宁否,它們之間可能進行賦值操作窒升,為確保對象中的字符串值不會無意間變動,應該在設置新屬性時拷貝一份慕匠。