iOS底层原理-几个面试题分析

1.objc_object 与 对象的关系

所有的OC对象都是以objc_object为模板继承过来的
所有的对象都是NSObject(OC)子类,但底层的是一个objc_object(C/C++)的结构体类型

总结:objc_object与对象是继承关系

2.什么是属性、成员变量、实例变量?

2.1)属性(property):在OC中是通过@property定义,实质带下划线成员变量+setter+ getter方法的变量组合

注意:定义了属性,默认是@synthesize修饰,此时,你不能同时重写这个属性的setter、getter方法。原因是:自己定义了setter、getter方法,相当于是自己管理这个属性,且属性要用@dynamic <#property#>

2.2)成员变量(ivar):在OC的类中{}中定义的,且没有下划线的变量
2.3)实例变量:通过当前对象类型实例化的变量,是一种特殊的成员变量

3.元类中为什么会有类方法?

在中的探索中,我们知道了实例方法存储在类中,类方法存储在元类中。为了探索这个问题,定义了以下几个方法,来探索方法的归属问题:
在Person中定义一个实例方法和一个类方法

@interface Person : NSObject
//赚钱
- (void)earnMoney;
//花钱
+ (void)spendMoney;

@end

@implementation Person

- (void)earnMoney{
    NSLog(@"Person earn momey!!!");
}

+ (void)spendMoney{
    NSLog(@"Person spend money!!!");
}

@end

main 主函数,用于调用自定义的方法

int main(int argc, char * argv[]) {
    @autoreleasepool {
        
        Person *person = [Person alloc];
        Class pClass     = object_getClass(person);
        cwObjc_copyMethodList(pClass);
        cwInstanceMethod_classToMetaclass(pClass);
        cwClassMethod_classToMetaclass(pClass);
        cwIMP_classToMetaclass(pClass);
        NSLog(@"Hello, World!");
        
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}

cwObjc_copyMethodList 函数:用于获取类的方法列表

void cwObjc_copyMethodList(Class pClass){
    unsigned int count = 0;
    Method *methods = class_copyMethodList(pClass, &count);
    for (unsigned int i=0; i < count; i++) {
        Method const method = methods[i];
        //获取方法名
        NSString *key = NSStringFromSelector(method_getName(method));
        
        NSLog(@"Method, name: %@", key);
    }
    free(methods);
}

cwInstanceMethod_classToMetaclass 函数:用于获取类的实例方法

void cwInstanceMethod_classToMetaclass(Class pClass){
    
    const char *className = class_getName(pClass);
    Class metaClass = objc_getMetaClass(className);
    
    Method method1 = class_getInstanceMethod(pClass, @selector(earnMoney));
    Method method2 = class_getInstanceMethod(metaClass, @selector(earnMoney));

    Method method3 = class_getInstanceMethod(pClass, @selector(spendMoney));
    Method method4 = class_getInstanceMethod(metaClass, @selector(spendMoney));
    
    NSLog(@"%s - %p-%p-%p-%p",__func__,method1,method2,method3,method4);
}

cwClassMethod_classToMetaclass 函数:用于获取类的类方法

void cwClassMethod_classToMetaclass(Class pClass){
    
    const char *className = class_getName(pClass);
    Class metaClass = objc_getMetaClass(className);
    
    Method method1 = class_getClassMethod(pClass, @selector(earnMoney));
    Method method2 = class_getClassMethod(metaClass, @selector(earnMoney));

    Method method3 = class_getClassMethod(pClass, @selector(spendMoney));
    Method method4 = class_getClassMethod(metaClass, @selector(spendMoney));
    
    NSLog(@"%s-%p-%p-%p-%p",__func__,method1,method2,method3,method4);
}

cwIMP_classToMetaclass 函数:用于获取方法的实现

void cwIMP_classToMetaclass(Class pClass){
    
    const char *className = class_getName(pClass);
    Class metaClass = objc_getMetaClass(className);

    // - (void)earnMoney;
    // + (void)spendMoney;
    IMP imp1 = class_getMethodImplementation(pClass, @selector(earnMoney));
    IMP imp2 = class_getMethodImplementation(metaClass, @selector(earnMoney));

    IMP imp3 = class_getMethodImplementation(pClass, @selector(spendMoney));
    IMP imp4 = class_getMethodImplementation(metaClass, @selector(spendMoney));

    NSLog(@"%p-%p-%p-%p",imp1,imp2,imp3,imp4);
    NSLog(@"%s",__func__);
}

以下是几个函数调用的打印结果:


result.png

3.1)首先分析class_copyMethodList,从官方文档中可以得知,他是获取类实例方法,且不包含父类的实例方法。类没有实例方法或为nil,返回NULL。所以,只有earnMoney。


class_copyMethodList.png

