你会遇到的runtime面试题(详)

导读:
11、12月注定是不太平的月份,好多小型互联网创业公司都突然崩塌,最近一个朋友跟我抱怨道,说终于感受到了互联网的瞬息万变了,昨天我还在公司敲代码,今天就被通知说公司倒闭了,可以不用来公司了,然后他开始了为期一个月的找工作经历,期间问到我下面一道题

1、了解runtime吗?是什么?
2、你怎么知道的?
3、对象如何找到对应方法去调用的

**于是我总结了很多网上被问到的一些关于runtime的题目,并做了详细的回答,并在后面补充了我在学习runtime时敲的一些代码,如果想吃透runtime的朋友,可以把后面补充的内容好好看完 **

一、你会被问到的关于runtime笔试题:

1. runtime怎么添加属性、方法等
2. runtime 如何实现 weak 属性
3. runtime如何通过selector找到对应的IMP地址?(分别考虑类方法和实例方法)
4. 使用runtime Associate方法关联的对象,需要在主对象dealloc的时候释放么?
5. _objc_msgForward函数是做什么的?直接调用它将会发生什么?
6. 能否向编译后得到的类中增加实例变量?能否向运行时创建的类中添加实例变量?为什么?
7. 简述下Objective-C中调用方法的过程(runtime)
8. 什么是method swizzling(俗称黑魔法)

如果上面的题目你全部答得出来,那就不要浪费时间,直接return吧,程序猿的时间很宝贵的

二、解答

1. runtime怎么添加属性、方法等
  • ivar表示成员变量
  • class_addIvar
  • class_addMethod
  • class_addProperty
  • class_addProtocol
  • class_replaceProperty
2. runtime 如何实现 weak 属性

首先要搞清楚weak属性的特点
weak策略表明该属性定义了一种“非拥有关系” (nonowning relationship)。为这种属性设置新值时,设置方法既不保留新值,也不释放旧值。此特质同assign类似;然而在属性所指的对象遭到摧毁时,属性值也会清空(nil out)

那么runtime如何实现weak变量的自动置nil?

runtime对注册的类,会进行布局,会将 weak 对象放入一个 hash 表中。用 weak 指向的对象内存地址作为 key,当此对象的引用计数为0的时候会调用对象的 dealloc 方法,假设 weak 指向的对象内存地址是a,那么就会以a为key,在这个 weak hash表中搜索,找到所有以a为key的 weak 对象,从而设置为 nil。

weak属性需要在dealloc中置nil么
在ARC环境无论是强指针还是弱指针都无需在 dealloc 设置为 nil , ARC 会自动帮我们处理
即便是编译器不帮我们做这些,weak也不需要在dealloc中置nil
在属性所指的对象遭到摧毁时,属性值也会清空

objc// 模拟下weak的setter方法,大致如下- (void)setObject:(NSObject *)object{ objc_setAssociatedObject(self, "object", object, OBJC_ASSOCIATION_ASSIGN); [object cyl_runAtDealloc:^{ _object = nil; }];}

3. runtime如何通过selector找到对应的IMP地址?(分别考虑类方法和实例方法)
  • 每一个类对象中都一个对象方法列表(对象方法缓存)
  • 类方法列表是存放在类对象中isa指针指向的元类对象中(类方法缓存)
  • 方法列表中每个方法结构体中记录着方法的名称,方法实现,以及参数类型,其实selector本质就是方法名称,通过这个方法名称就可以在方法列表中找到对应的方法实现.
  • 当我们发送一个消息给一个NSObject对象时,这条消息会在对象的类对象方法列表里查找
  • 当我们发送一个消息给一个类时,这条消息会在类的Meta Class对象的方法列表里查找
4. 使用runtime Associate方法关联的对象,需要在主对象dealloc的时候释放么?

无论在MRC下还是ARC下均不需要被关联的对象在生命周期内要比对象本身释放的晚很多,它们会在被 NSObject -dealloc 调用的object_dispose()方法中释放
补充:对象的内存销毁时间表,分四个步骤

>1、调用 -release :引用计数变为零
* 对象正在被销毁,生命周期即将结束. 
* 不能再有新的 __weak 弱引用,否则将指向 nil.
* 调用 [self dealloc]
>
2、 父类调用 -dealloc 
* 继承关系中最直接继承的父类再调用 -dealloc 
* 如果是 MRC 代码 则会手动释放实例变量们(iVars)
* 继承关系中每一层的父类 都再调用 -dealloc

