#內(nèi)存管理

OC對(duì)象的內(nèi)存管理

在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

- (void)setDog:(MJDog *)dog
{
    if (_dog != dog) {
        [_dog release];
        _dog = [dog retain];
    }
}

內(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);

copy和mutableCopy區(qū)別
  • copy 產(chǎn)生不可變對(duì)象
  • mutableCopy產(chǎn)生可變對(duì)象
copy

注意點(diǎn)
可變數(shù)組copy后會(huì)變成不可變數(shù)組 , 添加元素 會(huì)報(bào)錯(cuò) 變成了不可變對(duì)象

引用計(jì)數(shù)的存儲(chǔ)

在64bit中,引用計(jì)數(shù)可以直接存儲(chǔ)在優(yōu)化過的isa指針中鸭栖,也可能存儲(chǔ)在SideTable類中

struct SideTable {
    spinlock_t slock;
    RefcountMap refcnts;
    weak_table_t weak_table;
}

refcnts是一個(gè)存放著對(duì)象引用計(jì)數(shù)的散列表

RetainCount
objc_object::rootRetainCount()
{
    if (isTaggedPointer()) return (uintptr_t)this;//指針

    sidetable_lock();
    isa_t bits = LoadExclusive(&isa.bits);
    ClearExclusive(&isa.bits);
    if (bits.nonpointer) {//非指針類型 歌馍,是否優(yōu)化過
        uintptr_t rc = 1 + bits.extra_rc;//引用計(jì)數(shù)
        if (bits.has_sidetable_rc) {// 1不是存儲(chǔ)在isa中,而是存儲(chǔ)在sideTable中
            rc += sidetable_getExtraRC_nolock();
        }
        sidetable_unlock();
        return rc;
    }

    sidetable_unlock();
    return sidetable_retainCount();
}
dealloc
objc_object::clearDeallocating()
{
    if (slowpath(!isa.nonpointer)) {
        // Slow path for raw pointer isa.
        sidetable_clearDeallocating();
    }
    else if (slowpath(isa.weakly_referenced  ||  isa.has_sidetable_rc)) {
        // Slow path for non-pointer isa with weak refs and/or side table data.
        clearDeallocating_slow();
    }

    assert(!sidetable_present());
}

__weak指針原理

runtime維護(hù)著一張弱引用表,用于存儲(chǔ)指向某個(gè)對(duì)象的所有Weak指針晕鹊,弱引用以散列表的方式存儲(chǔ)到弱引用表里松却,釋放時(shí)調(diào)?用clearDeallocating函數(shù),通過對(duì)象地址值作為key & 掩碼獲取索引 ,取出當(dāng)前對(duì)象的弱引用表 溅话,將里面的弱引用都清除掉晓锻,并將所有Weak指針的值設(shè)為nil

objc_object::clearDeallocating_slow()
{
    assert(isa.nonpointer  &&  (isa.weakly_referenced || isa.has_sidetable_rc));

    SideTable& table = SideTables()[this]; //取出弱引用表
    table.lock();
    if (isa.weakly_referenced) {//弱引用 移除
        weak_clear_no_lock(&table.weak_table, (id)this);
    }
//將引用計(jì)數(shù)表里的 東西也擦除掉
    if (isa.has_sidetable_rc) {
        table.refcnts.erase(this);
    }
    table.unlock();
}

weak_clear_no_lock(weak_table_t *weak_table, id referent_id) 
{
    objc_object *referent = (objc_object *)referent_id;

    weak_entry_t *entry = weak_entry_for_referent(weak_table, referent);
    if (entry == nil) {
        /// XXX shouldn't happen, but does with mismatched CF/objc
        //printf("XXX no entry for clear deallocating %p\n", referent);
        return;
    }
    weak_entry_remove(weak_table, entry);
}


