美文网首页
iOS面试题-Runtime

iOS面试题-Runtime

作者: 一亩三分甜 | 来源:发表于2020-08-16 00:29 被阅读0次

Runtime

一、消息机制:对象根据方法编号SEL去映射表查找对应的方法实现。

/*
说明:
eat(无参) 和 run(有参NSInteger) 是 LNPerson模型类中的私有方法「runtime 作用:可以调用私有方法」
示例分别以 OC写法 和 最底层写法 对照验证.
 */
- (void)msgSend
{
    // 方法一:
    //id objc = [NSObject alloc];
    LNPerson *person = objc_msgSend(objc_getClass("LNPerson"), sel_registerName("alloc"));

    //objc = [objc init];
    person = objc_msgSend(person, sel_registerName("init"));

    // 调用
    //[objc eat];
    //[objc run:10];
    objc_msgSend(person,@selector(eat)); // 无参
    objc_msgSend(person,@selector(run:),10); // 有参
}
/*
 注解:
    // 用最底层写
    objc_getClass(const char *name) 获取当前类
    sel_registerName(const char *str) 注册个方法编号
    objc_msgSend(id self:谁发送消息, SEL op:发送什么消息, ...)
    让LNPerson这个类对象发送了一个alloc消息,返回一个分配好的内存对象给你,再发送一个消息初始化.
 */
// 方法二:
#pragma mark - 也许下面这种好理解一点
- (void)test
{
    // id objc = [NSObject alloc];
    id objc = objc_msgSend([NSObject class], @selector(alloc));

    // objc = [objc init];
    objc = objc_msgSend(objc, @selector(eat));

}

objc_msgSend参数概念

 objc_msgSend(<#id  _Nullable self#>, <#SEL  _Nonnull op, ...#>)

 1、objc_msgSend
    这是个最基本的用于发送消息的函数。
    其实编译器会根据情况在`objc_msgSend`, `objc_msgSend_stret`,,`objc_msgSendSuper`, 或 `objc_msgSendSuper_stret` 四个方法中选择一个来调用。如果消息是传递给超类,那么会调用名字带有 `Super` 的函数;如果消息返回值是数据结构而不是简单值时,那么会调用名字带有`stret`的函数。

 2、SEL
    `objc_msgSend`函数第二个参数类型为`SEL`,它是`selector`在Objc中的表示类型(Swift中是Selector类)。`selector`是方法选择器,可以理解为区分方法的 `ID`,而这个 `ID` 的数据结构是`SEL`:
    `typedef struct objc_selector *SEL;`
    其实它就是个映射到方法的C字符串,你可以用 Objc 编译器命令`@selector()``或者 Runtime` 系统的`sel_registerName`函数来获得一个`SEL`类型的方法选择器。

 3、id
    `objc_msgSend`第一个参数类型为`id`,大家对它都不陌生,它是一个指向类实例的指针:
    `typedef struct objc_object *id;`
    那`objc_object`又是啥呢:
    `struct objc_object { Class isa; };`
    `objc_object`结构体包含一个`isa`指针,根据`isa`指针就可以顺藤摸瓜找到对象所属的类。

消息机制方法调用流程

面试:消息机制方法调用流程❓
怎么去调用eat方法,
对象方法:(保存到类对象的方法列表) ,类方法:(保存到元类(Meta Class)中方法列表)。

1.OC 在向一个对象发送消息时,runtime 库会根据对象的 isa指针找到该对象对应的类或其父类中查找方法。。
2.注册方法编号(这里用方法编号的好处,可以快速查找)。
3.根据方法编号去查找对应方法。
4.找到只是最终函数实现地址,根据地址去方法区调用对应函数。

补充:一个objc 对象的 isa 的指针指向什么?有什么作用?
每一个对象内部都有一个isa指针,这个指针是指向它的真实类型,根据这个指针就能知道将来调用哪个类的方法。

Demo地址

Runtime常见作用

/*
 1.动态交换两个方法的实现
 2.动态添加属性
 3.实现字典转模型的自动转换
 4.动态添加方法
 5.拦截并替换方法
 6.实现 NSCoding 的自动归档和解档

补充常用runtime示例:Demo中有体现
    1.添加属性和交换方法示例:UITextField占位文字颜色placeholderColor
    2.交换方法示例:交换dealloc方法实现,添加功能那个控制器被销毁了
 */

1、runtime交换方法

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

需求:加载一张图片直接用[UIImage imageNamed:@"image"];是无法知道到底有没有加载成功。给系统的imageNamed添加额外功能(是否加载图片成功)。
方案一:继承系统的类,重写方法.(弊端:每次使用都需要导入)
方案二:使用 runtime,交换方法.

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

场景代码:方法+调用+打印输出

/*
作用:把类加载进内存的时候调用,只会调用一次
调用:方法应先交换,再去调用
*/
+ (void)load {

    // 1.获取 imageNamed方法地址
    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(@"runtime交互方法 -> 图片加载成功");
    } else {
        NSLog(@"runtime交互方法 -> 图片加载失败");
    }
    return image;
}
/*
注解:
   不能在分类中重写系统方法imageNamed,因为会把系统的功能给覆盖掉,而且分类中不能调用super
   所以第二步,我们要 自己实现一个带有扩展功能的方法.
+ (UIImage *)imageNamed:(NSString *)name {

}

    self.imageView.image = [UIImage imageNamed:@"cloud.jpeg"];
    [self.view addSubview:self.imageView];
    
    2020-07-18 11:51:31.425229+0800 RuntimeExchangeMethod[29829:5349976] runtime交互方法 -> 图片加载成功
*/

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

Demo地址

2、给系统分类动态添加属性

场景:给系统的类添加额外属性的时候,可以使用runtime动态添加属性方法。
原理:给一个类声明属性,其实本质就是给这个类添加关联,并不是直接把这个值的内存空间添加到类存空间。
注解:给系统 NSObject 添加一个分类,我们知道在分类中是不能够添加成员属性的,虽然我们用了@property,但是仅仅会自动生成get和set方法的声明,并没有带下划线的属性和方法实现生成。但是我们可以通过runtime就可以做到给它方法的实现。

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

场景代码:方法+调用+打印

@interface NSObject (Property)
@property NSString *name;
@end