>3、NSObject 调 -dealloc 
* 只做一件事:调用 Objective-C runtime 中object_dispose() 方法

>4. 调用 object_dispose()
* 为 C++ 的实例变量们(iVars)调用 destructors
* 为 ARC 状态下的 实例变量们(iVars) 调用 -release 
* 解除所有使用 runtime Associate方法关联的对象 
* 解除所有 __weak 引用 
* 调用 free()
5. _objc_msgForward函数是做什么的?直接调用它将会发生什么?

_objc_msgForward是IMP类型,用于消息转发的:当向一个对象发送一条消息,但它并没有实现的时候,_objc_msgForward会尝试做消息转发
直接调用_objc_msgForward是非常危险
的事,这是把双刃刀,如果用不好会直接导致程序Crash,但是如果用得好,能做很多非常酷的事
JSPatch就是直接调用_objc_msgForward来实现其核心功能的
详细解说参见这里的第一个问题解答

6. 能否向编译后得到的类中增加实例变量?能否向运行时创建的类中添加实例变量?为什么?
  • 不能向编译后得到的类中增加实例变量;
  • 能向运行时创建的类中添加实例变量;
  • 分析如下:
    • 因为编译后的类已经注册在runtime中,类结构体中的objc_ivar_list 实例变量的链表和instance_size实例变量的内存大小已经确定,同时runtime 会调用class_setIvarLayout 或 class_setWeakIvarLayout来处理strong weak引用,所以不能向存在的类中添加实例变量
    • 运行时创建的类是可以添加实例变量,调用 class_addIvar函数,但是得在调用objc_allocateClassPair之后,objc_registerClassPair之前,原因同上。
7. 简述下Objective-C中调用方法的过程(runtime)
  • Objective-C是动态语言,每个方法在运行时会被动态转为消息发送,即:objc_msgSend(receiver, selector),整个过程介绍如下:
* objc在向一个对象发送消息时,runtime库会根据对象的isa指针找到该对象实际所属的类
* 然后在该类中的方法列表以及其父类方法列表中寻找方法运行
* 如果,在最顶层的父类(一般也就NSObject)中依然找不到相应的方法时,程序在运行时会挂掉并抛出异常unrecognized selector sent to XXX
* 但是在这之前,objc的运行时会给出三次拯救程序崩溃的机会,这三次拯救程序奔溃的说明见问题《什么时候会报unrecognized selector的异常》中的说明
  • 补充说明:Runtime 铸就了Objective-C 是动态语言的特性,使得C语言具备了面向对象的特性,在程序运行期创建,检查,修改类、对象及其对应的方法,这些操作都可以使用runtime中的对应方法实现。
8. 什么是method swizzling(俗称黑魔法)
  • 简单说就是进行方法交换
  • 在Objective-C中调用一个方法,其实是向一个对象发送消息,查找消息的唯一依据是selector的名字。利用Objective-C的动态特性,可以实现在运行时偷换selector对应的方法实现,达到给方法挂钩的目的
  • 每个类都有一个方法列表,存放着方法的名字和方法实现的映射关系,selector的本质其实就是方法名,IMP有点类似函数指针,指向具体的Method实现,通过selector就可以找到对应的IMP
Snip20161207_7.png
  • 交换方法的几种实现方式
    • 利用 method_exchangeImplementations 交换两个方法的实现
    • 利用 class_replaceMethod 替换方法的实现
    • 利用 method_setImplementation 来直接设置某个方法的IMP
      Snip20161207_8.png

三、补充(重要)

1、消息机制

  • 1、方法调用底层实现


    Snip20161107_5.png
  • 2、runtime:千万不要随便使用,不得已才使用

//消息机制:
//作用:调用已知私有方法,如调用没有在.h文件申明但是在.m文件实现了的方法
// 用runtime调用私有方法:方法编号后面开始,依次就是传入给方法的参数

