iOS程序的內(nèi)存布局
低地址
\|/
|保留
|
|代碼段(_TEXT) 代碼段:編譯之后的代碼
|
|
|數(shù)據(jù)段(_DATA) 數(shù)據(jù)段:
| | 字符串常量 字符串常量:比如NSString *str = @"123"
| | 已初始化數(shù)據(jù) 已初始化數(shù)據(jù):已初始化的全局變量车吹、靜態(tài)變量等
| \|/ 未初始化數(shù)據(jù) 未初始化數(shù)據(jù):未初始化的全局變量、靜態(tài)變量等
|
|
|
|堆(heap) 堆: 通過alloc咨堤、malloc派任、calloc等動(dòng)態(tài)分配的空間,分配的內(nèi)存空間地址值越來越大(由低地址往高地址分)
| \|/
|
|
| /|\
|棧(stack) 棧:函數(shù)調(diào)用開銷悉罕,比如局部變量赤屋。分配的內(nèi)存空間地址值越來越小(由高地址往低地址分)
|
|內(nèi)核區(qū)
\|/
高地址
Tagged Pointer
從64bit開始立镶,iOS引入了Tagged Pointer技術(shù),用于優(yōu)化NSNumber类早、NSDate媚媒、NSString等小對(duì)象的存儲(chǔ)
在沒有使用Tagged Pointer之前, NSNumber等對(duì)象需要?jiǎng)討B(tài)分配內(nèi)存涩僻、維護(hù)引用計(jì)數(shù)等缭召,NSNumber指針存儲(chǔ)的是堆中NSNumber對(duì)象的地址值
使用Tagged Pointer之后,NSNumber指針里面存儲(chǔ)的數(shù)據(jù)變成了:Tag + Data逆日,也就是將數(shù)據(jù)直接存儲(chǔ)在了指針中,Tagged Pointer 指針的值不再是地址了嵌巷,而且包含了真正的值.所以,實(shí)際上它不再是一個(gè)對(duì)象了室抽,它只是一個(gè)披著對(duì)象皮的普通變量而已搪哪。其中tag為標(biāo)記。
當(dāng)指針不夠存儲(chǔ)數(shù)據(jù)時(shí)坪圾,才會(huì)使用動(dòng)態(tài)分配內(nèi)存的方式來存儲(chǔ)數(shù)據(jù)
因?yàn)閠agged pointer 不是一個(gè)真正的對(duì)象晓折,如果使用isa指針在編譯時(shí)會(huì)報(bào)錯(cuò)。
NSNumber *number1 = @1;
po number1->isa會(huì)報(bào)error
☆☆☆待考證☆☆☆
比如NSNumber *number1 = @4;想把它轉(zhuǎn)為int時(shí)兽泄,要調(diào)用number1.intValue;漓概,相當(dāng)于objc_msgSend(number1,@selector(intValue));,但是number1在這里面被Tagged Pointer了不是對(duì)象啊,其實(shí)objc_msgSend會(huì)識(shí)別是不是Tagged Pointer后的指針病梢,如果是胃珍,直接從指針提取數(shù)據(jù),節(jié)省了以前的調(diào)用開銷蜓陌。
如何判斷一個(gè)指針是否為Tagged Pointer觅彰?
iOS平臺(tái),最高有效位是1(第64bit)
Mac平臺(tái)护奈,最低有效位是1
//下面代碼執(zhí)行時(shí)會(huì)怎么樣
@property (nonatomic , copy) NSString *name;
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
for (int i = 0; i < 1000; i++) {
dispatch_async(queue, ^{
self.name = [NSString stringWithFormat:@"abcdefghijk"];
});
}
會(huì)崩潰缔莲。崩潰如下:
libobjc.A.dylib`objc_release:
0x7fff51411000 <+0>: testq %rdi, %rdi
0x7fff51411003 <+3>: je 0x7fff51411007 ; <+7>
0x7fff51411005 <+5>: jns 0x7fff51411008 ; <+8>
0x7fff51411007 <+7>: retq
0x7fff51411008 <+8>: movq (%rdi), %rax
-> 0x7fff5141100b <+11>: testb $0x4, 0x20(%rax)
0x7fff5141100f <+15>: je 0x7fff5141101b ; <+27>
0x7fff51411011 <+17>: movl $0x1, %esi
0x7fff51411016 <+22>: jmp 0x7fff51411028 ; objc_object::sidetable_release(bool)
0x7fff5141101b <+27>: movq 0x389f549e(%rip), %rsi ; "release"
0x7fff51411022 <+34>: jmpq *0x36625268(%rip) ; (void *)0x00007fff513f7780: objc_msgSend
可以看到在objc_release時(shí)候開始崩潰
ARC代碼會(huì)轉(zhuǎn)成MRC哥纫,self.name在MRC里如下
- (void)setName:(NSString *)name
{
if (_name != name) {
[_name release];
_name = [name copy];
}
}
多條線程同時(shí)調(diào)用"[_name release]"進(jìn)而導(dǎo)致程序崩潰,壞內(nèi)存訪問
解決辦法就是加鎖,在
self.name = [NSString stringWithFormat:@"abcdefghijk"];這句話前后加鎖.比如直接設(shè)置屬性為atomic
//把代碼改動(dòng)下成為下面這樣霉旗,會(huì)如何
@property (nonatomic , copy) NSString *name;
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
for (int i = 0; i < 1000; i++) {
dispatch_async(queue, ^{
self.name = [NSString stringWithFormat:@"abc"];
});
}
不會(huì)崩潰,字符串內(nèi)容變少了蛀骇,通過Tagged Pointer技術(shù)能夠存在指針里厌秒,self.name就不用走set方法里的"某些"操作,直接把數(shù)據(jù)存在指針里擅憔,所以就不會(huì)產(chǎn)生崩潰鸵闪。
MRC
在iOS中,使用引用計(jì)數(shù)來管理OC對(duì)象的內(nèi)存
一個(gè)新創(chuàng)建的OC對(duì)象引用計(jì)數(shù)默認(rèn)是1暑诸,當(dāng)引用計(jì)數(shù)減為0蚌讼,OC對(duì)象就會(huì)銷毀辟灰,釋放其占用的內(nèi)存空間
調(diào)用retain會(huì)讓OC對(duì)象的引用計(jì)數(shù)+1,調(diào)用release會(huì)讓OC對(duì)象的引用計(jì)數(shù)-1
內(nèi)存管理的經(jīng)驗(yàn)總結(jié)
當(dāng)調(diào)用alloc篡石、new芥喇、copy、mutableCopy方法返回了一個(gè)對(duì)象凰萨,在不需要這個(gè)對(duì)象時(shí)继控,要調(diào)用release或者autorelease來釋放它
想擁有某個(gè)對(duì)象,就讓它的引用計(jì)數(shù)+1胖眷;不想再擁有某個(gè)對(duì)象武通,就讓它的引用計(jì)數(shù)-1
可以通過以下私有函數(shù)來查看自動(dòng)釋放池的情況
extern void _objc_autoreleasePoolPrint(void);
下面用MRC來實(shí)現(xiàn)一個(gè)簡單的調(diào)用
#import <Foundation/Foundation.h>
#import "MJDog.h"
@interface MJPerson : NSObject
{
MJDog *_dog;
int _age;
}
- (void)setAge:(int)age;
- (int)age;
- (void)setDog:(MJDog *)dog;
- (MJDog *)dog;
@end
#import "MJPerson.h"
@implementation MJPerson
- (void)setAge:(int)age
{
_age = age;
}
- (int)age
{
return _age;
}
- (void)setDog:(MJDog *)dog
{
//判定是不是同一只狗,同一只狗不做事情珊搀,是因?yàn)橥恢还返脑捯背溃绻鸐JPerson擁有dog后,外界釋放dog食棕,此時(shí)dog引用計(jì)數(shù)為1朗和,走到這兒,在釋放的話引用計(jì)數(shù)就為0了簿晓,導(dǎo)致dog成為僵尸對(duì)象了眶拉,在進(jìn)行retain就有問題了
if (_dog != dog) {
[_dog release]; //先釋放上次的狗是因?yàn)槿绻粋€(gè)人調(diào)setDog初始化一只狗1,再調(diào)setDog初始化一只狗2憔儿,那么一個(gè)人就有了兩只狗忆植,但是只使用狗2,所以要把MJPerson的狗1釋放谒臼,狗2的釋放交給MJPerson負(fù)責(zé)(銷毀時(shí)釋放)
_dog = [dog retain]; //在set方法里面進(jìn)行計(jì)數(shù)器的增加朝刊,是想讓person對(duì)dog負(fù)責(zé),從而讓j外界初始化的dog盡情釋放蜈缤。因?yàn)镸JPerson擁有dog拾氓,如果想正常的使用dog,就不能讓dog出現(xiàn)MJPerson還沒銷毀就找不到dog問題底哥。比如這里不進(jìn)行增加的話咙鞍,外界初始化MJPerson和MJDog后就要releasse,這時(shí)候通過MJPerson調(diào)MJDog的run方法(MJDog里有一個(gè)run方法)就會(huì)出現(xiàn)壞內(nèi)存訪問。
}
}
- (MJDog *)dog
{
return _dog;
}
- (void)dealloc
{
// [_dog release];
// _dog = nil;
self.dog = nil;
NSLog(@"%s", __func__);
// 父類的dealloc放到最后
[super dealloc];
}
@end
//在viewcontroller里調(diào)用
- (void)viewDidLoad {
[super viewDidLoad];
MJDog *dog = [[MJDog alloc] init];
MJPerson *person = [[MJPerson alloc] init];
[person setDog:dog];
[dog release];
[person setDog:dog];
[person setDog:dog];
[person setDog:dog];
[person setDog:dog];
[person setDog:dog];
[[person dog] run];
[person release];
}
@property在MRC下也是能用的趾徽。修飾詞為retain续滋,assign什么的。自動(dòng)生成成員變量和屬性的setter孵奶、getter方法的聲明
.m寫@synthesize自動(dòng)生成成員變量和屬性的setter疲酌、getter實(shí)現(xiàn)
@synthesize age = _age;
現(xiàn)在xcode自動(dòng)實(shí)現(xiàn)@synthesize,所以寫@property聲明屬性后就包含了成員變量、聲明朗恳、實(shí)現(xiàn)了湿颅。
修飾詞寫成retain后,生成的set方法里會(huì)自動(dòng)管理這個(gè)屬性的引用計(jì)數(shù)器粥诫。但是dealloc里以及其他的方法里還是需要自己對(duì)額外的引用做內(nèi)存的增加或釋放肖爵。
//聲明一個(gè)屬性
@property (retain, nonatomic) NSMutableArray *data;
//之后,下面開始調(diào)用時(shí)
//這幾句代碼
NSMutableArray *data = [[NSMutableArray alloc] init];
self.data = data;
[data release];
//可簡化為
self.data = [[NSMutableArray alloc] init];
[self.data release];
//還可以簡化為
self.data = [[[NSMutableArray alloc] init] autorelease];
//再可以簡化為
self.data = [NSMutableArray array]; //這種可以通過類方法來創(chuàng)建的臀脏,不需要自己寫release劝堪,內(nèi)部實(shí)現(xiàn)了這次釋放
"注意",在ARC下手動(dòng)調(diào)用retain揉稚、release秒啦、retainCount、autorelease這幾個(gè)方法會(huì)報(bào)錯(cuò)
copy
拷貝的目的:產(chǎn)生一個(gè)副本對(duì)象搀玖,跟"源對(duì)象互不影響"
修改了源對(duì)象余境,不會(huì)影響副本對(duì)象
修改了副本對(duì)象,不會(huì)影響源對(duì)象
淺拷貝和深拷貝
1.淺拷貝:指針拷貝灌诅,沒有產(chǎn)生新的對(duì)象芳来,但會(huì)影響引用計(jì)數(shù)
2.深拷貝:內(nèi)容拷貝,產(chǎn)生新的對(duì)象猜拾,不會(huì)影響引用計(jì)數(shù)
//無論是不可變字符還是可變字符即舌,不可變拷貝拷貝出來類型都變成NSString,可變拷貝拷貝出來類型都變成NSMutableString
NSString *str1 = [NSString stringWithFormat:@"test"];
NSString *str2 = [str1 copy]; // 返回的是NSString
NSMutableString *str3 = [str1 mutableCopy]; // 返回的是NSMutableString
NSMutableString *str4 = [[NSMutableString alloc] initWithFormat:@"test"];
NSString *str5 = [str4 copy]; // 返回的是NSString
NSMutableString *str6 = [str4 mutableCopy];// 返回的是NSMutableString
NSString *str1 = [NSString stringWithFormat:@"test"]; //地址一樣
NSString *str2 = [str1 copy]; //地址一樣 雖然跟str1是同一個(gè)對(duì)象挎袜,不過這句話相當(dāng)于[str1 retain]
NSMutableString *str3 = [str1 mutableCopy]; //地址不一樣
//為什么NSString的copy地址和本身地址一樣顽聂,mutableCopy地址和本身地址不一樣呢?
首先盯仪,針對(duì)不能隨意再次改變字符長度的NSString來說紊搪,copy之后還是一樣不可變的字符,也不能隨意再次改變字符長度全景,既然都不可變耀石,所以干脆地址一樣,都指向一塊內(nèi)存爸黄。針對(duì)NSString的mutableCopy是一個(gè)新對(duì)象滞伟,所以地址不一樣,指向的內(nèi)存也不一樣馆纳。
NSMutableString *str1 = [[NSMutableString alloc] initWithFormat:@"test"];
NSString *str2 = [str1 copy]; // 深拷貝
NSMutableString *str3 = [str1 mutableCopy]; // 深拷貝
//為什么NSMutableString的copy和mutableCopy都是深拷貝呢诗良?
因?yàn)閟tr1是一個(gè)可變的汹桦,經(jīng)過copy后變成NSString了鲁驶,是一個(gè)新對(duì)象。經(jīng)過mutableCopy后變成NSMutableString了舞骆,也是一個(gè)新對(duì)象钥弯。
很好奇為什么經(jīng)過NSMutableString的copy后返回的是NSString径荔?是因?yàn)槭遣豢勺兛截惪截惡笫遣豢勺兊脑虬桑豢勺儾豢勺?.....令人窒息
表格
| copy | mutableCopy
------------------------------------------------------------
NSString | NSString | NSMutableString
| 淺拷貝 | 深拷貝
------------------------------------------------------------
NSMutableString | NSString | NSMutableString
| 深拷貝 | 深拷貝
------------------------------------------------------------
NSArray | NSArray | NSMutableArray
| 淺拷貝 | 深拷貝
------------------------------------------------------------
NSMutableArray | NSArray | NSMutableArray
| 深拷貝 | 深拷貝
------------------------------------------------------------
NSDictionary | NSDictionary | NSMutableDictionary
| 淺拷貝 | 深拷貝
------------------------------------------------------------
NSMutableDictionary | NSDictionary | NSMutableDictionary
| 深拷貝 | 深拷貝
------------------------------------------------------------
@property (nonatomic , retain) NSArray *datArr;
和
@property (nonatomic , copy) NSArray *datArr;
的區(qū)別
用retain修飾脆霎,會(huì)釋放舊值总处,然后保留新值,再然后將新值設(shè)置上去
- (void)setDatArr:(NSArray *)datArr {
if (_datArr != datArr) {
[_datArr release];
_datArr = [datArr retain];
}
}
用copy修飾睛蛛,會(huì)釋放舊值鹦马,但是不保留新值,而是將其“拷貝”
- (void)setDatArr:(NSArray *)datArr {
if (_datArr != datArr) {
[_datArr release];
_datArr = [datArr copy];
}
}
有個(gè)例子
假設(shè)Person類里有一個(gè)屬性
@property (nonatomic , copy) NSMutableArray *dataArr;
我們?cè)诹硪粋€(gè)控制器里使用這個(gè)東西
Person *per = [Person alloc]init];
per.dataArr = [NSMutableArray array];
[per.dataArr addObject:@""];
[Person release];
那么代碼會(huì)怎么樣忆肾?
會(huì)因找不到addObject方法崩潰荸频。因?yàn)槭褂胮er.dataArr的時(shí)候已經(jīng)把dataArr通過copy變成不可變數(shù)組了,再次[per.dataArr addObject:@""]就會(huì)沒有addObject方法
waring
屬性的修飾只有copy,沒有"mutableCopy".
mutableCopy只是單獨(dú)針對(duì)幾種特定的類才有的權(quán)力客冈,比如字典(含可變字典)旭从、數(shù)組(含可變數(shù)組)、字符串(含可變字符串)场仲、NSData(含可變NSData)和悦、NSSet(含可變NSSet)等
copy其他補(bǔ)充
NSString、NSArray渠缕、NSDictionary 等等經(jīng)常使用copy關(guān)鍵字鸽素,是因?yàn)樗麄冇袑?duì)應(yīng)的可變類型:NSMutableString、NSMutableArray亦鳞、NSMutableDictionary付鹿;
block 也經(jīng)常使用 copy 關(guān)鍵字。block 使用 copy 是從 MRC 遺留下來的“傳統(tǒng)”,在 MRC 中,方法內(nèi)部的 block 是在棧區(qū)的,使用 copy 可以把它放到堆區(qū).在 ARC 中寫不寫都行:對(duì)于 block 使用 copy 還是 strong 效果是一樣的蚜迅,但寫上 copy 也無傷大雅舵匾,還能時(shí)刻提醒我們:編譯器自動(dòng)對(duì) block 進(jìn)行了 copy 操作。如果不寫 copy 谁不,該類的調(diào)用者有可能會(huì)忘記或者根本不知道“編譯器會(huì)自動(dòng)對(duì) block 進(jìn)行了 copy 操作”坐梯,他們有可能會(huì)在調(diào)用之前自行拷貝屬性值。這種操作多余而低效刹帕。
用@property聲明的NSString(或NSArray吵血,NSDictionary)經(jīng)常使用copy關(guān)鍵字,為什么偷溺?如果改用strong關(guān)鍵字蹋辅,可能造成什么問題?
1.因?yàn)楦割愔羔樋梢灾赶蜃宇悓?duì)象,使用 copy 的目的是為了讓本對(duì)象的屬性不受外界影響,使用 copy 無論給我傳入是一個(gè)可變對(duì)象還是不可對(duì)象,我本身持有的就是一個(gè)不可變的副本.
2.如果我們使用是 strong ,那么這個(gè)屬性就有可能指向一個(gè)可變對(duì)象,如果這個(gè)可變對(duì)象在外部被修改了,那么會(huì)影響該屬性.
eg:
@property (nonatomic ,readwrite, strong) NSArray *array;
@property (nonatomic ,readwrite, copy) NSArray *array1;
然后進(jìn)行下面的操作:
NSArray *array = @[ @1, @2, @3, @4 ];
NSMutableArray *mutableArray = [NSMutableArray arrayWithArray:array];
self.array = mutableArray; //倆指針指向了同一個(gè)區(qū)域
self.array1 = mutableArray;
[mutableArray removeAllObjects];;
NSLog(@"%@",self.array);
NSLog(@"%@",self.array1);
[mutableArray addObjectsFromArray:array];
self.array = [mutableArray copy];
[mutableArray removeAllObjects];;
NSLog(@"%@",self.array);
打印結(jié)果如下所示:
2019-05-10 10:34:55.636762+0800 Test[10681:713670] (
)
2019-05-10 14:26:39.048384+0800 Test[4445:427749] (
1,
2,
3,
4
)
2019-05-10 10:34:55.636762+0800 Test[10681:713670] (
1,
2,
3,
4
)
什么情況下不會(huì)autosynthesis(自動(dòng)合成)挫掏?
1.同時(shí)重寫了 setter 和 getter 時(shí)
2.重寫了只讀屬性的 getter 時(shí)
3.使用了 @dynamic 時(shí)
4.在 @protocol 中定義的所有屬性
5.在 category 中定義的所有屬性
6.重載的屬性侦另。當(dāng)你在子類中重載了父類中的屬性,你必須 使用 @synthesize 來手動(dòng)合成ivar。
自定義對(duì)象的copy
想讓一個(gè)自定義對(duì)象可以copy還不報(bào)錯(cuò)褒傅,需要讓自定義對(duì)象遵守<NSCopying>協(xié)議驹闰,并實(shí)現(xiàn)- (id)copyWithZone:(NSZone *)zone方法诅需。在- (id)copyWithZone:(NSZone *)zone方法里分配空間,賦值屬性。
引用計(jì)數(shù)的存儲(chǔ)
在64bit中蓄诽,引用計(jì)數(shù)可以直接存儲(chǔ)在優(yōu)化過的isa指針中意敛,也可能存儲(chǔ)在SideTableS()中牧挣,SideTableS()是散列表結(jié)構(gòu)泳唠,其中SideTableS()中有很多SideTable結(jié)構(gòu),實(shí)例對(duì)象的地址作為key礼搁,SideTable結(jié)構(gòu)作為value柳洋。非嵌入式系統(tǒng)中,使用64個(gè)SideTable
struct SideTable {
spinlock_t slock; //自旋鎖叹坦,是一種“忙等”的鎖
RefcountMap refcnts; //引用計(jì)數(shù)表熊镣,哈希表
weak_table_t weak_table; //弱引用表,實(shí)際上也是哈希表
}
"為什么不用一個(gè)SideTable呢募书?
假如說只有一個(gè)SideTable绪囱,那么所有實(shí)例對(duì)象的引用計(jì)數(shù)和弱引用什么的都放在一個(gè)結(jié)構(gòu)里。這時(shí)候如果我們要操作某一個(gè)對(duì)象的引用計(jì)數(shù)值進(jìn)行修改莹捡,由于多個(gè)對(duì)象可能是在不同線程進(jìn)行分配和銷毀的鬼吵,那么我們想正確的修改某一個(gè)對(duì)象就要進(jìn)行加鎖才能夠保證資源訪問正確。這時(shí)候大大減弱了效率篮赢。系統(tǒng)為了解決這種效率問題齿椅,采用了分離鎖技術(shù)。將8個(gè)SideTable共用一把鎖启泣,共8把鎖(N為64)涣脚。這樣能保證最大并發(fā)量。但是劣勢(shì)在于與采用單個(gè)鎖來實(shí)現(xiàn)獨(dú)占訪問相比寥茫,要獲取多個(gè)鎖來實(shí)現(xiàn)獨(dú)占訪問將更加困難并且開銷更高
"那么系統(tǒng)如何快速的實(shí)現(xiàn)SideTableS()分流呢遣蚀?
SideTableS()本質(zhì)是哈希表。通過對(duì)象地址經(jīng)過hash函數(shù)計(jì)算后得出SideTable地址纱耻。
"RefcountMap"結(jié)構(gòu)
引用計(jì)數(shù)表也就是RefcountMap是一個(gè)哈希表芭梯,通過一個(gè)指針快速找到對(duì)象的引用計(jì)數(shù)進(jìn)行操作,避免循環(huán)遍歷弄喘。
RefcountMap結(jié)構(gòu)的key是指針(大牛沒說是否是對(duì)象地址)玖喘,value為size_t,size_t是一個(gè)無符號(hào)的long型的變量。
size_t在這里面是64位來表示蘑志,最低的一位(也就是第一個(gè)二進(jìn)制位)用來表示是否有弱引用累奈;第二位用來表示當(dāng)前對(duì)象是否正在釋放贬派;其他62位用來存儲(chǔ)這個(gè)對(duì)象的引用計(jì)數(shù)值,當(dāng)我們想要獲得這個(gè)對(duì)象的引用計(jì)數(shù)值時(shí)需要將這個(gè)值向右偏移兩位才能取到真實(shí)的值费尽。
"weak_table_t"弱引用表,實(shí)際上也是哈希表
weak_table_t的key為對(duì)象指針羊始,通過hash運(yùn)算旱幼,可以獲得一個(gè)結(jié)構(gòu)體為weak_entry_t,weak_entry_t為一個(gè)結(jié)構(gòu)體數(shù)組突委,weak_entry_t這個(gè)結(jié)構(gòu)體數(shù)組里面存的就是一個(gè)個(gè)的WeakPtr(對(duì)象)柏卤,即弱引用指針地址。
alloc實(shí)現(xiàn)
經(jīng)過一系列函數(shù)調(diào)用匀油,最終調(diào)用了C函數(shù)calloc缘缚,此時(shí)并沒有設(shè)置引用計(jì)數(shù)為1(疑問:那為啥此時(shí)打印retainCount時(shí)是1)
retain實(shí)現(xiàn)原理(源碼可以查看,源碼片段如下)
//通過對(duì)象的指針敌蚜,去SideTableS()中獲取SideTable
SideTable& table = SideTableS()[this];
//拿到SideTable中的引用計(jì)數(shù)表桥滨,然后獲取引用計(jì)數(shù)值size_t
size_t& refcntStorage = table.refcnts[this];
//將引用計(jì)數(shù)值加上一個(gè)宏定義,這個(gè)宏是偏移兩位的1弛车,即4齐媒,因?yàn)閟ize_t最后兩位是記錄其他信息,所以需要加偏移兩位的1
refcntStorage += SIDE_TABLE_RC_ONE
release實(shí)現(xiàn)原理(剛好跟retain相反纷跛,但是代碼為啥不一樣不曉得)
//通過對(duì)象的指針喻括,去SideTableS()中獲取SideTable
SideTable& table = SideTableS()[this];
//拿到SideTable中的引用計(jì)數(shù)表進(jìn)行查找
RefcountMap::iterator it = table.refcnts.find(this);
//將引用計(jì)數(shù)值減上一個(gè)宏定義,這個(gè)宏是偏移兩位的1
it->second -= SIDE_TABLE_RC_ONE;
retainCount實(shí)現(xiàn)原理
//通過對(duì)象的指針贫奠,去SideTableS()中獲取SideTable
SideTable& table = SideTableS()[this];
//聲明一個(gè)局部變量為1
size_t refcnt_result = 1;
//拿到SideTable中的引用計(jì)數(shù)表進(jìn)行查找
RefcountMap::iterator it = table.refcnts.find(this);
//將查找的結(jié)果向右偏移兩位然后+1 (所以說新alloc的對(duì)象唬血,在引用計(jì)數(shù)表里實(shí)際沒有相關(guān)聯(lián)的key、value唤崭,那么此時(shí)讀出來的值就為0拷恨,然后偏移后加1即上面所說的新alloc的對(duì)象打印retainCount時(shí)是1)
refcnt_result += it->second >> SIDE_TABLE_RC_SHIFT;
dealloc實(shí)現(xiàn)原理
開始
|
\|/
_objc_rootDealloc()
\|/ |- nonpointer_isa 是否使用非指針I(yè)SA
rootDealloc() | weakly_referenced 是否有weak指針指向
\|/ | has_assoc 是否有關(guān)聯(lián)對(duì)象
yes--是否可以直接釋放?-條件-需全否->| has_cxx_dtor 是否有C++析構(gòu)函數(shù)
| |NO |- has_sidetable_rc 是否是通過sidetable()存儲(chǔ)引用計(jì)數(shù)
\|/ |
C函數(shù)free() |
\|/
object_dispose()
object_dispose()內(nèi)部實(shí)現(xiàn)
針對(duì)上面沒法直接釋放的五個(gè)條件谢肾,全部清除后再調(diào)用C函數(shù)free()釋放挑随。
比如如果有關(guān)聯(lián)對(duì)象,object_dispose()內(nèi)部需先移除關(guān)聯(lián)對(duì)象后才能釋放
比如針對(duì)是否有weak指針指向勒叠,會(huì)將weak指針置nil
ARC是啥兜挨?
自動(dòng)引用計(jì)數(shù)
ARC是LLVM編譯器和Runtime共同協(xié)作的結(jié)果。
禁止手動(dòng)調(diào)用retain眯分、release拌汇、retainCount、autorelease這幾個(gè)方法
ARC新增weak弊决、strong屬性關(guān)鍵字
ARC和MRC區(qū)別噪舀?
1 MRC是手動(dòng)管理內(nèi)存魁淳,ARC是LLVM編譯器和Runtime共同協(xié)作來自動(dòng)進(jìn)行引用計(jì)數(shù)的內(nèi)存管理。
2 MRC可以手動(dòng)調(diào)用retain与倡、release界逛、retainCount、autorelease這幾個(gè)方法纺座,ARC不能手動(dòng)調(diào)用
ARC
一息拜、簡介
ARC是自iOS 5之后增加的新特性,完全消除了手動(dòng)管理內(nèi)存的煩瑣净响,編譯器會(huì)自動(dòng)在適當(dāng)?shù)牡胤讲迦脒m當(dāng)?shù)膔etain少欺、release、autorelease語句馋贤。你不再需要擔(dān)心內(nèi)存管理,因?yàn)榫幾g器為你處理了一切 注意:ARC 是編譯器特性赞别,而不是 iOS 運(yùn)行時(shí)特性(除了weak指針系統(tǒng)),它也不是類似于其它語言中的垃圾收集器配乓。因此 ARC 和手動(dòng)內(nèi)存管理性能是一樣的仿滔,有時(shí)還能更加快速,因?yàn)榫幾g器還可以執(zhí)行某些優(yōu)化
二犹芹、原理
ARC 的規(guī)則非常簡單:只要還有一個(gè)變量指向?qū)ο蟮棠欤瑢?duì)象就會(huì)保持在內(nèi)存中。當(dāng)指針指向新值,或者指針不再存在時(shí),相關(guān)聯(lián)的對(duì)象就會(huì)自動(dòng)釋放羽莺。這條規(guī)則對(duì)于實(shí)例變量实昨、synthesize屬性、局部變量都是適用的
strong
strong修飾的屬性一般不會(huì)自動(dòng)釋放盐固,在OC中荒给,對(duì)象默認(rèn)是強(qiáng)指針;
strong的實(shí)現(xiàn)
使用strong關(guān)鍵字刁卜,引用計(jì)數(shù)自動(dòng)加1志电,該指針指向的對(duì)象不會(huì)由于其他指針指向的改變而銷毀。
weak
我們都知道weak表示的是一個(gè)弱引用蛔趴,這個(gè)引用不會(huì)增加對(duì)象的引用計(jì)數(shù)挑辆,并且在所指向的對(duì)象被釋放之后, weak指針會(huì)被設(shè)置的為nil孝情。weak引用通常是用于處理循環(huán)引用的問題鱼蝉,如代理及block的使用中,相對(duì)會(huì)較多的使用到weak箫荡。
weak
先看個(gè)例子
// ARC是LLVM編譯器和Runtime系統(tǒng)相互協(xié)作的一個(gè)結(jié)果
__strong MJPerson *person1;
__weak MJPerson *person2;
__unsafe_unretained MJPerson *person3;
NSLog(@"111");
{
MJPerson *person = [[MJPerson alloc] init];
//?1 person1 = person;
//?2 person2 = person;
person3 = person;
}
//?1 NSLog(@"222 - %@", person1); //有值
//?2 NSLog(@"222 - %@", person2); //null
NSLog(@"222 - %@", person3); //崩潰:野指針訪問
__unsafe_unretained 和 __weak 區(qū)別: _unsafe_unretained:和__weak 一樣魁亦,唯一的區(qū)別便是,對(duì)象即使被銷毀羔挡,指針也不會(huì)自動(dòng)置空洁奈, 此時(shí)指針指向的是一個(gè)無用的野地址间唉。如果使用此指針,程序會(huì)拋出 BAD_ACCESS 的異常利术。
weak的實(shí)現(xiàn)原理 (大牛講解呈野,MJ講解沒聽懂)
http://www.cocoachina.com/ios/20170328/18962.html
{
id __weak obj1 = obj;
}
上面代碼通過編譯之后成為下面這個(gè)樣子
{
id obj1;
//objc_initWeak接收兩個(gè)參數(shù),一個(gè)弱引用地址印叁,一個(gè)對(duì)象
objc_initWeak(&obj1,obj);
}
那么objc_initWeak函數(shù)做了什么呢被冒?(源碼可看。下面過程就是弱引用變量添加的過程)
objc_initWeak函數(shù)會(huì)調(diào)用 objc_storeWeak()函數(shù)喉钢,objc_storeWeak() 會(huì)調(diào)用一個(gè)weak_register_no_lock()函數(shù),weak_register_no_lock()函數(shù)會(huì)進(jìn)行弱引用變量的添加姆打,具體添加位置是通過hash運(yùn)算找到對(duì)應(yīng)位置良姆,如果查找位置已經(jīng)有當(dāng)前對(duì)象對(duì)應(yīng)的弱引用數(shù)組肠虽,就把弱引用變量添加進(jìn)數(shù)組當(dāng)中;如果沒有那么久創(chuàng)建一個(gè)弱引用數(shù)組玛追,把弱引用變量添加到第0個(gè)位置税课。
那么一個(gè)弱引用指針又是如何置nil的呢?
dealloc()經(jīng)過一系列的調(diào)用痊剖,最終會(huì)調(diào)用weak_clear_no_lock(),weak_clear_no_lock()接收兩個(gè)參數(shù)韩玩,一個(gè)弱引用表,一個(gè)dealloc對(duì)象陆馁。weak_clear_no_lock()會(huì)根據(jù)當(dāng)前對(duì)象指針查找弱引用表找颓,進(jìn)而拿到當(dāng)前對(duì)象的所有弱引用(是一個(gè)數(shù)組),遍歷這個(gè)弱引用數(shù)組叮贩,把所有指針置為nil
/*
Runtime維護(hù)了一個(gè)weak表击狮,用于存儲(chǔ)指向某個(gè)對(duì)象的所有weak指針。weak表其實(shí)是一個(gè)hash(哈希)表益老,Key是所指對(duì)象的地址彪蓬,Value是weak指針的地址(這個(gè)地址的值是所指對(duì)象的地址)數(shù)組.
weak 的實(shí)現(xiàn)原理可以概括一下三步:
1、初始化時(shí):runtime會(huì)調(diào)用objc_initWeak函數(shù)捺萌,初始化一個(gè)新的weak指針指向?qū)ο蟮牡刂贰?br>
2档冬、添加引用時(shí):objc_initWeak函數(shù)會(huì)調(diào)用 objc_storeWeak() 函數(shù), objc_storeWeak() 的作用是更新指針指向桃纯,創(chuàng)建對(duì)應(yīng)的弱引用表酷誓。
3、釋放時(shí)态坦,調(diào)用clearDeallocating函數(shù)呛牲。clearDeallocating函數(shù)首先根據(jù)對(duì)象地址獲取所有weak指針地址的數(shù)組,然后遍歷這個(gè)數(shù)組把其中的數(shù)據(jù)設(shè)為nil驮配,最后把這個(gè)entry從weak表中刪除娘扩,最后清理對(duì)象的記錄着茸。
*/
自動(dòng)釋放池(大牛講解,MJ講解沒聽懂)
AutoreleasePool原理是怎樣的琐旁?
編譯器會(huì)將
autoreleasepool{}
改寫成
void *ctx = objc_autoreleasePoolPush();
{}中的代碼
objc_autoreleasePoolPop(ctx);
其中objc_autoreleasePoolPush()的內(nèi)部實(shí)現(xiàn)
void* objc_autoreleasePoolPush()
|
|會(huì)調(diào)用C++類AutoreleasePoolPage類里的push函數(shù)
|
\|/
void* AutoreleasePoolPage::push(void)
其中objc_autoreleasePoolPop()的內(nèi)部實(shí)現(xiàn)
void* objc_autoreleasePoolPop(void* ctxt)
|
|會(huì)調(diào)用C++類AutoreleasePoolPage類里的pop函數(shù)
|
\|/
void* AutoreleasePoolPage::pop(void* ctxt)
一次pop相當(dāng)于一次批量pop操作涮阔,即在pop時(shí)會(huì)往{}的所有對(duì)象都會(huì)發(fā)送一次release
自動(dòng)釋放池的數(shù)據(jù)結(jié)構(gòu)
是以棧為結(jié)點(diǎn)通過雙向鏈表的形式組合而成。
是和線程一一對(duì)應(yīng)的灰殴。
雙向鏈表
NULL <——父指針—— <——父指針—— <——父指針——
頭結(jié)點(diǎn) 后續(xù)結(jié)點(diǎn)... 最后結(jié)點(diǎn) NULL
——child指針——> ——child指針——> ——child指針——>
棧
|————————— 低地址
棧頂 ——————>|obj(n) /|\
|obj(n-1) |
|... |
棧底 ——————>|obj(1) |
|———————— 高地址
AutoreleasePoolPage類的結(jié)構(gòu)如下
id* next; //next指針指向棧當(dāng)中下一個(gè)可填充的位置
AutoreleasePoolPage* const parent; //雙向鏈表的父指針
AutoreleasePoolPage* child; //雙向鏈表的child指針
pthread_t const thread; //線程
[obj autorelease]實(shí)現(xiàn)原理
下面先講push過程
開始
\|/
增加一個(gè)棧結(jié)點(diǎn)到鏈表上<——yes——————next == 棧頂敬特?
| |NO
| \|/
|————————————————————————————>add(obj) //入棧添加
現(xiàn)在來講下pop流程
根據(jù)傳入的哨兵對(duì)象找到對(duì)應(yīng)位置。
給上次push操作后添加的對(duì)象依次發(fā)送release
回退next指針到正確位置牺陶。
講解上述三個(gè)步驟:
比如一個(gè)棧里有五個(gè)元素伟阔,棧底為5,依次掰伸,棧頂為1皱炉,next指針此時(shí)指向1的位置,即棧頂
這時(shí)候接收到一次AutoreleasePop操作狮鸭,那么要給AutoreleasePool包含的元素全部release合搅。假設(shè)AutoreleasePool包含的是3、2歧蕉、1這三個(gè)指針灾部,那么清空3、2惯退、1赌髓。此時(shí)next指針此時(shí)指向3的位置.
總結(jié)自動(dòng)釋放池
在當(dāng)次runloop將要結(jié)束的時(shí)候調(diào)用objc_autoreleasePoolPop
為什么AutoreleasePool可以嵌套使用呢?
多層嵌套就是多次插入哨兵對(duì)象催跪。
什么場景下會(huì)需要手動(dòng)創(chuàng)建AutoreleasePool呢锁蠕?
在for循環(huán)中alloc圖片數(shù)據(jù)等內(nèi)存消耗較大的場景手動(dòng)插入AutoreleasePool(比如每一次for就就釋放防止內(nèi)存峰值)
循環(huán)引用
三種類型:
自循環(huán)引用
相互循環(huán)引用
多循環(huán)引用
自循環(huán)引用
對(duì)象強(qiáng)持有成員變量obj,然后我們給obj賦值為原對(duì)象叠荠,那么就自循環(huán)引用
相互循環(huán)引用
A對(duì)象成員變量obj引用B對(duì)象匿沛,B對(duì)象成員變量obj引用A對(duì)象
多循環(huán)引用
A對(duì)象成員變量obj引用B對(duì)象,B對(duì)象成員變量obj引用C對(duì)象榛鼎,C對(duì)象成員變量obj引用A對(duì)象
循環(huán)引用考點(diǎn)
代理
block
NSTimer
大環(huán)引用