iOS面试题-Runtime

Runtime怎么添加属性、方法等

  • ivar表示成员变量
  • class_addIvar
  • class_addMethod
  • class_addProperty
  • class_addProtocol
  • class_replaceProperty

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。

下面的代码输出什么?


@implementation Son : Father
- (id)init
{
    self = [super init];
    if (self) {
        NSLog(@"%@", NSStringFromClass([self class]));
        NSLog(@"%@", NSStringFromClass([super class]));
    }
    return self;
}
@end
  • 答案:都输出 Son
  • 这个题目主要是考察关于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如何通过selector找到对应的IMP地址?(分别考虑类方法和实例方法)

  • 每一个类对象中都一个对象方法列表(对象方法缓存)
  • 类方法列表是存放在类对象中isa指针指向的元类对象中(类方法缓存)
  • 方法列表中每个方法结构体中记录着方法的名称,方法实现,以及参数类型,其实selector本质就是方法名称,通过这个方法名称就可以在方法列表中找到对应的方法实现.
  • 当我们发送一个消息给一个NSObject对象时,这条消息会在对象的类对象方法列表里查找
  • 当我们发送一个消息给一个类时,这条消息会在类的Meta Class对象的方法列表里查找

什么时候会报unrecognized selector的异常?

  • 当调用该对象上某个方法,而该对象上没有实现这个方法的时候, 可以通过“消息转发”进行解决,如果还是不行就会报unrecognized selector异常
  • objc是动态语言,每个方法在运行时会被动态转为消息发送,即:objc_msgSend(receiver, selector),整个过程介绍如下:
    • objc在向一个对象发送消息时,runtime库会根据对象的isa指针找到该对象实际所属的类
    • 然后在该类中的方法列表以及其父类方法列表中寻找方法运行
    • 如果,在最顶层的父类中依然找不到相应的方法时,程序在运行时会挂掉并抛出异常unrecognized selector sent to XXX 。但是在这之前,objc的运行时会给出三次拯救程序崩溃的机会
  • 三次拯救程序崩溃的机会
    • Method resolution
      • objc运行时会调用+resolveInstanceMethod:或者 +resolveClassMethod:,让你有机会提供一个函数实现。
      • 如果你添加了函数并返回 YES,那运行时系统就会重新启动一次消息发送的过程
      • 如果 resolve 方法返回 NO ,运行时就会移到下一步,消息转发
    • Fast forwarding
      • 如果目标对象实现了-forwardingTargetForSelector:,Runtime 这时就会调用这个方法,给你把这个消息转发给其他对象的机会
      • 只要这个方法返回的不是nil和self,整个消息发送的过程就会被重启,当然发送的对象会变成你返回的那个对象。
      • 否则,就会继续Normal Fowarding。
      • 这里叫Fast,只是为了区别下一步的转发机制。因为这一步不会创建任何新的对象,但Normal forwarding转发会创建一个NSInvocation对象,相对Normal forwarding转发更快点,所以这里叫Fast forwarding
    • Normal forwarding
      • 这一步是Runtime最后一次给你挽救的机会。
      • 首先它会发送-methodSignatureForSelector:消息获得函数的参数和返回值类型。
      • 如果-methodSignatureForSelector:返回nil,Runtime则会发出-doesNotRecognizeSelector:消息,程序这时也就挂掉了。
      • 如果返回了一个函数签名,Runtime就会创建一个NSInvocation对象并发送-forwardInvocation:消息给目标对象

简述下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中的对应方法实现。

什么是method swizzling(俗称黑魔法)

  • 简单说就是进行方法交换
  • 在Objective-C中调用一个方法,其实是向一个对象发送消息,查找消息的唯一依据是selector的名字。利用Objective-C的动态特性,可以实现在运行时偷换selector对应的方法实现,达到给方法挂钩的目的
  • 每个类都有一个方法列表,存放着方法的名字和方法实现的映射关系,selector的本质其实就是方法名,IMP有点类似函数指针,指向具体的Method实现,通过selector就可以找到对应的IMP


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