#import "NSObject+Property.h"
#import <objc/message.h>
@implementation NSObject (Property)
-(NSString *)name{
    // 利用参数key 将对象object中存储的对应值取出来
    return objc_getAssociatedObject(self, @"name");
}
- (void)setName:(NSString *)name
{
    /**
     将某个值跟某个对象关联起来,将某个值存储到某个对象中
     objc_setAssociatedObject(<#id  _Nonnull object#>:给哪个对象添加属性, <#const void * _Nonnull key#>:属性名称, <#id  _Nullable value#>:属性值, <#objc_AssociationPolicy policy#>:保存策略)
     */
    objc_setAssociatedObject(self, @"name", name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    NSLog(@"name---->%p",name);
}

        // 调用
        NSObject *objc = [[NSObject alloc] init];
        objc.name = @"CoderLN";
        NSLog(@"runtime动态添加属性name==%@",objc.name);
        
2020-07-18 12:20:52.360374+0800 RuntimeCategory[48090:5398907] name---->0x1000010c0
2020-07-18 12:20:52.361810+0800 RuntimeCategory[48090:5398907] runtime动态添加属性name==CoderLN

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

3、字典转模型

字典转模型的方式:

给模型中属性,在 .m 依次赋值(初学者)。

字典转模型 KVC 实现

KVC 字典转模型弊端:必须保证,模型中的属性和字典中的key 一一对应。
如果不一致,就会调用[ setValue:forUndefinedKey:] 报key找不到的错。
分析:模型中的属性和字典的key不一一对应,系统就会调用setValue:forUndefinedKey:报错。
解决:重写对象的setValue:forUndefinedKey:,把系统的方法覆盖,就能继续使用KVC,字典转模型了。
字典转模型 Runtime 实现

思路:利用运行时,遍历模型中所有属性,根据模型的属性名,去字典中查找key,取出对应的值,给模型的属性赋值(从提醒:字典中取值,不一定要全部取出来);提供一个NSObject分类,专门字典转模型,以后所有模型都可以通过这个分类实现字典转模型。

考虑情况:
1.当字典的key和模型的属性匹配不上。
2.模型中嵌套模型(模型属性是另外一个模型对象)。
3.数组中装着模型(模型的属性是一个数组,数组中是一个个模型对象)。

注解:
根据上面的三种特殊情况,先是字典的key和模型的属性不对应的情况。不对应有两种,一种是字典的键值大于模型属性数量,这时候我们不需要任何处理,因为runtime是先遍历模型所有属性,再去字典中根据属性名找对应值进行赋值,多余的键值对也当然不会去看了;另外一种是模型属性数量大于字典的键值对,这时候由于属性没有对应值会被赋值为nil,就会导致crash,我们只需加一个判断即可。考虑三种情况下面一一注解

MJExtension 字典转模型实现

底层也是对 runtime 的封装,才可以把一个模型中所有属性遍历出来。(我之所以看不懂,是MJ封装了很多层而已^_^.)。
示例:runtime 字典转模型考虑三种情况
        //定义字典
        NSDictionary *modelDict = @{
                @"attitudes":@1,
                @"comments":@0,
                @"created":@"刚刚",
                @"idstr":@"3824316137609270",
                @"picUrls":@[@{
                    @"thumbnail_pic":@"https://www.baidu.com/thumnnail/bd297795gw1eqhwikf145g208c0b41"
                }],
                @"reposts":@0,
                @"source":@"来自即可笔记",
                @"text":@"这是我见过的剪刀中。。最丧心病狂的。。。",
                @"user":@{
                    @"mbrank":@6,
                    @"mbtype":@12,
                    @"name":@"嘴巴笑抽筋了",
                    @"profile_image_url":@"http://tp2.sinaimg.cn/265548536",
                    @"vip":@YES
                }
        };

1.runtime 字典转模型-->字典的 key 和模型的属性不匹配「模型属性数量大于字典键值对数」,这种情况处理如下:

// 思路:利用runtime 遍历模型中所有属性,根据模型中属性,去字典中取出对应的value给模型属性赋值
+ (instancetype)modelWithDict:(NSDictionary *)dict
{
    // 1.创建对应的对象
    id objc = [[self alloc] init];
    
    // 2.利用runtime给对象中的属性赋值
    /**
     获取类中的所有成员变量
     class_copyIvarList(Class _Nullable cls:表示获取哪个类中的成员变量, unsigned int * _Nullable outCount:表示这个类有多少成员变量,传入一个Int变量地址,会自动给这个变量赋值)
     返回值Ivar * =
     指的是一个ivar数组,会把所有成员属性放在一个数组中,通过返回的数组就能全部获取到
     */
    // 成员变量个数
    unsigned int count = 0;
    // 获取类中的所有成员变量
    Ivar *ivarList = class_copyIvarList(self, &count);
    
    // 遍历所有成员变量
    for (int i = 0; i < count; i++) {
        // 根据角标,从数组取出对应的成员变量(Ivar:成员变量,以下划线开头)
        Ivar ivar = ivarList[i];
        
        // 获取成员变量名字
        NSString *ivarName = [NSString stringWithUTF8String:ivar_getName(ivar)];
        
        // 处理成员变量名,字典中的key(去掉 _ ,从第一个角标开始截取)
        NSString *key = [ivarName substringFromIndex:1];
        
        // 根据成员属性名去字典中查找对应的value
        id value = dict[key];
        
        //【如果模型属性数量大于字典键值对数量,模型属性会被赋值为nil】
        // 而报错 (could not set nil as the value for the key age.)
        if (value) {
            // 给模型中属性赋值
            [objc setValue:value forKey:key];
        }
    }
    return objc;
}

注解:
这里在获取模型类中的所有属性名,是采取 class_copyIvarList 先获取成员变量(以下划线开头) ,然后再处理成员变量名,字典中的key(去掉 _ ,从第一个角标开始截取) 得到属性名。
原因:

{
    int _a; // 成员变量
}
@property (nonatomic, assign) NSInteger attitudes_count; // 属性

`Ivar:成员变量,以下划线开头`,
`Property 属性`
`class_copyPropertyList` 获取类里面属性 
`class_copyIvarList` 获取类中的所有成员变量 
这里有成员变量,就不会漏掉属性;如果有属性,可能会漏掉成员变量;
使用`runtime`字典转模型获取模型属性名的时候,最好获取成员属性名`Ivar`因为可能会有个属性是没有`setter`和`getter`方法的。

@interface Blogs : NSObject
{
    int _a;
}
@property(nonatomic,strong)NSNumber *attitudes;
@property(nonatomic,strong)NSNumber *comments;
@property(nonatomic,copy)NSString *created;
@property(nonatomic,copy)NSString *idstr;
@property(nonatomic,strong)NSArray *picUrls;
@property(nonatomic,strong)NSNumber *reposts;
@property(nonatomic,copy)NSString *source;
@property(nonatomic,copy)NSString *text;
@property(nonatomic,strong)NSDictionary *user;
@property(nonatomic,copy)NSString *password;
@end

    // 成员变量个数
    unsigned int count = 0;
    // 获取类中的所有成员变量
    Ivar *ivarList = class_copyIvarList(self, &count);
    NSLog(@"对象成员变量个数:----------%d------------",count);
    //获取所有属性
    Ivar *ivarPropertyList = class_copyPropertyList(self, &count);
    
    NSLog(@"对象属性个数:----------%d------------",count);
    
    2020-07-18 17:52:49.916341+0800 RuntimeCategory[88395:5752217] 对象成员变量个数:----------11------------
    2020-07-18 17:52:49.916393+0800 RuntimeCategory[88395:5752217] 对象属性个数:----------10------------
    
    //定义字典
        NSDictionary *modelDict = @{
                @"attitudes":@1,
                @"comments":@0,
                @"created":@"刚刚",
                @"idstr":@"3824316137609270",
                @"picUrls":@[@{
                    @"thumbnail_pic":@"https://www.baidu.com/thumnnail/bd297795gw1eqhwikf145g208c0b41"
                }],
                @"reposts":@0,
                @"source":@"来自即可笔记",
                @"text":@"这是我见过的剪刀中。。最丧心病狂的。。。",
                @"user":@{
                    @"mbrank":@6,
                    @"mbtype":@12,
                    @"name":@"嘴巴笑抽筋了",
                    @"profile":@"http://tp2.sinaimg.cn/265548536",
                    @"vip":@YES
                }
        };
        Blogs *blogs = [Blogs modelWithDict:modelDict];
        NSLog(@"%@\n,%@\n,%@\n,%@\n,%@\n,%@\n,%@\n,%@\n,%@\n,%@",blogs.attitudes,blogs.comments,blogs.created,blogs.idstr,blogs.picUrls,blogs.reposts,blogs.source,blogs.text,blogs.user);
        
2020-07-18 18:15:36.494095+0800 RuntimeCategory[2553:5785228] 1
,0
,刚刚
,3824316137609270
,(
        {
        "thumbnail_pic" = "https://www.baidu.com/thumnnail/bd297795gw1eqhwikf145g208c0b41";
    }
)
,0
,来自即可笔记
,这是我见过的剪刀中。。最丧心病狂的。。。
,{
    mbrank = 6;
    mbtype = 12;
    name = "\U5634\U5df4\U7b11\U62bd\U7b4b\U4e86";
    profile = "http://tp2.sinaimg.cn/265548536";
    vip = 1;
}
,{
    mbrank = 6;
    mbtype = 12;
    name = "\U5634\U5df4\U7b11\U62bd\U7b4b\U4e86";
    profile = "http://tp2.sinaimg.cn/265548536";
    vip = 1;
}

2.runtime 字典转模型-->模型中嵌套模型「模型属性是另外一个模型对象」,这种情况处理如下:

// 思路:利用runtime 遍历模型中所有属性,根据模型中属性,去字典中取出对应的value给模型属性赋值
+ (instancetype)modelWithDict2:(NSDictionary *)dict{
    // 1.创建对应的对象
    id objc = [[self alloc] init];

    // 2.利用runtime给对象中的属性赋值
    // 成员变量个数
    unsigned int count = 0;
    // 获取类中的所有成员变量
    Ivar *ivarList = class_copyIvarList(self, &count);

    // 遍历所有成员变量
    for (int i = 0; i < count; i++) {
        // 根据角标,从数组取出对应的成员变量(Ivar:成员变量,以下划线开头)
        Ivar ivar = ivarList[i];

        // 获取成员变量名字
        NSString *ivarName = [NSString stringWithUTF8String:ivar_getName(ivar)];
        // 获取成员变量类型
        NSString *ivarType = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)];

        // 替换: @\"User\" -> User
        ivarType = [ivarType stringByReplacingOccurrencesOfString:@"\"" withString:@""];
        ivarType = [ivarType stringByReplacingOccurrencesOfString:@"@" withString:@""];

        // 处理成员变量名->字典中的key(去掉 _ ,从第一个角标开始截取)
        NSString *key = [ivarName substringFromIndex:1];

        // 根据成员属性名去字典中查找对应的value
        id value = dict[key];

        // 二级转换:如果字典中还有字典,也需要把对应的字典转换成模型
        // 判断下value是否是字典,并且是自定义对象才需要转换
        if ([value isKindOfClass:[NSDictionary class]] && ![ivarType hasPrefix:@"NS"]) {

            // 字典转换成模型 userDict => User模型, 转换成哪个模型
            // 根据字符串类名生成类对象
            Class modelClass = NSClassFromString(ivarType);

            if (modelClass) { // 有对应的模型才需要转
                // 把字典转模型
                value = [modelClass modelWithDict2:value];
            }
        }

        // 给模型中属性赋值
        if (value) {
            [objc setValue:value forKey:key];
        }
    }
    return objc;
}

