iOS 內(nèi)存管理 部分二

主要講解CADisplayLink 和 NSTimer 的循環(huán)引用問(wèn)題

iOS 內(nèi)存管理 部分一
iOS 內(nèi)存管理 部分二
iOS 內(nèi)存管理 部分三
iOS 內(nèi)存管理 部分四


1. CADisplayLink 和 NSTimer的循環(huán)引用

關(guān)于什么是 CADisplayLink不再贅述, 網(wǎng)上有很多講解很好的教程; 正常的使用時(shí)我們這樣寫, 但是這樣寫即使是在dealloc中寫了invalid也不會(huì)釋放, 因?yàn)橛袕?qiáng)引用環(huán)的存在,

#NSTiemr 的使用
- (void)timerAction {
    self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(printAction) userInfo:nil repeats:YES];
    [self.timer fire];
}
- (void)printAction {
    NSLog(@"%s", __func__);
}

- (void)dealloc {
    [self.timer invalidate];
    NSLog(@"%s", __func__);
}

#CADisplayLink 的使用
- (void)displaylinkAction {
    self.link = [CADisplayLink displayLinkWithTarget:self selector:@selector(printAction)];
    [self.link addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
}
- (void)printAction {
    NSLog(@"%s", __func__);
}
- (void)dealloc {
    [self.link invalidate];
    NSLog(@"%s", __func__);
}

他們的引用關(guān)系如下, 所以導(dǎo)致不能釋放;


2. 解決方案

1 . 使用 Block

通過(guò)使用_weak, 來(lái)使block對(duì) self弱引用, 進(jìn)而打到打破循環(huán)引用的問(wèn)題;關(guān)于block對(duì)self的引用問(wèn)題請(qǐng)看這篇文章;


測(cè)試代碼

- (void)timerBlockAction {
    __weak typeof(self) weakSelf = self;
    self.timer = [NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
        [weakSelf printAction];
    }];
}
2. 使用中轉(zhuǎn)轉(zhuǎn)發(fā)對(duì)象

使用一個(gè)三方轉(zhuǎn)發(fā)對(duì)象來(lái)斷開這個(gè)引用環(huán)


2.1 為了對(duì)比我們對(duì)NSTimer使用NSObject的類型來(lái)中轉(zhuǎn) 轉(zhuǎn)發(fā);
代碼如下

///NSObject 類型中轉(zhuǎn)轉(zhuǎn)發(fā)對(duì)象的.h文件
#import <Foundation/Foundation.h>
@interface ObjectObj : NSObject
+ (ObjectObj *)ShareTarget:(id)obj;
@property (nonatomic, weak) id target;
@end
///NSObject 類型中轉(zhuǎn)轉(zhuǎn)發(fā)對(duì)象的.m文件
#import "ObjectObj.h"
@implementation ObjectObj
+ (ObjectObj *)ShareTarget:(id)obj {
    ObjectObj *object = [[ObjectObj alloc] init];
    object.target = obj;
    return  object;
}
- (id)forwardingTargetForSelector:(SEL)aSelector {
    return self.target;
}
@end

#調(diào)用部分
- (void)timerAction {
    self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:[ObjectObj ShareTarget:self] selector:@selector(printAction) userInfo:nil repeats:YES];
    [self.timer fire];
}

我們從上面的圖中可以確定只要環(huán)中有一個(gè)弱引用就可以破環(huán)循環(huán)引用;但是為什么這種方式可行呢, 其實(shí)這樣做的本質(zhì)是使用了消息轉(zhuǎn)發(fā), 中轉(zhuǎn)對(duì)象并不能響應(yīng)方法printAction(), 所以會(huì)進(jìn)行方法查找(父類/緩存)-動(dòng)態(tài)解析- 消息轉(zhuǎn)發(fā), 最終返回一個(gè)可以處理printAction()方法的對(duì)象, 最后的效果就是 VC在執(zhí)行pop操作時(shí)可以進(jìn)行調(diào)用dealloc()方法釋放內(nèi)存; 關(guān)于消息發(fā)送方法的查找過(guò)程;
2.2為了對(duì)比我們對(duì)CADisplayLink使用NSProxy的類型來(lái)中轉(zhuǎn) 轉(zhuǎn)發(fā);
代碼如下

