iOS底层学习——类的结构探索补充

语言: CN / TW / HK

上一篇文章类的结构探索中,学习了isa指针superclass指针的走位;对象、类、元类、根元类的关系;类的结构;类中bits数据结构;rorwrwe的关系等。本编在类的结构探索基础上做一些补充!

一.类的加载

前面的一些探索中我们遇到了一些类加载相关的知识点,这里简单做一些补充和说明。类加载的详细知识点会在以后的篇幅中说明!

1.firstSubclass为什么是nil

比如上一篇文章中的案例,LGTeacher继承自LGPersonLGPerson继承自NSObject,所以LGTeacherLGPerson的子类。但是在进行LGPersonclass_rw_t的数据结构打印时,发现其子类为空,为什么呢?见下图:

image.png

LGTeacher不是继承自LGPerson吗,这里应该输出LGTeacher才对啊!做个调整,在LGPerson初始化之前,先进行LGTeacher的初始化!见下图:

image.png

这里面成功输出了LGTeacher为什呢?因为对象需要初始化,类也要初始化(加载类)!但是什么时候初始化类呢?这里涉及到懒加载类和非懒加载类的区别。

简单说就是: - 懒加载类:没有实现+load()方法的类,会在第一次消息发送的时候加载 - 非懒加载类:实现+load()方法的类,会在main函数之前,dyld进行动态库加载的时候进行类的初始化

上面的案例中,进行LGTeacher对象初始化时,发送了消息,所以完成了类的加载。所以在LGPerson类的class_rw_tfirstSubclass中输出了LGTeacher

2.cls->instanceSize

其实在对象初始化的时候,进行内存空间计算的时候已经涉及到了类加载的知识点!见下面的代码:

``` inline size_t instanceSize(size_t extraBytes) const { if (fastpath(cache.hasFastInstanceSize(extraBytes))) { return cache.fastInstanceSize(extraBytes); }

    size_t size = alignedInstanceSize() + extraBytes;
    // CF requires all objects be at least 16 bytes.
    if (size < 16) size = 16;
    return size;
}

`iffastInstanceSize16字节对齐算法,alignedInstanceSize()8字节对齐,并且一定会走16字节对齐流程,也就是cache.fastInstanceSize一定返回YES``!

为什么呢? - 如果类是懒加载,对象初始化,发送alloc消息时,会对类进行初始化,调用类的实现方法realizeClassWithoutSwift - 如果类是非懒加载,对象初始化在main函数之前,同样会调用类的实现方法realizeClassWithoutSwift

realizeClassWithoutSwit中,对fastInstanceSize方法进行设置:

// Set fastInstanceSize if it wasn't set already. cls->setInstanceSize(ro->instanceSize); 所以,cls->instanceSize一定是16字节对齐返回!

总结

  • 懒加载类:没有实现+load()方法的类,会在第一次消息发送的时候加载
  • 非懒加载类:实现+load()方法的类,会在main函数之前,dyld进行动态库加载的时候进行类的初始化
  • 不管是懒加载还是非懒加载,都会调用类的实现方法realizeClassWithoutSwift

详细的类加载流程会在之后的文章中补充!!!

二.属性、成员变量、实例变量

见下面案例,该案例和上一篇文章,类的结构探索中的案例是一样的,一个成员变量subject,两个属性namehobby,两个方法-(void)sayNB+ (void)say666,见下面案例:

``` @interface LGPerson : NSObject{ NSString subject; } @property (nonatomic, copy) NSString name; @property (nonatomic, strong) NSString *hobby;

  • (void)sayNB;
  • (void)say666; @end

int main(int argc, const char * argv[]) { @autoreleasepool { LGPerson *t1 = [[LGPerson alloc] init]; } return 0; } ```

1.lldb探索

通过调用class_rw_t中的properties()方法,可以获取2个属性namehobby。见下图:

image.png

在获取方法列表时,多出了四个对象方法,分别是两个属性的get\set方法,见下图:

image.png

说明系统会自动为属性添加get\set方法。同时在获取成员变量列表时,也多出了两个成员变量分别是_name_hobby。见下图:

image.png

可以得出个结论: - 属性 = get\set方法 + 带下划线的成员变量; - 方法在底层是以method_t的结构体形式呈现,包括方法名称类型编码方法实现,其中类型编码OC对象的本质与isa中也已经做了说明。

