举个🌰
我们使用clang
命令转成c++
文件
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main.cpp
通过上述命令获取 Test1
Test2
相关的源码:
初探
上图代码看出:
OC对象在底层的本质就是结构体,结构体中包含了所有属性方法和父类信息
typedef struct objc_object Test1
, 可看出Test1
是objc_object
类型的结构体
typedef struct objc_class *Class;
typedef struct objc_object *id;
可看出:
Class
类实际上是struct objc_class *
结构体指针类型,Class
其实就是个别名-
id
实际上是struct objc_object *
结构体指针类型, 这就是为什么我们平常定义id
类型不带*
原因
-
NSObject_IMPL
内部只有一个成员变量isa
@property属性在底层取消了属性而是转换成"下划线+成员变量"以及set, get方法的形式
@ interface{}成员变量在底层还是以成员变量的存放, 不会有set, get方法
struct NSObject_IMPL NSObject_IVARS
其实就是ISA
结构体是可以继承的, 在C++是可以继承的, 在C可以伪继承。这种继承的方式是直接将NSObject结构体定义为 Test1中的第一个成员变量, 意味着 Test1 拥有 NSObject中的所有成员变量。所以
NSObject_IVARS
等效于NSObject
中的isa
同理,
Test2
中的第一个成员变量是struct Test1_IMPL Test1_IVARS;
OC对象在底层被C语言编程成结构体。而C语言结构体的继承方式,是每一个结构体第一个属性都包含父结构体的所有信息。如此,实现了OC类的继承关系
所以, Test2
的结构简化后是:
struct Test2_IMPL {
Class isa;
NSString *ivar_name;
NSString *_name;
NSString *_name0;
};
Getter
和Setter
偏移量定义
通过上述代码有可看出:
NSString *_name; NSString *_name0;
是自定义的属性, 在底层是以成员变量的形式存放的定义的属性,底层自动添加了 get 和 set 方法。get 方法名为
_I_类名_属性名
,set 方法名为_I_类名_set属性名_
,例如:_I_Test1_name
_I_Test1_setName_
就是他们的get
set
方法Test1 * self, SEL _cmd
,这些是隐藏参数,只存在于底层get
方法取值return (*(NSString **)((char *)self + OBJC_IVAR_$_TestObj$_SAName));
, 实际上也是一种平移取值方法,self(首地址) + OBJC_IVAR_$_TestObj$_SAName(偏移量) = 想要获取值的地址
, 然后通过直接访问指针地址,返回指针地址的值set
方法则是通过调用objc_setProperty
什么是对象
对象
: 所有继承于objc_object
的都叫做对象实例对象
: 即是类对象objc_class
实例化出来的isa
: 所有对象都有一个isa
,指向它的类或元类,里面存储这类或元类的信息
struct objc_object
对象类型的底层结构,首元素是
isa
(这里涉及到struct结构的内存优化,我们这里记住结论。isa在objc_object的首位元素即可)
内存优化看这里
struct objc_class
从上图的结构可以看出,在类的内存中,首地址表示
isa
,superclass
在isa
后面,需要内存地址偏移8位获取
深入探索
示例:
寻找父类
- 通过内存位移获取到了实例对象
m
的类对象Man
- 根据类对象
objc_class
结构,可以看出superclass
在isa
之后,isa
占8位,所以通过类地址偏移8位获取到父类Person
先获取到
Person
的isa
是0x00000001000089d8
然后内存偏移8位,获取到它的父类
NSObject
- 同上继续查找
NSObjet
的父类是nil(0x0000000000000000)
over!
isa溯源
元类
的定义和创建都由系统控制,由编译器自动完成,不受我们管理
实例对象的isa
来自于类,类也是对象。那类的isa
就指向元类
类既然是对象,就需要管理方法、属性的存储和归属。而这个管理者,就是元类(Meta)
- 通过获取
Man
类的isa
,打印出来还是Man
,即是Man的元类
继续打印
Man
的isa
,获取到Man
的根元类NSObject: 0x00000001003720f0
,根元类
NSObject
的isa
则指向自己0x00000001003720f0
通过直接打印
NSObject
类地址,发现与上图中的不一致而且
NSObject
的isa
也指向上图的NSObject
所以上图的
NSObject: 0x00000001003720f0
是根元类
- 按照上面的方法,获取根元类
0x00000001003720f0
的父类
可以看到,根元类的父类
isa
刚好指向NSObject
本类0x0000000100372140
- 获取
Man
元类的父类数据
可以看出,
Man
元类的父类是Person
的元类
以上得出结论如下图:
Set方法
set
方法最终会走到reallySetProperty
做处理
objc_setProperty
相当于一个承上启下接口, 上层许多个set
方法直接对接llvm, 则llvm需要针对每一个set
做对应处理, 则会很麻烦。所以苹果设置了一个中间层(接口隔离层)objc_setProperty
, 令他处理一部分set
方法, 保证无论上层怎么变, 传入llvm的格式不会有变化。主要是达到上下层接口隔离的目的
- 外部set方法: 个性化定制层(例如setName、setAge等)
↓
- objc_setProperty:接口隔离层 (将外界信息转化为对内存地址和值的操作)
↓
- reallySetProperty:底层实现层 (赋值和内存管理)
LLVM
中的objc_setProperty
- 在llvm源码中搜索
"objc_setProperty"
发现getSetPropertyFn()方法中调用CGM.CreateRuntimeFunction(FTy, "objc_setProperty");CGM创建runtime函数objc_setProperty
Fn: 为function函数的缩写
- 全局搜索
getSetPropertyFn()
- 查询
GetPropertySetFunction
可看到如果case为GetSetProperty和SetPropertyAndExpressionGet会调用GetPropertySetFunction
其中UseOptimizedSetter(CGM)这个判断后面标注为// 10.8 and iOS 6.0 code and GC is off即10.8和iOS 6.0代码,GC关闭, 所以新版本直接走GetPropertySetFunction
- 查询GetSetProperty
IsCopy: 如果属性修饰符为copy, 直接会调用objc_setProperty
Retain & !IsAtomic: 如果属性修饰符为retain, 并且为非原子性nonatomic, 也会调用objc_setProperty
- 验证:
可看到 copy或者 retain&nonatomic,会调用objc_setProperty
assgin
的话
直接进行偏移后赋值操作
retain和copy都是调用了objc_setProperty。 不同的是objc_setProperty内部实现不同(详看objc4源码中的objc_setProperty代码)
copy和mutableCopy:是新开辟空间,旧值release;
其他修饰类型:是新值retain,旧值release。
strong、assign类型都是直接使用地址进行赋值(通过对象地址偏移相应字节找到属性地址)
如果在set方法后加入断点,可以在汇编层看到所有属性赋值后,会调用objc_storeStrong。
ARC
下, retain和strong都走这里:
先
retain
新值,然后赋值再将旧值release
探索
+ (id)self {
/// 当前类对象
return (id)self;
}
- (id)self {
/// 返回当前实例
return self;
}
+ (Class)class {
/// 返回当前类
return self;
}
- (Class)class {
/// 当前实例对象的类
return object_getClass(self);
}
+ (Class)superclass {
/// 返回当前类的父类
return self->getSuperclass();
}
- (Class)superclass {
/// 返回当前类的父类
return [self class]->getSuperclass();
}
+ (BOOL)isMemberOfClass:(Class)cls {
/// 当前类的元类是否与传入的类相等
return self->ISA() == cls;
}
- (BOOL)isMemberOfClass:(Class)cls {
/// 当前实例对象的类是否与传入的类相等
return [self class] == cls;
}
+ (BOOL)isKindOfClass:(Class)cls {
// 循环判断
// 元类 vs 传入类
// 父元类 vs 传入类
// 根元类 vs 传入类
// 根类 vs 传入类
for (Class tcls = self->ISA(); tcls; tcls = tcls->getSuperclass()) {
if (tcls == cls) return YES;
}
return NO;
}
- (BOOL)isKindOfClass:(Class)cls {
// 循环判断
// 类 vs 传入类
// 父类 vs 传入类
// 根类 vs 传入类
// nil vs 传入类
for (Class tcls = [self class]; tcls; tcls = tcls->getSuperclass()) {
if (tcls == cls) return YES;
}
return NO;
}
isKindOfClass
的底层实现:
// Calls [obj isKindOfClass]
BOOL
objc_opt_isKindOfClass(id obj, Class otherClass)
{
#if __OBJC2__
if (slowpath(!obj)) return NO;
// 做了一步obj->getIsa() 获取isa, 即
// 如果obj是对象,则isa是类,
// 如果obj是类,则isa是元类
Class cls = obj->getIsa();
// 缓存中是否能查找到当前类的isKindOfClass方法
// 找到走if
if (fastpath(!cls->hasCustomCore())) {
// 循环判断
// 如果是对象
// 当前类 vs 传入类
// 父类 vs 传入类
// 根类 vs 传入类
// nil vs 传入类
// 如果是类
// 元类 vs 传入类
// 父元类 vs 传入类
// 根元类 vs 传入类
for (Class tcls = cls; tcls; tcls = tcls->getSuperclass()) {
if (tcls == otherClass) return YES;
}
return NO;
}
#endif
/// 没找到,直接发送消息
return ((BOOL(*)(id, SEL, Class))objc_msgSend)(obj, @selector(isKindOfClass:), otherClass);
}