//B頁面中添加timer和對應的執行方法 A頁面就僅僅添加push到B頁面的代碼
@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);
}
複製代碼
B
頁面建立一個timer
,而後從A
頁面push
到B
此時timer
開始執行而後再pop
回到A
頁面,部分人可能會以爲此時timer
會暫停執行,由於timer
是B
頁面持有,pop
回來以後B
頁面也就銷燬了因此相應的timer
也因該被銷燬,因此對應的應該是timer
中止執行。可是結果其實否則。能夠看一下運行結果 能夠發現pop
回來以後 timer
同樣還在執行。B
不能釋放,看一下下面的官方文檔 官方文檔中明確說明了 timer
會對 self
進行強持有,而此時 self
有持有 timer
因此形成了循環引用,也就形成了 B
頁面不能釋放,因此即便 pop
計時器還在執行。__weak
去修飾 self
,此時 self
的引用計數不會加一,因此不會形成循環引用問題,在這裏不妨試一下用 __weak
去修飾而後再看執行結果 發現這個地方 __weak
修飾並不能解決循環引用的問題。一樣的在文章Block的底層分析咱們知道,用 __weak
修飾的話底層 block
會走到 _Block_object_assign
方法,發現 block
底層其實僅僅存儲了對象的指針地址也就是 weakSelf
的地址。這裏咱們先分別打印一下 self
的引用計數和 __weak
修飾以後的引用計數,而後在分別打印一下 self
和 weakSelf
和這二者的地址 首先能夠肯定的是 __weak
修飾的變量指向對象並不會形成引用計數加一的狀況,其次經過地址打印、值打印咱們能夠肯定的是 self
和 weakSelf
是兩個變量指向了同一片的內存空間以下圖所示 block
能經過存儲的 weakSelf
的地址找到對象的地址從而獲取對象的屬性修改對象相關的屬性等。而且也可以解決循環引用的問題。 可是 timer
就不同了,上圖的官方文檔咱們能夠知道,timer
強持有的是對象,並非對象的指針地址了,因此 timer
的引用臉就是timer -> weakSelf -> 對象
timer
又被 runloop
持有,引用鏈以下:runloop -> timer -> weakSelf -> 對象
runloop
的生命週期又很長(大於對象和 timer
的生命週期)runloop
沒有停那麼 timer
就不會被釋放,進而致使 weakSelf
以及對象都不會釋放. 也就致使了不一樣於 block
的解決循環引用的方法也就是 __weak
不能解決強持有的問題。timer
timer
持有的是當前對象因此對象不能被釋放,因此解決辦法其實也很簡單就是pop
出去的時候只須要釋放 timer
就行。上文的官方文檔也有提到 只要釋放 timer
對象也就會被釋放。因此只須要在 didMoveToParentViewController
方法中調用 [self.timer invalidate];
和 self.timer = nil;
就好了效果以下 這樣強持有後不能釋放的問題也就解決了timer
回調方法判斷timer
可是除了 didMoveToParentViewController
方法中釋放還能夠考慮專門建立一個添加 timer
的類,在該類中新建一個方法,而後和傳入的方法作交換,該方法中須要判斷傳入的 target
是否爲空了,若是不爲空則使用傳入的 target
調用傳入的方法。若是爲空則釋放 timer
。釋放 timer
對應 target
引用計數就會減一。若是減到0就會被正常釋放。一樣的也能夠解決問題具體代碼以下 #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);
self.timer = [NSTimer scheduledTimerWithTimeInterval:ti target:self selector:aSelector userInfo:userInfo repeats:yesOrNo];
}
}
return self;
}
void fireHomeWapper(LGTimerWapper *warpper){
if (warpper.target) { // vc - dealloc
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
複製代碼
proxy
虛基類的方式proxy
這裏其實也相似,這裏使用 proxy
的思想主要是想使用一箇中間者,這樣 timer
不會再持有對象而是 proxy
,因此對象的引用計數不會再加一,從而對象釋放的時候對應的 timer
和 proxy
也就釋放了也就解決了強持有的問題。具體代碼以下; #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;
}
複製代碼
從這個官方文檔中咱們能夠知道,在 Runloop
開始的時候會自動建立一個自動釋放池,當 Runloop
此次循環結束的時候,那麼就會銷燬自動釋放池,從而釋放全部 autorelease
對象,固然若是在一個事務中須要建立多個臨時變量此時就能夠本身手動建立一個自動釋放池來管理這些對象能夠很大程度地減小內存峯值。(例如一個代碼塊中須要建立循環建立10000個 image
對象而後渲染出來,此時徹底可使用自動釋放池,正常狀況下不使用自動釋放池的話會等到這個代碼塊執行完成以後才能釋放這10000個對象,而是用自動釋放池以後每次循環完成自動釋放池的代碼也執行完成那麼該對象也就會被釋放。這樣就減小了內存峯值) 結合文檔和上圖的理解總結:xcode
runloop
,建立完事件以後會建立一個自動釋放池autorelease
對象放到自動釋放池中去runloop
結束以前,會向自動釋放池中全部對象發送release
消息,而後銷燬自動釋放池main
函數中使用自動釋放池的區別xcode11
以前建立的項目是這樣的 xcode11
以後建立的工程是這樣的 能夠發現 xcode11
以前整個程序都是放在自動釋放池中的,當 runloop
啓動會再建立一個自動釋放池嵌套在 main
函數的這個釋放池中,這樣使用的結果是 main
函數自動釋放池中建立的對象只有程序結束以後才能被釋放,再看 xcode11
以後建立的 main
函數發現程序在自動釋放池的外面,因此在自動釋放池中建立的對象只要程序啓動就能被釋放,這樣節省了程序的內存markdown
Clang
分析能夠將 main
文件 clang
一下看編譯後的源碼 發現底層其實就是一個 __AtAutoreleasePool
對象。而後再全局搜索 __AtAutoreleasePool
而且自動釋放池中的代碼是使用 {}
包裹的 不出意外的是個結構體,裏面有構造函數 objc_autoreleasePoolPush
返回了 atautoreleasepoolobj
對象,還有一個析構函數 objc_autoreleasePoolPop
須要傳入 atautoreleasepoolobj
對象,上文也說了自動釋放池的代碼是在一個做用域中的,因此開始的時候就會調用構造方法,做用域結束的時候就會調用析構方法也能夠經過斷點調試查看彙編代碼驗證此結論 app
上文經過 clang
查看編譯後的代碼得知自動吃其實也就是個對象,就是個結構體,其中有構造方法和析構方法,接下來就能夠經過源碼查詢構造和析構方法看源碼是如何實現的同時也能夠深刻探索自動釋放池這個對象函數
AutoreleasePoolPage
中的方法點擊 AutoreleasePoolPage
查看源碼 發現自動釋放池就是經過AutoreleasePoolPage
來實現的註釋中也說道了自動釋放池的實現方法大概意思以下:
POOL_BOUNDARY
,它是自動釋放池的邊界。AutoreleasePoolPage
對象,從定義中能夠看出AutoreleasePoolPage
是以棧爲結點經過雙向鏈表的形式組合而成,每一個頁的大小是4096
,再看AutoreleasePoolPageData
結構 發現一共 56
字節因此通常狀況下共有 4096-56=4040
字節存儲 autorelease
對象也就是一共能夠存 4040/8=505
個對象,可是從定義中知道還有一個POOL_BOUNDARY
(注意哨兵對象只有在第一頁中存在)因此第一頁能夠存儲 504
個對象剩下的能夠存儲 505
個對象,這裏可已經過打印自動釋放池的狀況驗證(_objc_autoreleasePoolPrint
方法打印自動釋放池的狀況) 此時是建立了504個對象 多加一個對象則又建立了一頁,而且把新建立的頁設置成 hot
,而後第二頁的第一個對象再也不是哨兵對象直接就是 autorelease
對象 具體內存分佈圖以下: objc_autoreleasePoolPush
源碼分析AutoreleasePoolPage
是經過構造方法建立的 再看 autoreleaseFullPage
方法 這個方法就比較簡單了就是一個鏈表的查詢工做,查到了則設置成聚焦頁面並添加對象,沒查到則新建立一個頁面並插入到鏈表中,新頁面設置成聚焦頁面而後添加對象。 最後再看add
方法 ,這裏就是將對象存到next
指針,而後next++
。autorelease
源碼分析autorelease
底層實現就是調用autoreleaseFast
方法objc_autoreleasePoolPop
源碼分析 再看 releaseUntil
方法 kill
方法 具體流程圖以下: oop
AutoreleasePool
底層就是一個 AutoreleasePoolPage
對象 AutoreleasePoolPage
對象又是一個棧結構而且是個雙向兩邊(應爲每個 AutoreleasePoolPage
都是有大小限制的超出了再添加對象則須要建立新的頁,因此是個雙向連接結構)AutoreleasePool
是個棧結構而且是雙向鏈表結構,因此 push
可添加對象就是壓棧,棧壓滿了則建立新頁面對象壓棧到新頁面中去,而後將新頁面插入到鏈表結構中。 pop
就是出棧而後釋放對象,釋放pageAutoreleasePool
會在每次 runloop
啓動的時候自動建立一個自動釋放池,而後在這次循環結束的時候釋放自動釋放池,因此若是對象添加 __autoreleasing
屬性修飾則將對象添加到了系統建立的自動釋放池中,那麼該對象的釋放也就是系統干預釋放了,也就是要等到這次 runloop
結束以後釋放對象,AutoreleasePool
還一種狀況是手動建立自動釋放池也是就是經過 @autoreleasepool
建立自動釋放池,在該做用域中建立的 autorelease
對象會放到手動建立的自動釋放池中此時該對象就會在手動建立的自動釋放池做用域結束以後就會被釋放,這樣作能夠下降內存峯值