iOS開(kāi)發(fā) Runtime 使用objc_msgSend()創(chuàng)建對(duì)象不能釋放的問(wèn)題

好幾個(gè)月沒(méi)有發(fā)表點(diǎn)東西了,但也一直關(guān)注簡(jiǎn)書(shū)上優(yōu)秀的博文學(xué)習(xí),漫長(zhǎng)的這段時(shí)間一直在不斷的重構(gòu)公司項(xiàng)目,為了使代碼盡可能的優(yōu)雅,除了不斷地進(jìn)行組件化重寫(xiě)之外,很多地方也逐漸的開(kāi)始使用runtime進(jìn)行編程。說(shuō)實(shí)話,這樣編程比較爽,卻會(huì)出現(xiàn)一系列之前沒(méi)有考慮過(guò)的問(wèn)題,比如這篇文章記錄的使用objc_msgSend()創(chuàng)建對(duì)象不能釋放的問(wèn)題。

先列出常用的初始化方法,這里就以最常用的ViewController為例。

普遍的初始化

UIViewController * viewController = [[UIViewController alloc]init];

//或者

UIViewController * viewController = [UIViewController new];

樓主比較喜歡后面一種,因?yàn)楫吘鼓苌俅驇讉€(gè)字母嘛,當(dāng)然這不是重點(diǎn)。

runtime的初始化

Class class = objc_getClass("UIViewController");

id viewController = (id(*)(id,SEL)objc_msgSend)(class,NSSelectorFromString(@"new"));

兩者對(duì)比一下,貌似使用runtime要更加的麻煩,因?yàn)樗踔列枰獌尚斜容^長(zhǎng)代碼來(lái)完成一個(gè)控制器的初始化,至于為什么選擇runtime后面會(huì)說(shuō)一下個(gè)人的看法,如果非要說(shuō)裝X - - (我也說(shuō)不了什么其實(shí)??)。

問(wèn)題

先來(lái)說(shuō)明一下問(wèn)題,利用Demo中的Let's push oneSelf來(lái)不斷的push與pop當(dāng)前控制器類的實(shí)例對(duì)象, 查看當(dāng)前實(shí)例對(duì)象的個(gè)數(shù),這個(gè)很簡(jiǎn)單,在類中定義一個(gè)全局變量來(lái)記錄當(dāng)前類對(duì)象實(shí)力對(duì)象的個(gè)數(shù)

static NSUInteger classValue = 0;

RITLRootViewController控制器的ViewDidLoad以及deallco方法中分別對(duì)數(shù)字進(jìn)行++或者--操作,并打印當(dāng)前類的個(gè)數(shù):

使用普通的初始化方法以及打印結(jié)果:

RITLRootViewController * viewController = [RITLRootViewController new];
[self.navigationController pushViewController:viewController animated:true];

//result:
現(xiàn)在存在1個(gè)RITLRootViewController
現(xiàn)在存在2個(gè)RITLRootViewController
現(xiàn)在存在1個(gè)RITLRootViewController
現(xiàn)在存在2個(gè)RITLRootViewController
現(xiàn)在存在1個(gè)RITLRootViewController
現(xiàn)在存在2個(gè)RITLRootViewController
現(xiàn)在存在1個(gè)RITLRootViewController

可以看出,數(shù)字在viewDidLoad中進(jìn)行了累加,在dealloc中進(jìn)行了累減,這樣是平衡的。

使用runtime方法初始化打出結(jié)果:

Class class = objc_getClass("RITLRootViewController");
id viewController = ((id(*)(id,SEL))objc_msgSend)(class,NSSelectorFromString(@"new"));
[self.navigationController pushViewController:viewController animated:true];

//result:
現(xiàn)在存在1個(gè)RITLRootViewController
現(xiàn)在存在2個(gè)RITLRootViewController
現(xiàn)在存在3個(gè)RITLRootViewController
現(xiàn)在存在4個(gè)RITLRootViewController
現(xiàn)在存在5個(gè)RITLRootViewController

對(duì)象的個(gè)數(shù)是不斷上升的,間接地告訴我們實(shí)例對(duì)象是沒(méi)有釋放掉的。

原因

用群里一個(gè)朋友的話說(shuō):"C語(yǔ)言的方式還要用C語(yǔ)言來(lái)解決"。

runtime大家都知道它就是C/C++ , 再直接一點(diǎn)就是直接使用runtime執(zhí)行初始化方法創(chuàng)建的對(duì)象的時(shí)候是不在ARC控制之下的,也就是說(shuō)在對(duì)應(yīng)創(chuàng)建的那個(gè)文件里銷毀的時(shí)候需要我們自行release。

樓主分析的小過(guò)程,很簡(jiǎn)單:

  • 初始化完畢之后,當(dāng)前對(duì)象的retainCount = 1。
  • 導(dǎo)航控制器push的時(shí)候,ARC下的編譯器幫我們進(jìn)行了一次retain,當(dāng)前對(duì)象的retainCount = 2。
  • 導(dǎo)航控制器pop的時(shí)候,ARC下的編譯器又幫我們進(jìn)行了一次release,當(dāng)前對(duì)象的retainCount = 1。
  • 此時(shí)當(dāng)前控制器的retainCount恒為非0正整數(shù),無(wú)法釋放。

解決

