Runtime总结(三)

322 阅读4分钟

目录

1.Runtime常用API

2.Runtime的应用

3.Runtime相关面试题


1.Runtime常用API

类相关:

 //动态创建一个类(参数:父类,类名,额外的内存空间)
 Class objc_allocateClassPair(<#Class  _Nullable __unsafe_unretained superclass#>, <#const char * _Nonnull name#>, <#size_t extraBytes#>)
 例:Class myClass = objc_allocateClassPair([NSObject class], "MyClass", 0);
 
 //注册一个类(要在类注册之前添加成员变量)
 void objc_registerClassPair(Class cls) 

 //销毁一个类
 void objc_disposeClassPair(Class cls)
 
 //获取isa指向的Class
 Class object_getClass(id obj)
 
 //设置isa指向的Class
 Class object_setClass(id obj, Class cls)
 
 //判断一个OC对象是否为Class
 BOOL object_isClass(id obj)
 
 //判断一个Class是否为元类
 BOOL class_isMetaClass(Class cls)
 
 //获取 父类
 Class class_getSuperclass(Class cls)
 

成员变量相关:

//获取一个实例变量信息
Ivar class_getInstanceVariable(Class cls, const char *name)

//拷贝实例变量列表(最后需要调用free释放)
Ivar *class_copyIvarList(Class cls, unsigned int *outCount)

//设置和获取成员变量的值
void object_setIvar(id obj, Ivar ivar, id value)
id object_getIvar(id obj, Ivar ivar)

//动态添加成员变量(已经注册的类是不能动态添加成员变量的)
BOOL class_addIvar(Class cls, const char * name, size_t size, uint8_t alignment, const char * types)

//获取成员变量的相关信息
const char *ivar_getName(Ivar v)
const char *ivar_getTypeEncoding(Ivar v)

属性相关:

//获取一个属性
objc_property_t class_getProperty(Class cls, const char *name)

//拷贝属性列表(最后需要调用free释放)
objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount)

//动态添加属性
BOOL class_addProperty(Class cls, const char *name, const objc_property_attribute_t *attributes,
                  unsigned int attributeCount)

//动态替换属性
void class_replaceProperty(Class cls, const char *name, const objc_property_attribute_t *attributes,
                      unsigned int attributeCount)

//获取属性的一些信息
const char *property_getName(objc_property_t property)
const char *property_getAttributes(objc_property_t property)

方法相关:

//获得一个实例方法、类方法
Method class_getInstanceMethod(Class cls, SEL name)
Method class_getClassMethod(Class cls, SEL name)

//方法实现相关操作
IMP class_getMethodImplementation(Class cls, SEL name) 
IMP method_setImplementation(Method m, IMP imp)
void method_exchangeImplementations(Method m1, Method m2) 

//拷贝方法列表(最后需要调用free释放)
Method *class_copyMethodList(Class cls, unsigned int *outCount)

//动态添加方法
BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)

//动态替换方法
IMP class_replaceMethod(Class cls, SEL name, IMP imp, const char *types)

//获取方法的相关信息(带有copy的需要调用free去释放)
SEL method_getName(Method m)
IMP method_getImplementation(Method m)
const char *method_getTypeEncoding(Method m)
unsigned int method_getNumberOfArguments(Method m)
char *method_copyReturnType(Method m)
char *method_copyArgumentType(Method m, unsigned int index)

//选择器相关
const char *sel_getName(SEL sel)
SEL sel_registerName(const char *str)

//用block作为方法实现
IMP imp_implementationWithBlock(id block)
id imp_getBlock(IMP anImp)
BOOL imp_removeBlock(IMP anImp)

2.Runtime的应用

1.给分类扩展属性

在某些场景系统类提供的属性并不能满足我们的业务需求,所以我们可以通过Runtime来实现给系统类添加属性,比如下面给UIButton添加一个字典属性:

//UIButton+Category.h
#import <UIKit/UIKit.h>
@interface UIButton (Category)

@property(nonatomic,strong)NSDictionary * dict;

@end

//UIButton+Category.m
#import "UIButton+Category.h"
#import <objc/runtime.h>

@implementation UIButton (Category)

