底層原理(九)------內(nèi)存管理

CADisplayLink、NSTimer使用注意

CADisplayLink:保證調(diào)用頻率和屏幕的刷幀頻率一致,60FPS
NSProxy對象不需要調(diào)用init,因為它本來就沒有init方法,少了一步從父類搜索代碼的流程,直接進入消息轉(zhuǎn)發(fā)
如果進去NSObject的話,需要從父類搜索代碼,然后動態(tài)方法解析

GCD定時器


GCD定時器代碼封裝

#import <Foundation/Foundation.h>

@interface MJTimer : NSObject

+ (NSString *)execTask:(void(^)(void))task
           start:(NSTimeInterval)start
        interval:(NSTimeInterval)interval
         repeats:(BOOL)repeats
           async:(BOOL)async;

+ (NSString *)execTask:(id)target
              selector:(SEL)selector
                 start:(NSTimeInterval)start
              interval:(NSTimeInterval)interval
               repeats:(BOOL)repeats
                 async:(BOOL)async;

+ (void)cancelTask:(NSString *)name;

@end

#import "MJTimer.h"

@implementation MJTimer

static NSMutableDictionary *timers_;
dispatch_semaphore_t semaphore_;
+ (void)initialize
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        timers_ = [NSMutableDictionary dictionary];
        semaphore_ = dispatch_semaphore_create(1);
    });
}

+ (NSString *)execTask:(void (^)(void))task start:(NSTimeInterval)start interval:(NSTimeInterval)interval repeats:(BOOL)repeats async:(BOOL)async
{
    if (!task || start < 0 || (interval <= 0 && repeats)) return nil;
    
    // 隊列
    dispatch_queue_t queue = async ? dispatch_get_global_queue(0, 0) : dispatch_get_main_queue();
    
    // 創(chuàng)建定時器
    dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
    
    // 設(shè)置時間
    dispatch_source_set_timer(timer,
                              dispatch_time(DISPATCH_TIME_NOW, start * NSEC_PER_SEC),
                              interval * NSEC_PER_SEC, 0);
    
    
    dispatch_semaphore_wait(semaphore_, DISPATCH_TIME_FOREVER);
    // 定時器的唯一標識
    NSString *name = [NSString stringWithFormat:@"%zd", timers_.count];
    // 存放到字典中
    timers_[name] = timer;
    dispatch_semaphore_signal(semaphore_);
    
    // 設(shè)置回調(diào)
    dispatch_source_set_event_handler(timer, ^{
        task();
        
        if (!repeats) { // 不重復的任務(wù)
            [self cancelTask:name];
        }
    });
    
    // 啟動定時器
    dispatch_resume(timer);
    
    return name;
}

+ (NSString *)execTask:(id)target selector:(SEL)selector start:(NSTimeInterval)start interval:(NSTimeInterval)interval repeats:(BOOL)repeats async:(BOOL)async
{
    if (!target || !selector) return nil;
    
    return [self execTask:^{
        if ([target respondsToSelector:selector]) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
            [target performSelector:selector];
#pragma clang diagnostic pop
        }
    } start:start interval:interval repeats:repeats async:async];
}

+ (void)cancelTask:(NSString *)name
{
    if (name.length == 0) return;
    
    dispatch_semaphore_wait(semaphore_, DISPATCH_TIME_FOREVER);
    
    dispatch_source_t timer = timers_[name];
    if (timer) {
        dispatch_source_cancel(timer);
        [timers_ removeObjectForKey:name];
    }

    dispatch_semaphore_signal(semaphore_);
}

@end

iOS程序的內(nèi)存布局

Tagged Pointer

判斷是否是Tagged Pointer

面試題

1、思考以下2段代碼能發(fā)生什么事?有什么區(qū)別?



答案:
第一個會報壞內(nèi)存釋放錯誤,因為ARC本身就是MRC轉(zhuǎn)換過來的,name其實內(nèi)部會實現(xiàn)[_name release];釋放的操作,同時有好幾千挑數(shù)據(jù)同時訪問一塊內(nèi)存地址,有可能釋放多次,就會報錯壞內(nèi)存
改進:
1、nonatomic改成atomic
2、進行加鎖 解鎖操作在線程里面
第二個不會報錯,abc是調(diào)用了Tagged Pointer方法,不會調(diào)用set方法,也不會觸發(fā)release,直接是指針地址值賦值給對象
看地址的最后一位是否為0,0的話 為對象,1的話為Tagged Pointer

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

