推薦系統直接學習小碼哥iOS底層原理班---MJ老師的課確實不錯,強推一波。
別的
CADisplayLink與NSTimer
CADisplayLink(
保證呼叫頻率和螢幕的刷幀頻率一致,60FPS(60次/s)
)、NSTimer會對target產生強引用,如果target又對它們產生強引用,那麼就會引發迴圈引用
target導致迴圈引用
如下程式碼是釋放不掉的
- (void)viewDidLoad {
[super viewDidLoad];
// 保證呼叫頻率和螢幕的刷幀頻率一致,60FPS
self.link = [CADisplayLink displayLinkWithTarget:[MJProxy proxyWithTarget:self] selector:@selector(linkTest)];
[self.link addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
}
- (void)linkTest
{
NSLog(@"%s", __func__);
}
複製程式碼
__weak為什麼解決target的強引用
block是捕獲變數,timer是傳遞引數。
- block在捕獲變數時根據變數型別自行進行若引用處理。
- timer作為引數傳遞時,內部接收到的都是物件的地址值,無法獲取引用型別。
不過如果是NSTimer的block版本用__weak是可以的
中間代理
- 用代理隔離self與timer
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:[MJProxy proxyWithTarget:self] selector:@selector(timerTest) userInfo:nil repeats:YES];
複製程式碼
- 用訊息轉發將selector傳送回self
- (id)forwardingTargetForSelector:(SEL)aSelector
{
return self.target;
}
複製程式碼
其他方式釋放timer
比如removeSuperView時之類吧
NSObject與NSProxy
NSProxy專門用來做訊息轉發
- 訊息轉發速度快
NSProxy在本類沒有該方法的情況下會直接進入訊息轉發(methodSignatureForSelector:) 與 (forwardInvocation:
),並不會去查詢父類,動態方法解析等等。
- 大部分方法都能正確轉發
原生方法如果未主動實現,內部直接進入訊息轉發。比如class,isKindOfClass等等
GCD定時器
GCD的定時器會更加準時
NSTimer依賴於RunLoop,如果RunLoop的任務過於繁重,可能會導致NSTimer不準時
而GCD定時器依賴於系統核心,並不依賴Runloop
記憶體佈局
Tagged Pointer
從64bit開始,iOS引入了Tagged Pointer技術,用於優化NSNumber、NSDate、NSString等小物件的儲存
-
在沒有使用Tagged Pointer之前,NSNumber與正常的OC物件一樣:
需要動態分配記憶體、維護引用計數等,NSNumber指標儲存的是堆中NSNumber物件的地址值。
-
使用Tagged Pointer之後,NSNumber指標裡面儲存的資料變成了:Tag + Data,也就是將資料直接儲存在了指標中
-
當指標不夠儲存資料時,才會使用動態分配記憶體的方式來儲存資料
-
objc_msgSend能識別Tagged
-
Pointer,比如NSNumber的intValue方法,直接從指標提取資料,節省了以前的呼叫開銷
-
如何判斷一個指標是否為Tagged Pointer? class
iOS平臺,最高有效位是1(第64bit)
Mac平臺,最低有效位是1(16進位制下為7)
通常來講,判斷最後一位不是0即可
NSLog(@"Person例項的記憶體地址=%p---指標變數p的記憶體地址=%p---指標變數p儲存的記憶體地址=%p", p, &p, p);
複製程式碼
MRC的記憶體管理
- 在iOS中,使用引用計數來管理OC物件的記憶體
- 一個新建立的OC物件引用計數預設是1,當引用計數減為0,OC物件就會銷燬,釋放其佔用的記憶體空間
- 呼叫retain會讓OC物件的引用計數+1,呼叫release會讓OC物件的引用計數-1
記憶體管理的經驗總結
- 當呼叫alloc、new、copy、mutableCopy方法返回了一個物件,在不需要這個物件時,要呼叫release或者autorelease來釋放它
- 想擁有某個物件,就讓它的引用計數+1;不想再擁有某個物件,就讓它的引用計數-1
MRC
使用return關鍵字只會管理setget方法中的記憶體,dealloc中仍然需要自己釋放。
copy和mutableCopy
引用計數
- 在64bit中,引用計數可以直接儲存在優化過的isa指標中
- 如果引用計數過大,isa中改為1並且將計數儲存到SideTable中
SideTable
一個全域性table
refcnts是一個存放著物件引用計數的雜湊表 weak_table存放著若引用的指標與物件
weak
當一個物件A被若引用指標持有,將會以[&A,weak指標表]的形式新增進SideTable中
當物件A被釋放,可以根據&A查詢到所有指向他的weak指標並進行釋放
- (void)dealloc {
_objc_rootDealloc(self);
}
_objc_rootDealloc(id obj)
{
assert(obj);
obj->rootDealloc();
}
objc_object::rootDealloc()
{
if (isTaggedPointer()) return; // fixme necessary?
if (fastpath(isa.nonpointer && //新isa指標
!isa.weakly_referenced && //檢視該物件是否被若引用了
!isa.has_assoc && //關聯物件
!isa.has_cxx_dtor && //c++析構器
!isa.has_sidetable_rc)) //大額引用計數
{
assert(!sidetable_present());
free(this); //直接釋放
}
else {
object_dispose((id)this);
}
}
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(); //將指向當前物件的弱指標置位nil
}
return obj;
}
objc_object::clearDeallocating_slow()
{
assert(isa.nonpointer && (isa.weakly_referenced || isa.has_sidetable_rc));
SideTable& table = SideTables()[this]; //獲得全域性的SideTable
table.lock();
if (isa.weakly_referenced) {
//從表中根據物件地址,釋放所有指向他的弱引用指標
weak_clear_no_lock(&table.weak_table, (id)this);
}
if (isa.has_sidetable_rc) {
table.refcnts.erase(this);
}
table.unlock();
}
複製程式碼
AutoreleasePool
@autoreleasepool {
for (int i = 0; i < 1000; i++) {
MJPerson *person = [[[MJPerson alloc] init] autorelease];
}
}
複製程式碼
cpp中
{
__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"));
}
struct __AtAutoreleasePool {
__AtAutoreleasePool() { // 建構函式,在建立結構體的時候呼叫
atautoreleasepoolobj = objc_autoreleasePoolPush();
}
~__AtAutoreleasePool() { // 解構函式,在結構體銷燬的時候呼叫
objc_autoreleasePoolPop(atautoreleasepoolobj);
}
void * atautoreleasepoolobj;
};
複製程式碼
所以本質上就等於
atautoreleasepoolobj = objc_autoreleasePoolPush(); //建立釋放池
MJPerson *person = [[[MJPerson alloc] init] autorelease];
objc_autoreleasePoolPop(atautoreleasepoolobj); //釋放釋放池
複製程式碼
AutoreleasePool的結構
每個AutoreleasePoolPage物件佔用4096位元組記憶體,除了用來存放它內部的成員變數,剩下的空間用來存放autorelease物件的地址
push,pop,autorelease
-
在呼叫
objc_autoreleasePoolPush()
時,插入POLL_BOUNDARY
並返回地址0x1038 -
每個呼叫
autorelease
的物件都會被插入到AutoreleasePoolPage中 -
在呼叫
objc_autoreleasePoolPop(0x1038)
時,從當前位置到0x1038
所有的物件都會被執行release
操作。
可以通過以下私有函式來檢視自動釋放池的情況
extern void _objc_autoreleasePoolPrint(void);
複製程式碼
AutoreleasePool的維護
- 始終有一個被標記
hotPage
的活躍AutoreleasePoolPage
被系統持有 page
之間通過雙向連結串列連結- 如果
push/autorelease
操作時當前page已滿,將會建立一個page
或跳轉到下一個page
。
runloop與AutoreleasePool
iOS在主執行緒的Runloop中註冊了2個Observer,監聽了三個狀態。並適時操作AutoreleasePool
-
第1個Observer監聽了kCFRunLoopEntry事件
在進入runloop時,會呼叫objc_autoreleasePoolPush()
-
第2個Observer監聽了kCFRunLoopBeforeExit事件
在退出runloop時,會呼叫objc_autoreleasePoolPop()
-
第2個Observer還監聽了kCFRunLoopBeforeWaiting事件
在當前迴圈結束,準備休眠時時,會呼叫objc_autoreleasePoolPop()隨後再呼叫一次objc_autoreleasePoolPush()
autorelease物件
借用群裡一位大佬的解釋
一般除了init其他基本上都是autorelease的,包括C函式返回物件
也就是說init方法放回的物件,預設是會被retain/release
,而其他的物件預設會autorelease
。
很顯然的,二者的釋放時機不同,所以才會有如下情況發生。