3.2)cwInstanceMethod_classToMetaclass是通过class_getInstanceMethod获取实例方法,我们看看源码:
Method class_getInstanceMethod(Class cls, SEL sel)
{
    if (!cls  ||  !sel) return nil;

    // This deliberately avoids +initialize because it historically did so.

    // This implementation is a bit weird because it's the only place that
    // wants a Method instead of an IMP.

#warning fixme build and search caches
        
    // Search method lists, try method resolver, etc.
    lookUpImpOrForward(nil, sel, cls, LOOKUP_RESOLVER);

#warning fixme build and search caches

    return _class_getMethod(cls, sel);
}
⬇️
static Method _class_getMethod(Class cls, SEL sel)
{
    mutex_locker_t lock(runtimeLock);
    return getMethod_nolock(cls, sel);
}
⬇️
static method_t *
getMethod_nolock(Class cls, SEL sel)
{
    method_t *m = nil;

    runtimeLock.assertLocked();

    // fixme nil cls?
    // fixme nil sel?

    ASSERT(cls->isRealized());

    while (cls  &&  ((m = getMethodNoSuper_nolock(cls, sel))) == nil) {
        cls = cls->superclass;
    }

    return m;
}
⬇️
static method_t *
getMethodNoSuper_nolock(Class cls, SEL sel)
{
    runtimeLock.assertLocked();

    ASSERT(cls->isRealized());
    // fixme nil cls?
    // fixme nil sel?

    auto const methods = cls->data()->methods();
    for (auto mlists = methods.beginLists(),
              end = methods.endLists();
         mlists != end;
         ++mlists)
    {
        //  getMethodNoSuper_nolock is the hottest
        // caller of search_method_list, inlining it turns
        // getMethodNoSuper_nolock into a frame-less function and eliminates
        // any store from this codepath.
        method_t *m = search_method_list_inline(*mlists, sel);
        if (m) return m;
    }

    return nil;
}

从源码中可以看到,根据传入的Class与SEL查找到Method。earnMoney是存储在类中的,元类中没有;spendMoney是存储在元类中的,元类中没有。

注意:
1.明明是去查找方法,为什么要调用lookUpImpOrForward?
lookUpImpOrForward是查找方法的实现,但是,在这个方法中,会检测CFI攻击及保证类进行了初始化。
2.CFI:控制流完整性(Control Flow Integrity, CFI)是由加州大学和微软公司于2005年提出的一种防御控制流劫持攻击的安全机制。

3.3)cwClassMethod_classToMetaclass是通过class_getClassMethod获取类方法的,我们可以看看源码

Method class_getClassMethod(Class cls, SEL sel)
{
    if (!cls  ||  !sel) return nil;

    return class_getInstanceMethod(cls->getMeta(), sel);
}
⬇️
Class getMeta() {
    if (isMetaClass()) return (Class)this;
    else return this->ISA();
}  

哎哟,类方法查找本质是查找元类的实例方法。在获取元类中,如果是元类,直接返回,不是元类会去获取元类。class_getClassMethod中的cls不管传入是类还是元类,只要sel是类方法且存在,就能找到改方法

3.4)cwIMP_classToMetaclass是通过class_getMethodImplementation函数获取imp的,获取结果:


image.png

class_getMethodImplementation的源码:

IMP class_getMethodImplementation(Class cls, SEL sel)
{
    IMP imp;

    if (!cls  ||  !sel) return nil;

    imp = lookUpImpOrNil(nil, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER);

    // Translate forwarding function to C-callable external version
    if (!imp) {
        return _objc_msgForward;
    }

    return imp;
}

可以看到,他返回的IMP有两种,一种是方法的实现,一种是消息转发的函数指针。如果SEL没有实现,会返回消息转发函数指针,函数地址是一样的。所以,在类可以找到earnMoney方法实现,元类中可以找到spendMoney方法实现

4.iskindOfClass&isMemberOfClass的理解

iskindOfClass & isMemberOfClass 类方法调用

void isKindOrMember(){
    //-----使用 iskindOfClass & isMemberOfClass 类方法
    BOOL re1 = [(id)[NSObject class] isKindOfClass:[NSObject class]];//1
    BOOL re2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]];//0
    BOOL re3 = [(id)[Person class] isKindOfClass:[Person class]];//0
    BOOL re4 = [(id)[Person class] isMemberOfClass:[Person class]];//0
    NSLog(@" re1 :%hhd\n re2 :%hhd\n re3 :%hhd\n re4 :%hhd\n",re1,re2,re3,re4);
    //iskindOfClass & isMemberOfClass 实例方法调用
    //------iskindOfClass & isMemberOfClass 实例方法
    BOOL re5 = [(id)[NSObject alloc] isKindOfClass:[NSObject class]];//1
    BOOL re6 = [(id)[NSObject alloc] isMemberOfClass:[NSObject class]];//1
    BOOL re7 = [(id)[Person alloc] isKindOfClass:[Person class]];// 1
    BOOL re8 = [(id)[Person alloc] isMemberOfClass:[Person class]];//1
    NSLog(@" re5 :%hhd\n re6 :%hhd\n re7 :%hhd\n re8 :%hhd\n",re5,re6,re7,re8);
}

其最终结果打印如下:


class.png

我们看源码:

+ (BOOL)isMemberOfClass:(Class)cls {
    return self->ISA() == cls;
}