///NSProxy 類型中轉(zhuǎn)轉(zhuǎn)發(fā)對(duì)象的.h文件
#import <Foundation/Foundation.h>
@interface ProxyObj : NSProxy
+ (ProxyObj *)ShareTarget:(id)obj;
@property (nonatomic, weak) id target;
@end

///NSProxy 類型中轉(zhuǎn)轉(zhuǎn)發(fā)對(duì)象的.m文件
#import "ProxyObj.h"
@implementation ProxyObj
+ (ProxyObj *)ShareTarget:(id)obj {
    ///注意NSProxy實(shí)例對(duì)象創(chuàng)建不需要 init
    ProxyObj *object = [ProxyObj alloc] ;
    object.target = obj;
    return  object;
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
    ///返回 target 的方法簽名
   return [self.target methodSignatureForSelector:sel];
}
- (void)forwardInvocation:(NSInvocation *)invocation {
    ///將invocation的 target 設(shè)置為 self.target
    invocation.target = self.target;
    [invocation invoke];
}
@end

#調(diào)用部分
- (void)displaylinkAction {
    self.link = [CADisplayLink displayLinkWithTarget:[ProxyObj ShareTarget:self] selector:@selector(printAction)];
    [self.link addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
}

跟上面的NSObject 類型的中轉(zhuǎn)轉(zhuǎn)發(fā)一樣, 也可以實(shí)現(xiàn)最終效果, 但是使用NSProxy效率更高, 因?yàn)?code>NSObject的過(guò)程要經(jīng)過(guò)方法查找(父類/緩存)-動(dòng)態(tài)解析-消息轉(zhuǎn)發(fā)三個(gè)階段, 而 NSProxy只有消息轉(zhuǎn)發(fā)這個(gè)步驟(從其 API可以查看到它只有消息轉(zhuǎn)發(fā)的方法, 沒(méi)有其他兩個(gè)步驟的方法), 省去前面兩個(gè)步驟, 從而使效率更高;

2. 使用 NSProxy 的注意事項(xiàng)

在討論這個(gè)問(wèn)題之前, 我們先補(bǔ)充下什么是 GNUStep:
GNUStep是 GNU 計(jì)劃的項(xiàng)目之一, 我們都知道 iOSFoundation 框架是不開源的; 因此 GNUStepFoundation重新實(shí)現(xiàn)了一遍, 雖然不是Apple官方的源碼, 但是目前仍然是最有參考價(jià)值的源碼;

通過(guò)上面我們知道了NSProxy的用法, 但是有一些注意事項(xiàng)我們需要注意, 看下面代碼

- (void)proxyAttention {
    ObjectObj *obj1 = [ObjectObj ShareTarget:self];
    ProxyObj  *obj2 = [ProxyObj ShareTarget:self];
    NSLog(@"%ld___%ld", (long)[obj1 isKindOfClass:[ViewController1 class]],
                        (long)[obj2 isKindOfClass:[ViewController1 class]]);
}
#打印結(jié)果為
2020-08-03 15:13:22.910122+0800 MemoryMore1[6035:1149461] 0___1

至于第一個(gè)為什么會(huì)打印0, 我們可以看這篇文章中的isKindOfClass()方法的講解, 但是為什么第二個(gè)會(huì)打印1呢;
這個(gè)就需要去GNUStep源碼中找下NSProxy的實(shí)現(xiàn);

/**
 * Calls the -forwardInvocation: method to determine if the 'real' object
 * referred to by the proxy is an instance of the specified class.
 * Returns the result.<br />
 * NB. The default operation of -forwardInvocation: is to raise an exception.
 */
- (BOOL) isKindOfClass: (Class)aClass
{
  NSMethodSignature *sig;
  NSInvocation      *inv;
  BOOL          ret;

  sig = [self methodSignatureForSelector: _cmd];
  inv = [NSInvocation invocationWithMethodSignature: sig];
  [inv setSelector: _cmd];
  [inv setArgument: &aClass atIndex: 2];
  [self forwardInvocation: inv];
  [inv getReturnValue: &ret];
  return ret;
}

