iOS 面试题(十):runtime 使用——(动态添加方法/动态交换方法/动态添加属性)

动态添加方法

应用场景:如果一个类方法非常多,加载类到内存的时候也比较耗费资源,需要给每个方法生成映射表,可以使用动态给某个类,添加方法解决。

注解:OC 中我们很习惯的会用懒加载,当用到的时候才去加载它,但是实际上只要一个类实现了某个方法,就会被加载进内存。当我们不想加载这么多方法的时候,就会使用到 runtime 动态的添加方法。

需求:runtime 动态添加方法处理调用一个未实现的方法 和 去除报错。

案例代码:方法+调用+打印输出

//调用

Person *pers=[[Person alloc]init];

[pers performSelector:@selector(eat)];

// void(*)()

// 默认方法都有两个隐式参数,

void eat(id self,SEL sel)

{

    NSLog(@"%@ %@",self,NSStringFromSelector(sel));

}


// 当一个对象调用未实现的方法,会调用这个方法处理,并且会把对应的方法列表传过来.

// 刚好可以用来判断,未实现的方法是不是我们想要动态添加的方法

+ (BOOL)resolveInstanceMethod:(SEL)sel

{

    if (sel == @selector(eat)) {

        // 动态添加eat方法

        

        // 第一个参数:给哪个类添加方法

        // 第二个参数:添加方法的方法编号

        // 第三个参数:添加方法的函数实现(函数地址)

        // 第四个参数:函数的类型,(返回值+参数类型) v:void @:对象->self :表示SEL->_cmd

        class_addMethod(self, @selector(eat), eat, "v@:");

    }

    return [super resolveInstanceMethod:sel];

}

//打印输出

2018-03-09 21:55:44.788062+0800 2[1489:107592] <Person: 0x60400001ae60> eat


runtime 交换方法

应用场景:当第三方框架 或者 系统原生方法功能不能满足我们的时候,我们可以在保持系统原有方法功能的基础上,添加额外的功能。

需求:加载一张图片直接用[UIImage imageNamed:@"image"];是无法知道到底有没有加载成功。给系统的imageNamed添加额外功能(是否加载图片成功)。

  • 方案一:继承系统的类,重写方法.(弊端:每次使用都需要导入)
  • 方案二:使用 runtime,交换方法.

实现步骤

  • 1.给系统的方法添加分类
  • 2.自己实现一个带有扩展功能的方法
  • 3.交换方法,只需要交换一次,

案例代码:方法+调用+打印输出

//调用

UIImage *image=[UIImage imageNamed:@"1"];


/**

 load方法: 把类加载进内存的时候调用,只会调用一次

 方法应先交换,再去调用

 */

+ (void)load {

    

    // 1.获取 imageNamed方法地址

    // class_getClassMethod(获取某个类的方法)

    Method imageNamedMethod = class_getClassMethod(self, @selector(imageNamed:));

    // 2.获取 ln_imageNamed方法地址

    Method ln_imageNamedMethod = class_getClassMethod(self, @selector(ln_imageNamed:));

    

    // 3.交换方法地址,相当于交换实现方式;「method_exchangeImplementations 交换两个方法的实现」

    method_exchangeImplementations(imageNamedMethod, ln_imageNamedMethod);

}


/**

 看清楚下面是不会有死循环的

 调用 imageNamed => ln_imageNamed

 调用 ln_imageNamed => imageNamed

 */

// 加载图片 且 带判断是否加载成功

+ (UIImage *)ln_imageNamed:(NSString *)name {

    

    UIImage *image = [UIImage ln_imageNamed:name];

    if (image) {

        NSLog(@"加载成功");

    } else {

        NSLog(@"加载失败");

    }

    return image;

}

//打印输出

2018-03-09 22:01:43.907363+0800 2[1553:112712] 加载成功


总结:我们所做的就是在方法调用流程第三步的时候,交换两个方法地址指向。而且我们改变指向要在系统的imageNamed:方法调用前,所以将代码写在了分类的load方法里。最后当运行的时候系统的方法就会去找我们的方法的实现。


runtime 给分类动态添加属性

原理:给一个类声明属性,其实本质就是给这个类添加关联,并不是直接把这个值的内存空间添加到类存空间。

应用场景:给系统的类添加属性的时候,可以使用runtime动态添加属性方法。
注解:系统 NSObject 添加一个分类,我们知道在分类中是不能够添加成员属性的,虽然我们用了@property,但是仅仅会自动生成getset方法的声明,并没有带下划线的属性和方法实现生成。但是我们可以通过runtime就可以做到给它方法的实现。

需求:给系统 NSObject 类动态添加属性 name 字符串。

案例代码:方法+调用+打印

//调用

- (void)loadPropert{

    image=[[UIImage alloc]init];

    image.names=@"huang.png";

    image.heights=@"123";

}

// @property分类:只会生成get,set方法声明,不会生成实现,也不会生成下划线成员属性

@property NSString *names;

@property NSString *heights;


@implementation UIImage (Property)

- (void)setNames:(NSString *)names {

    // objc_setAssociatedObject(将某个值跟某个对象关联起来,将某个值存储到某个对象中)

    // object:给哪个对象添加属性

    // key:属性名称

    // value:属性值

    // policy:保存策略

    objc_setAssociatedObject(self, @"names", names, OBJC_ASSOCIATION_RETAIN_NONATOMIC);

}


- (NSString *)names {

    return objc_getAssociatedObject(self, @"names");

}

- (void)setHeights:(NSString *)heights{

    objc_setAssociatedObject(self, @"heights", heights, OBJC_ASSOCIATION_RETAIN_NONATOMIC);

}

- (NSString *)heights{

    return objc_getAssociatedObject(self, @"heights");

}

//打印输出

2018-03-09 22:05:08.392957+0800 2[1620:116061] huang.png===123


总结:其实,给属性赋值的本质,就是让属性与一个对象产生关联,所以要给NSObject的分类的name属性赋值就是让nameNSObject产生关联,而runtime可以做到这一点。


猜你喜欢

转载自blog.csdn.net/huanglinxiao/article/details/79503565