BlogsModel *blogsModel = [BlogsModel modelWithDict2:modelDict];
NSLog(@"-----------%@\n,%@\n,%@\n,%@\n,%@\n,%@\n,%@\n,%@\n,%@\n",blogsModel.attitudes,blogsModel.comments,blogsModel.created,blogsModel.idstr,blogsModel.picUrls,blogsModel.reposts,blogsModel.source,blogsModel.text,blogsModel.user);
        NSLog(@"%@,%@,%@,%@,%@",blogsModel.user.mbrank,blogsModel.user.mbtype,blogsModel.user.name,blogsModel.user.profile,blogsModel.user.vip);
        
        -----------1
,0
,刚刚
,3824316137609270
,(
        {
        "thumbnail_pic" = "https://www.baidu.com/thumnnail/bd297795gw1eqhwikf145g208c0b41";
    }
)
,0
,来自即可笔记
,这是我见过的剪刀中。。最丧心病狂的。。。
,<User: 0x10068e100>
2020-07-18 21:48:42.337253+0800 RuntimeCategory[88304:5990538] 6,12,嘴巴笑抽筋了,http://tp2.sinaimg.cn/265548536,1

3.runtime 字典转模型-->数组中装着模型「模型的属性是一个数组,数组中是字典模型对象」,这种情况处理如下:

// 思路:利用runtime 遍历模型中所有属性,根据模型中属性,去字典中取出对应的value给模型属性赋值
+ (instancetype)modelWithDict3:(NSDictionary *)dict
{
    // 1.创建对应的对象
    id objc = [[self alloc] init];

    // 2.利用runtime给对象中的属性赋值
    // 成员变量个数
    unsigned int count = 0;
    // 获取类中的所有成员变量
    Ivar *ivarList = class_copyIvarList(self, &count);

    // 遍历所有成员变量
    for (int i = 0; i < count; i++) {
        // 根据角标,从数组取出对应的成员变量(Ivar:成员变量,以下划线开头)
        Ivar ivar = ivarList[i];

        // 获取成员变量名字
        NSString *ivarName = [NSString stringWithUTF8String:ivar_getName(ivar)];

        // 处理成员属性名->字典中的key(去掉 _ ,从第一个角标开始截取)
        NSString *key = [ivarName substringFromIndex:1];

        // 根据成员属性名去字典中查找对应的value
        id value = dict[key];


        //--------------------------- <#我是分割线#> ------------------------------//
        //

        // 三级转换:NSArray中也是字典,把数组中的字典转换成模型.
        // 判断值是否是数组
        if ([value isKindOfClass:[NSArray class]]) {
            // 判断对应类有没有实现字典数组转模型数组的协议
            // arrayContainModelClass 提供一个协议,只要遵守这个协议的类,都能把数组中的字典转模型
            if ([self respondsToSelector:@selector(arrayContainModelClass)]) {

                // 转换成id类型,就能调用任何对象的方法
                id idSelf = self;

                // 获取数组中字典对应的模型
                NSString *type =  [idSelf arrayContainModelClass][key];

                // 生成模型
                Class classModel = NSClassFromString(type);
                NSMutableArray *arrM = [NSMutableArray array];
                // 遍历字典数组,生成模型数组
                for (NSDictionary *dict in value) {
                    // 字典转模型
                    id model =  [classModel modelWithDict3:dict];
                    [arrM addObject:model];
                }

                // 把模型数组赋值给value
                value = arrM;     
            }
        }

        // 如果模型属性数量大于字典键值对数理,模型属性会被赋值为nil,而报错
        if (value) {
            // 给模型中属性赋值
            [objc setValue:value forKey:key];
        }
    }
    return objc;
}
        //定义字典
        NSDictionary *modelDict = @{
                @"tendency_direction":@"all",
                @"type":@"网站",
                @"field":@"文字",
                @"attitudes":@1,
                @"comments":@0,
                @"created":@"刚刚",
                @"idstr":@"3824316137609270",
                @"picUrls":@[@{
                    @"thumbnailPic":@"https://www.baidu.com/thumnnail/bd297795gw1eqhwikf145g208c0b41",
                    @"book":@"简书",
                    @"type":@"博客"
                },@{
                    @"thumbnailPic":@"https://www.juejin.com/thumnnail/bd297795gw1eqhwikf145g208c0b41",
                    @"book":@"掘金",
                    @"type":@"博客"
                }],
                @"reposts":@0,
                @"source":@"来自即可笔记",
                @"text":@"这是我见过的剪刀中。。最丧心病狂的。。。",
                @"user":@{
                    @"mbrank":@6,
                    @"mbtype":@12,
                    @"name":@"嘴巴笑抽筋了",
                    @"profile":@"http://tp2.sinaimg.cn/265548536",
                    @"vip":@YES
                }
        };
        
@interface BlogsArrayModel : Model<ContainModelClassProtocol>
@property(nonatomic,copy)NSString *tendencyDirection;
@property(nonatomic,strong)NSNumber *attitudes;
@property(nonatomic,strong)NSNumber *comments;
@property(nonatomic,copy)NSString *created;
@property(nonatomic,copy)NSString *idstr;
@property(nonatomic,strong)NSArray *picUrls;
@property(nonatomic,strong)NSNumber *reposts;
@property(nonatomic,copy)NSString *source;
@property(nonatomic,copy)NSString *text;
@property(nonatomic,strong)NSDictionary *user;
@end
+(NSDictionary *)arrayContainModelClass{
    return @{@"picUrls":@"Item"};
}
@interface Item : NSObject
@property(nonatomic,copy)NSString *book;
@property(nonatomic,copy)NSString *type;
@property(nonatomic,copy)NSString *thumbnailPic;
@end
@implementation Item
- (NSString *)description {
    return @"Item Class";
}
@end

    BlogsArrayModel *blogsArrayModel = [BlogsArrayModel modelWithDict3:modelDict];
    NSLog(@"--------3-----%@\n,%@\n,%@\n,%@\n,%@\n,%@\n,%@\n,%@\n,%@\n",blogsArrayModel.attitudes,blogsArrayModel.comments,blogsArrayModel.created,blogsArrayModel.idstr,blogsArrayModel.picUrls,blogsArrayModel.reposts,blogsArrayModel.source,blogsArrayModel.text,blogsArrayModel.user);
    --------3-----1
,0
,刚刚
,3824316137609270
,(
    "Item Class",
    "Item Class"
)
,0
,来自即可笔记
,这是我见过的剪刀中。。最丧心病狂的。。。
,{
    mbrank = 6;
    mbtype = 12;
    name = "\U5634\U5df4\U7b11\U62bd\U7b4b\U4e86";
    profile = "http://tp2.sinaimg.cn/265548536";
    vip = 1;
}

4.在3的基础上缓存对象中的成员变量,减少计算量。

+ (instancetype)modelWithDict4:(NSDictionary *)dict{
    // 1.创建对应的对象
    id objc = [[self alloc] init];
    NSArray *propertys = [self getPropertys];
    
    for (Property *p in propertys) {
        NSString *key = p.name;
        // 根据成员属性名去字典中查找对应的value
        id value = dict[key];
        
        
        //--------------------------- <#我是分割线#> ------------------------------//
        //
        
        // 三级转换:NSArray中也是字典,把数组中的字典转换成模型.
        // 判断值是否是数组
        //objcect_getClass(self)
        if ([value isKindOfClass:[NSArray class]]) {
            // 判断对应类有没有实现字典数组转模型数组的协议
            // arrayContainModelClass 提供一个协议,只要遵守这个协议的类,都能把数组中的字典转模型
            if ([self respondsToSelector:@selector(arrayContainModelClass)]) {
                
                // 转换成id类型,就能调用任何对象的方法
                id idSelf = self;
                
                // 获取数组中字典对应的模型
                NSString *type =  [idSelf arrayContainModelClass][key];
                
                // 生成模型
                Class classModel = NSClassFromString(type);
                NSMutableArray *arrM = [NSMutableArray array];
                // 遍历字典数组,生成模型数组
                for (NSDictionary *dict in value) {
                    // 字典转模型
                    id model =  [classModel modelWithDict3:dict];
                    [arrM addObject:model];
                }
                
                // 把模型数组赋值给value
                value = arrM;
            }
        }
        // 如果模型属性数量大于字典键值对数理,模型属性会被赋值为nil,而报错
        if (value) {
            // 给模型中属性赋值
            [objc setValue:value forKey:key];
        }
    }
    return objc;
}


//NSMutableArray<Property *> *NSObject_getPropertys(id self, SEL _cmd) {
//
//}
//[self getPropertys] ====> NSObject_getPropertys([xxx class], @selector(getPropertys));