weak_entry_for_referent(weak_table_t *weak_table, objc_object *referent)
{
    assert(referent);

    weak_entry_t *weak_entries = weak_table->weak_entries;

    if (!weak_entries) return nil;
    //地址值 & 掩碼 獲取 一個(gè)索引
    size_t begin = hash_pointer(referent) & weak_table->mask;
    size_t index = begin;
    size_t hash_displacement = 0;
    while (weak_table->weak_entries[index].referent != referent) {
        index = (index+1) & weak_table->mask;
        if (index == begin) bad_weak_table(weak_table->weak_entries);
        hash_displacement++;
        if (hash_displacement > weak_table->max_hash_displacement) {
            return nil;
        }
    }
    
    return &weak_table->weak_entries[index];
}
ARC幫我們做了什么

利用LLVM 編譯器自動(dòng)幫我們添加retain,release
利用Runtime 監(jiān)聽對(duì)象銷毀的時(shí)候,找到弱引用并清除

自動(dòng)釋放池

/*
 struct __AtAutoreleasePool {
    __AtAutoreleasePool() { // 構(gòu)造函數(shù)飞几,在創(chuàng)建結(jié)構(gòu)體的時(shí)候調(diào)用
        atautoreleasepoolobj = objc_autoreleasePoolPush();
    }
 
    ~__AtAutoreleasePool() { // 析構(gòu)函數(shù)砚哆,在結(jié)構(gòu)體銷毀的時(shí)候調(diào)用
        objc_autoreleasePoolPop(atautoreleasepoolobj);
    }
 
    void * atautoreleasepoolobj;
 };
 
 {
    __AtAutoreleasePool __autoreleasepool;
    MJPerson *person = ((MJPerson *(*)(id, SEL))(void *)objc_msgSend)((id)((MJPerson *(*)(id, SEL))(void *)objc_msgSend)((id)((MJPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("MJPerson"), sel_registerName("alloc")), sel_registerName("init")), sel_registerName("autorelease"));
 }


    atautoreleasepoolobj = objc_autoreleasePoolPush();
 
    MJPerson *person = [[[MJPerson alloc] init] autorelease];
 
    objc_autoreleasePoolPop(atautoreleasepoolobj);
 */

  • 自動(dòng)釋放池的主要底層數(shù)據(jù)結(jié)構(gòu)是:__AtAutoreleasePool、AutoreleasePoolPage
  • 調(diào)用了autorelease的對(duì)象最終都是通過AutoreleasePoolPage對(duì)象來管理的
AutoreleasePoolPage 結(jié)構(gòu)
  • 每個(gè)AutoreleasePoolPage對(duì)象占用4096字節(jié)內(nèi)存屑墨,除了用來存放它內(nèi)部的成員變量躁锁,剩下的空間用來存放autorelease對(duì)象的地址
  • 所有的AutoreleasePoolPage對(duì)象通過雙向鏈表的形式連接在一起
AutoreleasePoolPage結(jié)構(gòu)
  • 調(diào)用push方法會(huì)將一個(gè)POOL_BOUNDARY入棧,并且返回其存放的內(nèi)存地址
  • 調(diào)用pop方法時(shí)傳入一個(gè)POOL_BOUNDARY的內(nèi)存地址绪钥,會(huì)從最后一個(gè)入棧的對(duì)象開始發(fā)送release消息灿里,直到遇到這個(gè)POOL_BOUNDARY
autorelease什么時(shí)候釋放

iOS在主線程的Runloop中注冊(cè)了2個(gè)Observer

  • 第1個(gè)Observer監(jiān)聽了kCFRunLoopEntry事件,會(huì)調(diào)用objc_autoreleasePoolPush()
  • 第2個(gè)Observer監(jiān)聽了kCFRunLoopBeforeWaiting事件程腹,會(huì)調(diào)用objc_autoreleasePoolPop()匣吊、objc_autoreleasePoolPush()
    監(jiān)聽了kCFRunLoopBeforeExit事件,會(huì)調(diào)用objc_autoreleasePoolPop()
@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    // 這個(gè)Person什么時(shí)候調(diào)用release,是由RunLoop來控制的
    // 它可能是在某次RunLoop循環(huán)中色鸳,RunLoop休眠之前調(diào)用了release
//    MJPerson *person = [[[MJPerson alloc] init] autorelease];
    
    MJPerson *person = [[MJPerson alloc] init];
    
    NSLog(@"%s", __func__);
}


