推薦系統直接學習小碼哥iOS底層原理班---MJ老師的課確實不錯,強推一波。ios
CADisplayLink(
保證調用頻率和屏幕的刷幀頻率一致,60FPS(60次/s)
)、NSTimer會對target產生強引用,若是target又對它們產生強引用,那麼就會引起循環引用c++
以下代碼是釋放不掉的bash
- (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__);
}
複製代碼
block是捕獲變量,timer是傳遞參數。ide
不過若是是NSTimer的block版本用__weak是能夠的函數
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:[MJProxy proxyWithTarget:self] selector:@selector(timerTest) userInfo:nil repeats:YES];
複製代碼
- (id)forwardingTargetForSelector:(SEL)aSelector
{
return self.target;
}
複製代碼
好比removeSuperView時之類吧oop
NSProxy專門用來作消息轉發佈局
NSProxy在本類沒有該方法的狀況下會直接進入消息轉發(methodSignatureForSelector:) 與 (forwardInvocation:
),並不會去查找父類,動態方法解析等等。學習
原生方法若是未主動實現,內部直接進入消息轉發。好比class,isKindOfClass等等優化
GCD的定時器會更加準時ui
NSTimer依賴於RunLoop,若是RunLoop的任務過於繁重,可能會致使NSTimer不許時
而GCD定時器依賴於系統內核,並不依賴Runloop
從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);
複製代碼
使用return關鍵字只會管理setget方法中的內存,dealloc中仍然須要本身釋放。
一個全局table
![]()
refcnts是一個存放着對象引用計數的散列表 weak_table存放着若引用的指針與對象
當一個對象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 {
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); //釋放釋放池
複製代碼
每一個AutoreleasePoolPage對象佔用4096字節內存,除了用來存放它內部的成員變量,剩下的空間用來存放autorelease對象的地址
在調用objc_autoreleasePoolPush()
時,插入POLL_BOUNDARY
並返回地址0x1038
每一個調用autorelease
的對象都會被插入到AutoreleasePoolPage中
在調用objc_autoreleasePoolPop(0x1038)
時,從當前位置到0x1038
全部的對象都會被執行release
操做。
能夠經過如下私有函數來查看自動釋放池的狀況
extern void _objc_autoreleasePoolPrint(void);
複製代碼
hotPage
的活躍AutoreleasePoolPage
被系統持有page
之間經過雙向鏈表連接push/autorelease
操做時當前page已滿,將會建立一個page
或跳轉到下一個page
。iOS在主線程的Runloop中註冊了2個Observer,監聽了三個狀態。並適時操做AutoreleasePool
第1個Observer監聽了kCFRunLoopEntry事件
在進入runloop時,會調用objc_autoreleasePoolPush()
第2個Observer監聽了kCFRunLoopBeforeExit事件
在退出runloop時,會調用objc_autoreleasePoolPop()
第2個Observer還監聽了kCFRunLoopBeforeWaiting事件
在當前循環結束,準備休眠時時,會調用objc_autoreleasePoolPop()隨後再調用一次objc_autoreleasePoolPush()
借用羣裏一位大佬的解釋
通常除了init其餘基本上都是autorelease的,包括C函數返回對象
也就是說init方法放回的對象,默認是會被retain/release
,而其餘的對象默認會autorelease
。
很顯然的,兩者的釋放時機不一樣,因此纔會有以下狀況發生。