+ (NSMutableArray<Property *> *)getPropertys {
    static void *getPropertysKey = &getPropertysKey;
    NSMutableArray<Property *> *propertys = objc_getAssociatedObject(self, getPropertysKey);
    if (propertys) {
        return propertys;
    }
    
    
    uint count = 0;
    Ivar *ivarList = class_copyIvarList(self, &count);
    propertys = [NSMutableArray array];
    
    // 遍历所有成员变量
    for (int i = 0; i < count; i++) {
        // 根据角标,从数组取出对应的成员变量(Ivar:成员变量,以下划线开头)
        Ivar ivar = ivarList[i];
        
        // 获取成员变量名字
        NSString *ivarName = [NSString stringWithUTF8String:ivar_getName(ivar)];
        // 获取成员变量类型
        NSString *ivarType = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)];
        
        // 替换: @\"User\" -> User
        ivarType = [ivarType stringByReplacingOccurrencesOfString:@"\"" withString:@""];
        ivarType = [ivarType stringByReplacingOccurrencesOfString:@"@" withString:@""];
        
        // 处理成员变量名->字典中的key(去掉 _ ,从第一个角标开始截取)
        NSString *key = [ivarName substringFromIndex:1];
        Property *p = [Property new];
        p.name = key;
        p.type = ivarType;
        
        [propertys addObject:p];
        
    }
    free(ivarList);
    objc_setAssociatedObject(self, getPropertysKey, propertys, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    return propertys;
}
BlogsArrayModel *blogsArrayModel0 = [BlogsArrayModel modelWithDict4:modelDict];
    NSLog(@"--------4-----%@\n,%@\n,%@\n,%@\n,%@\n,%@\n,%@\n,%@\n,%@\n",blogsArrayModel0.attitudes,blogsArrayModel0.comments,blogsArrayModel0.created,blogsArrayModel0.idstr,blogsArrayModel0.picUrls,blogsArrayModel0.reposts,blogsArrayModel0.source,blogsArrayModel0.text,blogsArrayModel0.user);
2020-07-27 17:08:58.745354+0800 RuntimeCategory[44208:1659681] --------4-----1
,0
,刚刚
,3824316137609270
,(
    "Item Class",
    "Item Class"
)
,0
,来自即可笔记
,这是我见过的剪刀中。。最丧心病狂的。。。
,{
    mbrank = 6;
    mbtype = 12;
    name = "\U5634\U5df4\U7b11\U62bd\U7b4b\U4e86";
    profile = "http://tp2.sinaimg.cn/265548536";
    vip = 1;
}

5.//通过缓存对象中的成员变量减少计算量,同时遍历父类中的成员变量BlogsArrayModel------->Model-------->type,field

@interface Model : NSObject
@property(nonatomic,copy)NSString *type;
@property(nonatomic,copy)NSString *field;
@end
@implementation Model

@end
+ (instancetype)modelWithDict5:(NSDictionary *)dict{
    // 1.创建对应的对象
    id objc = [[self alloc] init];
    Class cls = self;
    while (cls && cls != [NSObject class]) {
        
        NSArray *propertys = [cls getPropertys];
        
        for (Property *p in propertys)
        {
            NSString *key = p.name;
            // 根据成员属性名去字典中查找对应的value
            id value = dict[key];
            
            //--------------------------- <#我是分割线#> ------------------------------//
            //
            
            // 三级转换:NSArray中也是字典,把数组中的字典转换成模型.
            // 判断值是否是数组
            //objcect_getClass(self)
            if ([value isKindOfClass:[NSArray class]]) {
                // 判断对应类有没有实现字典数组转模型数组的协议
                // arrayContainModelClass 提供一个协议,只要遵守这个协议的类,都能把数组中的字典转模型
                if ([self respondsToSelector:@selector(arrayContainModelClass)]) {
                    
                    // 转换成id类型,就能调用任何对象的方法
                    id idSelf = self;
                    
                    // 获取数组中字典对应的模型
                    NSString *type =  [idSelf arrayContainModelClass][key];
                    
                    // 生成模型
                    Class classModel = NSClassFromString(type);
                    NSMutableArray *arrM = [NSMutableArray array];
                    // 遍历字典数组,生成模型数组
                    for (NSDictionary *dict in value) {
                        // 字典转模型
                        id model =  [classModel modelWithDict3:dict];
                        [arrM addObject:model];
                    }
                    
                    // 把模型数组赋值给value
                    value = arrM;
                }
            }
            // 如果模型属性数量大于字典键值对数理,模型属性会被赋值为nil,而报错
            if (value) {
                // 给模型中属性赋值
                [objc setValue:value forKey:key];
            }
        }
    
        cls = [cls superclass];
    }
    return objc;
}
BlogsArrayModel *blogsArrayModel1 = [BlogsArrayModel modelWithDict5:modelDict];
    NSLog(@"--------5-----%@\n,%@\n,%@\n,%@\n,%@\n,%@\n,%@\n,%@\n,%@\n,%@\n,%@\n",blogsArrayModel1.type,blogsArrayModel1.field,blogsArrayModel1.attitudes,blogsArrayModel1.comments,blogsArrayModel1.created,blogsArrayModel1.idstr,blogsArrayModel1.picUrls,blogsArrayModel1.reposts,blogsArrayModel1.source,blogsArrayModel1.text,blogsArrayModel1.user);
2020-07-27 17:08:58.745442+0800 RuntimeCategory[44208:1659681] --------5-----网站
,文字
,1
,0
,刚刚
,3824316137609270
,(
    "Item Class",
    "Item Class"
)
,0
,来自即可笔记
,这是我见过的剪刀中。。最丧心病狂的。。。
,{
    mbrank = 6;
    mbtype = 12;
    name = "\U5634\U5df4\U7b11\U62bd\U7b4b\U4e86";
    profile = "http://tp2.sinaimg.cn/265548536";
    vip = 1;
}

6.//通过缓存对象中的成员变量减少计算量,同时遍历父类中的成员变量,增加映射关系,如后台返回下划线命名方式,转换为驼峰命名方式tendency_direction-------->tendencyDirection?