/*
 typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
 kCFRunLoopEntry = (1UL << 0),  1
 kCFRunLoopBeforeTimers = (1UL << 1), 2
 kCFRunLoopBeforeSources = (1UL << 2), 4
 kCFRunLoopBeforeWaiting = (1UL << 5), 32
 kCFRunLoopAfterWaiting = (1UL << 6), 64
 kCFRunLoopExit = (1UL << 7), 128
 kCFRunLoopAllActivities = 0x0FFFFFFFU
 };
 */

/*
 kCFRunLoopEntry  push
 
 <CFRunLoopObserver 0x60000013f220 [0x1031c8c80]>{valid = Yes, activities = 0x1, repeats = Yes, order = -2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x103376df2), context = <CFArray 0x60000025aa00 [0x1031c8c80]>{type = mutable-small, count = 1, values = (\n\t0 : <0x7fd0bf802048>\n)}}
 
 
 kCFRunLoopBeforeWaiting | kCFRunLoopExit
 kCFRunLoopBeforeWaiting pop社痛、push
 kCFRunLoopExit pop
 
 <CFRunLoopObserver 0x60000013f0e0 [0x1031c8c80]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x103376df2), context = <CFArray 0x60000025aa00 [0x1031c8c80]>{type = mutable-small, count = 1, values = (\n\t0 : <0x7fd0bf802048>\n)}}
 */

@end

方法里有局部變量 ,出了 方法會(huì)立即釋放嗎
分情況討論,如果生成的是autorelease 代碼 不會(huì)立馬釋放命雀,會(huì)等到runloop休眠的時(shí)候會(huì)釋放
如果是生成release 會(huì)立馬釋放

使用CADisplayLink蒜哀、NSTimer有什么注意點(diǎn)?

CADisplayLink吏砂、NSTimer會(huì)對(duì)target產(chǎn)生強(qiáng)引用撵儿,如果target又對(duì)它們產(chǎn)生強(qiáng)引用,那么就會(huì)引發(fā)循環(huán)引用

解決方案

  • 使用block
    __weak typeof(self) weakSelf = self;
    self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
        [weakSelf timerTest];
    }];

- (void)timerTest
{
    NSLog(@"%s", __func__);
}

  • 使用中間對(duì)象
@interface MJProxy : NSObject
+ (instancetype)proxyWithTarget:(id)target;
@property (weak, nonatomic) id target;
@end
@implementation MJProxy
+ (instancetype)proxyWithTarget:(id)target
{
    MJProxy *proxy = [[MJProxy alloc] init];
    proxy.target = target;
    return proxy;
}
- (id)forwardingTargetForSelector:(SEL)aSelector
{
    return self.target;
}
@end

@interface MJProxy : NSProxy
+ (instancetype)proxyWithTarget:(id)target;
@property (weak, nonatomic) id target;
@end

@implementation MJProxy

NSProxy 專門用來消息轉(zhuǎn)發(fā)狐血,少了去父類里找方法的過程,比NSObject效率要高
+ (instancetype)proxyWithTarget:(id)target
{
    // NSProxy對(duì)象不需要調(diào)用init淀歇,因?yàn)樗緛砭蜎]有init方法
    MJProxy *proxy = [MJProxy alloc];
    proxy.target = target;
    return proxy;
}

- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel
{
    return [self.target methodSignatureForSelector:sel];
}

- (void)forwardInvocation:(NSInvocation *)invocation
{
    [invocation invokeWithTarget:self.target];
}
@end

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ǔ)在了指針中

當(dāng)指針不夠存儲(chǔ)數(shù)據(jù)時(shí),才會(huì)使用動(dòng)態(tài)分配內(nèi)存的方式來存儲(chǔ)數(shù)據(jù)

objc_msgSend能識(shí)別Tagged Pointer劲件,比如NSNumber的intValue方法掸哑,直接從指針提取數(shù)據(jù)约急,節(jié)省了以前的調(diào)用開銷

如何判斷一個(gè)指針是否為Tagged Pointer零远?
iOS平臺(tái),最高有效位是1(第64bit)
Mac平臺(tái)厌蔽,最低有效位是1

    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"];
            // 解鎖
        });
    }
    //沒有采用 Tagged Point 技術(shù)牵辣,多線程重復(fù)釋放崩潰
    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"];
        });
    }
 //采用 Tagged Point 技術(shù),不會(huì)訪問setter方法
