在一個4G
內存的移動設備中,內核區約佔1GB
。 內存分區:代碼段、數據段、BSS段,棧區,堆區。棧區地址通常爲0x7
開頭,堆區地址通常爲0x6
開頭。數據段通常0x1
開頭。c++
0x70000000
對其進行轉換,恰好爲3GB
面試
block copy
,靈活方便,數據適應面普遍,棧的內存是訪問寄存器直接訪問其內存空間,堆裏的對象的訪問是經過存在棧區的指針存儲的地址,再找到堆區對應的地址。安全
Block
中能夠修改全局變量。bash
在LGPerson
中,定義一個全局變量personNum
,並定義兩個方法,對全局變量++
,而後在ViewController
調用打印,結果是什麼樣的?多線程
static int personNum = 100;
NS_ASSUME_NONNULL_BEGIN
@interface LGPerson : NSObject
- (void)run;
+ (void)eat;
@end
#import "LGPerson.h"
@implementation LGPerson
- (void)run{
personNum ++;
NSLog(@"LGPerson內部:%@-%p--%d",self,&personNum,personNum);
}
+ (void)eat{
personNum ++;
NSLog(@"LGPerson內部:%@-%p--%d",self,&personNum,personNum);
}
- (NSString *)description{
return @"";
}
@end
NSLog(@"vc:%p--%d",&personNum,personNum); // 100
personNum = 10000;
NSLog(@"vc:%p--%d",&personNum,personNum); // 10000
[[LGPerson new] run]; // 100 + 1 = 101
NSLog(@"vc:%p--%d",&personNum,personNum); // 10000
[LGPerson eat]; // 102
NSLog(@"vc:%p--%d",&personNum,personNum); // 10000
[[LGPerson alloc] cate_method];
複製代碼
打印結果以下: 架構
static
修飾的靜態變量,只針對文件有效。在vc中和LGPerson中的兩個全局變量的地址不相同。
使用TaggedPointer存儲小對象NSNumber、NSDate
,優化內存管理。app
首先,看下面的代碼,能正常執行麼?點擊屏幕時會有什麼問題?less
- (void)taggedPointerDemo {
self.queue = dispatch_queue_create("com.cooci.cn", DISPATCH_QUEUE_CONCURRENT);
for (int i = 0; i<10000; i++) {
dispatch_async(self.queue, ^{
self.nameStr = [NSString stringWithFormat:@"cooci"];
NSLog(@"%@",self.nameStr);
});
}
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
NSLog(@"來了");
for (int i = 0; i<10000; i++) {
dispatch_async(self.queue, ^{
self.nameStr = [NSString stringWithFormat:@"和諧學習不急不躁"];
NSLog(@"%@",self.nameStr);
});
}
}
複製代碼
經過測試,上述代碼,能正常執行,當點擊屏幕時,發生崩潰。那麼爲何在點擊屏幕是,會發生崩潰呢?dom
其實在多線程代碼塊中賦值,打印,是調用的setter
和getter
,當setter
和getter
加入多線程時,就會不安全。async
在setter
方法底層是retian newvalue
,而後realase oldvalue
。多加入多線程時,就會出現屢次釋放,形成野指針。
那麼,爲何第一段可以正常執行呢?
經過上面的斷點調試,發現第一段代碼中_nameStr
的類型並非NSString
而是taggedPointer
類型,而第二段中是NSString
類型。
接下來看一下objc_release
和objc_retain
的源碼:
void
objc_release(id obj)
{
if (!obj) return;
if (obj->isTaggedPointer()) return;
return obj->release();
}
objc_retain(id obj)
{
if (!obj) return obj;
if (obj->isTaggedPointer()) return obj;
return obj->retain();
}
複製代碼
在release
時,先判斷是不是isTaggedPointer
,是,則直接返回,並無真的進行release
操做,而在retain
時也是一樣的操做,先判斷isTaggedPointer
,並無進行retain
,這也就解釋了爲何第一段代碼能正常執行,由於其底層並無retain
和release
,即便搭配多線程,也不會出現屢次釋放的問題,也就不會出現野指針,也不會崩潰。
其實在read_images
中的的initializeTaggedPointerObfuscator()
中,會初始化一個objc_debug_taggedpointer_obfuscator
,在構造TaggedPointer
時,經過對這個值的^
操做,進行編碼和解碼。
static void
initializeTaggedPointerObfuscator(void)
{
if (sdkIsOlderThan(10_14, 12_0, 12_0, 5_0, 3_0) ||
// Set the obfuscator to zero for apps linked against older SDKs,
// in case they're relying on the tagged pointer representation. DisableTaggedPointerObfuscation) { objc_debug_taggedpointer_obfuscator = 0; } else { // Pull random data into the variable, then shift away all non-payload bits. arc4random_buf(&objc_debug_taggedpointer_obfuscator, sizeof(objc_debug_taggedpointer_obfuscator)); objc_debug_taggedpointer_obfuscator &= ~_OBJC_TAG_MASK; } } 複製代碼
在initializeTaggedPointerObfuscator
中,在iOS以前低版本時,objc_debug_taggedpointer_obfuscator = 0,以後的版本objc_debug_taggedpointer_obfuscator &= ~_OBJC_TAG_MASK
在構建TaggedPointer
時,會進行編碼,在獲取TaggedPointer
時,解碼。
_objc_makeTaggedPointer(objc_tag_index_t tag, uintptr_t value)
{
// PAYLOAD_LSHIFT and PAYLOAD_RSHIFT are the payload extraction shifts.
// They are reversed here for payload insertion.
// ASSERT(_objc_taggedPointersEnabled());
if (tag <= OBJC_TAG_Last60BitPayload) {
// ASSERT(((value << _OBJC_TAG_PAYLOAD_RSHIFT) >> _OBJC_TAG_PAYLOAD_LSHIFT) == value);
uintptr_t result =
(_OBJC_TAG_MASK |
((uintptr_t)tag << _OBJC_TAG_INDEX_SHIFT) |
((value << _OBJC_TAG_PAYLOAD_RSHIFT) >> _OBJC_TAG_PAYLOAD_LSHIFT));
// ✅ 返回一個編碼的值
return _objc_encodeTaggedPointer(result);
} else {
// ASSERT(tag >= OBJC_TAG_First52BitPayload);
// ASSERT(tag <= OBJC_TAG_Last52BitPayload);
// ASSERT(((value << _OBJC_TAG_EXT_PAYLOAD_RSHIFT) >> _OBJC_TAG_EXT_PAYLOAD_LSHIFT) == value);
uintptr_t result =
(_OBJC_TAG_EXT_MASK |
((uintptr_t)(tag - OBJC_TAG_First52BitPayload) << _OBJC_TAG_EXT_INDEX_SHIFT) |
((value << _OBJC_TAG_EXT_PAYLOAD_RSHIFT) >> _OBJC_TAG_EXT_PAYLOAD_LSHIFT));
return _objc_encodeTaggedPointer(result);
}
}
_objc_getTaggedPointerTag(const void * _Nullable ptr)
{
// ASSERT(_objc_isTaggedPointer(ptr));
// ✅ 解碼,而後進行一系列偏移運算,返回
uintptr_t value = _objc_decodeTaggedPointer(ptr);
uintptr_t basicTag = (value >> _OBJC_TAG_INDEX_SHIFT) & _OBJC_TAG_INDEX_MASK;
uintptr_t extTag = (value >> _OBJC_TAG_EXT_INDEX_SHIFT) & _OBJC_TAG_EXT_INDEX_MASK;
if (basicTag == _OBJC_TAG_INDEX_MASK) {
return (objc_tag_index_t)(extTag + OBJC_TAG_First52BitPayload);
} else {
return (objc_tag_index_t)basicTag;
}
}
複製代碼
其實編碼和解碼的操做就是與上objc_debug_taggedpointer_obfuscator
_objc_encodeTaggedPointer(uintptr_t ptr)
{
return (void *)(objc_debug_taggedpointer_obfuscator ^ ptr);
}
/**
1000 0001
^0001 1000
1001 1001
^0001 1000
1000 0001
*/
static inline uintptr_t
_objc_decodeTaggedPointer(const void * _Nullable ptr)
{
return (uintptr_t)ptr ^ objc_debug_taggedpointer_obfuscator;
}
複製代碼
咱們能夠調用解碼方法,來打印一下具體的TaggedPointer
:
NSString *str1 = [NSString stringWithFormat:@"a"];
NSString *str2 = [NSString stringWithFormat:@"b"];
NSLog(@"%p-%@",str1,str1);
NSLog(@"%p-%@",str2,str2);
NSLog(@"0x%lx",_objc_decodeTaggedPointer_(str2));
NSNumber *number1 = @1;
NSNumber *number2 = @1;
NSNumber *number3 = @2.0;
NSNumber *number4 = @3.2;
NSLog(@"%@-%p-%@ - 0x%lx",object_getClass(number1),number1,number1,_objc_decodeTaggedPointer_(number1));
NSLog(@"0x%lx",_objc_decodeTaggedPointer_(number2));
NSLog(@"0x%lx",_objc_decodeTaggedPointer_(number3));
NSLog(@"0x%lx",_objc_decodeTaggedPointer_(number4));
複製代碼
打印結果:
上圖打印結果中,0xb000000000000012
,b
表示數字,1
就是變量的值。
不一樣類型的標記:
{
// 60-bit payloads
OBJC_TAG_NSAtom = 0,
OBJC_TAG_1 = 1,
OBJC_TAG_NSString = 2,
OBJC_TAG_NSNumber = 3,
OBJC_TAG_NSIndexPath = 4,
OBJC_TAG_NSManagedObjectID = 5,
OBJC_TAG_NSDate = 6,
// 60-bit reserved
OBJC_TAG_RESERVED_7 = 7,
// 52-bit payloads
OBJC_TAG_Photos_1 = 8,
OBJC_TAG_Photos_2 = 9,
OBJC_TAG_Photos_3 = 10,
OBJC_TAG_Photos_4 = 11,
OBJC_TAG_XPC_1 = 12,
OBJC_TAG_XPC_2 = 13,
OBJC_TAG_XPC_3 = 14,
OBJC_TAG_XPC_4 = 15,
OBJC_TAG_NSColor = 16,
OBJC_TAG_UIColor = 17,
OBJC_TAG_CGColor = 18,
OBJC_TAG_NSIndexSet = 19,
OBJC_TAG_First60BitPayload = 0,
OBJC_TAG_Last60BitPayload = 6,
OBJC_TAG_First52BitPayload = 8,
OBJC_TAG_Last52BitPayload = 263,
OBJC_TAG_RESERVED_264 = 264
};
複製代碼
Tagged Pointer
指針的值再也不是地址了,而是真正的值。因此,實際上它再也不 是一個對象了,它只是一個披着對象皮的普通變量而已。因此,它的內存並不存儲 在堆中,也不須要malloc
和free
,也不用retain
和release
在內存讀取上有着3倍
的效率,建立時比之前快106倍
,通常一個變量的位數在8-10位時,系統默認會使用Tagged Pointer
經過對NONPOINTER_ISA
64個字節位置的存儲,來內存管理。 isa
結構:
union isa_t {
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls;
uintptr_t bits;
#if defined(ISA_BITFIELD)
struct {
ISA_BITFIELD; // defined in isa.h
};
#endif
};
複製代碼
nonpointer:表示是否對 isa
指針開啓指針優化
0:純isa指針,1:不止是類對象地址,isa
中包含了類信息、對象的引用計數當對象引用技術大於 10 時,則須要借用該變量存儲進位等
has_assoc:關聯對象標誌位,0沒有,1存在
has_cxx_dtor:該對象是否有 C++
或者 Objc
的析構器,若是有析構函數,則須要作析構邏輯, 若是沒有,則能夠更快的釋放對象
shiftcls:存儲類指針的值。開啓指針優化的狀況下,在 arm64 架構中有 33 位用來存儲類指針。
magic :用於調試器判斷當前對象是真的對象仍是沒有初始化的空間
weakly_referenced:標誌對象是否被指向或者曾經指向一個 ARC 的弱變量, 沒有弱引用的對象能夠更快釋放
deallocating:標誌對象是否正在釋放
has_sidetable_rc:當對象引用技術大於 10 時,則須要借用該變量存儲進位
extra_rc:當表示該對象的引用計數值,其實是引用計數值減 1, 例如,若是對象的引用計數爲 10,那麼 extra_rc
爲 9。若是引用計數大於 10, 則須要使用到下面的 has_sidetable_rc
。
首先咱們看一下retain
的源碼:
objc_retain(id obj)
{
if (!obj) return obj;
if (obj->isTaggedPointer()) return obj;
return obj->retain();
}
複製代碼
從源碼中能夠看出,在retain
時,先判斷是不是isTaggedPointer
,是則直接返回,不是,則開始retain
。
最終進入到rootRetain
方法中。
objc_object::rootRetain(bool tryRetain, bool handleOverflow)
{
if (isTaggedPointer()) return (id)this;
bool sideTableLocked = false;
bool transcribeToSideTable = false;
isa_t oldisa;
isa_t newisa;
// retain 引用計數處理
//
do {
transcribeToSideTable = false;
oldisa = LoadExclusive(&isa.bits);
newisa = oldisa;
// ✅ 判斷不是nonpointer,散列表的引用計數表 進行處理 ++
if (slowpath(!newisa.nonpointer)) {
ClearExclusive(&isa.bits);
if (rawISA()->isMetaClass()) return (id)this;
// 在散列表中存儲引用計數++
if (!tryRetain && sideTableLocked) sidetable_unlock();
if (tryRetain) return sidetable_tryRetain() ? (id)this : nil;
else return sidetable_retain();
}
// don't check newisa.fast_rr; we already called any RR overrides // ✅ 判斷是否正在析構 if (slowpath(tryRetain && newisa.deallocating)) { ClearExclusive(&isa.bits); if (!tryRetain && sideTableLocked) sidetable_unlock(); return nil; } // ✅ isa 中extra_rc++ uintptr_t carry; newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry); // extra_rc++ if (slowpath(carry)) { // newisa.extra_rc++ overflowed if (!handleOverflow) { ClearExclusive(&isa.bits); return rootRetain_overflow(tryRetain); } // Leave half of the retain counts inline and // prepare to copy the other half to the side table. if (!tryRetain && !sideTableLocked) sidetable_lock(); sideTableLocked = true; transcribeToSideTable = true; newisa.extra_rc = RC_HALF; newisa.has_sidetable_rc = true; } } while (slowpath(!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits))); if (slowpath(transcribeToSideTable)) { // Copy the other half of the retain counts to the side table. sidetable_addExtraRC_nolock(RC_HALF); } if (slowpath(!tryRetain && sideTableLocked)) sidetable_unlock(); return (id)this; } 複製代碼
在rootRetain
中,對引用計數進行處理,先獲取isa.bits
,
nonpointer isa
,若是不是nonpointer
,在散列表中對引用計數進行++
(先sidetable_unlock
,在sidetable_retain()
)。在對散列邊解鎖時(sidetable_unlock
),
SideTables()
中(從
安全和性能的角度考慮,
sidetable
有多張表),找到對應的散列表(底層是哈希結構,經過下標快速查找到對應
table
),進行開鎖。而後
sidetable_retain()
。
在sidetable_retain
時,
objc_object::sidetable_retain()
{
#if SUPPORT_NONPOINTER_ISA
ASSERT(!isa.nonpointer);
#endif
SideTable& table = SideTables()[this];
table.lock();
size_t& refcntStorage = table.refcnts[this];
if (! (refcntStorage & SIDE_TABLE_RC_PINNED)) {
refcntStorage += SIDE_TABLE_RC_ONE;
}
table.unlock();
return (id)this;
}
複製代碼
先加鎖,而後存儲引用計數++(refcntStorage += 往左偏移兩位
),而後再解鎖。
爲何是偏移兩位呢?
判斷是否是正在析構
判斷是nonpointer isa
,則addc(newisa.bits, RC_ONE, 0, &carry)
。對isa
中的extra_rc++
。
其實就是平移RC_ONE(在真機上是56位)位
,找到extra_rc
(nonpointer isa
的引用計數存在這個地方),對其進行++
。
當時nonpointer isa
時,會判斷是不是slowpath(carry)
,即:extra_rc
的引用計數是否存滿,
isa
中的引用計數砍去一半,而後修改isa
中引進計數借位標識,而後將另外一半的引用計數存儲到散列表將通常的引用計數存儲到散列表中,以下:
sidetable_addExtraRC_nolock
源碼以下:
objc_object::sidetable_addExtraRC_nolock(size_t delta_rc)
{
ASSERT(isa.nonpointer);
SideTable& table = SideTables()[this];
size_t& refcntStorage = table.refcnts[this];
size_t oldRefcnt = refcntStorage;
// isa-side bits should not be set here
ASSERT((oldRefcnt & SIDE_TABLE_DEALLOCATING) == 0);
ASSERT((oldRefcnt & SIDE_TABLE_WEAKLY_REFERENCED) == 0);
if (oldRefcnt & SIDE_TABLE_RC_PINNED) return true;
uintptr_t carry;
size_t newRefcnt =
addc(oldRefcnt, delta_rc << SIDE_TABLE_RC_SHIFT, 0, &carry);
if (carry) {
refcntStorage =
SIDE_TABLE_RC_PINNED | (oldRefcnt & SIDE_TABLE_FLAG_MASK);
return true;
}
else {
refcntStorage = newRefcnt;
return false;
}
}
複製代碼
分析完了retain
,那麼release
就相對的比較簡單了,最終也會進入到rootRelease
方法,先查看源碼:
objc_object::rootRelease(bool performDealloc, bool handleUnderflow)
{
// ✅1. 判斷TaggedPointer
if (isTaggedPointer()) return false;
bool sideTableLocked = false;
isa_t oldisa;
isa_t newisa;
retry:
do {
oldisa = LoadExclusive(&isa.bits);
newisa = oldisa;
// ✅2. 判斷是nonpointer,當不是nonpointer時,獲取對應散列表,對引用計數表--
if (slowpath(!newisa.nonpointer)) {
ClearExclusive(&isa.bits);
if (rawISA()->isMetaClass()) return false;
if (sideTableLocked) sidetable_unlock();
return sidetable_release(performDealloc);
}
// don't check newisa.fast_rr; we already called any RR overrides // ✅3.是nonpointer,對extra_rc-- uintptr_t carry; newisa.bits = subc(newisa.bits, RC_ONE, 0, &carry); // extra_rc-- // ✅4.當減到必定程度時,直接underflow if (slowpath(carry)) { // don't ClearExclusive()
goto underflow;
}
} while (slowpath(!StoreReleaseExclusive(&isa.bits,
oldisa.bits, newisa.bits)));
if (slowpath(sideTableLocked)) sidetable_unlock();
return false;
underflow:
// newisa.extra_rc-- underflowed: borrow from side table or deallocate
// abandon newisa to undo the decrement
newisa = oldisa;
// ✅判斷是否借用引用計數標誌,
if (slowpath(newisa.has_sidetable_rc)) {
if (!handleUnderflow) {
ClearExclusive(&isa.bits);
return rootRelease_underflow(performDealloc);
}
// Transfer retain count from side table to inline storage.
// 操做散列表
if (!sideTableLocked) {
ClearExclusive(&isa.bits);
sidetable_lock();
sideTableLocked = true;
// Need to start over to avoid a race against
// the nonpointer -> raw pointer transition.
goto retry;
}
// Try to remove some retain counts from the side table.
// ✅ 將引用計數表中的引用計數,移到extra_rc中
size_t borrowed = sidetable_subExtraRC_nolock(RC_HALF);
// To avoid races, has_sidetable_rc must remain set
// even if the side table count is now zero.
// ✅對移出來的引用計數大於0時
if (borrowed > 0) {
// Side table retain count decreased.
// Try to add them to the inline count.
newisa.extra_rc = borrowed - 1; // redo the original decrement too
bool stored = StoreReleaseExclusive(&isa.bits,
oldisa.bits, newisa.bits);
// ✅將移出來的引用計數加到extra_rc中。
if (!stored) {
// Inline update failed.
// Try it again right now. This prevents livelock on LL/SC
// architectures where the side table access itself may have
// dropped the reservation.
isa_t oldisa2 = LoadExclusive(&isa.bits);
isa_t newisa2 = oldisa2;
if (newisa2.nonpointer) {
uintptr_t overflow;
newisa2.bits =
addc(newisa2.bits, RC_ONE * (borrowed-1), 0, &overflow);
if (!overflow) {
stored = StoreReleaseExclusive(&isa.bits, oldisa2.bits,
newisa2.bits);
}
}
}
if (!stored) {
// Inline update failed.
// Put the retains back in the side table.
sidetable_addExtraRC_nolock(borrowed);
goto retry;
}
// Decrement successful after borrowing from side table.
// This decrement cannot be the deallocating decrement - the side
// table lock and has_sidetable_rc bit ensure that if everyone
// else tried to -release while we worked, the last one would block.
sidetable_unlock();
return false;
}
else {
// Side table is empty after all. Fall-through to the dealloc path.
}
}
// Really deallocate.
if (slowpath(newisa.deallocating)) {
ClearExclusive(&isa.bits);
if (sideTableLocked) sidetable_unlock();
return overrelease_error();
// does not actually return
}
newisa.deallocating = true;
if (!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)) goto retry;
if (slowpath(sideTableLocked)) sidetable_unlock();
__c11_atomic_thread_fence(__ATOMIC_ACQUIRE);
if (performDealloc) {
((void(*)(objc_object *, SEL))objc_msgSend)(this, @selector(dealloc));
}
return true;
}
複製代碼
在rootRelease
中,先判斷TaggedPointer
,而後判斷是否nonpointer isa
,當不是nonpointer
時,獲取對應散列表,對引用計數表--,當時nonpointer isa
時,對extra_rc--
,當減到必定程度時,直接調用underflow
,判斷引用計數借用標識,將暫存到引用計數表中的引用計數,存到extra_rc
中。
小結:
retain
1. 判斷是不是nonpointer isa,不是,則對散列表處理,對引用計數表處理
2. 是nonpointer isa,對extra_rc++
3. 超出時,將extra_rc中的一半存在儲到引用計數表中
爲何超出時不所有存在引用計數表中?
散列表要開鎖解鎖,優先選擇extra_rc
release
1. 先判斷TaggedPointer,
2. 判斷是否nonpointer isa,當不是nonpointer時,獲取對應散列表,對引用計數表--,
3. 當時 nonpointer isa 時,對 extra_rc--
4. 當引用計數減到必定程度時,直接調用 underflow
5. underflow中,判斷引用計數借用標識,將暫存到引用計數表中的引用計數,存到 extra_rc 中。
複製代碼
首先咱們看下面的一個問題:
NSObject *objc = [NSObject alloc];
NSLog(@"%ld",CFGetRetainCount((__bridge CFTypeRef)objc));
複製代碼
問:上面的代碼,引用計數打印是多少?alloc
出來的對象,引用計數是多少?
這個打印結果,咱們都知道是1
,那麼alloc
出來的對象引用計數真的是1
麼?
接下來,咱們深刻看一下RetainCount
的源碼,以下:
inline uintptr_t
objc_object::rootRetainCount() // 1
{
if (isTaggedPointer()) return (uintptr_t)this;
sidetable_lock();
isa_t bits = LoadExclusive(&isa.bits);
ClearExclusive(&isa.bits);
if (bits.nonpointer) {
// bits.extra_rc = 0;
// ✅ 對isa 的 bits.extra_rc + 1,即對引用計數+1
uintptr_t rc = 1 + bits.extra_rc; // isa
if (bits.has_sidetable_rc) {
rc += sidetable_getExtraRC_nolock(); // 散列表
}
sidetable_unlock();
return rc; // 1
}
sidetable_unlock();
return sidetable_retainCount();
}
複製代碼
經過源碼的斷點調試,發現alloc
出來的對象的引用計數extra_rc
爲0
,而經過retainCount
打印出來的引用計數爲1
,是經過uintptr_t rc = 1 + bits.extra_rc
得來的,而單純alloc
出來的對象的引用計數爲0
,默認給1
,防止被錯誤釋放
在dealloc
底層,必然會調用rootDealloc()
方法,源碼以下:
inline void
objc_object::rootDealloc()
{
// ✅ 判斷是TaggedPointer,直接返回,不須要dealloc
if (isTaggedPointer()) return; // fixme necessary?
// ✅ 判斷是否是nonponiter isa,不是則直接free
if (fastpath(isa.nonpointer &&
!isa.weakly_referenced &&
!isa.has_assoc &&
!isa.has_cxx_dtor &&
!isa.has_sidetable_rc))
{
assert(!sidetable_present());
free(this);
}
else {
// ✅ 是nonponinter isa
object_dispose((id)this);
}
}
複製代碼
在rootDealloc
中,會講過上面的幾個判斷,當不是nonponinter isa
時,調用object_dispose((id)this)
方法。
在object_dispose
中,經過objc_destructInstance(obj)
對cxx和關聯對象進行釋放,而後經過obj->clearDeallocating()
對weak表
和引用計數表
object_dispose(id obj)
{
if (!obj) return nil;
// weak
// cxx
// 關聯對象
// ISA 64
objc_destructInstance(obj);
free(obj);
return nil;
}
void *objc_destructInstance(id obj)
{
if (obj) {
// Read all of the flags at once for performance.
bool cxx = obj->hasCxxDtor();
bool assoc = obj->hasAssociatedObjects();
// This order is important.
if (cxx) object_cxxDestruct(obj);
// 移除關聯對象
if (assoc) _object_remove_assocations(obj);
obj->clearDeallocating();
}
return obj;
}
複製代碼
清除weak表
和引用計數表
。
objc_object::clearDeallocating_slow()
{
ASSERT(isa.nonpointer && (isa.weakly_referenced || isa.has_sidetable_rc));
SideTable& table = SideTables()[this];
table.lock();
// 清除weak表
if (isa.weakly_referenced) {
weak_clear_no_lock(&table.weak_table, (id)this);
}
if (isa.has_sidetable_rc) { // 清理引用計數表
table.refcnts.erase(this);
}
table.unlock();
}
複製代碼
小結:
dealloc 底層調用 rootDealloc,
rootDealloc 判斷 aggedPointer 直接返回,而後判斷不是nonponiter isa 則直接釋放
判斷是nonponiter isa則調用 object_dispose 開始釋放
在 object_dispose 中,對cxx、關聯對象表、weak表和引用計數表進行釋放。
複製代碼
在開發中,咱們常常會使用NSTimer
定時器,以下:
// 定義屬性
@property (nonatomic, strong) NSTimer *timer;
// 初始化
self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(fireHome) userInfo:nil repeats:YES];
- (void)fireHome{
num++;
NSLog(@"hello word - %d",num);
}
// 釋放
- (void)dealloc{
[self.timer invalidate];
self.timer = nil;
NSLog(@"%s",__func__);
}
複製代碼
首先在VC
中定義timer
屬性,在dealloc
中,調用[self.timer invalidate] self.timer = nil
,對timer
進行析構和置空。
當咱們把dealloc
中的代碼去掉,當頻繁的push
和pop
頁面時,就會出現問題,而形成問題的緣由是循環引用。
首先VC
對timer
是強持有,timer
對target
屬性強持有,這樣就形成了循環引用。
那麼怎麼解決這個循環引用呢?
按照一般的處理方法,就是使用weakSelf
,即下面的方式:
self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:weakSelf selector:@selector(fireHome) userInfo:nil repeats:YES];
複製代碼
經過測試,發現並無打破self -> block -> self
的循環引用。那麼爲何呢?
由於,NSTimer
依賴於 NSRunLoop
,timer
須要加到NSRunLoop
才能運行,NSRunLoop
對NSTimer
強持有,timer
對self
或者weakself
,強持有,timer
沒法釋放,致使weakself
沒法釋放,self
沒法釋放。
那麼爲何block
的循環引用中,使用weakself
能夠解決,爲何這個不能釋放呢?
__weak typeof(self) weakSelf = self;
複製代碼
經過打印weakSelf
和self
的地址以下:
經過上面打印發現weakSelf
和self
是兩個不一樣的地址。通過weakSelf 弱引用
後,並無對引用計數處理。
NSRunLoop
-> timer
-> weakSelf
-> self
,weakSelf
間接操做self
,間接強持有了self
,因此沒法釋放
而block
解決循環引用時,使用weakSelf
,實際上是一個臨時變量的指針地址,block
強持有的是一個新的指針地址。因此打破了循環引用的問題。 self
-> block
-> weakSelf
(臨時變量的指針地址)
block
的循環引用操做的是對象地址,timer
循環引用操做的是對象。
pop
的時候就銷燬timer
- (void)didMoveToParentViewController:(UIViewController *)parent{
// 不管push 進來 仍是 pop 出去 正常跑
// 就算繼續push 到下一層 pop 回去仍是繼續
if (parent == nil) {
[self.timer invalidate];
self.timer = nil;
NSLog(@"timer 走了");
}
}
複製代碼
定義一個self.target = [[NSObject alloc] init]
,讓其做爲target
來響應。
self.target = [[NSObject alloc] init];
class_addMethod([NSObject class], @selector(fireHome), (IMP)fireHomeObjc, "v@:");
self.timer = [NSTimer scheduledTimerWvoid fireHomeObjc(id obj){
NSLog(@"%s -- %@",__func__,obj);
}
- (void)fireHome{
num++;
NSLog(@"hello word - %d",num);
}
ithTimeInterval:1 target:self.target selector:@selector(fireHome) userInfo:nil repeats:YES];
複製代碼
@interface LGTimerWapper : NSObject
- (instancetype)lg_initWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo;
- (void)lg_invalidate;
@end
#import "LGTimerWapper.h"
#import <objc/message.h>
@interface LGTimerWapper()
@property (nonatomic, weak) id target;
@property (nonatomic, assign) SEL aSelector;
@property (nonatomic, strong) NSTimer *timer;
@end
@implementation LGTimerWapper
- (instancetype)lg_initWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo{
if (self == [super init]) {
self.target = aTarget; // vc
self.aSelector = aSelector; // 方法 -- vc 釋放
if ([self.target respondsToSelector:self.aSelector]) {
Method method = class_getInstanceMethod([self.target class], aSelector);
const char *type = method_getTypeEncoding(method);
class_addMethod([self class], aSelector, (IMP)fireHomeWapper, type);
// 難點: lgtimer
// 沒法響應
// 時間點: timer invalid
// vc -> lgtimerwarpper
// runloop -> timer -> lgtimerwarpper
self.timer = [NSTimer scheduledTimerWithTimeInterval:ti target:self selector:aSelector userInfo:userInfo repeats:yesOrNo];
}
}
return self;
}
// 一直跑 runloop
void fireHomeWapper(LGTimerWapper *warpper){
if (warpper.target) {
void (*lg_msgSend)(void *,SEL, id) = (void *)objc_msgSend;
lg_msgSend((__bridge void *)(warpper.target), warpper.aSelector,warpper.timer);
}else{ // warpper.target
[warpper.timer invalidate];
warpper.timer = nil;
}
}
- (void)lg_invalidate{
[self.timer invalidate];
self.timer = nil;
}
- (void)dealloc{
NSLog(@"%s",__func__);
}
@end
self.timerWapper = [[LGTimerWapper alloc] lg_initWithTimeInterval:1 target:self selector:@selector(fireHome) userInfo:nil repeats:YES];
複製代碼
proxy
虛基類的方式@interface LGProxy : NSProxy
+ (instancetype)proxyWithTransformObject:(id)object;
@end
#import "LGProxy.h"
@interface LGProxy()
@property (nonatomic, weak) id object;
@end
@implementation LGProxy
+ (instancetype)proxyWithTransformObject:(id)object{
LGProxy *proxy = [LGProxy alloc];
proxy.object = object;
return proxy;
}
// 僅僅添加了weak類型的屬性還不夠,爲了保證中間件可以響應外部self的事件,須要經過消息轉發機制,讓實際的響應target仍是外部self,這一步相當重要,主要涉及到runtime的消息機制。
// 轉移
-(id)forwardingTargetForSelector:(SEL)aSelector {
return self.object;
}
@end
self.proxy = [LGProxy proxyWithTransformObject:self];
self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self.proxy selector:@selector(fireHome) userInfo:nil repeats:YES];
複製代碼
首先,咱們來看幾道面試題目:
接下來,咱們來探索一下自動釋放池AutoreleasePool
的底層,首先在main.m
文件中寫一行代碼,以下:
經過clang -rewrite-objc main.m -o main.cpp
將其編譯成cpp
文件,查看AutoreleasePool
的底層結構。
編譯後查看main.cpp
:
AutoreleasePool
,在底層是一個
__AtAutoreleasePool
類型的結構體,以下:
struct __AtAutoreleasePool {
__AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}
~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}
void * atautoreleasepoolobj;
};
複製代碼
在__AtAutoreleasePool
結構體裏,有構造函數和析構函數。
當咱們建立__AtAutoreleasePool
這樣一個結構體時,就會調用構造函數和析構函數。
在調用構造函數時,會調用objc_autoreleasePoolPush()
,由函數名可直接定位到objc
源碼中,接着在objc
源碼中查看objc_autoreleasePoolPush()
函數的具體實現,
void *
objc_autoreleasePoolPush(void)
{
return AutoreleasePoolPage::push();
}
複製代碼
其中會經過一個AutoreleasePoolPage
來調用push()
,那麼AutoreleasePoolPage
是什麼東西呢?咱們查看一下源碼。
發現AutoreleasePoolPage
繼承自私有類AutoreleasePoolPageData
/***********************************************************************
Autorelease pool implementation
// 先進後出
A thread's autorelease pool is a stack of pointers. Each pointer is either an object to release, or POOL_BOUNDARY which is an autorelease pool boundary. A pool token is a pointer to the POOL_BOUNDARY for that pool. When the pool is popped, every object hotter than the sentinel is released. The stack is divided into a doubly-linked list of pages. Pages are added and deleted as necessary. Thread-local storage points to the hot page, where newly autoreleased objects are stored. **********************************************************************/ BREAKPOINT_FUNCTION(void objc_autoreleaseNoPool(id obj)); BREAKPOINT_FUNCTION(void objc_autoreleasePoolInvalid(const void *token)); class AutoreleasePoolPage : private AutoreleasePoolPageData { friend struct thread_data_t; ... } 複製代碼
從上面的註釋能夠看出Autorelease池
是實現:
線程的自動釋放池是指針的堆棧。每一個指針要麼是要釋放的對象,要麼是要釋放的POOL_BOUNDARY
(自動釋放池邊界能夠理解爲哨兵,釋放到這個位置,就到邊界了,釋放完了)。
池標記是指向該池的POOL_BOUNDARY
的指針。當池被彈出,每個比哨兵更熱的物體被釋放。
該堆棧被劃分爲一個頁面的雙連接列表。頁面在必要時進行添加和刪除。
新的自動釋放的對象,存儲在聚焦頁(hot Page
)
而AutoreleasePoolPageData
的具體源碼實現以下:
class AutoreleasePoolPage;
struct AutoreleasePoolPageData
{
magic_t const magic; // 16
__unsafe_unretained id *next; //8
pthread_t const thread; // 8
AutoreleasePoolPage * const parent; //8
AutoreleasePoolPage *child; //8
uint32_t const depth; // 4
uint32_t hiwat; // 4
AutoreleasePoolPageData(__unsafe_unretained id* _next, pthread_t _thread, AutoreleasePoolPage* _parent, uint32_t _depth, uint32_t _hiwat)
: magic(), next(_next), thread(_thread),
parent(_parent), child(nil),
depth(_depth), hiwat(_hiwat)
{
}
};
複製代碼
上面的代碼中能夠看出,在AutoreleasePoolPageData
中有一系列的屬性。那麼全部AutoreleasePool
的對象都有上面的屬性。
每一個屬性的含義:
magic
:用來校驗AutoreleasePoolPage
的結構是否完成next
:指向新添加的autoreleased
對象的下一個位置,初始化時指向begin()
thread
:指向當前線程parent
:指向父結點,第一個結點的parent
值爲nil
child
:指向子結點,最後一個結點的子結點值爲nil
depth
:表明深度,從0
開始,日後遞增1
hiwat
:表明 high water mark
最大入棧數量標記而屬性parent
和child
證實了上面註釋所解釋的自動釋放池是雙向鏈表結構。
在構建AutoreleasePoolPage
時,會調用AutoreleasePoolPageData
的構建函數,傳入參數,以下:
傳入的第一個參數begin()
,實現以下:
那麼爲何要this+sizeof(*this)
呢?經過斷點調試,並打印sizeof(*this)
的值,如上圖,爲56
。
那麼爲何要 +56呢?
實際上是恰好往下偏移了AutoreleasePoolPageData
屬性所佔的空間(AutoreleasePoolPageData
的屬性中next、thread、parent、child
都是指針類型,佔8字節,depth、hiwat
各佔4字節,magic
是一個結構體,所佔內存是由結構體內部屬性決定,因此佔4*4
個字節,屬性共佔56字節),開始存儲autoreleased對象
。
咱們能夠經過打印自動釋放池,來驗證一下,在main()
中寫一下代碼:
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
// 1 + 504 + 505 + 505
for (int i = 0; i < 5; i++) {
NSObject *objc = [[NSObject alloc] autorelease];
NSLog(@"objc = %@",objc);
}
_objc_autoreleasePoolPrint();
}
return 0;
}
複製代碼
打印結果以下:
6個釋放對象
,包括循環建立加入的
5個
和一個
邊界哨兵對象
,而從
0x101803000
到
0x101803038
恰好是
56字節
,正是
AutoreleasePoolPageData
的屬性,進而驗證了上面的推測。
自動釋放池是否是能無限添加對象呢?
咱們對上面的循環進行修改,循環505次
:
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
// 1 + 504 + 505 + 505
for (int i = 0; i < 505; i++) {
NSObject *objc = [[NSObject alloc] autorelease];
// NSLog(@"objc = %@",objc);
}
_objc_autoreleasePoolPrint();
}
return 0;
}
複製代碼
打印結果:
從打印結果看出,有506個對象
,其進行了分頁,第一個page(full)
,已存儲滿,而最後一個對象存儲在page(hot)
,
因而可知,AutoreleasePool
每一頁恰好存儲505個8字節
的對象,而第一頁存儲的是1(邊界對象)+504(添加到釋放池的對象)
經過查看AutoreleasePoolPage
源碼,
最終獲得size = 4096
,減去屬性
所佔的56個字節
,恰好是505個8字節對象
。
因此,自動釋放池AutoreleasePool第一頁存儲504個8字節對象,其餘頁505個8字節對象
當自動釋放池建立進行析構時,會調用push()
,
push()
函數源碼
static inline void *push()
{
id *dest;
if (slowpath(DebugPoolAllocation)) {
// Each autorelease pool starts on a new pool page.
dest = autoreleaseNewPage(POOL_BOUNDARY);
} else {
dest = autoreleaseFast(POOL_BOUNDARY);
}
ASSERT(dest == EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY);
return dest;
}
複製代碼
在push()
中,進入autoreleaseFast(POOL_BOUNDARY)
函數,源碼以下:
static inline id *autoreleaseFast(id obj)
{
AutoreleasePoolPage *page = hotPage();
// ✅ 1. 判斷page存在&非滿的狀態
if (page && !page->full()) {
return page->add(obj);
} else if (page) { // ✅ 判斷page滿了
return autoreleaseFullPage(obj, page);
} else { // ✅ 沒有page
return autoreleaseNoPage(obj);
}
}
複製代碼
page
存在而且非滿的狀態
的狀況下,將對象添加的page
;id *add(id obj)
{
ASSERT(!full());
unprotect();
id *ret = next; // faster than `return next-1` because of aliasing
*next++ = obj;
protect();
return ret;
}
複製代碼
獲取next指針
,將要添加的obj
存儲到next指針
指向的位置,而後next指針
往下偏移8字節
,準備下一次存儲。
page
已經滿的狀況,調用autoreleaseFullPage()
id *autoreleaseFullPage(id obj, AutoreleasePoolPage *page)
{
// The hot page is full.
// Step to the next non-full page, adding a new page if necessary.
// Then add the object to that page.
ASSERT(page == hotPage());
ASSERT(page->full() || DebugPoolAllocation);
// ✅循環判斷page的child是否full,最後一頁的child爲nil,而後建立新的page
do {
if (page->child) page = page->child;
else page = new AutoreleasePoolPage(page);
} while (page->full());
// ✅將新的page設置爲聚焦頁,(打印時,顯示page(hot))
setHotPage(page);
return page->add(obj);
}
複製代碼
在autoreleaseFullPage()
函數中,遞歸,而後建立一個新的page
,並設置爲HotPage
page
的狀況,通過一系列判斷,而後建立一個新的page
,並設置爲 HotPage
注意:
而在MRC
狀況下,autorelease
也會一步一步最終調用autoreleaseFast()
函數,進入上面的判斷流程。
autorelease
底層調用順序:
- (id)autorelease {
return _objc_rootAutorelease(self);
}
_objc_rootAutorelease(id obj)
{
ASSERT(obj);
return obj->rootAutorelease();
}
inline id
objc_object::rootAutorelease()
{
if (isTaggedPointer()) return (id)this;
if (prepareOptimizedReturn(ReturnAtPlus1)) return (id)this;
return rootAutorelease2();
}
id
objc_object::rootAutorelease2()
{
ASSERT(!isTaggedPointer());
return AutoreleasePoolPage::autorelease((id)this);
}
static inline id autorelease(id obj)
{
ASSERT(obj);
ASSERT(!obj->isTaggedPointer());
id *dest __unused = autoreleaseFast(obj);
ASSERT(!dest || dest == EMPTY_POOL_PLACEHOLDER || *dest == obj);
return obj;
}
複製代碼
objc_autoreleasePoolPop
底層調用以下:
void
_objc_autoreleasePoolPop(void *ctxt)
{
objc_autoreleasePoolPop(ctxt);
}
objc_autoreleasePoolPop(void *ctxt)
{
AutoreleasePoolPage::pop(ctxt);
}
複製代碼
其中ctxt
上下文參數,傳入的關聯push
和pop
的對象,即:atautoreleasepoolobj
對象(經過上面的cpp
文件得知),atautoreleasepoolobj
對象是進行push
時返回的對象,最終傳到pop
中。
pop()
實現以下:
static inline void
pop(void *token)
{
AutoreleasePoolPage *page;
id *stop;
// ✅ 判斷標識是否爲空,
// 爲空:沒有壓棧對象,爲空直接將標識設置爲begin,即佔位的地方。
// 不爲空:返回當前page
if (token == (void*)EMPTY_POOL_PLACEHOLDER) {
// Popping the top-level placeholder pool.
page = hotPage();
if (!page) {
// Pool was never used. Clear the placeholder.
return setHotPage(nil);
}
// Pool was used. Pop its contents normally.
// Pool pages remain allocated for re-use as usual.
page = coldPage();
token = page->begin();
} else {
page = pageForPointer(token);
}
stop = (id *)token;
// ✅ 越界判斷
if (*stop != POOL_BOUNDARY) {
// 第一個節點 - 沒有父節點
if (stop == page->begin() && !page->parent) {
// Start of coldest page may correctly not be POOL_BOUNDARY:
// 1. top-level pool is popped, leaving the cold page in place
// 2. an object is autoreleased with no pool
} else {
// Error. For bincompat purposes this is not
// fatal in executables built with old SDKs.
return badPop(token);
}
}
if (slowpath(PrintPoolHiwat || DebugPoolAllocation || DebugMissingPools)) {
return popPageDebug(token, page, stop);
}
// ✅ 開始釋放
return popPage<false>(token, page, stop);
}
複製代碼
在pop()
中,先判斷token
標識是否爲空,而後進行越界判斷,最終執行popPage()
開始釋放,
popPage()
源碼以下:
static void
popPage(void *token, AutoreleasePoolPage *page, id *stop)
{
if (allowDebug && PrintPoolHiwat) printHiwat();
// ✅ release 對象
page->releaseUntil(stop);
// memory: delete empty children
// ✅ 殺表
if (allowDebug && DebugPoolAllocation && page->empty()) {
// special case: delete everything during page-per-pool debugging
AutoreleasePoolPage *parent = page->parent;
page->kill();
setHotPage(parent);
} else if (allowDebug && DebugMissingPools && page->empty() && !page->parent) {
// special case: delete everything for pop(top)
// when debugging missing autorelease pools
page->kill();
setHotPage(nil);
} else if (page->child) {
// hysteresis: keep one empty child if page is more than half full
if (page->lessThanHalfFull()) {
page->child->kill();
}
else if (page->child->child) {
page->child->child->kill();
}
}
}
複製代碼
在釋放時,先釋放對象,而後對建立的page
進行釋放(殺表)。而具體的對象釋放,則以下:
void releaseUntil(id *stop)
{
// Not recursive: we don't want to blow out the stack // if a thread accumulates a stupendous amount of garbage // ✅ 從next位置開始,一直釋放,next--,知道stop位置 while (this->next != stop) { // Restart from hotPage() every time, in case -release // autoreleased more objects AutoreleasePoolPage *page = hotPage(); // fixme I think this `while` can be `if`, but I can't prove it
// ✅ empty (next == begin())當next == begin()時,page釋放完了
while (page->empty()) {
page = page->parent;
setHotPage(page);
}
page->unprotect();
id obj = *--page->next;
memset((void*)page->next, SCRIBBLE, sizeof(*page->next));
page->protect();
// ✅ 釋放對象
if (obj != POOL_BOUNDARY) {
objc_release(obj);
}
}
setHotPage(this);
#if DEBUG
// we expect any children to be completely empty
for (AutoreleasePoolPage *page = child; page; page = page->child) {
ASSERT(page->empty());
}
#endif
}
複製代碼
解讀:從next
位置開始,一直釋放對象,next--
,直到stop
位置,每個page
釋放到next == begin()
時,該page
釋放完。
在main
中,寫一下代碼:
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
// 1 + 504 + 505 + 505
NSObject *objc = [[NSObject alloc] autorelease];
NSLog(@"objc = %@",objc);
dispatch_async(dispatch_get_global_queue(0, 0), ^{
@autoreleasepool {
NSObject *obj = [[NSObject alloc] autorelease];
NSLog(@"obj = %@",obj);
_objc_autoreleasePoolPrint();
}
});
_objc_autoreleasePoolPrint();
}
sleep(2);
return 0;
}
複製代碼
打印結果以下:
當兩個autoreleasePool
進行嵌套時,只會建立一個page
,可是有兩個哨兵
。
小結
1. autoreleasePool和線程關聯,不一樣的線程autoreleasePool地址不一樣
2. 在建立autoreleasePool時,先構造,調用objc_autoreleasePoolPush,再析構,調用objc_autoreleasePoolPop
3. 在壓棧對象時,判斷page存在且不滿,而後添加,不存在或者已滿的時候,建立新的page
4. 自動釋放池,第一頁存儲504個8字節對象,其餘頁505個8字節對象
5. 對象出棧時,先對對象release,而後釋放page
6. 在MRC狀況下,autorelease也會一步一步最終調用autoreleaseFast()函數,而後進行判斷,而後將對象入棧
複製代碼
RunLoop
是一個運行循環,底層是一個do...while
循環。 做用:
performSelector
)RunLoop
的六大事件:
咱們一般經過下面的方式,獲取主運行循環和當前運行循環。
// 主運行循環
CFRunLoopRef mainRunloop = CFRunLoopGetMain();
// 當前運行循環
CFRunLoopRef currentRunloop = CFRunLoopGetCurrent();
複製代碼
在CFRunLoop
源碼中,CFRunLoopGetMain
調用_CFRunLoopGet0
方法。而CFRunLoopGetCurrent
獲取當前線程的底層也是調用_CFRunLoopGet0
方法。
CFRunLoopRef CFRunLoopGetMain(void) {
CHECK_FOR_FORK();
static CFRunLoopRef __main = NULL; // no retain needed
if (!__main) __main = _CFRunLoopGet0(pthread_main_thread_np()); // no CAS needed
return __main;
}
複製代碼
_CFRunLoopGet0
函數實現:
CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
if (pthread_equal(t, kNilPthreadT)) {
t = pthread_main_thread_np();
}
__CFSpinLock(&loopsLock);
// 判斷runloop
if (!__CFRunLoops) {
__CFSpinUnlock(&loopsLock);
// ✅建立一個dict
CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
// ✅建立一個mainLoop
CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
// ✅經過key-value的形式,將線程和mainloop存在dict中
CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) {
CFRelease(dict);
}
CFRelease(mainLoop);
__CFSpinLock(&loopsLock);
}
CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
__CFSpinUnlock(&loopsLock);
if (!loop) {
CFRunLoopRef newLoop = __CFRunLoopCreate(t);
__CFSpinLock(&loopsLock);
loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
if (!loop) {
CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop);
loop = newLoop;
}
// don't release run loops inside the loopsLock, because CFRunLoopDeallocate may end up taking it __CFSpinUnlock(&loopsLock); CFRelease(newLoop); } if (pthread_equal(t, pthread_self())) { _CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL); if (0 == _CFGetTSD(__CFTSDKeyRunLoopCntr)) { _CFSetTSD(__CFTSDKeyRunLoopCntr, (void *)(PTHREAD_DESTRUCTOR_ITERATIONS-1), (void (*)(void *))__CFFinalizeRunLoop); } } return loop; } 複製代碼
底層線程和runLoop是key-value
的形式一一對應的。
而RunLoop
也是一個對象,裏面有一系列的屬性,好比(線程、commonModes
、commonModeItems
、currentMode
)。
struct __CFRunLoop {
CFRuntimeBase _base;
pthread_mutex_t _lock; /* locked for accessing mode list */
__CFPort _wakeUpPort; // used for CFRunLoopWakeUp
Boolean _unused;
volatile _per_run_data *_perRunData; // reset for runs of the run loop
pthread_t _pthread;
uint32_t _winthread;
CFMutableSetRef _commonModes;
CFMutableSetRef _commonModeItems;
CFRunLoopModeRef _currentMode;
CFMutableSetRef _modes;
struct _block_item *_blocks_head;
struct _block_item *_blocks_tail;
CFTypeRef _counterpart;
};
複製代碼
runLoop
和線程一對一綁定,runLoop
底層依賴一個CFRunLoopMode
,是一對多的關係,而commonModes
中的items
(好比:source
、timer
、observe
)也是一對多的關係。
子線程runloop
默認不開啓,須要run
一下。
接下來,咱們以NSTimer
爲例,分析一下是如何回調的,首先以下斷點,打印調用堆棧,
發現,先調用__CFRunLoopRun
,而後調用__CFRunLoopDoTimers
,
在__CFRunLoopDoTimers
中:
static Boolean __CFRunLoopDoTimers(CFRunLoopRef rl, CFRunLoopModeRef rlm, uint64_t limitTSR) { /* DOES CALLOUT */
Boolean timerHandled = false;
CFMutableArrayRef timers = NULL;
// ✅ 準備times
for (CFIndex idx = 0, cnt = rlm->_timers ? CFArrayGetCount(rlm->_timers) : 0; idx < cnt; idx++) {
CFRunLoopTimerRef rlt = (CFRunLoopTimerRef)CFArrayGetValueAtIndex(rlm->_timers, idx);
if (__CFIsValid(rlt) && !__CFRunLoopTimerIsFiring(rlt)) {
if (rlt->_fireTSR <= limitTSR) {
if (!timers) timers = CFArrayCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeArrayCallBacks);
CFArrayAppendValue(timers, rlt);
}
}
}
// ✅ 取出全部的timer ,開始__CFRunLoopDoTimer
for (CFIndex idx = 0, cnt = timers ? CFArrayGetCount(timers) : 0; idx < cnt; idx++) {
CFRunLoopTimerRef rlt = (CFRunLoopTimerRef)CFArrayGetValueAtIndex(timers, idx);
// ✅ 調用 __CFRunLoopDoTimer
Boolean did = __CFRunLoopDoTimer(rl, rlm, rlt);
timerHandled = timerHandled || did;
}
if (timers) CFRelease(timers);
return timerHandled;
}
複製代碼
先準備timer
,而後取出全部的timer
開始__CFRunLoopDoTimer
,而後釋放timers
。
在__CFRunLoopDoTimer
中,經過下面的代碼進行回調,
__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__(rlt->_callout, rlt, context_info);
複製代碼
也驗證了調用堆棧的方法調用
同理,mode
中的其餘事件(sources0
、source1
、observe
、block
、GCD
)也是經過這樣的方式回調的。
runloop
中會添加不少items
,items
的運行依賴於mode
(UITrackingRunLoopMode
, GSEventReceiveRunLoopMode
, kCFRunLoopDefaultMode
)。
void CFRunLoopRun(void) { /* DOES CALLOUT */
int32_t result;
do {
result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
CHECK_FOR_FORK();
} while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);
}
複製代碼
do...while
循環,若是返回的結果不是stop
或者finished
,一直執行CFRunLoopRunSpecific
,一直循環,是則,跳出循環。
在CFRunLoopRunSpecific
中,
SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) { /* DOES CALLOUT */
CHECK_FOR_FORK();
if (__CFRunLoopIsDeallocating(rl)) return kCFRunLoopRunFinished;
__CFRunLoopLock(rl);
//根據modeName找到本次運行的mode
CFRunLoopModeRef currentMode = __CFRunLoopFindMode(rl, modeName, false);
//若是沒找到 || mode中沒有註冊任何事件,則就此中止,不進入循環
if (NULL == currentMode || __CFRunLoopModeIsEmpty(rl, currentMode, rl->_currentMode)) {
Boolean did = false;
if (currentMode) __CFRunLoopModeUnlock(currentMode);
__CFRunLoopUnlock(rl);
return did ? kCFRunLoopRunHandledSource : kCFRunLoopRunFinished;
}
volatile _per_run_data *previousPerRun = __CFRunLoopPushPerRunData(rl);
//取上一次運行的mode
CFRunLoopModeRef previousMode = rl->_currentMode;
//若是本次mode和上次的mode一致
rl->_currentMode = currentMode;
//初始化一個result爲kCFRunLoopRunFinished
int32_t result = kCFRunLoopRunFinished;
if (currentMode->_observerMask & kCFRunLoopEntry )
/// ✅ 1. 通知 Observers: RunLoop 即將進入 loop。
__CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
if (currentMode->_observerMask & kCFRunLoopExit )
/// ✅ 10. 通知 Observers: RunLoop 即將退出。
__CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
__CFRunLoopModeUnlock(currentMode);
__CFRunLoopPopPerRunData(rl, previousPerRun);
rl->_currentMode = previousMode;
__CFRunLoopUnlock(rl);
return result;
}
複製代碼
modeName
獲取mode
,若是沒有找到mode
或者mode
中沒有註冊事件,則直接中止,不進入循環,mode
對當前mode
進行對比kCFRunLoopEntry
,是則__CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry)
,通知 Observer
:RunLoop
即將進入loop
kCFRunLoopExit
,是則__CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit)
,通知 Observer
:RunLoop
即將退出。在__CFRunLoopRun
中,先進行超時判斷和runloop狀態的判斷,而後開始一個do...while
循環,
在這個do...while
循環中,循環執行下面的判斷
依次判斷
rlm->_observerMask & kCFRunLoopBeforeTimers
,是則,通知Observers
,RunLoop
即將出發Timer
回調;
即:__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers)
判斷rlm->_observerMask & kCFRunLoopBeforeSources
,是則,通知Observers
,RunLoop
即將出發Sources0
(非port
)回調;
即:__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources)
,
而後執行被加入的block
,__CFRunLoopDoBlocks(rl, rlm)
RunLoop
觸發 Source0
(非port) 回調,即處理Source0
若是有 Source1
(基於port
) 處於 ready
狀態,直接處理這個 Source1
而後跳轉去處理消息。
通知 Observers
: RunLoop
的線程即將進入休眠(sleep
)。而後設置RunLoop
爲休眠狀態
而後進入一個do...while
內循環,用於接收等待端口的消息,進入這個循環後,線程進入休眠,直達收到下面的消息才被喚醒,跳出循環,執行runloop
。
而後通知 Observers
: RunLoop
的線程剛剛被喚醒;
而後處理被喚醒時的消息:
Timer
到時間了,觸發這個Timer的回調,__CFRunLoopDoTimers
,而後準備times
,循環執行__CFRunLoopDoTimer
,最後調用__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__(rlt->_callout, rlt, context_info)
回調方法。dispatch
到main_queue
的block
,執行block
。__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg)
Source1
(基於port
) 事件