objc_msgSend(p, @selector(run: str:),20,@"haha");
objc_msgSend(p, @selector(eat));
// [p eat] => objc_msgSend(p, @selector(eat));
  • 3、对象如何找到对应的方法去调用

    // 方法保存到什么地方?对象方法保存到类中,类方法保存到元类(meta class),每一个类都有方法列表methodList
    //明确去哪个类中调用,通过isa指针
    * 1.根据对象的isa去对应的类查找方法,isa:判断去哪个类查找对应的方法 指向方法调用的类
    * 2.根据传入的方法编号SEL,里面有个哈希列表,在列表中找到对应方法Method(方法名)
    * 3.根据方法名(函数入口)找到函数实现,函数实现在方法区

2、交换方法

  • 1、需求:比如我有个项目,已经开发2年,之前都是使用UIImage去加载图片,组长想要在调用imageNamed,就给我提示,是否加载成功,如果用方法2,每个调用imageNamed方法的,都要改成xmg_imageNamed:才能拥有这个功能,很麻烦。解决:用runtime交换方法,即下面方法3
   ①解决方式 自定义UIImage类,缺点:每次用要导入自己的类
   ②解决方法:UIImage分类扩充一个这样方法,缺点:需要导入,无法写super和self,会干掉系统方法,解决:给系统方法加个前缀,与系统方法区分,如:xmg_imageNamed:
   ③交互方法实现,步骤: 1.提供分类 2.写一个有这样功能方法 3.用系统方法与这个功能方法交互实现,在+load方法中实现
注意:在分类一定不要重写系统方法,就直接把系统方法干掉,如果真的想重写,在系统方法前面加前缀,方法里面去调用系统方法

思想:什么时候需要自定义,系统功能不完善,就自定义一个这样类,去扩展这个类
/#import "UIImage+Image.h"
/#import <objc/message.h>
@implementation UIImage (Image)
// 加载类的时候调用,肯定只会调用一次

 +(void)load
{
    // 交互方法实现xmg_imageNamed,imageNamed
    /**
     获取类方法名
     @param Class cls,#> 获取哪个类方法 description#>
     @param SEL name#> 方法编号 description#>
     @return 返回Method(方法名)
     class_getClassMethod(<#__unsafe_unretained Class cls#>, <#SEL name#>)
     */
    /**
     获取对象方法名
     @param Class cls,#> 获取哪个对象方法 description#>
     @param SEL name#> 方法编号 description#>
     @return 返回Method(方法名)
     class_getInstanceMethod(<#__unsafe_unretained Class cls#>, <#SEL name#>)
     */
    
   Method imageNameMethod = class_getClassMethod(self, @selector(imageNamed:));
    Method xmg_imageNameMethod = class_getClassMethod(self, @selector(xmg_imageNamed:));
    //用runtime对imageNameMethod和xmg_imageNameMethod方法进行交换
    method_exchangeImplementations(imageNameMethod, xmg_imageNameMethod);
}
//外界调用imageNamed:方法,其实是调用下面方法,调用xmg_imageNamed就是调用imageNamed:
+ (UIImage *)xmg_imageNamed:(NSString *)name
{
    //已经把xmg_imageNamed换成imageNamed,所以下面其实是调用的imageNamed:
   UIImage *image = [UIImage xmg_imageNamed:name];
    
    if (image == nil) {
        NSLog(@"加载失败");
    }
    return image;
}
@end

3、动态添加方法

动态添加方法:

为什么动态添加方法? OC都是懒加载,有些方法可能很久不会调用
应用场景:电商,视频,社交,收费项目:会员机制中,只要会员才拥有这些功能

  • 1、美团面试题:有没有使用过performSelector,使用,什么时候使用?动态添加方法的时候使用? 为什么动态添加方法
// 默认OC方法都有两个默认存在的隐式参数,self(哪个类的方法),_cmd(方法编号)
void run(id self, SEL _cmd, NSNumber *metre) {
    NSLog(@"跑了%@",metre);
}
  • 2、什么时候调用:只要调用没有实现的方法 就会调用方法去解决,这里可以拿到那个未实现的方法名