/**
- (void)setName:(NSString *)name
{
    if (_name != name) {
        [_name release];  //多線程重復(fù)釋放
        _name = [name retain];
    }
}
*/

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末奴饮,一起剝皮案震驚了整個(gè)濱河市纬向,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌戴卜,老刑警劉巖逾条,帶你破解...
    沈念sama閱讀 218,755評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異投剥,居然都是意外死亡师脂,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來吃警,“玉大人糕篇,你說我怎么就攤上這事∽眯模” “怎么了拌消?”我有些...
    開封第一講書人閱讀 165,138評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長安券。 經(jīng)常有香客問我墩崩,道長,這世上最難降的妖魔是什么侯勉? 我笑而不...
    開封第一講書人閱讀 58,791評(píng)論 1 295
  • 正文 為了忘掉前任泰鸡,我火速辦了婚禮,結(jié)果婚禮上壳鹤,老公的妹妹穿的比我還像新娘盛龄。我一直安慰自己,他們只是感情好芳誓,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評(píng)論 6 392
  • 文/花漫 我一把揭開白布余舶。 她就那樣靜靜地躺著,像睡著了一般锹淌。 火紅的嫁衣襯著肌膚如雪匿值。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,631評(píng)論 1 305
  • 那天赂摆,我揣著相機(jī)與錄音挟憔,去河邊找鬼。 笑死烟号,一個(gè)胖子當(dāng)著我的面吹牛绊谭,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播汪拥,決...
    沈念sama閱讀 40,362評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼达传,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了迫筑?” 一聲冷哼從身側(cè)響起宪赶,我...
    開封第一講書人閱讀 39,264評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎脯燃,沒想到半個(gè)月后搂妻,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,724評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡辕棚,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年欲主,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了追他。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,040評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡岛蚤,死狀恐怖邑狸,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情涤妒,我是刑警寧澤单雾,帶...
    沈念sama閱讀 35,742評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站她紫,受9級(jí)特大地震影響硅堆,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜贿讹,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評(píng)論 3 330
  • 文/蒙蒙 一渐逃、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧民褂,春花似錦茄菊、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至哭廉,卻和暖如春脊僚,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背遵绰。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評(píng)論 1 270
  • 我被黑心中介騙來泰國打工辽幌, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人椿访。 一個(gè)月前我還...
    沈念sama閱讀 48,247評(píng)論 3 371
  • 正文 我出身青樓乌企,卻偏偏與公主長得像,于是被迫代替她去往敵國和親赎离。 傳聞我的和親對(duì)象是個(gè)殘疾皇子逛犹,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評(píng)論 2 355

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

  • 內(nèi)存管理ARC處理原理ARC是Objective-C編譯器的特性,而不是運(yùn)行時(shí)特性或者垃圾回收機(jī)制梁剔,ARC所做的只...
    陽明先生_X自主閱讀 336評(píng)論 0 3
  • 一、拋出一個(gè)問題:使用CADisplayLink舞蔽、NSTimer 有什么注意點(diǎn)荣病?1.1-1.6的demo1.1、分...
    IIronMan閱讀 1,370評(píng)論 1 3
  • 內(nèi)存管理ARC處理原理ARC是Objective-C編譯器的特性渗柿,而不是運(yùn)行時(shí)特性或者垃圾回收機(jī)制个盆,ARC所做的只...
    陽明先生_X自主閱讀 484評(píng)論 1 3
  • 內(nèi)存管理 簡(jiǎn)述OC中內(nèi)存管理機(jī)制脖岛。與retain配對(duì)使用的方法是dealloc還是release,為什么颊亮?需要與a...
    丶逐漸閱讀 1,965評(píng)論 1 16
  • ARC 處理原理 arc 是oc 編譯器的特性柴梆,而不是運(yùn)行時(shí)特性或者垃圾回收機(jī)制,ARC 所做的只不過是在代碼編譯...
    iChuck閱讀 368評(píng)論 0 0