iOS开发 Runtime 使用objc_msgSend()创建对象不能释放的问题

好几个月没有发表点东西了,但也一直关注简书上优秀的博文学习,漫长的这段时间一直在不断的重构公司项目,为了使代码尽可能的优雅,除了不断地进行组件化重写之外,很多地方也逐渐的开始使用runtime进行编程。说实话,这样编程比较爽,却会出现一系列之前没有考虑过的问题,比如这篇文章记录的使用objc_msgSend()创建对象不能释放的问题。

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

普遍的初始化

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

//或者

UIViewController * viewController = [UIViewController new];

楼主比较喜欢后面一种,因为毕竟能少打几个字母嘛,当然这不是重点。

runtime的初始化

Class class = objc_getClass("UIViewController");

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

两者对比一下,貌似使用runtime要更加的麻烦,因为它甚至需要两行比较长代码来完成一个控制器的初始化,至于为什么选择runtime后面会说一下个人的看法,如果非要说装X - - (我也说不了什么其实😀)。

问题

先来说明一下问题,利用Demo中的Let's push oneSelf来不断的push与pop当前控制器类的实例对象, 查看当前实例对象的个数,这个很简单,在类中定义一个全局变量来记录当前类对象实力对象的个数

static NSUInteger classValue = 0;

RITLRootViewController控制器的ViewDidLoad以及deallco方法中分别对数字进行++或者--操作,并打印当前类的个数:

使用普通的初始化方法以及打印结果:

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

//result:
现在存在1个RITLRootViewController
现在存在2个RITLRootViewController
现在存在1个RITLRootViewController
现在存在2个RITLRootViewController
现在存在1个RITLRootViewController
现在存在2个RITLRootViewController
现在存在1个RITLRootViewController

可以看出,数字在viewDidLoad中进行了累加,在dealloc中进行了累减,这样是平衡的。

使用runtime方法初始化打出结果:

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

//result:
现在存在1个RITLRootViewController
现在存在2个RITLRootViewController
现在存在3个RITLRootViewController
现在存在4个RITLRootViewController
现在存在5个RITLRootViewController

对象的个数是不断上升的,间接地告诉我们实例对象是没有释放掉的。

原因

用群里一个朋友的话说:"C语言的方式还要用C语言来解决"。

runtime大家都知道它就是C/C++ , 再直接一点就是直接使用runtime执行初始化方法创建的对象的时候是不在ARC控制之下的,也就是说在对应创建的那个文件里销毁的时候需要我们自行release。

楼主分析的小过程,很简单:

  • 初始化完毕之后,当前对象的retainCount = 1。
  • 导航控制器push的时候,ARC下的编译器帮我们进行了一次retain,当前对象的retainCount = 2。
  • 导航控制器pop的时候,ARC下的编译器又帮我们进行了一次release,当前对象的retainCount = 1。
  • 此时当前控制器的retainCount恒为非0正整数,无法释放。

解决

上面的步骤看完之后,基本就能进行解决位置的定位了,那么我们在导航控制器retain之后进行自身的release即可,ARC下由于不能使用release方法,解决办法如下:

// 由于导航控制器持有viewController,所以viewController不会释放
[self.navigationController pushViewController:viewController animated:true];
    
//release,这个时候viewController的retainCount = 1,也就是在导航控制器释放的时候viewController也就会跟着进行释放
((void(*)(id,SEL))objc_msgSend)(viewController,NSSelectorFromString(@"release"));

楼主的使用场景

说明一下楼主使用runtime的场景之一,既然问题那么多,并且代码这么麻烦,为什么还要用它呢,难道就是为了装X吗,实际上并不是这样的。

比如一个viewController需要跳转不同的控制器,由于楼主比较喜欢使用MVVM模型,每次处理完毕数据就要进行一个回调,跳入不同的控制器,如果有跳入100个不同的控制器的可能,那我岂不是要写N个回调,但是如果是使用runtime,我貌似只使用一个或者根据情况使用几个回调,返回控制器的类名以及相应的参数就好了吧。

下面是一个逻辑十分简单的Demo,有三个不同的按钮,分别表示跳入三个不同的控制器,每点击一个按钮都会触发ViewModel的方法(MVVM模型的数据是在ViewModel中的呢,ViewController只是作为一个View层,不会有任何的逻辑),由viewModel通过某些方法进行数据处理完毕后进行界面的回调。

简单的效果.git

控制器的viewModel很简单,如下:

RITLRootViewModel.h

NS_ASSUME_NONNULL_BEGIN

/// RITLRootViewController的viewModel对象
@interface RITLRootViewModel : NSObject

///buttonDidTapWithTag:触发的block
@property (nonatomic, copy, nullable)void(^ButtonDidTapBlock)(NSString * controllerName);

/**
 根据不同的tag进行响应不同的事件,触发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点击的时候调用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];
    };
}

// 导航控制器获得控制权后进行release即可
- (void)ritl_pushViewController:(__kindof UIViewController *)viewController
{
    [self pushViewController:viewController];
    
    //release
    ((void(*)(id,SEL))objc_msgSend)(viewController,NSSelectorFromString(@"release"));
}

补充

这里附上此博文中的小demo,如果喜欢请Star支持一下 传送门

我知道很多人提到MVVM首先会想到ReactiveCocoa或者RxSwfit,个人看来,它们只是帮助我们更优雅地实现MVVM模型,并不代表MVVM必须使用它们,毕竟MVVM是一种架构,一种思想。

快到新年了,楼主还要接着对项目进行整改,也不知道会不会还有博文了,提前祝大家新年快乐啊。😀

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,911评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,014评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 142,129评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,283评论 1 264
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,159评论 4 357
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,161评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,565评论 3 382
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,251评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,531评论 1 292
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,619评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,383评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,255评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,624评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,916评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,199评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,553评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,756评论 2 335

推荐阅读更多精彩内容