上一篇文章类的结构探索中,学习了isa指针
和superclass指针
的走位;对象、类、元类、根元类的关系;类的结构;类中bits
数据结构;ro
、rw
、rwe
的关系等。本编在类的结构探索基础上做一些补充!
一.类的加载
前面的一些探索中我们遇到了一些类加载
相关的知识点,这里简单做一些补充和说明。类加载的详细知识点会在以后的篇幅中说明!
1.firstSubclass为什么是nil
比如上一篇文章中的案例,LGTeacher
继承自LGPerson
,LGPerson
继承自NSObject
,所以LGTeacher
是LGPerson
的子类。但是在进行LGPerson
类class_rw_t
的数据结构打印时,发现其子类为空,为什么呢?见下图:
LGTeacher
不是继承自LGPerson
吗,这里应该输出LGTeacher
才对啊!做个调整,在LGPerson
初始化之前,先进行LGTeacher
的初始化!见下图:
这里面成功输出了LGTeacher
为什呢?因为对象需要初始化,类也要初始化(加载类)
!但是什么时候初始化类呢?这里涉及到懒加载类和非懒加载类
的区别。
简单说就是:
懒加载类
:没有实现+load()
方法的类,会在第一次消息发送
的时候加载非懒加载类
:实现+load()
方法的类,会在main函数
之前,dyld
进行动态库加载的时候进行类的初始化
上面的案例中,进行LGTeacher
对象初始化时,发送了消息,所以完成了类的加载。所以在LGPerson
类的class_rw_t
的firstSubclass
中输出了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;
}
if
中fastInstanceSize
是16字节对齐
算法,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
,两个属性name
和hobby
,两个方法-(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
个属性name
和hobby
。见下图:
在获取方法列表时,多出了四个对象方法
,分别是两个属性的get\set方法
,见下图:
说明系统会自动为属性
添加get\set方法
。同时在获取成员变量列表时,也多出了两个成员变量分别是_name
和_hobby
。见下图:
可以得出个结论:
属性
=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);
}
结果分析:
- 类中是否有
对象方法sayHello
?有! - 元类中是和否有
对象方法sayHello
?没有! - 类中是否有
类方法sayHappy
?没有! - 元类中是否有
类方法sayHappy
?有!
结果验证:
总结:这里不能被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);
}
结果分析:
class_getClassMethod
是获取类方法
,也就是元类的对象方法
,是从元类中获取,所以-(void)sayHello
一定是不存在的!所以method1
和method2
一定是空。method3
呢?虽然传入的是类,但是源码中会通过类找到元类,从而获取对象方法。所以method3
是存在的!method4
没啥好说的,一定存在!
结果验证:
总结: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);
}
结果分析:
- 类中是否有
对象方法sayHello
的方法实现?有! - 元类中是和否有
对象方法sayHello
的方法实现?没有! - 类中是否有
类方法sayHappy
的方法实现?没有! - 元类中是否有
类方法sayHappy
的方法实现?有!
结果验证:
总结:很意外,imp2
和imp3
竟然不为空,并且方法实现地址是一样的,为啥?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;
}
补充:
objc_msgSend
objc_msgSend
是Objective-C
消息系统的入口,它是使用汇编实现的,所有Objective-C
的消息都会转换成使用objc_msgSend
来进行消息发送。
_objc_msgForward
_objc_msgForward
是消息转发的入口,它也是使用汇编实现的。当在方法查找loopUpImpOrForward
时,没有查找到对应Selector
的方法的IMP
,并且动态方法决议(resolveInstanceMethod/resolveClassMethod)
失败的时候会返回_objc_msgForward
,代表需要进行消息转发
。通过把方法的实现指向_objc_msgForward
,可以使方法直接进行消息转发。
_objc_msgForward_impcache
跟踪慢速方法查找的源码,发现class_getMethodImplementation
返回的_objc_msgForward
和loopUpImpOrForward
中默认赋值的_objc_msgForward_impcache
地址不一样,同时消息转发,为啥不同呢?
在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);
}
运行结果:
逐个分析:
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]];
- 获取
NSObject
类的元类,此时tcls
是根元类,cls
是根类,所以不相等; for
循环,tcls
等于根元类的父类,也就是根类,相等;- 在循环的第二次判断是返回
YES
; - 最终结果为
YES
。
路径是:
根类(self->ISA()) -> 根元类(getSuperclass()) -> 根类 == 根类[NSObject class]
- 获取
-
[(id)[LGPerson class] isKindOfClass:[LGPerson class]];
- 获取
LGPerson
类的元类,此时tcls
是LGPerson
元类,cls
是LGPerson
类,所以不相等; for
循环,tcls
等于根元类,不相等;for
循环,tcls
等于根类,不相等;for
循环,tcls
等于nil
,不相等;- 最终结果为
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]];
NSObject
对象获取类,得到根类,等于[NSObject class]
;- 在循环的第一次即返回
YES
。
-
[(id)[LGPerson alloc] isKindOfClass:[LGPerson class]];
LGPerson
对象获取类,得到LGPerson
类,等于[LGPerson class]
;- 在循环的第一次即返回
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
。