iOS基本功将来跳槽用

iOS面试要点之Runtime面试要点

2020-12-19  本文已影响0人  iOS开发面试题技术合集

作者:akon
原文地址:https://xiaozhuanlan.com/topic/6130895427

Runtime原理

Runtime是iOS核心运行机制之一,iOS App加载库、加载类、执行方法调用,全靠Runtime,这一块的知识个人认为是最基础的,基本面试必问。

Runtime消息发送机制

1)iOS调用一个方法时,实际上会调用objc_msgSend(receiver, selector, arg1, arg2, ...),该方法第一个参数是消息接收者,第二个参数是方法名,剩下的参数是方法参数;
2)iOS调用一个方法时,会先去该类的方法缓存列表里面查找是否有该方法,如果有直接调用,否则走第3)步;
3)去该类的方法列表里面找,找到直接调用,把方法加入缓存列表;否则走第4)步;
4)沿着该类的继承链继续查找,找到直接调用,把方法加入缓存列表;否则消息转发流程;
很多面试者大体知道这个流程,但是有关细节不是特别清楚。

Runtime消息转发机制

如果在消息发送阶段没有找到方法,iOS会走消息转发流程,流程图如下所示:

1)动态消息解析。检查是否重写了resolveInstanceMethod 方法,如果返回YES则可以通过class_addMethod 动态添加方法来处理消息,否则走第2)步;
2)消息target转发。forwardingTargetForSelector 用于指定哪个对象来响应消息。如果返回nil 则走第3)步;
3)消息转发。这步调用 methodSignatureForSelector 进行方法签名,这可以将函数的参数类型和返回值封装。如果返回 nil 执行第四步;否则返回 methodSignature,则进入 forwardInvocation ,在这里可以修改实现方法,修改响应对象等,如果方法调用成功,则结束。否则执行第4)步;
4)报错 unrecognized selector sent to instance。
很多人知道这四步,但是笔者一般会问:

消息缓存机制

/* When _class_slow_grow is non-zero, any given cache is actually grown
 * only on the odd-numbered times it becomes full; on the even-numbered
 * times, it is simply emptied and re-used.  When this flag is zero,
 * caches are grown every time. */
static const int _class_slow_grow = 1;

注释中说明,当_class_slow_grow是非0值的时候,只有当方法缓存第奇数次满(使用的槽位超过3/4)的时候,方法缓存的大小才会增长(会清空缓存,否则hash值就不对了);当第偶数次满的时候,方法缓存会被清空并重新利用。 如果_class_slow_grow值为0,那么每一次方法缓存满的时候,其大小都会增长。
所以单就问题而言,答案是没有限制,虽然这个值被设置为1,方法缓存的大小增速会慢一点,但是确实是没有上限的。

load与initialize

load与initialize调用时机

+load在main函数之前被Runtime调用,+initialize 方法是在类或它的子类收到第一条消息之前被调用的,这里所指的消息包括实例方法和类方法的调用。

load与initialize在分类、继承链的调用顺序

load方法调用顺序

父类->主类->分类

initialize的调用顺序

+initialize 方法的调用与普通方法的调用是一样的,走的都是消息发送的流程。如果子类没有实现 +initialize 方法,那么继承自父类的实现会被调用;如果一个类的分类实现了 +initialize 方法,那么就会对这个类中的实现造成覆盖。

确保在load和initialize的调用只执行一次

由于initialize可能会调用多次,所以在这两个方法里面做的初始化操作需要保证只初始化一次,用dispatch_once来控制

类别

OC不像C++等高级语言能直接继承多个类,不过OC可以使用类别和协议来实现多继承。

类别加载时机

类别和扩展区别

类别添加属性、方法

@interface TestClass(ak)

@property(nonatomic,copy) NSString *name;

@end

@implementation TestClass (ak)

- (void)setName:(NSString *)name{

    objc_setAssociatedObject(self,  @selector(name), name, OBJC_ASSOCIATION_COPY);
}