objc中向一个nil对象发送消息将会发生什么?

  • 在Objective-C中向nil发送消息是完全有效的——只是在运行时不会有任何作用
    • 如果一个方法返回值是一个对象,那么发送给nil的消息将返回0(nil)
    • 如果方法返回值为指针类型,其指针大小为小于或者等于sizeof(void*)
    • float,double,long double 或者long long的整型标量,发送给nil的消息将返回0
    • 如果方法返回值为结构体,发送给nil的消息将返回0。结构体中各个字段的值将都是0
    • 如果方法的返回值不是上述提到的几种情况,那么发送给nil的消息的返回值将是未定义的
  • 具体原因分析
    • objc是动态语言,每个方法在运行时会被动态转为消息发送,即:objc_msgSend(receiver, selector)
    • 为了方便理解这个内容,还是贴一个objc的源代码
    struct objc_class
    
    {
    
    // isa指针指向Meta Class,因为Objc的类的本身也是一个Object,
    
    // 为了处理这个关系,runtime就创造了Meta Class,
    
    // 当给类发送[NSObject alloc]这样消息时,实际上是把这个消息发给了Class Object
    
    Class isa OBJC_ISA_AVAILABILITY;
    
    #if !__OBJC2__
    
    Class super_class OBJC2_UNAVAILABLE; // 父类
    
    const  char *name OBJC2_UNAVAILABLE; // 类名
    
    long version OBJC2_UNAVAILABLE; // 类的版本信息,默认为0
    
    long info OBJC2_UNAVAILABLE; // 类信息,供运行期使用的一些位标识
    
    long instance_size OBJC2_UNAVAILABLE; // 该类的实例变量大小
    
    struct objc_ivar_list *ivars OBJC2_UNAVAILABLE; // 该类的成员变量链表
    
    struct objc_method_list **methodLists OBJC2_UNAVAILABLE; // 方法定义的链表
    
    // 方法缓存,对象接到一个消息会根据isa指针查找消息对象,
    
    // 这时会在method Lists中遍历,
    
    // 如果cache了,常用的方法调用时就能够提高调用的效率。
    
    // 这个方法缓存只存在一份,不是每个类的实例对象都有一个方法缓存
    
    // 子类会在自己的方法缓存中缓存父类的方法,父类在自己的方法缓存中也会缓存自己的方法,而不是说子类就不缓存父类方法了
    
    struct objc_cache *cache OBJC2_UNAVAILABLE;
    
    struct objc_protocol_list *protocols OBJC2_UNAVAILABLE; // 协议链表
    
    #endif
    
    } OBJC2_UNAVAILABLE;
    
    • objc在向一个对象发送消息时,runtime库会根据对象的isa指针找到该对象实际所属的类,然后在该类中的方法列表以及其父类方法列表中寻找方法运行,然后再发送消息的时候,objc_msgSend方法不会返回值,所谓的返回内容都是具体调用时执行的。
    • 如果向一个nil对象发送消息,首先在寻找对象的isa指针时就是0地址返回了,所以不会出现任何错误
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,686评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,668评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,160评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,736评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,847评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,043评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,129评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,872评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,318评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,645评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,777评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,470评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,126评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,861评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,095评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,589评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,687评论 2 351

推荐阅读更多精彩内容

  • 本文转载自:http://yulingtianxia.com/blog/2014/11/05/objective-...
    ant_flex阅读 751评论 0 1
  • 简介 Runtime 又叫运行时,是一套底层的 C 语言 API,其为 iOS 内部的核心之一,我们平时编写的 O...
    专业男神经阅读 905评论 0 2
  • 我们常常会听说 Objective-C 是一门动态语言,那么这个「动态」表现在哪呢?我想最主要的表现就是 Obje...
    Ethan_Struggle阅读 2,186评论 0 7
  • 简介 iOS开发-Runtime详解(简书) 简介 Runtime 又叫运行时,是一套底层的 C 语言 API,其...
    jgj1218阅读 254评论 1 0
  • runtime 和 runloop 作为一个程序员进阶是必须的,也是非常重要的, 在面试过程中是经常会被问到的, ...
    made_China阅读 1,207评论 0 7