-(void)setDict:(NSDictionary *)dict{
    objc_setAssociatedObject(self, @selector(dict), dict, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}


-(NSDictionary*)dict{
    return objc_getAssociatedObject(self, _cmd);
}
@end

//使用
#import "UIButton+Category.h"
- (void)viewDidLoad {
    [super viewDidLoad];
    UIButton * button = [UIButton buttonWithType: UIButtonTypeCustom];
    button.dict = @{@"key":@"value"};
    NSLog(@"%@",button.dict[@"key"]);  
}

这里使用了runtime的关联对象,具体原理可看这篇文章最后一节Category底层原理分析

2.遍历类的所有成员变量(字典转模型、自动归档解档)

通过runtime API获取类的变量名及KVC实现一个简单的字典转模型功能

//NSObject+Model.h
#import <Foundation/Foundation.h>
@interface NSObject (Model)

+ (instancetype)ModelWithDict:(NSDictionary *)dict;

- (void)transformDict:(NSDictionary *)dict;
@end

//NSObject+Model.m
#import "NSObject+Model.h"
#import <objc/runtime.h>

@implementation NSObject (Model)
+ (instancetype)ModelWithDict:(NSDictionary *)dict {
    NSObject * obj = [[self alloc]init];
    [obj transformDict:dict];
    return obj;
}
- (void)transformDict:(NSDictionary *)dict {
    Class cla = self.class;
    // count:成员变量个数
    unsigned int outCount = 0;
    // 获取成员变量数组
    Ivar *ivars = class_copyIvarList(cla, &outCount);
    // 遍历所有成员变量
    for (int i = 0; i < outCount; i++) {
        // 获取成员变量
        Ivar ivar = ivars[i];
        // 获取成员变量名字
        NSString *key = [NSString stringWithUTF8String:ivar_getName(ivar)];
        // 成员变量名转为属性名(去掉下划线 _ )
        key = [key substringFromIndex:1];
        // 取出字典的值
        id value = dict[key];
        // 如果模型属性数量大于字典键值对数理,模型属性会被赋值为nil而报错
        if (value == nil) continue;
        // 利用KVC将字典中的值设置到模型上
        [self setValue:value forKeyPath:key];
    }
    //需要释放指针,因为ARC不适用C函数
    free(ivars);
}
@end

//调用:
- (void)viewDidLoad {
    [super viewDidLoad];
    NSDictionary *dict = @{
        @"name" : @"lanlin",
        @"age" : @18,
        @"sex" : @"男"
    };
    Student * student = [Student ModelWithDict:dict];
    NSLog(@"%@,%d,%@",student.name,student.age,student.sex);
    //打印结果:lanlin,18,男
}

同样使用runtime的API也能应用于对象的归解档:

// 解档使用
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
    if (self = [super init]) {
        unsigned int count = 0;
        Ivar *ivars = class_copyIvarList([self class], &count);
        for (int i = 0; i < count; i++) {
        Ivar ivar = ivars[i];
        const char *name = ivar_getName(ivar);
        NSString *key = [NSString stringWithUTF8String:name];
        id value = [aDecoder decodeObjectForKey:key];
        if(value){
        [self setValue:value forKey:key];
       }
    }
    free(ivars);
  }
    return self;
}

// 归档使用
- (void)encodeWithCoder:(NSCoder *)aCoder {
    unsigned int count = 0;
    Ivar *ivars = class_copyIvarList([self class], &count);
    for (int i = 0; i < count; i++) {
        Ivar ivar = ivars[i];
        const char *name = ivar_getName(ivar);
        NSString *key = [NSString stringWithUTF8String:name];
        id value = [self valueForKey:key];
        if(value){
        [aCoder encodeObject:value forKey:key];
     }
   }
   free(ivars);
}
4.方法交换

关于方法交换详细内容可参考runtime原理与实践: 方法交换篇(Method Swizzling),这里引用了这篇文章中的例子做了分析.

统计VC加载次数并打印

UIViewController+Logging.m

#import "UIViewController+Logging.h"
#import <objc/runtime.h>

@implementation UIViewController (Logging)

// 使用时机:在+load方法进行
+ (void)load
{
    swizzleMethod([self class], @selector(viewDidAppear:), @selector(swizzled_viewDidAppear:));
}

- (void)swizzled_viewDidAppear:(BOOL)animated
{
    //注意:这里表面看是死循环,实际上方法已经被交换了
    //调用swizzled_viewDidAppear 实际上是调用了系统的 viewDidAppear
    [self swizzled_viewDidAppear:animated];
     // Logging
    NSLog(@"%@", NSStringFromClass([self class]));
}

//交换方法
void swizzleMethod(Class class, SEL originalSelector, SEL swizzledSelector)
{
    // 获取原来的方法和要交换的方法
    Method originalMethod = class_getInstanceMethod(class, originalSelector);
    Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
    
    // 判断方法是否被实现
    BOOL didAddMethod = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
    
    // 如果没有实现则添加一个方法
    if (didAddMethod) {
        class_replaceMethod(class, swizzledSelector,          method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
    }
    else {
        //交换两个方法
        method_exchangeImplementations(originalMethod, swizzledMethod);
    }
    
}
5.KVO的实现

可以参考之前写的文章,包含系统如何实现KVO原理与自己利用runtime相关API实现一个KVO
探究 OC对象、isa指针及KVO实现原理

6.利用消息转发机制解决方法找不到的异常问题

可参考上篇文章,有详细的分析,实现消息转发的相关方法即可。 Runtime总结(二)

3.Runtime相关面试题

  • 网上看了许多题,主要是围绕runtime的isa结构,类的结构,源码,消息转发,应用场景几个方面,下面举几个常见例子和个人感觉比较好的一篇面试题分享。

1.讲一下runtime的消息机制

2.消息转发机制流程是怎么样的

3.什么是runtime?项目中有用过吗?

Runtime经典面试题(附答案)