好幾個(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)。

控制器的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è)啊。??