2.cpp源码探索

通过clang可以将m文件编译成cpp文件,这样我们可以了解更多的关于底层的实现原理。见下面的cpp源码中LGPerson的定义:

```

ifndef _REWRITER_typedef_LGPerson

define _REWRITER_typedef_LGPerson

typedef struct objc_object LGPerson; typedef struct {} _objc_exc_LGPerson;

endif

extern "C" unsigned long OBJC_IVAR_$LGPerson$_name; extern "C" unsigned long OBJC_IVAR$LGPerson$_hobby; extern "C" unsigned long OBJC_IVAR$_LGPerson$__age; struct LGPerson_IMPL { struct NSObject_IMPL NSObject_IVARS; NSString subject; NSString _name; NSString _hobby; NSString __age; // 定义属性为_age };

// @property (nonatomic, copy) NSString name; // @property (nonatomic, copy) NSString hobby; // @property (nonatomic, copy) NSString *_age;

// - (void)sayNB; // + (void)say666; / @end /

// @implementation LGPerson

static NSString * I_LGPerson_name(LGPerson * self, SEL _cmd) { return ((NSString )((char )self + OBJC_IVAR$_LGPerson$_name)); } extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool, bool);

static void I_LGPerson_setName(LGPerson * self, SEL _cmd, NSString *name) { objc_setProperty (self, _cmd, OFFSETOFIVAR(struct LGPerson, _name), (id)name, 0, 1); }

static NSString * I_LGPerson_hobby(LGPerson * self, SEL _cmd) { return ((NSString )((char )self + OBJC_IVAR$LGPerson$_hobby)); } static void _I_LGPerson_setHobby(LGPerson * self, SEL cmd, NSString hobby) { ((NSString *)((char )self + OBJC_IVAR$_LGPerson$_hobby)) = hobby; }

static NSString * I_LGPerson__age(LGPerson * self, SEL _cmd) { return ((NSString )((char )self + OBJC_IVAR$LGPerson$__age)); } static void _I_LGPerson_set_age(LGPerson * self, SEL _cmd, NSString *_age) { objc_setProperty (self, _cmd, OFFSETOFIVAR(struct LGPerson, __age), (id)_age, 0, 1); } // @end ```

  • 定义的三个属性生成了三个成员变量,分别是:_name_hobby__age
  • 如果定义的属性本身带下划线,会再添加一个下划线,如:__age
  • 属性自动生成了get\set方法,而成员变量不会生成get\set方法
  • objc_setProperty,在对实例变量进行设置时,会自动调用;objc_setProperty方法。该方法可以理解为set方法的底层适配器,通过统一的封装,实现set方法的统一入口。此部分在OC对象的本质与isa中已做了说明。

3.属性、成员变量、实例变量的区别

  • 属性 = 带下划线成员变量 + setter + getter ⽅法

  • 实例变量 : 特殊的成员变量 (类的实例化),非基本数据类

三.Runtime面试题

定期更新!!!

1.class_getInstanceMethod

测试案例如下: ``` LGPerson两个方法 //- (void)sayHello; //+ (void)sayHappy;

// 参数为LGPerson.class void lgInstanceMethod_classToMetaclass(Class pClass){ const char *className = class_getName(pClass); Class metaClass = objc_getMetaClass(className);

Method method1 = class_getInstanceMethod(pClass, @selector(sayHello));
Method method2 = class_getInstanceMethod(metaClass, @selector(sayHello));

Method method3 = class_getInstanceMethod(pClass, @selector(sayHappy));
Method method4 = class_getInstanceMethod(metaClass, @selector(sayHappy));

LGLog(@"%s - %p-%p-%p-%p",__func__,method1,method2,method3,method4);

}

` **结果分析**: 1. 类中是否有对象方法sayHello?有! 2. 元类中是和否有对象方法sayHello?没有! 3. 类中是否有类方法sayHappy?没有! 4. 元类中是否有类方法sayHappy``?有!

结果验证

image.png

总结:这里不能被class_getInstanceMethod中的instance所误导,误认为是获取类的对象的方法。这里需要明确一个概念是,对象是类的实例,类是元类的实例,所以在底层的封装中没有类方法这一说,都视为对象方法。如果传入的是类,那就是获取类的对象方法,如果传入的是元类,就是获取元类的对象方法。而源码实现的核心流程就是从类的rw中获取方法列表: auto const methods = cls->data()->methods();

