一、內(nèi)存布局
- 代碼段:編譯之后的代碼
- 數(shù)據(jù)段
- 字符串常量:比如
NSString *str = @"123"
- 已初始化數(shù)據(jù):已初始化的全局變量榛鼎、靜態(tài)變量等
- 未初始化數(shù)據(jù):未初始化的全局變量硅瞧、靜態(tài)變量等
- 字符串常量:比如
- 棧: 函數(shù)調用開銷朋譬,比如局部變量笨奠。分配的內(nèi)存空間地址越來越小
- 堆:通過
alloc
、malloc
淮蜈、calloc
等動態(tài)分配的空間,分配的內(nèi)存空間地址越來越大
/**
字符串常量
str=0x10dfa0068
已初始化的全局變量已卷、靜態(tài)變量
&a =0x10dfa0db8
&c =0x10dfa0dbc
未初始化的全局變量梧田、靜態(tài)變量
&d =0x10dfa0e80
&b =0x10dfa0e84
堆
objc=0x608000012210
棧
&f =0x7ffee1c60fe0
& = 0x7ffee1c60fe4
*/
二、自動釋放池
- 自動釋放池的主要底層數(shù)據(jù)結構是:
__AtAutoreleasePool
侧蘸、AutoreleasePoolPage
- 調用了
autorelease
的對象最終都是通過AutoreleasePoolPage
對象來管理的 - 源碼分析
- clang重寫
@autoreleasepool
- objc4源碼:
NSObject.mm
class AutoreleasePoolPage; struct AutoreleasePoolPageData { magic_t const magic; __unsafe_unretained id *next; pthread_t const thread; AutoreleasePoolPage * const parent; AutoreleasePoolPage *child; uint32_t const depth; uint32_t hiwat; };
- clang重寫
AutoreleasePoolPage的結構
- 每個
AutoreleasePoolPage
對象占用4096字節(jié)內(nèi)存裁眯,除了用來存放它內(nèi)部的成員變量,剩下的空間用來存放autorelease
對象的地址 - 所有AutoreleasePoolPage`對象通過雙向鏈表的形式連接在一起
- 調用
push
方法會將一個POOL_BOUNDARY
入棧讳癌,并且返回其存放的內(nèi)存地址 - 調用
pop
方法時傳入一個POOL_BOUNDARY
的內(nèi)存地址未状,會從最后一個入棧的對象開始發(fā)送release
消息,直到遇到這個POOL_BOUNDARY
-
id *next
指向了下一個能存放autorelease
對象地址的區(qū)域
Runloop和Autorelease
-
iOS
在主線程的Runloop
中注冊了2個Observer
- 第1個
Observer
監(jiān)聽了kCFRunLoopEntry
事件析桥,會調用objc_autoreleasePoolPush()
- 第2個
Observer
監(jiān)聽了kCFRunLoopBeforeWaiting
事件司草,會調用objc_autoreleasePoolPop()
、objc_autoreleasePoolPush()
監(jiān)聽了kCFRunLoopBeforeExit
事件泡仗,會調用objc_autoreleasePoolPop()
- 第1個
三埋虹、CADisplayLink、NSTimer使用注意
-
CADisplayLink
娩怎、NSTimer
會對target
產(chǎn)生強引用搔课,如果target
又對它們產(chǎn)生強引用,那么會引發(fā)循環(huán)引用 - 解決方案
- 使用
block
__weak typeof(self) weakSelf = self; [NSTimer scheduledTimerWithTimeInterval:2.0 repeats:YES block:^(NSTimer * _Nonnull timer) { [weakSelf test]; }];
- 使用代理對象(
NSProxy
)
+ (instancetype)proxyWithTarget:(id)target { TimerProxy *proxy = [self alloc]; proxy.target = target; return proxy; } - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector { return [self.target methodSignatureForSelector:aSelector]; } - (void)forwardInvocation:(NSInvocation *)anInvocation { [anInvocation invokeWithTarget:self.target]; } TimerProxy *proxy = [TimerProxy proxyWithTarget:self]; [NSTimer scheduledTimerWithTimeInterval:2.0 target:proxy selector:@selector(test) userInfo:nil repeats:YES];
- 使用
四截亦、GCD定時器
-
NSTimer
依賴于RunLoop
爬泥,如果RunLoop
的任務過于繁重柬讨,可能會導致NSTimer
不準時 -
GCD
的定時器會更加準時
//創(chuàng)建隊列
dispatch_queue_t queue = dispatch_get_main_queue();
//1.創(chuàng)建GCD中的定時器
/*
第一個參數(shù):創(chuàng)建source的類型 DISPATCH_SOURCE_TYPE_TIMER:定時器
第二個參數(shù):0
第三個參數(shù):0
第四個參數(shù):隊列
*/
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
//2.設置時間等
/*
第一個參數(shù):定時器對象
第二個參數(shù):DISPATCH_TIME_NOW 表示從現(xiàn)在開始計時
第三個參數(shù):間隔時間 GCD里面的時間最小單位為 納秒
第四個參數(shù):精準度(表示允許的誤差,0表示絕對精準)
*/
dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 1.0 * NSEC_PER_SEC, 0 * NSEC_PER_SEC);
//3.要調用的任務
dispatch_source_set_event_handler(timer, ^{
NSLog(@"GCD-----%@",[NSThread currentThread]);
});
//4.開始執(zhí)行
dispatch_resume(timer);
五、Tagged pointer
- 從64bit開始袍啡,iOS引入了
Tagged pointer
技術踩官,用于優(yōu)化NSNumber
、NSDate
境输、NSString
等小對象的存儲 - 在沒有使用
Tagged pointer
之前蔗牡,NSNumber
等對象需要動態(tài)分配內(nèi)存、維護引用計數(shù)等嗅剖,NSNumber
指針存儲的是堆中NSNumber
對象的地址值 - 使用
Tagged pointer
之后辩越,NSNumber
指針里面存儲的數(shù)據(jù)變成:Tag + Data,也就是將數(shù)據(jù)直接存儲在了指針中 - 當指針不夠存儲數(shù)據(jù)時信粮,才會使用動態(tài)分配內(nèi)存的方式來存儲數(shù)據(jù)
-
objc_msgSend
能識別Tagged pointer
黔攒,比如NSNumber
的intValue
方法,直接從指針提取數(shù)據(jù)强缘,節(jié)省了以前的調用開銷 - 如何判斷一個指針是否為
Tagged pointer
- iOS平臺亏钩,最高有效位是1(第64bit)
- Mac平臺,最低有效位是1
- 判斷是否為Tagged Pointer
static inline bool
_objc_isTaggedPointer(const void * _Nullable ptr)
{
return ((uintptr_t)ptr & _OBJC_TAG_MASK) == _OBJC_TAG_MASK;
}
#if (TARGET_OS_OSX || TARGET_OS_IOSMAC) && __x86_64__
// 64-bit Mac - tag bit is LSB
# define OBJC_MSB_TAGGED_POINTERS 0
#else
// Everything else - tag bit is MSB
# define OBJC_MSB_TAGGED_POINTERS 1
#endif
#if OBJC_MSB_TAGGED_POINTERS
# define _OBJC_TAG_MASK (1UL<<63)
#else
# define _OBJC_TAG_MASK 1UL
#endif
注:面試題
- 思考以下兩端代碼能發(fā)生什么事欺旧,有什么區(qū)別
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"];
});
}
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"];
});
}
注:可能涉及的面試題
- 使用CADisplayLink姑丑、NSTimer有什么注意點
- 介紹下內(nèi)存的幾大區(qū)域
- 講一下你對iOS內(nèi)存管理的理解
- autorelease在什么時機會被釋放
- 方法里有局部對象,出了方法后會立即釋放嗎
- ARC都幫我們做了什么
- weak指針的實現(xiàn)原理
上一篇:
OC底層基礎:多線程GCD
下一篇:
[OC底層基礎]