iOS开发 Runtime 使用objc_msgSend()

2017-01-13  本文已影响330人  RITL

好几个月没有发表点东西了,但也一直关注简书上优秀的博文学习,漫长的这段时间一直在不断的重构公司项目,为了使代码尽可能的优雅,除了不断地进行组件化重写之外,很多地方也逐渐的开始使用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。

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

解决

上面的步骤看完之后,基本就能进行解决位置的定位了,那么我们在导航控制器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是一种架构,一种思想。

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

上一篇下一篇

猜你喜欢

热点阅读