2.class_getClassMethod

引入下面的测试案例: ``` LGPerson两个方法 //- (void)sayHello; //+ (void)sayHappy;

// 参数为LGPerson.class void lgClassMethod_classToMetaclass(Class pClass){ const char *className = class_getName(pClass); Class metaClass = objc_getMetaClass(className);

Method method1 = class_getClassMethod(pClass, @selector(sayHello));
Method method2 = class_getClassMethod(metaClass, @selector(sayHello));

Method method3 = class_getClassMethod(pClass, @selector(sayHappy));
Method method4 = class_getClassMethod(metaClass, @selector(sayHappy));

LGLog(@"%s-%p-%p-%p-%p",__func__,method1,method2,method3,method4);

} ` **结果分析**: 1.class_getClassMethod是获取类方法,也就是元类的对象方法,是从元类中获取,所以-(void)sayHello一定是不存在的!所以method1method2一定是空。 2.method3呢?虽然传入的是类,但是源码中会通过类找到元类,从而获取对象方法。所以method3是存在的! 3.method4``没啥好说的,一定存在!

结果验证

image.png

总结class_getClassMethod的源码实现见下面的代码,通过传入的类找到元类,如果传入的已经是元类,直接使用该类获取。而获取Method调用的是class_getInstanceMethod方法,也就是获取对象方法。对象是类的实例,类是元类的实例,所以在底层的封装中没有类方法这一说,都视为对象方法。 ``` Method class_getClassMethod(Class cls, SEL sel) { if (!cls || !sel) return nil;、 return class_getInstanceMethod(cls->getMeta(), sel); }

Class getMeta() { if (isMetaClassMaybeUnrealized()) return (Class)this; else return this->ISA(); } ```

3.class_getMethodImplementation

类中获取方法实现,引入下面的测试案例: ``` LGPerson两个方法 //- (void)sayHello; //+ (void)sayHappy;

// 参数为LGPerson.class void lgIMP_classToMetaclass(Class pClass){ const char *className = class_getName(pClass); Class metaClass = objc_getMetaClass(className);

IMP imp1 = class_getMethodImplementation(pClass, @selector(sayHello));
IMP imp2 = class_getMethodImplementation(metaClass, @selector(sayHello));

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

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

} ```

结果分析: 1. 类中是否有对象方法sayHello的方法实现?有! 2. 元类中是和否有对象方法sayHello的方法实现?没有! 3. 类中是否有类方法sayHappy的方法实现?没有! 4. 元类中是否有类方法sayHappy的方法实现?有!

结果验证:

image.png

总结:很意外,imp2imp3竟然不为空,并且方法实现地址是一样的,为啥?class_getMethodImplementation实现源码去分析,在获取类的方法实现时,会进入慢速方法查找流程,如果没有找到则会返回一个默认的方法实现,也就是_objc_msgForward消息转发。首先元类中是没有对象方法的,类中也没有类方法,所以他们返回了相同的方法实现,也即是消息转发。 ``` IMP class_getMethodImplementation(Class cls, SEL sel) { IMP imp;

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

lockdebug_assert_no_locks_locked_except({ &loadMethodLock });

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

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

return imp;

} ```

补充

  1. objc_msgSend

objc_msgSendObjective-C消息系统的入口,它是使用汇编实现的,所有Objective-C的消息都会转换成使用objc_msgSend来进行消息发送。

  1. _objc_msgForward

_objc_msgForward是消息转发的入口,它也是使用汇编实现的。当在方法查找loopUpImpOrForward时,没有查找到对应Selector的方法的IMP,并且动态方法决议(resolveInstanceMethod/resolveClassMethod)失败的时候会返回_objc_msgForward,代表需要进行消息转发。通过把方法的实现指向_objc_msgForward,可以使方法直接进行消息转发。

  1. _objc_msgForward_impcache 跟踪慢速方法查找的源码,发现class_getMethodImplementation返回的_objc_msgForwardloopUpImpOrForward中默认赋值的_objc_msgForward_impcache地址不一样,同时消息转发,为啥不同呢?

image.png

