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)        堆: 通過(guò)alloc、malloc、calloc等動(dòng)態(tài)分配的空間,分配的內(nèi)存空間地址值越來(lái)越大(由低地址往高地址分)
     |       \|/
     |
     |
     |       /|\
     |棧(stack)       棧:函數(shù)調(diào)用開銷,比如局部變量。分配的內(nèi)存空間地址值越來(lái)越小(由高地址往低地址分)
     |
     |內(nèi)核區(qū)
    \|/
高地址

Tagged Pointer

從64bit開始,iOS引入了Tagged Pointer技術(shù),用于優(yōu)化NSNumber、NSDate、NSString等小對(duì)象的存儲(chǔ)

在沒(méi)有使用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)存的方式來(lá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)存訪問(wèn)
解決辦法就是加鎖,在
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)容變少了,通過(guò)Tagged Pointer技術(shù)能夠存在指針里,self.name就不用走set方法里的"某些"操作,直接把數(shù)據(jù)存在指針里,所以就不會(huì)產(chǎn)生崩潰。

MRC

在iOS中,使用引用計(jì)數(shù)來(lái)管理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來(lái)釋放它
想擁有某個(gè)對(duì)象,就讓它的引用計(jì)數(shù)+1;不想再擁有某個(gè)對(duì)象,就讓它的引用計(jì)數(shù)-1

可以通過(guò)以下私有函數(shù)來(lái)查看自動(dòng)釋放池的情況

extern void _objc_autoreleasePoolPrint(void);

下面用MRC來(lái)實(shí)現(xiàn)一個(gè)簡(jiǎn)單的調(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就有問(wèn)題了
    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還沒(méi)銷毀就找不到dog問(wèn)題。比如這里不進(jìn)行增加的話,外界初始化MJPerson和MJDog后就要releasse,這時(shí)候通過(guò)MJPerson調(diào)MJDog的run方法(MJDog里有一個(gè)run方法)就會(huì)出現(xiàn)壞內(nèi)存訪問(wèn)。
    }
}

- (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];
//可簡(jiǎn)化為
    self.data = [[NSMutableArray alloc] init];
    [self.data release];
//還可以簡(jiǎn)化為
    self.data = [[[NSMutableArray alloc] init] autorelease];
//再可以簡(jiǎn)化為
    self.data = [NSMutableArray array]; //這種可以通過(guò)類方法來(lái)創(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.淺拷貝:指針拷貝,沒(méi)有產(chǎn)生新的對(duì)象,但會(huì)影響引用計(jì)數(shù)
2.深拷貝:內(nèi)容拷貝,產(chǎn)生新的對(duì)象,不會(huì)影響引用計(jì)數(shù)

//無(wú)論是不可變字符還是可變字符,不可變拷貝拷貝出來(lái)類型都變成NSString,可變拷貝拷貝出來(lái)類型都變成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ì)象,不過(guò)這句話相當(dāng)于[str1 retain]
NSMutableString *str3 = [str1 mutableCopy]; //地址不一樣
//為什么NSString的copy地址和本身地址一樣,mutableCopy地址和本身地址不一樣呢?
首先,針對(duì)不能隨意再次改變字符長(zhǎng)度的NSString來(lái)說(shuō),copy之后還是一樣不可變的字符,也不能隨意再次改變字符長(zhǎng)度,既然都不可變,所以干脆地址一樣,都指向一塊內(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)過(guò)copy后變成NSString了,是一個(gè)新對(duì)象。經(jīng)過(guò)mutableCopy后變成NSMutableString了,也是一個(gè)新對(duì)象。
很好奇為什么經(jīng)過(guò)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通過(guò)copy變成不可變數(shù)組了,再次[per.dataArr addObject:@""]就會(huì)沒(méi)有addObject方法

waring

屬性的修飾只有copy,沒(méi)有"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 遺留下來(lái)的“傳統(tǒng)”,在 MRC 中,方法內(nèi)部的 block 是在棧區(qū)的,使用 copy 可以把它放到堆區(qū).在 ARC 中寫不寫都行:對(duì)于 block 使用 copy 還是 strong 效果是一樣的,但寫上 copy 也無(wú)傷大雅,還能時(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)鍵字,可能造成什么問(wèn)題?
1.因?yàn)楦割愔羔樋梢灾赶蜃宇悓?duì)象,使用 copy 的目的是為了讓本對(duì)象的屬性不受外界影響,使用 copy 無(wú)論給我傳入是一個(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 來(lái)手動(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)化過(guò)的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呢?
假如說(shuō)只有一個(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)行加鎖才能夠保證資源訪問(wèn)正確。這時(shí)候大大減弱了效率。系統(tǒng)為了解決這種效率問(wèn)題,采用了分離鎖技術(shù)。將8個(gè)SideTable共用一把鎖,共8把鎖(N為64)。這樣能保證最大并發(fā)量。但是劣勢(shì)在于與采用單個(gè)鎖來(lái)實(shí)現(xiàn)獨(dú)占訪問(wèn)相比,要獲取多個(gè)鎖來(lái)實(shí)現(xiàn)獨(dú)占訪問(wèn)將更加困難并且開銷更高
"那么系統(tǒng)如何快速的實(shí)現(xiàn)SideTableS()分流呢?
SideTableS()本質(zhì)是哈希表。通過(guò)對(duì)象地址經(jīng)過(guò)hash函數(shù)計(jì)算后得出SideTable地址。

"RefcountMap"結(jié)構(gòu)
引用計(jì)數(shù)表也就是RefcountMap是一個(gè)哈希表,通過(guò)一個(gè)指針快速找到對(duì)象的引用計(jì)數(shù)進(jìn)行操作,避免循環(huán)遍歷。
RefcountMap結(jié)構(gòu)的key是指針(大牛沒(méi)說(shuō)是否是對(duì)象地址),value為size_t,size_t是一個(gè)無(wú)符號(hào)的long型的變量。
size_t在這里面是64位來(lái)表示,最低的一位(也就是第一個(gè)二進(jìn)制位)用來(lái)表示是否有弱引用;第二位用來(lái)表示當(dāng)前對(duì)象是否正在釋放;其他62位用來(lái)存儲(chǔ)這個(gè)對(duì)象的引用計(jì)數(shù)值,當(dāng)我們想要獲得這個(gè)對(duì)象的引用計(jì)數(shù)值時(shí)需要將這個(gè)值向右偏移兩位才能取到真實(shí)的值。

"weak_table_t"弱引用表,實(shí)際上也是哈希表
weak_table_t的key為對(duì)象指針,通過(guò)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)過(guò)一系列函數(shù)調(diào)用,最終調(diào)用了C函數(shù)calloc,此時(shí)并沒(méi)有設(shè)置引用計(jì)數(shù)為1(疑問(wèn):那為啥此時(shí)打印retainCount時(shí)是1)