@interface BlogsArrayModel : Model<ContainModelClassProtocol>
@property(nonatomic,copy)NSString *tendencyDirection;
@property(nonatomic,strong)NSNumber *attitudes;
@property(nonatomic,strong)NSNumber *comments;
@property(nonatomic,copy)NSString *created;
@property(nonatomic,copy)NSString *idstr;
@property(nonatomic,strong)NSArray *picUrls;
@property(nonatomic,strong)NSNumber *reposts;
@property(nonatomic,copy)NSString *source;
@property(nonatomic,copy)NSString *text;
@property(nonatomic,strong)NSDictionary *user;
@end
+(NSDictionary *)arrayContainModelClass{
    return @{@"picUrls":@"Item"};
}
+(NSDictionary *)propertyMapper{
    return @{@"tendencyDirection":@"tendency_direction"};
}
+ (instancetype)modelWithDict6:(NSDictionary *)dict{
    // 1.创建对应的对象
    id objc = [[self alloc] init];
    Class cls = self;
    while (cls && cls != [NSObject class]) {
        
        NSArray *propertys = [cls getPropertys];
        
        NSDictionary *mapper = nil;
        if ([self respondsToSelector:@selector(propertyMapper)]) {
            // 转换成id类型,就能调用任何对象的方法
//            id idSelf = self;
//            mapper = [idSelf propertyMapper];
            mapper = [self propertyMapper];
        }
        for (Property *p in propertys)
        {
            NSString *key = p.name;
            NSString *dicKey = mapper[key] ?: key;
            // 根据成员属性名去字典中查找对应的value
            id value = dict[dicKey];
            
            //--------------------------- <#我是分割线#> ------------------------------//
            //
            
            // 三级转换:NSArray中也是字典,把数组中的字典转换成模型.
            // 判断值是否是数组
            //objcect_getClass(self)
            if ([value isKindOfClass:[NSArray class]]) {
                // 判断对应类有没有实现字典数组转模型数组的协议
                // arrayContainModelClass 提供一个协议,只要遵守这个协议的类,都能把数组中的字典转模型
                if ([self respondsToSelector:@selector(arrayContainModelClass)]) {
                    
                    // 转换成id类型,就能调用任何对象的方法
                    id idSelf = self;
                    
                    // 获取数组中字典对应的模型
                    NSString *type =  [idSelf arrayContainModelClass][key];
                    
                    // 生成模型
                    Class classModel = NSClassFromString(type);
                    NSMutableArray *arrM = [NSMutableArray array];
                    // 遍历字典数组,生成模型数组
                    for (NSDictionary *dict in value) {
                        // 字典转模型
                        id model =  [classModel modelWithDict3:dict];
                        [arrM addObject:model];
                    }
                    
                    // 把模型数组赋值给value
                    value = arrM;
                }
            }
            // 如果模型属性数量大于字典键值对数理,模型属性会被赋值为nil,而报错
            if (value) {
                // 给模型中属性赋值
                [objc setValue:value forKey:key];
            }
        }
        
        cls = [cls superclass];
    }
    return objc;
}
BlogsArrayModel *blogsArrayModel2 = [BlogsArrayModel modelWithDict6:modelDict];
    NSLog(@"--------6-----%@\n,%@\n,%@\n,%@\n,%@\n,%@\n,%@\n,%@\n,%@\n,%@\n,%@\n,%@\n",blogsArrayModel2.tendencyDirection,blogsArrayModel2.type,blogsArrayModel2.field,blogsArrayModel2.attitudes,blogsArrayModel2.comments,blogsArrayModel2.created,blogsArrayModel2.idstr,blogsArrayModel2.picUrls,blogsArrayModel2.reposts,blogsArrayModel2.source,blogsArrayModel2.text,blogsArrayModel2.user);
2020-07-27 17:08:58.745512+0800 RuntimeCategory[44208:1659681] --------6-----all
,网站
,文字
,1
,0
,刚刚
,3824316137609270
,(
    "Item Class",
    "Item Class"
)
,0
,来自即可笔记
,这是我见过的剪刀中。。最丧心病狂的。。。
,{
    mbrank = 6;
    mbtype = 12;
    name = "\U5634\U5df4\U7b11\U62bd\U7b4b\U4e86";
    profile = "http://tp2.sinaimg.cn/265548536";
    vip = 1;
}

Demo

4.动态添加方法

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

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

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

Implementation(IMP):
定义:typedef id (*IMP)(id, SEL, ...)

代表函数指针,即函数执行的入口。该函数使用标准的 C 调用。第一个参数指向 self(它代表当前类实例的地址,如果是类则指向的是它的元类),作为消息的接受者;第二个参数代表方法的选择子;... 代表可选参数,前面的 id 代表返回值。

@interface Person : NSObject
-(void)roll0:(char *)s;
@end
#import "Person.h"
#import <objc/message.h>
@implementation Person
/**
 调用:只要一个对象调用了一个未实现的方法就会调用这个方法,进行处理
 作用:动态添加方法,处理未实现
 注解:任何方法默认都有两个隐式参数,self,_cmd(当前方法的方法编号)
 */
//定义指向函数的指针变量
int (* p)(int x,int y);
//函数声明
int max(int,int);

void roll0(id self,SEL _cmd,char s[]){
    printf("roll0:%s走了多远方法\n",s);
}
void roll(id self,SEL _cmd,NSString *s){
    NSLog(@"roll:%@滚了多远方法\n",s);
}
void go(id self,SEL _cmd,NSString *s){
    NSLog(@"go:%@跑了多远方法\n",s);
}
void (* LNRoll)(id self,SEL _cmd,NSString *s) = roll;

void (* LNRoll0)(id self,SEL _cmd,char s[]) = roll0;

void (* LNGO)(id self,SEL _cmd,NSString *s) = go;

+ (BOOL)resolveInstanceMethod:(SEL)sel {
    if (sel == NSSelectorFromString(@"roll:")) {
        /**
         class_addMethod(<#Class  _Nullable __unsafe_unretained cls#>:给哪个类添加方法, <#SEL  _Nonnull name#>:添加哪个方法,即添加方法的方法编号, <#IMP  _Nonnull imp#>:方法实现 => 函数 => 函数入口 => 函数名(添加方法的函数实现(函数地址)), <#const char * _Nullable types#>:方法类型,(返回值+参数类型) v:void @:对象->self :表示SEL->_cmd)
         */
        // 给类添加roll:滚了多远方法
        class_addMethod(self, sel, (IMP)LNRoll, "v@:@");
        return YES;
    }
    if (sel == NSSelectorFromString(@"roll0:")) {
        // 给类添加roll0:走了多远方法
        class_addMethod(self, sel, (IMP)roll0, "v@:*");
        return YES;
    }
    if ([NSStringFromSelector(sel) isEqualToString:@"go:"]) {
        // 给类添加go:跑了多远方法
        class_addMethod(self, sel, (IMP)LNGO, "v@:@");
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}
@end
//输出
2020-08-15 19:30:03.320644+0800 RuntimeAddMethod[22856:6217287] roll:789滚了多远方法

2020-08-15 19:30:03.320863+0800 RuntimeAddMethod[22856:6217287] go:456跑了多远方法

roll0:123走了多远方法

Demo

5.实现NSCoding的自动归档和解档

如果你实现过自定义模型数据持久化的过程,那么你也肯定明白,如果一个模型有许多个属性,那么我们需要对每个属性都实现一遍encodeObject 和 decodeObjectForKey方法,如果这样的模型又有很多个,这还真的是一个十分麻烦的事情。下面来看看简单的实现方式。
假设现在有一个Movie类,有3个属性。先看下 .h文件

//1. 如果想要当前类可以实现归档与反归档,需要遵守一个协议NSCoding
@interface Movie : NSObject<NSCoding>
@property (nonatomic, copy) NSString *movieId;
@property (nonatomic, copy) NSString *movieName;
@property (nonatomic, copy) NSString *pic_url;
+ (NSString *)archivePath;
#import "Movie.h"
#import <objc/runtime.h>
@implementation Movie

- (void)encodeWithCoder:(NSCoder *)encoder
{
    unsigned int count = 0;
    Ivar *ivars = class_copyIvarList([Movie class], &count);
    
    for (int i = 0; i<count; i++) {
        // 取出i位置对应的成员变量
        Ivar ivar = ivars[i];
        // 查看成员变量
        const char *name = ivar_getName(ivar);
        // 归档
        NSString *key = [NSString stringWithUTF8String:name];
        id value = [self valueForKey:key];
        [encoder encodeObject:value forKey:key];
    }
    free(ivars);
}
- (id)initWithCoder:(NSCoder *)decoder
{
    if (self = [super init]) {
        unsigned int count = 0;
        Ivar *ivars = class_copyIvarList([Movie class], &count);
        for (int i = 0; i<count; i++) {
            // 取出i位置对应的成员变量
            Ivar ivar = ivars[i];
            // 查看成员变量
            const char *name = ivar_getName(ivar);
            // 归档
            NSString *key = [NSString stringWithUTF8String:name];
            id value = [decoder decodeObjectForKey:key];
            // 设置到成员变量身上
            [self setValue:value forKey:key];
            
        }
        free(ivars);
    }
    return self;
}
// 归档地址
+ (NSString *)archivePath {
    NSString *path = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES).firstObject;
    return [path stringByAppendingString:@"PersonCache"];
}
@end
        Movie *movie = [[Movie alloc] init];
        movie.movieId = @"1";
        movie.movieName = @"战狼";
        movie.pic_url = @"https://www.baidu.com/pictures";
        // 归档Person
        [NSKeyedArchiver archiveRootObject:movie toFile:[Movie archivePath]];
        // 解档Person
        Movie *movie0 = [NSKeyedUnarchiver unarchiveObjectWithFile:[Movie archivePath]];
        NSLog(@"%@",movie0.movieName);