父類的delloc放到子類的后邊

Copy和MutableCopy

// 拷貝的目的:產(chǎn)生一個副本對象,跟源對象互不影響
// 修改了源對象,不會影響副本對象
// 修改了副本對象,不會影響源對象

/*
 iOS提供了2個拷貝方法
 1.copy,不可變拷貝,產(chǎn)生不可變副本
 2.mutableCopy,可變拷貝,產(chǎn)生可變副本
 
 深拷貝和淺拷貝
 1.深拷貝:內(nèi)容拷貝,產(chǎn)生新的對象
 2.淺拷貝:指針拷貝,沒有產(chǎn)生新的對象
 */


引用計數(shù)的存儲

Weak的實現(xiàn)原理

將弱引用存放到哈希表里面,對象銷毀,他就取出我們當前對象的弱引用表,把當前對象的弱引用表里面的數(shù)據(jù)都清除掉
weak是有一個弱引用表維護,在調(diào)用dealloc的以后他會便利weak的弱引用表,完了進行釋放

__strong:強引用
__weak:弱引用,指向的對象銷毀時,對應(yīng)的指針也會銷毀
__unsafe_unretained:不安全的弱引用,指向的對象銷毀時,對應(yīng)的指針不會銷毀,也就是野指針

- (void)viewDidLoad {
    [super viewDidLoad];
    
    // ARC是LLVM編譯器和Runtime系統(tǒng)相互協(xié)作的一個結(jié)果
    
    __strong MJPerson *person1;
    __weak MJPerson *person2;
    __unsafe_unretained MJPerson *person3;
    
    
    NSLog(@"111");
    
    {
        MJPerson *person = [[MJPerson alloc] init];
        
        person3 = person;
    }
    
    NSLog(@"222 - %@", person3);
}

ARC都幫我們做了什么?
ARC其實是LLVM+Runtime實現(xiàn)的,LLVM編譯器自動幫我們處理了retain、release、autorelease等引用計數(shù)操作,在程序運行時,像弱引用的存在,則需要Runtime進行操作。
ARC其實就是LLVM編譯器和Runtime相互協(xié)作的一個結(jié)果

自動釋放池

/*
 // @autoreleasepool內(nèi)部結(jié)構(gòu)
 struct __AtAutoreleasePool {
    __AtAutoreleasePool() { // 構(gòu)造函數(shù),在創(chuàng)建結(jié)構(gòu)體的時候調(diào)用
        atautoreleasepoolobj = objc_autoreleasePoolPush();
    }
 
    ~__AtAutoreleasePool() { // 析構(gòu)函數(shù),在結(jié)構(gòu)體銷毀的時候調(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);
 */

AutoreleasePoolPage的結(jié)構(gòu)

達到先進后出的效果,
雙向鏈表:簡單的來說就是存放2個指針,一個可以查找到下邊的對象的指針,一個可以查到上面對象的指針



autorelease對象在什么時機會被調(diào)用release

autorelease實際上只是把對release的調(diào)用延遲了,對于每一個Autorelease,系統(tǒng)只是把該Object放入了當前的Autorelease pool中,當該pool被釋放時,該pool中的所有Object會被調(diào)用Release。對于每一個Runloop, 系統(tǒng)會隱式創(chuàng)建一個Autorelease pool,這樣所有的release pool會構(gòu)成一個象CallStack一樣的一個棧式結(jié)構(gòu),在每一個Runloop結(jié)束時,當前棧頂?shù)腁utorelease pool會被銷毀,這樣這個pool里的每個Object(就是autorelease的對象)會被release。那什么是一個Runloop呢? 一個UI事件,Timer call, delegate call, 都會是一個新的Runloop。那什么是一個Runloop呢? 一個UI事件,Timer call, delegate call, 都會是一個新的Runloop

RunLoop和Autorelease

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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

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