美文网首页
iOS runtime详解

iOS runtime详解

作者: 浅宇落 | 来源:发表于2019-08-09 16:00 被阅读0次

runtime的资料网上有很多了,部分有些晦涩难懂,我通过自己的学习方法总结一遍,主要讲一些常用的方法功能,以实用为主,我觉得用到印象才是最深刻的.

案例1:方法简单的交换

需要用到的头文件 <objc/runtime.h>

  • 获得某个类的类方法
Method class_getClassMethod(Class cls , SEL name)
  • 获得某个类的实例对象方法
Method class_getInstanceMethod(Class cls , SEL name)
  • 交换两个方法的实现
void method_exchangeImplementations(Method m1 , Method m2)
示例
  • 创建一个Person类
#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface Person : NSObject
+ (void)run;
+ (void)study;
@end

NS_ASSUME_NONNULL_END


#import "Person.h"

@implementation Person
+ (void)run {
    NSLog(@"跑");
}
+ (void)study {
    NSLog(@"学习");
}
@end

// 调用
 [Person run];
 [Person study];
// 结果: 跑    学习
  • 方法简单的交换
    // 获取两个类的类方法
    Method m1 = class_getClassMethod([Person class], @selector(run));
    Method m2 = class_getClassMethod([Person class], @selector(study));
    // 开始交换方法实现
    method_exchangeImplementations(m1, m2);
    
    [Person run];
    [Person study];
    // 结果: 学习   跑

案例2:拦截系统方法

  • 创建一个UIImage分类
#import <UIKit/UIKit.h>

NS_ASSUME_NONNULL_BEGIN

@interface UIImage (Extension)

@end

NS_ASSUME_NONNULL_END


#import "UIImage+Extension.h"
#import <objc/runtime.h>

@implementation UIImage (Extension)

+ (void)load {
    // 获取两个类的类方法
    Method m1 = class_getClassMethod([UIImage class], @selector(imageNamed:));
    Method m2 = class_getClassMethod([UIImage class], @selector(LZY_imageNamed:));
    // 开始交换方法实现
    method_exchangeImplementations(m1, m2);
}


+ (UIImage *)LZY_imageNamed:(NSString *)name {
    double version = [[UIDevice currentDevice].systemVersion doubleValue];
    if (version >= 7.0) {
        // 如果系统版本是7.0以上,使用另外一套文件名结尾是‘_os7’的扁平化图片
        name = [name stringByAppendingString:@"_os7"];
    }
    return [UIImage LZY_imageNamed:name];
}
@end

    // 调用
    UIImageView *imageView = [[UIImageView alloc] init];
    imageView.image = [UIImage imageNamed:@"头像"];
    imageView.frame = CGRectMake(100, 100, 200, 200);
    [self.view addSubview:imageView];
未替换方法之前.png
替换方法之后.png

案例3:在分类中设置属性,给任何一个对象设置属性

  • set方法,将值value 跟对象object 关联起来(将值value 存储到对象object 中)
    参数 object:给哪个对象设置属性
    参数 key:一个属性对应一个Key,将来可以通过key取出这个存储的值,key 可以是任何类型:double、int 等,建议用char 可以节省字节
    参数 value:给属性设置的值
    参数policy:存储策略 (assign 、copy 、 retain就是strong)
void objc_setAssociatedObject(id object , const void *key ,id value ,objc_AssociationPolicy policy)
  • 利用参数key 将对象object中存储的对应值取出来
id objc_getAssociatedObject(id object , const void *key)
  • 创建一个Person分类
#import "Person.h"

NS_ASSUME_NONNULL_BEGIN

@interface Person (Extension)
/** 昵称 */
@property (nonatomic, copy) NSString *name;
@end

NS_ASSUME_NONNULL_END


#import "Person+Extension.h"
#import <objc/runtime.h>

@implementation Person (Extension)

char nameKey;