- (NSString*)name{
    NSString *nameObject = objc_getAssociatedObject(self,  @selector(name));
    return nameObject;
}

类别同名方法覆盖问题

怎么调用被覆盖掉的方法

category其实并不是完全替换掉原来类的同名方法,只是category在方法列表的前面而已,所以我们只要顺着方法列表找到最后一个对应名字的方法,就可以调用原来类的方法。

Class currentClass = [TestClass class];
TestClass *my = [[TestClass alloc] init];

if (currentClass) {
    unsigned int methodCount;
    Method *methodList = class_copyMethodList(currentClass, &methodCount);
    IMP lastImp = NULL;
    SEL lastSel = NULL;
    for (NSInteger i = 0; i < methodCount; i++) {
        Method method = methodList[i];
        NSString *methodName = [NSString stringWithCString:sel_getName(method_getName(method)) 
                                        encoding:NSUTF8StringEncoding];
        if ([@"printName" isEqualToString:methodName]) {
            lastImp = method_getImplementation(method);
            lastSel = method_getName(method);
        }
    }
    typedef void (*fn)(id,SEL);

    if (lastImp != NULL) {
        fn f = (fn)lastImp;
        f(my, lastSel);
    }
    free(methodList);
}   

关于类别更深入的解析可以参见美团的技术文章深入理解Objective-C:Category

协议

定义

iOS中的协议类似于Java、C++中的接口类,协议在OC中可以用来实现多继承和代理。

方法声明

协议中的方法可以声明为@required(要求实现,如果没有实现,会发出警告,但编译不报错)或者@optional(不要求实现,不实现也不会有警告)。如果不声明,默认为@required。
笔者经常会问面试者如下两个问题:
-怎么判断一个类是否实现了某个协议?很多人不知道可以通过conformsToProtocol来判断。
-假如你要求业务方实现一个delegate,你怎么判断业务方有没有实现dalegate的某个方法?很多人不知道可以通过respondsToSelector来判断。

其他

Class的定义

在oc中打开objc.h

typedef struct objc_class *Class;     //Class是指向结构体objc_class的指针

struct objc_class {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY; //isa,代表的是该类类对象

#if !__OBJC2__
    Class _Nullable super_class                              OBJC2_UNAVAILABLE; //父类
    const char * _Nonnull name                               OBJC2_UNAVAILABLE; //类名
    long version                                             OBJC2_UNAVAILABLE; 
    long info                                                OBJC2_UNAVAILABLE;
    long instance_size                                       OBJC2_UNAVAILABLE; //对象大小
    struct objc_ivar_list * _Nullable ivars                  OBJC2_UNAVAILABLE; //成员变量列表
    struct objc_method_list * _Nullable * _Nullable methodLists                    OBJC2_UNAVAILABLE; //实例方法列表
    struct objc_cache * _Nonnull cache                       OBJC2_UNAVAILABLE; //方法缓存列表(是个hash表),用来消息发送时候,快速查找方法
    struct objc_protocol_list * _Nullable protocols          OBJC2_UNAVAILABLE; //类实现协议列表
#endif

} OBJC2_UNAVAILABLE;
/* Use `Class` instead of `struct objc_class *` */

怎么枚举一个类的方法列表?

class_copyMethodList

怎么枚举一个类的属性列表?

class_copyPropertyList

怎么枚举一个类的成员变量列表?

class_copyIvarList

怎么枚举一个类实现的协议列表?

class_copyProtocolList

id和instancetype的区别

Runtime开源代码

runtime是开源的,可以在Apple GithubApple OpenSource下载来阅读。
参考资料:
Objective-C中的Runtime

资料推荐

如果你正在跳槽或者正准备跳槽不妨动动小手,添加一下咱们的交流群1012951431来获取一份详细的大厂面试资料为你的跳槽多添一份保障。

上一篇 下一篇

猜你喜欢

热点阅读