//输出
RuntimeNSCoding[23039:6238863] 战狼

Demo

可以写成宏定义

#define encodeRuntime(A) \
\
unsigned int count = 0;\
Ivar *ivars = class_copyIvarList([A 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];\
[encoder encodeObject:value forKey:key];\
}\
free(ivars);\
\

#define initCoderRuntime(A) \
\
if (self = [super init]) {\
unsigned int count = 0;\
Ivar *ivars = class_copyIvarList([A 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 = [decoder decodeObjectForKey:key];\
[self setValue:value forKey:key];\
}\
free(ivars);\
}\
return self;\
\

6.runtime下Class的各项操作

  • 1.runtime 部分函数
#warning - 以下为功能模块相关的方法示例, 具体方法作用、使用、注解请移步 -> github.com/CoderLN
以下的这些方法应该算是`runtime`在实际场景中所应用的大部分的情况了,平常的编码中差不多足够用了。

 0、class_copyPropertyList 获取类中所有的属性
        objc_property_t *propertyList = class_copyPropertyList([self class], &count);
        for (unsigned int i=0; i<count; i++) {
            const char *propertyName = property_getName(propertyList[i]);
            NSLog(@"property---->%@", [NSString stringWithUTF8String:propertyName]);
        }

 0、class_copyMethodList 获取类的所有方法
        Method *methodList = class_copyMethodList([self class], &count);
        for (unsigned int i; i<count; i++) {
            Method method = methodList[i];
            NSLog(@"method---->%@", NSStringFromSelector(method_getName(method)));
        }

 0、class_copyIvarList 获取类中所有的成员变量(outCount 会返回成员变量的总数)
        Ivar *ivarList = class_copyIvarList([self class], &count);
        for (unsigned int i; i<count; i++) {
            Ivar myIvar = ivarList[i];
            const char *ivarName = ivar_getName(myIvar);
            NSLog(@"Ivar---->%@", [NSString stringWithUTF8String:ivarName]);
        }

 0、class_copyProtocolList 获取协议列表
    __unsafe_unretained Protocol **protocolList = class_copyProtocolList([self class], &count);
    for (unsigned int i; i<count; i++) {
        Protocol *myProtocal = protocolList[i];
        const char *protocolName = protocol_getName(myProtocal);
        NSLog(@"protocol---->%@", [NSString stringWithUTF8String:protocolName]);
    }

 0、object_getClass 获得类方法
        Class PersonClass = object_getClass([Person class]);
        SEL oriSEL = @selector(test1);
        Method oriMethod = _class_getMethod(xiaomingClass, oriSEL);

 0、class_getInstanceMethod 获得实例方法
        Class PersonClass = object_getClass([xiaoming class]);
        SEL oriSEL = @selector(test2);
        Method cusMethod = class_getInstanceMethod(xiaomingClass, oriSEL);

 0、class_addMethod 动态添加方法
        BOOL addSucc = class_addMethod(xiaomingClass, oriSEL, method_getImplementation(cusMethod), method_getTypeEncoding(cusMethod));

 0、class_replaceMethod 替换原方法实现
        class_replaceMethod(toolClass, cusSEL, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod));

 0、method_exchangeImplementations 交换两个方法的实现
        method_exchangeImplementations(method1, method2);

 0、根据名字得到类变量的Ivar指针,但是这个在OC中好像毫无意义
    Ivar oneCVIvar = class_getClassVariable([Person class], name);

 0、根据名字得到实例变量的Ivar指针
    Ivar oneIVIvar = class_getInstanceVariable([Person class], name);

 0、找到后可以直接对私有成员变量赋值(强制修改name属性)
    object_setIvar(_per, oneIVIvar, @"age");


 0、动态添加方法
    class_addMethod([person class]:Class cls 类型, @selector(eat):待调用的方法名称, (IMP)myAddingFunction:(IMP)myAddingFunction,IMP是一个函数指针,这里表示指定具体实现方法myAddingFunction, 0:0代表没有参数);

 0、获得某个类的类方法
    Method class_getClassMethod(Class cls , SEL name)

 0、获得成员变量的名字
    const char *ivar_getName(Ivar v);

 0、将某个值跟某个对象关联起来,将某个值存储到某个对象中
    void objc_setAssociatedObject(id object:表示关联者,是一个对象,变量名理所当然也是object , const void *key:获取被关联者的索引key ,id value :被关联者 ,objc_AssociationPolicy policy:关联时采用的协议,有assign,retain,copy等协议,一般使用OBJC_ASSOCIATION_RETAIN_NONATOMIC)

 0、利用参数key 将对象object中存储的对应值取出来
    id objc_getAssociatedObject(id object , const void *key)
 */
  • 2.method swizzling(俗称黑魔法)

简单说就是进行方法交换
在Objective-C中调用一个方法,其实是向一个对象发送消息,查找消息的唯一依据是selector的名字。利用Objective-C的动态特性,可以实现在运行时偷换selector对应的方法实现,达到给方法挂钩的目的

每个类都有一个方法列表,存放着方法的名字和方法实现的映射关系,selector的本质其实就是方法名,IMP有点类似函数指针,指向具体的Method实现,通过selector就可以找到对应的IMP。

交换方法的几种实现方式
利用 method_exchangeImplementations 交换两个方法的实现
利用 class_replaceMethod 替换方法的实现
利用 method_setImplementation 来直接设置某个方法的IMP

一道面试题的注解

下面的代码输出什么?

@implementation Son : NSObject
- (id)init
{
    self = [super init];
    if (self) {
        NSLog(@"%@", NSStringFromClass([self class]));
        NSLog(@"%@", NSStringFromClass([super class]));
    }
    return self;
}
@end

Demo

答案:都输出 Son