- (BOOL)isMemberOfClass:(Class)cls {
    return [self class] == cls;
}

+ (BOOL)isKindOfClass:(Class)cls {
    for (Class tcls = self->ISA(); tcls; tcls = tcls->superclass) {
        if (tcls == cls) return YES;
    }
    return NO;
}

- (BOOL)isKindOfClass:(Class)cls {
    for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {
        if (tcls == cls) return YES;
    }
    return NO;
}

断点的时候,发现isKindOfClass、class不会被调用。why?一想肯定是llvm这个坏人干的好事儿,去一查,果然是他,发现他悄悄地将isKindOfClass换成了objc_opt_isKindOfClass,class换成objc_opt_class,所以,最总走的源码应该是:

// Calls [obj isKindOfClass]
BOOL
objc_opt_isKindOfClass(id obj, Class otherClass)
{
#if __OBJC2__
    if (slowpath(!obj)) return NO;
    Class cls = obj->getIsa();
    if (fastpath(!cls->hasCustomCore())) {
        for (Class tcls = cls; tcls; tcls = tcls->superclass) {
            if (tcls == otherClass) return YES;
        }
        return NO;
    }
#endif
    return ((BOOL(*)(id, SEL, Class))objc_msgSend)(obj, @selector(isKindOfClass:), otherClass);
}

+ (BOOL)isMemberOfClass:(Class)cls {
    return self->ISA() == cls;
}

- (BOOL)isMemberOfClass:(Class)cls {
    return [self class] == cls;
}

// Calls [obj class]
Class
objc_opt_class(id obj)
{
#if __OBJC2__
    if (slowpath(!obj)) return nil;
    Class cls = obj->getIsa();
    if (fastpath(!cls->hasCustomCore())) {
        return cls->isMetaClass() ? obj : cls;
    }
#endif
    return ((Class(*)(id, SEL))objc_msgSend)(obj, @selector(class));
}

总结:
1.objc_opt_class很明显,如果传入的是实例对象,返回的是类对象。否则,传入什么返回什么(传入类对象返回该类对象,传入元类返回该元类)。
2.objc_opt_isKindOfClass方法是先获取传入对象的isa(类/元类),从isa开始查找,然后,从isa的继承链上查找。
3.+isMemberOfClass是取isa与传入对象比较
4.-isMemberOfClass是取类与传入对象比较

接下来,逐一解释:
4.1)BOOL re1 = [(id)[NSObject class] isKindOfClass:[NSObject class]];//1
objc_opt_class可以得知[NSObject class]返回的是类对象NSObject。由objc_opt_isKindOfClass可以的得知,将从元类NSObject开始遍历,并与类NSObject比较。由isa的走位可以知道元类NSObject的superclass是类NSObject,所以,NSObject元类.superclass == NSOject为true。

4.2)BOOL re2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]];//0
objc_opt_class可以得知[NSObject class]返回的是类对象NSObject。由isMemberOfClass类方法可以的得知,将从类NSObject取出isa,即元类NSObject与类NSObject比较。显然,这两个NSObject是不等的,一个是类,一个是元类。

4.3)BOOL re3 = [(id)[Person class] isKindOfClass:[Person class]];//0
objc_opt_class可以得知[Person class]返回的是类对象Person。由objc_opt_isKindOfClass可以的得知,将从元类Person开始遍历,并与类Person比较。由isa的走位可以知道:元类Personsuperclass是元类NSObject。所以,元类Person->元类NSObject->类NSObject->nil中没有一个和类Person相同。

4.4)BOOL re4 = [(id)[Person class] isMemberOfClass:[Person class]];//0
objc_opt_class可以得知[Person class]返回的是类对象Person。由isMemberOfClass可以的得知,将从类Person取出isa,即元类Person与类Person比较。显然,这两个Person是不等的,一个是类,一个是元类。

4.5)BOOL re5 = [(id)[NSObject alloc] isKindOfClass:[NSObject class]];//1
objc_opt_isKindOfClass可以的得知,将从实例NSObject中获取类NSObject,从类NSObject开始遍历,并与类NSObject比较。所以,类NSObject == 类NSObject为true。

4.6)BOOL re6 = [(id)[NSObject alloc] isMemberOfClass:[NSObject class]];//1
isMemberOfClass实例方法可以的得知,将从实例NSObject取出class,由objc_opt_class可以得知[NSObject class]是类NSObject。即类NSObject与类NSObject比较。显然,这两个NSObject是等的。

4.7)BOOL re7 = [(id)[Person alloc] isKindOfClass:[Person class]];// 1
objc_opt_isKindOfClass可以的得知,将从实例Person中获取类Person,从类Person开始遍历,并与类Person比较。所以,所以,类Person == 类Person为true。

4.8)BOOL re8 = [(id)[Person alloc] isMemberOfClass:[Person class]];//1
isMemberOfClass实例方法可以的得知,将从实例Person取出class,由objc_opt_class可以得知[person class]是类Person,即类Person与类Person比较。显然,这两个Person是等的。

你可能感兴趣的:(iOS底层原理-几个面试题分析)