- (void)setName:(NSString *)name {
    // 将某个值跟某个对象关联起来,将某个值存储到某个对象中
    objc_setAssociatedObject(self, &nameKey, name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)name {
    return objc_getAssociatedObject(self, &nameKey);
}

@end


// 调用
Person *p = [[Person alloc] init];
p.name = @"小王龙王";
NSLog(@"昵称: %@", p.name);
// 结果: 小王龙王

案例4: 在分类中添加方法

/**
 定义方法

 @param self 本身对象
 @param sel 定义的方法
 @param param 参数
 */
void eat(id self, SEL sel, NSString *param) {
    NSLog(@"调用eat 参数1:%@ 参数2:%@ 参数3:%@", self, NSStringFromSelector(sel), param);
}

/**
 定义方法
 
 @param self 本身对象
 @param sel 定义的方法
 */
void run(id self, SEL sel) {
    NSLog(@"调用run 参数1:%@ 参数2:%@", self, NSStringFromSelector(sel));
}
/**
 添加对象方法

 @param sel 定义的方法
 @return 返回是否添加成功
 */
+ (BOOL)resolveInstanceMethod:(SEL)sel {
    if (sel == @selector(eat:)) {
        
        /**
         动态添加方法

          self 为哪个类添加方法
          sel 添加方法的方法编号(方法名)是什么
          IMP 方法实现
          返回是否添加成功
         
         最后一个参数为"v@:@“, v:返回void, i:int ,f:double, c:unsigned char等等
         */
        
        BOOL isSuccess = class_addMethod(self, sel, (IMP)eat, "v@:@");
        
        return isSuccess;
    }else if (sel == @selector(run)) {
        
        BOOL isSuccess = class_addMethod(self, sel, (IMP)run, "v@:");
        
        return isSuccess;
    }
    
    return [super resolveInstanceMethod:sel];
}

// 调用
// 带参数
Person *p = [[Person alloc] init];
[p performSelector:@selector(eat:) withObject:@"吃牛排"]; 
// 结果: 用eat 参数1:<Person> 参数2:eat: 参数3:吃牛排

// 无参
[p performSelector:@selector(run)]; 
// 结果: 用run 参数1:<Person> 参数2:run

案例5:获得一个类的所有成员变量

  • 获得某个类的所有成员变量(outCount 会返回成员变量的总数)
    参数:
    1、哪个类
    2、放一个接收值的地址,用来存放属性的个数
    3、返回值:存放所有获取到的属性,通过下面两个方法可以调出名字和类型
Ivar *class_copyIvarList(Class cls , unsigned int *outCount)
  • 获得成员变量的名字
const char *ivar_getName(Ivar v)
  • 获得成员变量的类型
const char *ivar_getTypeEndcoding(Ivar v)
获取Person类中所有成员变量的名字和类型
unsigned int outCount = 0;
Ivar *ivars = class_copyIvarList([Person class], &outCount);

// 遍历所有成员变量
for (int i = 0; i < outCount; i++) {
    // 取出i位置对应的成员变量
    Ivar ivar = ivars[i];
    const char *name = ivar_getName(ivar);
    const char *type = ivar_getTypeEncoding(ivar);
    NSLog(@"成员变量名:%s 成员变量类型:%s",name,type);
}
// 注意释放内存!
free(ivars);

6. 根据类对象获取类名

NSString *name = [NSString stringWithUTF8String:object_getClassName(类对象)];
// 结果: 类对象的类名

7. 获取类的父类

导入 <objc/runtime.h> 头文件

#import "BaseVC.h"

@interface NotificationVC : BaseVC

@end

// 测试
Class class = class_getSuperclass([self class]);
NSLog(@"测试: %@ ", class);
// 结果: 测试: BaseVC


8.获取实例大小

size_t result = class_getInstanceSize([self class]);
NSLog(@"测试: %zu ", result);
// 结果: 测试: 1024

相关文章

网友评论

      本文标题:iOS runtime详解

      本文链接:https://www.haomeiwen.com/subject/ugjhjctx.html