objc_msg_arm64.s汇编中找到了相关说明。_objc_msgForward是外部可调用的由method_getImplementation()之类返回的函数。_objc_msgForward_impcache是实际存放在的函数指针方法缓存。为不同的方法或者不同的使用场景提供了不同的消息转发?

4.isKindOfClass、isMemberOfClass

引入案例:

``` void lgKindofDemo(void){ BOOL re1 = [(id)[NSObject class] isKindOfClass:[NSObject class]]; // BOOL re2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]]; // BOOL re3 = [(id)[LGPerson class] isKindOfClass:[LGPerson class]]; // BOOL re4 = [(id)[LGPerson class] isMemberOfClass:[LGPerson class]]; // NSLog(@"\n re1 :%hhd\n re2 :%hhd\n re3 :%hhd\n re4 :%hhd\n",re1,re2,re3,re4);

BOOL re5 = [(id)[NSObject alloc] isKindOfClass:[NSObject class]];       //
BOOL re6 = [(id)[NSObject alloc] isMemberOfClass:[NSObject class]];     //
BOOL re7 = [(id)[LGPerson alloc] isKindOfClass:[LGPerson class]];       //
BOOL re8 = [(id)[LGPerson alloc] isMemberOfClass:[LGPerson class]];     //
NSLog(@"\n re5 :%hhd\n re6 :%hhd\n re7 :%hhd\n re8 :%hhd\n",re5,re6,re7,re8);

} ```

运行结果:

image.png

逐个分析:

1.+ (BOOL)isKindOfClass

源码实现如下: + (BOOL)isKindOfClass:(Class)cls { for (Class tcls = self->ISA(); tcls; tcls = tcls->getSuperclass()) { if (tcls == cls) return YES; } return NO; }

  • [(id)[NSObject class] isKindOfClass:[NSObject class]];

    1. 获取NSObject类的元类,此时tcls是根元类,cls是根类,所以不相等;
    2. for循环,tcls等于根元类的父类,也就是根类,相等;
    3. 在循环的第二次判断是返回YES;
    4. 最终结果为YES

    路径是: 根类(self->ISA()) -> 根元类(getSuperclass()) -> 根类 == 根类[NSObject class]

  • [(id)[LGPerson class] isKindOfClass:[LGPerson class]];

    1. 获取LGPerson类的元类,此时tclsLGPerson元类,clsLGPerson类,所以不相等;
    2. for循环,tcls等于根元类,不相等;
    3. for循环,tcls等于根类,不相等;
    4. for循环,tcls等于nil,不相等;
    5. 最终结果为NO

    路径是: LGPerson类(self->ISA()) -> LGPerson元类(getSuperclass()) -> 根元类(getSuperclass()) -> 根类(getSuperclass()) -> nil != LGPerson类[LGPerson class]

2.+ (BOOL)isMemberOfClass

源码实现如下: + (BOOL)isMemberOfClass:(Class)cls { return self->ISA() == cls; }

  • [(id)[NSObject class] isMemberOfClass:[NSObject class]];

    根元类与根类比较,返回NO

  • [(id)[LGPerson class] isMemberOfClass:[LGPerson class]];

    LGPerson元类与LGPerson类比较,返回NO

3.- (BOOL)isKindOfClass

源码实现如下: ``` - (BOOL)isKindOfClass:(Class)cls { for (Class tcls = [self class]; tcls; tcls = tcls->getSuperclass()) { if (tcls == cls) return YES; } return NO; }

  • (Class)class { return object_getClass(self); } ```

  • [(id)[NSObject alloc] isKindOfClass:[NSObject class]];

    1. NSObject对象获取类,得到根类,等于[NSObject class]
    2. 在循环的第一次即返回YES
  • [(id)[LGPerson alloc] isKindOfClass:[LGPerson class]];

    1. LGPerson对象获取类,得到LGPerson类,等于[LGPerson class]
    2. 在循环的第一次即返回YES
4.- (BOOL)isMemberOfClass

源码实现如下: ``` - (BOOL)isMemberOfClass:(Class)cls { return [self class] == cls; }

  • (Class)class { return object_getClass(self); } ```

  • [(id)[NSObject alloc] isMemberOfClass:[NSObject class]];

    根类对象获取类,与根类比较,返回YES

  • [(id)[LGPerson alloc] isMemberOfClass:[LGPerson class]];

    LGPerson对象获取类与LGPerson类比较,返回YES