retain實(shí)現(xiàn)原理(源碼可以查看,源碼片段如下)

//通過(guò)對(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相反,但是代碼為啥不一樣不曉得)

//通過(guò)對(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)原理

//通過(guò)對(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 (所以說(shuō)新alloc的對(duì)象,在引用計(jì)數(shù)表里實(shí)際沒(méi)有相關(guān)聯(lián)的key、value,那么此時(shí)讀出來(lái)的值就為0,然后偏移后加1即上面所說(shuō)的新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 是否是通過(guò)sidetable()存儲(chǔ)引用計(jì)數(shù)
\|/           |
C函數(shù)free()   |
            \|/
        object_dispose()

object_dispose()內(nèi)部實(shí)現(xiàn)

針對(duì)上面沒(méi)法直接釋放的五個(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é)作來(lái)自動(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

一、簡(jiǎn)介
ARC是自iOS 5之后增加的新特性,完全消除了手動(dòng)管理內(nèi)存的煩瑣,編譯器會(huì)自動(dòng)在適當(dāng)?shù)牡胤讲迦脒m當(dāng)?shù)膔etain、release、autorelease語(yǔ)句。你不再需要擔(dān)心內(nèi)存管理,因?yàn)榫幾g器為你處理了一切 注意:ARC 是編譯器特性,而不是 iOS 運(yùn)行時(shí)特性(除了weak指針系統(tǒng)),它也不是類似于其它語(yǔ)言中的垃圾收集器。因此 ARC 和手動(dòng)內(nèi)存管理性能是一樣的,有時(shí)還能更加快速,因?yàn)榫幾g器還可以執(zhí)行某些優(yōu)化

二、原理

ARC 的規(guī)則非常簡(jiǎn)單:只要還有一個(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)引用的問(wè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);      //崩潰:野指針訪問(wèn)

__unsafe_unretained 和 __weak 區(qū)別: _unsafe_unretained:和__weak 一樣,唯一的區(qū)別便是,對(duì)象即使被銷毀,指針也不會(huì)自動(dòng)置空, 此時(shí)指針指向的是一個(gè)無(wú)用的野地址。如果使用此指針,程序會(huì)拋出 BAD_ACCESS 的異常。

weak的實(shí)現(xiàn)原理 (大牛講解,MJ講解沒(méi)聽懂)

http://www.cocoachina.com/ios/20170328/18962.html

{
    id __weak obj1 = obj;
}
上面代碼通過(guò)編譯之后成為下面這個(gè)樣子
{
    id obj1;
    //objc_initWeak接收兩個(gè)參數(shù),一個(gè)弱引用地址,一個(gè)對(duì)象
    objc_initWeak(&obj1,obj);
}

那么objc_initWeak函數(shù)做了什么呢?(源碼可看。下面過(guò)程就是弱引用變量添加的過(guò)程)

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)行弱引用變量的添加,具體添加位置是通過(guò)hash運(yùn)算找到對(duì)應(yīng)位置,如果查找位置已經(jīng)有當(dāng)前對(duì)象對(duì)應(yīng)的弱引用數(shù)組,就把弱引用變量添加進(jìn)數(shù)組當(dāng)中;如果沒(méi)有那么久創(chuàng)建一個(gè)弱引用數(shù)組,把弱引用變量添加到第0個(gè)位置。

那么一個(gè)弱引用指針又是如何置nil的呢?

dealloc()經(jīng)過(guò)一系列的調(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講解沒(méi)聽懂)

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)通過(guò)雙向鏈表的形式組合而成。
是和線程一一對(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過(guò)程

                                  開始
                                  \|/
增加一個(gè)棧結(jié)點(diǎn)到鏈表上<——yes——————next == 棧頂?
   |                               |NO
   |                              \|/
   |————————————————————————————>add(obj) //入棧添加

現(xiàn)在來(lái)講下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ì)象。

什么場(chǎng)景下會(huì)需要手動(dòng)創(chuàng)建AutoreleasePool呢?

在for循環(huán)中alloc圖片數(shù)據(jù)等內(nèi)存消耗較大的場(chǎng)景手動(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)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容