從源碼中我們可以看到, NSProxyisKindOfClass()方法不是跟NSObject那種進(jìn)行判斷是否是一個(gè)類或者子類, 而是調(diào)用了消息轉(zhuǎn)發(fā)的相關(guān)方法, 因此實(shí)際去跟isKindOfClass()進(jìn)行判斷的是NSInvocation內(nèi)部的target; 由于[ProxyObj ShareTarget:self]初始化時(shí)傳入的當(dāng)前VC并在消息轉(zhuǎn)發(fā)時(shí)將其設(shè)置為NSInvocation內(nèi)部的target, 所以打印出二者相等, 打印出 1;


參考文章和下載鏈接
文中測(cè)試代碼
CADisplayLink 詳解
NSProxy 簡(jiǎn)析
GNUStep
GNU 官網(wǎng)
Foundation 的 GNU 下載

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末汉额,一起剝皮案震驚了整個(gè)濱河市蝗肪,隨后出現(xiàn)的幾起案子莉钙,更是在濱河造成了極大的恐慌狱庇,老刑警劉巖匀钧,帶你破解...
    沈念sama閱讀 221,695評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件赁酝,死亡現(xiàn)場(chǎng)離奇詭異盼理,居然都是意外死亡竿报,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,569評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門溉委,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)鹃唯,“玉大人,你說(shuō)我怎么就攤上這事瓣喊∑禄牛” “怎么了?”我有些...
    開封第一講書人閱讀 168,130評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵藻三,是天一觀的道長(zhǎng)洪橘。 經(jīng)常有香客問(wèn)我,道長(zhǎng)棵帽,這世上最難降的妖魔是什么熄求? 我笑而不...
    開封第一講書人閱讀 59,648評(píng)論 1 297
  • 正文 為了忘掉前任,我火速辦了婚禮岖寞,結(jié)果婚禮上抡四,老公的妹妹穿的比我還像新娘。我一直安慰自己仗谆,他們只是感情好指巡,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,655評(píng)論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著隶垮,像睡著了一般藻雪。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上狸吞,一...
    開封第一講書人閱讀 52,268評(píng)論 1 309
  • 那天勉耀,我揣著相機(jī)與錄音指煎,去河邊找鬼。 笑死便斥,一個(gè)胖子當(dāng)著我的面吹牛至壤,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播枢纠,決...
    沈念sama閱讀 40,835評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼像街,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了晋渺?” 一聲冷哼從身側(cè)響起镰绎,我...
    開封第一講書人閱讀 39,740評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎木西,沒(méi)想到半個(gè)月后畴栖,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,286評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡八千,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,375評(píng)論 3 340
  • 正文 我和宋清朗相戀三年吗讶,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片叼丑。...
    茶點(diǎn)故事閱讀 40,505評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡关翎,死狀恐怖扛门,靈堂內(nèi)的尸體忽然破棺而出鸠信,到底是詐尸還是另有隱情,我是刑警寧澤论寨,帶...
    沈念sama閱讀 36,185評(píng)論 5 350
  • 正文 年R本政府宣布星立,位于F島的核電站,受9級(jí)特大地震影響葬凳,放射性物質(zhì)發(fā)生泄漏绰垂。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,873評(píng)論 3 333
  • 文/蒙蒙 一火焰、第九天 我趴在偏房一處隱蔽的房頂上張望劲装。 院中可真熱鬧,春花似錦昌简、人聲如沸占业。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,357評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)谦疾。三九已至,卻和暖如春犬金,著一層夾襖步出監(jiān)牢的瞬間念恍,已是汗流浹背六剥。 一陣腳步聲響...
    開封第一講書人閱讀 33,466評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留峰伙,地道東北人疗疟。 一個(gè)月前我還...
    沈念sama閱讀 48,921評(píng)論 3 376
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像瞳氓,于是被迫代替她去往敵國(guó)和親秃嗜。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,515評(píng)論 2 359