iOS 內(nèi)存管理

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)引用

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末逃呼,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子者娱,更是在濱河造成了極大的恐慌抡笼,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,639評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件黄鳍,死亡現(xiàn)場離奇詭異推姻,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)框沟,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,277評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門藏古,熙熙樓的掌柜王于貴愁眉苦臉地迎上來增炭,“玉大人,你說我怎么就攤上這事拧晕∠蹲耍” “怎么了?”我有些...
    開封第一講書人閱讀 157,221評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵厂捞,是天一觀的道長输玷。 經(jīng)常有香客問我,道長靡馁,這世上最難降的妖魔是什么欲鹏? 我笑而不...
    開封第一講書人閱讀 56,474評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮臭墨,結(jié)果婚禮上赔嚎,老公的妹妹穿的比我還像新娘。我一直安慰自己裙犹,他們只是感情好尽狠,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,570評(píng)論 6 386
  • 文/花漫 我一把揭開白布衔憨。 她就那樣靜靜地躺著叶圃,像睡著了一般。 火紅的嫁衣襯著肌膚如雪践图。 梳的紋絲不亂的頭發(fā)上掺冠,一...
    開封第一講書人閱讀 49,816評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音码党,去河邊找鬼德崭。 笑死,一個(gè)胖子當(dāng)著我的面吹牛揖盘,可吹牛的內(nèi)容都是我干的眉厨。 我是一名探鬼主播,決...
    沈念sama閱讀 38,957評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼兽狭,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼憾股!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起箕慧,我...
    開封第一講書人閱讀 37,718評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤服球,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后颠焦,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體斩熊,經(jīng)...
    沈念sama閱讀 44,176評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,511評(píng)論 2 327
  • 正文 我和宋清朗相戀三年伐庭,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了粉渠。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片分冈。...
    茶點(diǎn)故事閱讀 38,646評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖霸株,靈堂內(nèi)的尸體忽然破棺而出丈秩,到底是詐尸還是另有隱情,我是刑警寧澤淳衙,帶...
    沈念sama閱讀 34,322評(píng)論 4 330
  • 正文 年R本政府宣布蘑秽,位于F島的核電站,受9級(jí)特大地震影響箫攀,放射性物質(zhì)發(fā)生泄漏肠牲。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,934評(píng)論 3 313
  • 文/蒙蒙 一靴跛、第九天 我趴在偏房一處隱蔽的房頂上張望缀雳。 院中可真熱鬧,春花似錦梢睛、人聲如沸肥印。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,755評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽深碱。三九已至,卻和暖如春藏畅,著一層夾襖步出監(jiān)牢的瞬間敷硅,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,987評(píng)論 1 266
  • 我被黑心中介騙來泰國打工愉阎, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留绞蹦,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,358評(píng)論 2 360
  • 正文 我出身青樓榜旦,卻偏偏與公主長得像幽七,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子溅呢,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,514評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容