上面的步驟看完之后,基本就能進(jìn)行解決位置的定位了,那么我們?cè)趯?dǎo)航控制器retain之后進(jìn)行自身的release即可,ARC下由于不能使用release方法,解決辦法如下:

// 由于導(dǎo)航控制器持有viewController,所以viewController不會(huì)釋放
[self.navigationController pushViewController:viewController animated:true];
    
//release,這個(gè)時(shí)候viewController的retainCount = 1,也就是在導(dǎo)航控制器釋放的時(shí)候viewController也就會(huì)跟著進(jìn)行釋放
((void(*)(id,SEL))objc_msgSend)(viewController,NSSelectorFromString(@"release"));

樓主的使用場(chǎng)景

說(shuō)明一下樓主使用runtime的場(chǎng)景之一,既然問(wèn)題那么多,并且代碼這么麻煩,為什么還要用它呢,難道就是為了裝X嗎,實(shí)際上并不是這樣的。

比如一個(gè)viewController需要跳轉(zhuǎn)不同的控制器,由于樓主比較喜歡使用MVVM模型,每次處理完畢數(shù)據(jù)就要進(jìn)行一個(gè)回調(diào),跳入不同的控制器,如果有跳入100個(gè)不同的控制器的可能,那我豈不是要寫(xiě)N個(gè)回調(diào),但是如果是使用runtime,我貌似只使用一個(gè)或者根據(jù)情況使用幾個(gè)回調(diào),返回控制器的類名以及相應(yīng)的參數(shù)就好了吧。

下面是一個(gè)邏輯十分簡(jiǎn)單的Demo,有三個(gè)不同的按鈕,分別表示跳入三個(gè)不同的控制器,每點(diǎn)擊一個(gè)按鈕都會(huì)觸發(fā)ViewModel的方法(MVVM模型的數(shù)據(jù)是在ViewModel中的呢,ViewController只是作為一個(gè)View層,不會(huì)有任何的邏輯),由viewModel通過(guò)某些方法進(jìn)行數(shù)據(jù)處理完畢后進(jìn)行界面的回調(diào)。

簡(jiǎn)單的效果.git

控制器的viewModel很簡(jiǎn)單,如下:

RITLRootViewModel.h

NS_ASSUME_NONNULL_BEGIN

/// RITLRootViewController的viewModel對(duì)象
@interface RITLRootViewModel : NSObject

///buttonDidTapWithTag:觸發(fā)的block
@property (nonatomic, copy, nullable)void(^ButtonDidTapBlock)(NSString * controllerName);

/**
 根據(jù)不同的tag進(jìn)行響應(yīng)不同的事件,觸發(fā)ButtonDidTapBlock

 @param tag button的tag值
 */
- (void)buttonDidTapWithTag:(NSUInteger)tag;

@end

NS_ASSUME_NONNULL_END
RITLRootViewModel.m

@interface RITLRootViewModel ()

@property (nonatomic, copy)NSArray < NSString * > * controllerNames;

@end

@implementation RITLRootViewModel

-(instancetype)init
{
    if (self = [super init])
    {
        _controllerNames = @[@"RITLRootViewController",@"RITLViewControllerTwo",@"RITLViewControllerThree"];
    }
    
    return self;
}


-(void)buttonDidTapWithTag:(NSUInteger)tag
{
    NSUInteger realTag = tag - 10001;
    
    if (self.ButtonDidTapBlock)
    {
        self.ButtonDidTapBlock(self.controllerNames[realTag]);
    }
}

@end

只需要在button點(diǎn)擊的時(shí)候調(diào)用viewModel的buttonDidTapWithTag:方法即可:

- (IBAction)pushBtnDidTap:(id)sender
{
      UIButton * button = (UIButton *)sender;
      [self.viewModel buttonDidTapWithTag:button.tag];
 }

在viewDidLoad綁定好viewModel即可

- (void)bindViewModel
{
    __weak typeof(self) weakSelf = self;
    
    self.viewModel.ButtonDidTapBlock = ^(NSString * controllerName){
        
        __strong typeof(weakSelf) strongSelf = weakSelf;
        
        Class class = objc_getClass(controllerName.UTF8String);
        
        id viewController = ((id(*)(id,SEL))objc_msgSend)(class,NSSelectorFromString(@"new"));

        [strongSelf ritl_pushViewController:viewController];
    };
}

// 導(dǎo)航控制器獲得控制權(quán)后進(jìn)行release即可
- (void)ritl_pushViewController:(__kindof UIViewController *)viewController
{
    [self pushViewController:viewController];
    
    //release
    ((void(*)(id,SEL))objc_msgSend)(viewController,NSSelectorFromString(@"release"));
}

補(bǔ)充

這里附上此博文中的小demo,如果喜歡請(qǐng)Star支持一下 傳送門(mén)

我知道很多人提到MVVM首先會(huì)想到ReactiveCocoa或者RxSwfit,個(gè)人看來(lái),它們只是幫助我們更優(yōu)雅地實(shí)現(xiàn)MVVM模型,并不代表MVVM必須使用它們,畢竟MVVM是一種架構(gòu),一種思想。

快到新年了,樓主還要接著對(duì)項(xiàng)目進(jìn)行整改,也不知道會(huì)不會(huì)還有博文了,提前祝大家新年快樂(lè)啊。??

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容