// 作用:去解决没有实现方法,动态添加方法
+(BOOL)resolveInstanceMethod:(SEL)sel{
        class:给谁添加方法
        SEL:添加哪个方法
        IMP:方法实现,函数入口,函数名,如:(IMP)run,方法名run强转成IMP
        type:方法类型,通过查苹果官方文档,V:void,
     class_addMethod(<#__unsafe_unretained Class cls#>, <#SEL name#>, <#IMP imp#>, <#const char *types#>)
    // [NSStringFromSelector(sel) isEqualToString:@"eat"];
    if (sel == @selector(run:)) {
        // 添加方法
        class_addMethod(self, sel, (IMP)run,"v@:");
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}

4、动态添加属性

  • 1、需求:给NSObject添加一个name属性,动态添加属性 -> runtime

思考:
①给NSObject添加分类,在分类中添加属性。问题:@property在分类中作用:仅仅是生成get,set方法声明,不会生成get,set方法实现和下划线成员属性,所以要在.m文件实现setter/getter方法,用static保存下滑线属性,这样一来,当对象销毁时,属性无法销毁
②用runtime动态添加属性:本质是让属性与某个对象产生一段关联
使用场景:给系统的类添加属性时

#import <objc/message.h>
@implementation NSObject (Property)
//static  NSString *_name;      //通过这样去保存属性没法做到对象销毁,属性也销毁,static依然会让属性存在缓存池中,所以需要动态的添加成员属性
// 只要想调用runtime方法,思考:谁的事情
-(void)setName:(NSString *)name
{
    // 保存name
    // 动态添加属性 = 本质:让对象的某个属性与值产生关联
    /*
        object:保存到哪个对象中 
        key:用什么属性保存 属性名
        value:保存值
        policy:策略,strong,weak
     objc_setAssociatedObject(<#id object#>, <#const void *key#>, <#id value#>, <#objc_AssociationPolicy policy#>)
     */
    objc_setAssociatedObject(self, "name", name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
//    _name = name;

}

- (NSString *)name
{
    return objc_getAssociatedObject(self, "name");
//    return _name;

}
@end

5、自动生成属性代码

开发中,从网络数据中解析出字典数组,将数组转为模型时,如果有几百个key需要用,要写很多@property成员属性,下面提供一个万能的方法,可直接将字典数组转为全部@property成员属性,打印出来,这样直接复制在模型中就好了

#import "NSDictionary+PropertyCode.h"
@implementation NSDictionary (PropertyCode)

//1️⃣通过这个方法,自动将字典转成模型中需要用的属性代码

// 私有API:真实存在,但是苹果没有暴露出来,不给你。如BOOL值,不知道类型,打印得知是__NSCFBoolean,但是无法敲出来,只能用NSClassFromString(@"__NSCFBoolean")

// isKindOfClass:判断下是否是当前类或者子类,BOOL是NSNumber的子类,要先判断BOOL
- (void)createPropetyCode
{
    // 模型中属性根据字典的key
    // 有多少个key,生成多少个属性
    NSMutableString *codes = [NSMutableString string];
    // 遍历字典
    [self enumerateKeysAndObjectsUsingBlock:^(id  _Nonnull key, id  _Nonnull value, BOOL * _Nonnull stop) {
        NSString *code = nil;
        

//        NSLog(@"%@",[value class]);
        
        if ([value isKindOfClass:[NSString class]]) {
          code = [NSString stringWithFormat:@"@property (nonatomic, strong) NSString *%@;",key];
        } else if ([value isKindOfClass:NSClassFromString(@"__NSCFBoolean")]){
            code = [NSString stringWithFormat:@"@property (nonatomic, assign) BOOL %@;",key];
        } else if ([value isKindOfClass:[NSNumber class]]) {
             code = [NSString stringWithFormat:@"@property (nonatomic, assign) NSInteger %@;",key];
        } else if ([value isKindOfClass:[NSArray class]]) {
            code = [NSString stringWithFormat:@"@property (nonatomic, strong) NSArray *%@;",key];
        } else if ([value isKindOfClass:[NSDictionary class]]) {
            code = [NSString stringWithFormat:@"@property (nonatomic, strong) NSDictionary *%@;",key];
        }
        
        // 拼接字符串
        [codes appendFormat:@"%@\n",code];

    }];
    
    NSLog(@"%@",codes);
    
}

@end

6、KVC字典转模型

// 需求:就是在开发中,通常后台会给你很多数据,但是并不是每个数据都有用,这些没有用的数据,需不需要保存到模型中

@implementation Status
+ (instancetype)statusWithDict:(NSDictionary *)dict{
    // 创建模型
    Status *s = [[self alloc] init];
    
    // 字典value转模型属性保存
    [s setValuesForKeysWithDictionary:dict];

//    s.reposts_count = dict[@"reposts_count"];
    // 4️⃣MJExtension:可以字典转模型,而且可以不用与字典中属性一一对应,runtime,遍历模型中有多少个属性,直接去字典中取出对应value,给模型赋值
    
    // 1️⃣setValuesForKeysWithDictionary:方法底层实现:遍历字典中所有key,去模型中查找对应的属性,把值给模型属性赋值,即调用下面方法:
    /*
    [dict enumerateKeysAndObjectsUsingBlock:^(id  _Nonnull key, id  _Nonnull obj, BOOL * _Nonnull stop) {
        // source
        // 这行代码才是真正给模型的属性赋值
        [s setValue:dict[@"source"] forKey:@"source"];      //底层实现是:
                                 2️⃣ [s setValue:dict[@"source"] forKey:@"source"];
                                 1.首先会去模型中查找有没有setSource方法,直接调用set方法 [s setSource:dict[@"source"]];
                                 2.去模型中查找有没有source属性,source = dict[@"source"]
                                 3.去米线中查找有没有_source属性,_source = dict[@"source"]
                                 4.调用对象的 setValue:forUndefinedKey:直接报错
        [s setValue:obj forKey:key];
    }];
    */
    return s;
}
     

// 3️⃣用KVC,不想让系统报错,重写系统方法思想:
// 1.想给系统方法添加功能
// 2.不想要系统实现
- (void)setValue:(id)value forUndefinedKey:(NSString *)key
{
}


@end

7、MJExtention的底层实现

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

//    class_copyPropertyList(<#__unsafe_unretained Class cls#>, <#unsigned int *outCount#>) 获取属性列表

@implementation NSObject (Model)

/**
 字典转模型
 @param dict 传入需要转模型的字典
 @return 赋值好的模型
 */

+ (instancetype)modelWithDict:(NSDictionary *)dict

{
    id objc = [[self alloc] init];

    //思路: runtime遍历模型中属性,去字典中取出对应value,在给模型中属性赋值
    // 1.获取模型中所有属性 -> 保存到类
    // ivar:下划线成员变量 和 Property:属性
    // 获取成员变量列表
    // class:获取哪个类成员变量列表
    // count:成员变量总数
    //这个方法得到一个装有成员变量的数组
    //class_copyIvarList(<#__unsafe_unretained Class cls#>, <#unsigned int *outCount#>)
    
    int count = 0;
    // 成员变量数组 指向数组第0个元素
    Ivar *ivarList = class_copyIvarList(self, &count);


    // 遍历所有成员变量
    for (int i = 0; i < count; i++) {
        
        // 获取成员变量 user
        Ivar ivar = ivarList[i];
        // 获取成员变量名称,即将C语言的字符转为OC字符串
        NSString *ivarName = [NSString stringWithUTF8String:ivar_getName(ivar)];
        
        // 获取成员变量类型,用于获取二级字典的模型名字
        NSString *type = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)];
        
        //  将type这样的字符串@"@\"User\"" 转成 @"User"
        type = [type stringByReplacingOccurrencesOfString:@"@\"" withString:@""];
        type = [type stringByReplacingOccurrencesOfString:@"\"" withString:@""];
        
        // 成员变量名称转换key,即去掉成员变量前面的下划线
        NSString *key = [ivarName substringFromIndex:1];
        
        // 从字典中取出对应value dict[@"user"] -> 字典
        id value = dict[key];
        
        // 二级转换
        // 并且是自定义类型,才需要转换
        if ([value isKindOfClass:[NSDictionary class]] && ![type containsString:@"NS"]) { // 只有是字典才需要转换
           
            Class className = NSClassFromString(type);
            
            // 字典转模型
            value = [className modelWithDict:value];
        }
        
        // 给模型中属性赋值 key:user value:字典 -> 模型
        if (value) {
            [objc setValue:value forKey:key];
        }
        
    }
    
    return objc;

}

@end

更多内容请持续关注我的简书和主页博客……

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,298评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,701评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,078评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,687评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,018评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,410评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,729评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,412评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,124评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,379评论 2 242
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,903评论 1 257
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,268评论 2 251
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,894评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,014评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,770评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,435评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,312评论 2 260

推荐阅读更多精彩内容