class 获取当前方法的调用者的类,superClass 获取当前方法的调用者的父类,super 仅仅是一个编译指示器,就是给编译器看的,不是一个指针。

本质:只要编译器看到super这个标志,就会让当前对象去调用父类方法,本质还是当前对象在调用

这个题目主要是考察关于objc中对 self 和 super 的理解:

self 是类的隐藏参数,指向当前调用方法的这个类的实例。而 super 本质是一个编译器标示符,和 self 是指向的同一个消息接受者

当使用 self 调用方法时,会从当前类的方法列表中开始找,如果没有,就从父类中再找;

而当使用 super时,则从父类的方法列表中开始找。然后调用父类的这个方法

调用 [self class] 时,会转化成 objc_msgSend 函数

id objc_msgSend(id self, SEL op, ...)
- 调用 `[super class]`时,会转化成 `objc_msgSendSuper` 函数.

id objc_msgSendSuper(struct objc_super *super, SEL op, ...)
第一个参数是 objc_super 这样一个结构体,其定义如下
 struct objc_super {
 __unsafe_unretained id receiver;
 __unsafe_unretained Class super_class;
 };

第一个成员是 receiver, 类似于上面的 objc_msgSend函数第一个参数self
第二个成员是记录当前类的父类是什么,告诉程序从父类中开始找方法,找到方法后,最后内部是使用 objc_msgSend(objc_super->receiver, @selector(class))去调用, 此时已经和[self class]调用相同了,故上述输出结果仍然返回 Son

objc Runtime 开源代码对- (Class)class方法的实现
-(Class)class { return object_getClass(self); 
}

Runtime & Runloop 常面问题整理(附答案)

同一个面试问题并非只有一个答案,而同一个答案并不是在任何面试场合都有效,关键在于应聘者掌握了规律后,对面试的具体情况进行把握,有意识地揣摩面试官提出问题的心理 (真实问答),要 get 的到问的点,然后答其所问,算是“ 投其所好 ”吧。
摘录:
http://www.jianshu.com/p/56e40ea56813
http://www.jianshu.com/p/f9eb6b315c08

Runtime

01 / objc在向一个对象发送消息时,发生了什么?
参考1:根据对象的 isa 指针找到类对象 id,在查询类对象里面的 methodLists 方法函数列表,如果没有在好到,在沿着 superClass ,寻找父类,再在父类 methodLists 方法列表里面查询,最终找到 SEL ,根据 id 和 SEL 确认 IMP(指针函数),在发送消息;

02 / 问题:什么时候会报unrecognized selector错误?iOS有哪些机制来避免走到这一步?
参考1:当发送消息的时候,我们会根据类里面的 methodLists 列表去查询我们要动用的SEL,当查询不到的时候,我们会一直沿着父类查询,当最终查询不到的时候我们会报 unrecognized selector 错误,当系统查询不到方法的时候,会调用 +(BOOL)resolveInstanceMethod:(SEL)sel动态解释的方法来给我一次机会来添加,调用不到的方法。或者我们可以再次使用 -(id)forwardingTargetForSelector:(SEL)aSelector重定向的方法来告诉系统,该调用什么方法,一来保证不会崩溃。

03 / 问题:能否向编译后得到的类中增加实例变量?能否向运行时创建的类中添加实例变量?为什么?
参考1:1、不能向编译后得到的类增加实例变量 2、能向运行时创建的类中添加实例变量。
分析:1. 编译后的类已经注册在 runtime 中,类结构体中的 objc_ivar_list 实例变量的链表和 instance_size 实例变量的内存大小已经确定,runtime会调用 class_setvarlayout 或 class_setWeaklvarLayout 来处理strong weak 引用.所以不能向存在的类中添加实例变量。2. 运行时创建的类是可以添加实例变量,调用class_addIvar函数. 但是的在调用 objc_allocateClassPair 之后,objc_registerClassPair 之前,原因同上.

04 / 问题:runtime如何实现weak变量的自动置nil?
参考1:runtime 对注册的类, 会进行布局,对于 weak 对象会放入一个 hash 表中。 用 weak 指向的对象内存地址作为 key,当此对象的引用计数为0的时候会 dealloc,假如 weak 指向的对象内存地址是a,那么就会以a为键, 在这个 weak 表中搜索,找到所有以a为键的 weak 对象,从而设置为 nil。

05 / 问题:给类添加一个属性后,在类结构体里哪些元素会发生变化?
参考1:instance_size :实例的内存大小;objc_ivar_list *ivars:属性列表
RunLoop

01 / 问题:runloop是来做什么的?runloop和线程有什么关系?主线程默认开启了runloop么?子线程呢?
参考1:runloop: 从字面意思看:运行循环、跑圈,其实它内部就是do-while循环,在这个循环内部不断地处理各种任务(比如Source、Timer、Observer)事件。runloop和线程的关系:一个线程对应一个RunLoop,主线程的RunLoop默认创建并启动,子线程的RunLoop需手动创建且手动启动(调用run方法)。RunLoop只能选择一个Mode启动,如果当前Mode中没有任何Source(Sources0、Sources1)、Timer,那么就直接退出RunLoop。

02 / 问题:runloop的mode是用来做什么的?有几种mode?
参考1:model:是runloop里面的运行模式,不同的模式下的runloop处理的事件和消息有一定的差别。系统默认注册了5个Mode:(1)kCFRunLoopDefaultMode: App的默认 Mode,通常主线程是在这个 Mode 下运行的。(2)UITrackingRunLoopMode: 界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响。(3)UIInitializationRunLoopMode: 在刚启动 App 时第进入的第一个 Mode,启动完成后就不再使用。(4)GSEventReceiveRunLoopMode: 接受系统事件的内部 Mode,通常用不到。(5)kCFRunLoopCommonModes: 这是一个占位的 Mode,没有实际作用。注意iOS 对以上5中model进行了封装 NSDefaultRunLoopMode、NSRunLoopCommonModes

03 / 问题:为什么把NSTimer对象以NSDefaultRunLoopMode(kCFRunLoopDefaultMode)添加到主运行循环以后,滑动scrollview的时候NSTimer却不动了?
参考1:nstime对象是在 NSDefaultRunLoopMode下面调用消息的,但是当我们滑动scrollview的时候,NSDefaultRunLoopMode模式就自动切换到UITrackingRunLoopMode模式下面,却不可以继续响应nstime发送的消息。所以如果想在滑动scrollview的情况下面还调用nstime的消息,我们可以把nsrunloop的模式更改为NSRunLoopCommonModes.

04 / 问题:苹果是如何实现Autorelease Pool的?
参考1:Autorelease Pool作用:缓存池,可以避免我们经常写relase的一种方式。其实就是延迟release,将创建的对象,添加到最近的autoreleasePool中,等到autoreleasePool作用域结束的时候,会将里面所有的对象的引用计数器 - autorelease.

转自Runtime & Runloop 是否被套路过?

相关文章

网友评论

      本文标题:iOS面试题-Runtime

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