Objc4-818底层探索(五):类信息内容补充
建议先看下Objc4-818底层探索(三):isa
建议先看下Objc4-818底层探索(四):isa与类
主要针对于类信息, 补充一些知识点:
知识点1:成员变量与属性
成员变量
@interface ViewController (){
NSString *name ; // 成员变量, 实例变量
int age; // 成员变量, 基本数据类型变量
id data; // 成员变量, 实例变量
}
- 通常在.h/.m文件以
@interface{ }
形式定义的变量
成员变量的访问权限
@interface ViewController (){
NSString * A;
@public
NSString * B;
@protected
NSString * C;
@private
NSString * D;
@package
NSString * E;
}
-
@public:
在任何地方都能直接访问对象的成员变量 -
@private:
只能在当前类的对象方法中直接访问, 如果子类要访问需要调用父类的get/set方法 -
@protected:
可以在当前类及其子类对象方法中直接访问,变量默认的访问权限就是 protected -
@package:
只能在framework内部的类是@protected的权限,对于外部的类是@private,相当于框架级的保护权限,适合使用在静态库.a中。
实例变量与基础数据类型变量
-
如果
成员变量
是一个类(类的实例化), 则这个变量
为实例变量
, 例如上面例子name, data (id 是 OC特有的类型。从本质上讲, id 等同于 (void *))都是实例变量。而age
是int
型, 像int
,double
,short
等是基础数据类型变量
-
实例变量
+基础数据类型变量
=成员变量
属性(属性变量)
@property (nonatomic, strong) NSString *hobby; //属性
-
通常在.h/.m文件以
@interface{ }
形式定义的变量 -
编译器会自动为属性生成
set
,get
方法, 以及生成成员变量_documentsDirectory
即成员变量名前加下划线形式, 详细看下面的知识点2 -
属性是用于与其他对象交互的变量, 正因为要与其他对象交互, 就有了属性修饰符或者叫属性特质, 如:nonatomic, readwrite, copy 等等
知识点2:关于成员变量和属性在底层
创建一个只有main
的项目 (创建main项目可以参考我这一篇章: IOS创建个只有main.m工程) 。里面添加一个对象继承NSObject
, 并添加一些的成员变量和属性。
@interface SATest : NSObject {
NSString *insStr;
}
@property (atomic, copy) NSString *atoCopyStr;
@property (nonatomic, copy) NSString *nonCopyStr;
@property (atomic) NSString *atoStr;
@property (nonatomic) NSString *nonStr;
@end
clang
一下, 看下前端编译器中底层实现
clang -rewrite-objc main.m -o main.cpp
clang
-
@property属性
在底层取消了属性
而是转换成"下划线+成员变量"
以及set, get
方法的形式 -
@ interface{}成员变量
在底层还是以成员变量
的存放, 不会有set
,get
方法
之前的探索我们还能得到以下结论
-
通过
@interface XXXX {}
定义的成员变量
,会存储在类
的bits
属性中,通过bits → data() → ro() → ivars
获取成员变量
列表,除了包括成员变量
,还包括属性
的成员变量
-
通过
@property
定义的属性
,不仅仅在ro() → ivars
中以下划线存在, 并且还存储在bits
属性中,通过bits --> data() --> properties() --> list
获取属性列表
,其中只包含property
属性
知识点3: 关于objc_setProperty
objc_setProperty在clang底层中, 会发现属性有些set
方法里面有一个objc_setProperty
方法, 而且有些方法有, 有些没有, 我们接下来探索下objc_setProperty
。818源码查找下objc_setProperty
有
可看到除了objc_setProperty
以外还有objc_setProperty_atomic
, objc_setProperty_nonatomic
, objc_setProperty_atomic_copy
, objc_setProperty_nonatomic_copy
而且他们都直接调用一个reallySetProperty
static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy)
{
if (offset == 0) {
object_setClass(self, newValue);
return;
}
id oldValue;
id *slot = (id*) ((char*)self + offset);
if (copy) {
newValue = [newValue copyWithZone:nil];
} else if (mutableCopy) {
newValue = [newValue mutableCopyWithZone:nil];
} else {
if (*slot == newValue) return;
newValue = objc_retain(newValue);
}
if (!atomic) {
oldValue = *slot;
*slot = newValue;
} else {
spinlock_t& slotlock = PropertyLocks[slot];
slotlock.lock();
oldValue = *slot;
*slot = newValue;
slotlock.unlock();
}
objc_release(oldValue);
}
可看到他在底层的操作主要是对新增的retain
, 对旧值的release
objc_setProperty
相当于一个承上启下接口, 上层许多个set
方法直接对接llvm
, 则llvm
需要针对每一个set做对应处理, 则会很麻烦。所以苹果设置了一个中间层(接口隔离层)objc_setProperty
, 令他处理一部分set
方法, 保证无论上层怎么变, 传入llvm
的格式不会有变化。主要是达到上下层接口隔离的目的。
LLVM中的objc_setProperty
看底层可看出, 有些需要有些需要objc_setProperty
, 有些并无调用objc_setProperty
, 这块要看底层LLVM
, 看下LLVM
怎么处理的objc_setProperty
。这里我们要用逆推法
[第一步] 查询objc_setProperty
因为要查找objc_setProperty
, 全局搜索objc_setProperty
。发现getSetPropertyFn()
方法中调用CGM.CreateRuntimeFunction(FTy, "objc_setProperty");
CGM创建runtime
函数objc_setProperty
- Fn: 为function函数的缩写
[第二步] 查询getSetPropertyFn()
全局搜索什么方法调用getSetPropertyFn()
, 可看到GetPropertySetFunction
调用
[第三步] 查询GetPropertySetFunction
全局搜索什么方法调用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
全局搜索什么方法调用GetSetProperty
, 可看见PropertyImplStrategy
中有
-
IsCopy
: 如果属性修饰符为copy
, 直接会调用objc_setProperty
-
Retain
&!IsAtomic
: 如果属性修饰符为retain
, 并且为非原子性nonatomic
, 也会调用objc_setProperty
[第五步] 验证objc_setProperty
验证
可看到 copy
或者 retain
&nonatomic
,会调用objc_setProperty
知识点4: 关于编码
编码clang
命令之后我们会发现会出现很多T
, @
, v
...这些符号, 那这些又是什么呢?
iOS系统提供了一个叫做@encode
的指令
,可以将具体的类型表示成字符串编码
- @encode实际上是编译器指令其中的一种。
- @encode能够返回一个Objective-C 类型编码(Objective-C Type Encodings)。
- @encode是一种编译器内部表示的字符串,方便识别,类似于 ANSI C 的 typeof 操作。
具体看下苹果官方定义:
苹果Type Encodings:
https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html
苹果Property Type and Functions:
https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtPropertyIntrospection.html#//apple_ref/doc/uid/TP40008048-CH101-SW1
当然我们也可以通过命令查看编码
#pragma mark - 各种类型编码
void lgTypes(void){
NSLog(@"char --> %s",@encode(char));
NSLog(@"int --> %s",@encode(int));
NSLog(@"short --> %s",@encode(short));
NSLog(@"long --> %s",@encode(long));
NSLog(@"long long --> %s",@encode(long long));
NSLog(@"unsigned char --> %s",@encode(unsigned char));
NSLog(@"unsigned int --> %s",@encode(unsigned int));
NSLog(@"unsigned short --> %s",@encode(unsigned short));
NSLog(@"unsigned long --> %s",@encode(unsigned long long));
NSLog(@"float --> %s",@encode(float));
NSLog(@"bool --> %s",@encode(bool));
NSLog(@"void --> %s",@encode(void));
NSLog(@"char * --> %s",@encode(char *));
NSLog(@"id --> %s",@encode(id));
NSLog(@"Class --> %s",@encode(Class));
NSLog(@"SEL --> %s",@encode(SEL));
int array[] = {1,2,3};
NSLog(@"int[] --> %s",@encode(typeof(array)));
typedef struct person{
char *name;
int age;
}Person;
NSLog(@"struct --> %s",@encode(Person));
typedef union union_type{
char *name;
int a;
}Union;
NSLog(@"union --> %s",@encode(Union));
int a = 2;
int *b = {&a};
NSLog(@"int[] --> %s",@encode(typeof(b)));
}
编码代码打印
知识点5: 关于类与isa走位图面试题
先看下isa
走位图
熟悉走位图之后我们看几个题目
题目1:
看下这个例子返回打印结果(回复有或者没有即可)
//- (void)sayHello;
//+ (void)sayHappy;
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));
NSLog(@"method1: %p", method1);
NSLog(@"method2: %p", method2);
NSLog(@"method3: %p", method3);
NSLog(@"method4: %p", method4);
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
SATest *test = [SATest alloc];
Class pClass = object_getClass(test);
lgInstanceMethod_classToMetaclass(pClass);
//lgClassMethod_classToMetaclass(pClass);
//lgIMP_classToMetaclass(pClass);
}
return 0;
}
解题思路:
先看下class_getInstanceMethod
源码
/***********************************************************************
* class_getInstanceMethod. Return the instance method for the
* specified class and selector.
**********************************************************************/
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);
}
就是判断类是否有指定的实例方法, 实例方法存在当前类中, 类方法存在元类中
// - (void)sayHello; 实例方法
// + (void)sayHappy; 类方法
-
class_getInstanceMethod(pClass, @selector(sayHello));
, 判断当前类
是否有实例方法sayHello
, 有 -
class_getInstanceMethod(metaClass, @selector(sayHello));
, 判断元类
是否有实例方法sayHello
, 无 -
class_getInstanceMethod(pClass, @selector(sayHappy));
, 判断当前类
是否有类方法sayHappy
, 无 -
class_getInstanceMethod(metaClass, @selector(sayHappy));
, 判断元类
是否有类方法sayHappy
, 有
结果:
问题1结果题目2:
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));
NSLog(@"method1: %p", method1);
NSLog(@"method2: %p", method2);
NSLog(@"method3: %p", method3);
NSLog(@"method4: %p", method4);
}
/***********************************************************************
* class_getClassMethod. Return the class method for the specified
* class and selector.
**********************************************************************/
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();
}
那么
-
class_getClassMethod(pClass, @selector(sayHello))
, 在当前类
中找实例方法sayHello
, 无 -
class_getClassMethod(metaClass, @selector(sayHello))
, 在元类
中找实例方法sayHello
, 无 -
class_getClassMethod(pClass, @selector(sayHappy))
, 在当前类
中找类方法sayHappy
, 有 -
class_getClassMethod(metaClass, @selector(sayHappy))
, 在元类
中找类方法sayHappy
, 因为传入的是元类, 这里cls->getMeta()返回元类本身, 所以有
结果:
问题2结果题目3:
#import "SATest.h"
void lgKindofDemo(void){
BOOL re1 = [(id)[NSObject class] isKindOfClass:[NSObject class]];
BOOL re2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]];
BOOL re3 = [(id)[SATest class] isKindOfClass:[SATest class]];
BOOL re4 = [(id)[SATest class] isMemberOfClass:[SATest 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)[SATest alloc] isKindOfClass:[SATest class]];
BOOL re8 = [(id)[SATest alloc] isMemberOfClass:[SATest class]];
NSLog(@"\n re5 :%hhd \n re6 :%hhd \n re7 :%hhd \n re8 :%hhd \n",re5,re6,re7,re8);
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
lgKindofDemo();
NSLog(@"Hello World");
}
return 0;
}
解题思路:
需要先看isKindOfClass
, isMemberOfClass
底层
isMemberOfClass
先看下isMemberOfClass
底层实现
// 类方法
+ (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;
}
-
[XXX class]
: 取得类, 调用类方法
-
[XXX alloc]
: 开辟对象, 调用对象/实例方法
-
[(id)[NSObject class] isKindOfClass:[NSObject class]];
: 类方法, 判断NSObject
即与传入类NSObject
。相等
2.[(id)[NSObject class] isMemberOfClass:[NSObject class]];
: 类方法, 判断NSObject元类
即根元类
与传入类NSObject
。不相等
3.[(id)[SATest class] isKindOfClass:[SATest class]]
: 类方法, 依次判断SATest父类
, 根类
, nil
与传入类SATest
。不相等
4.[(id)[SATest class] isMemberOfClass:[SATest class]];
: 类方法, 判断SATest
的元类
即元类
与传入类SATest
。不相等
5.[(id)[NSObject alloc] isKindOfClass:[NSObject class]];
: 实例方法, 循环判断NSObject
与传入类NSObject
。相等
6.[(id)[NSObject alloc] isMemberOfClass:[NSObject class]];
: 实例方法, 判断NSObject
与传入类NSObject
。相等
7.[(id)[SATest alloc] isKindOfClass:[SATest class]];
: 实例方法, 循环判断SATest
与传入类SATest
。相等
-
[(id)[SATest alloc] isMemberOfClass:[SATest class]];
: 实例方法, 判断SATest
类与传入类SATest
。相等
固结果为 1, 0, 0, 0, 1, 1, 1, 1
结果:
问题3结果但实际上这么思考, 结果没问题但过程有问题, 因为我们打开终端, 会发现系统实际上没有调用 isKindOfClass
, 而是调用objc_opt_isKindOfClass
, 系统重定向了isKindOfClass
方法。
那么我们需要查找objc_opt_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 传入类
// 根类 vs 传入类
for (Class tcls = cls; tcls; tcls = tcls->getSuperclass()) {
if (tcls == otherClass) return YES;
}
return NO;
}
#endif
// 没找到走消息转发, 发送`isKindOfClass`
return ((BOOL(*)(id, SEL, Class))objc_msgSend)(obj, @selector(isKindOfClass:), otherClass);
}
// class or superclass has default new/self/class/respondsToSelector/isKindOfClass
// 判断当前类是否有个默认的 isKindOfClass
#define FAST_CACHE_HAS_DEFAULT_CORE (1<<15)
bool hasCustomCore() const {
return !cache.getBit(FAST_CACHE_HAS_DEFAULT_CORE);
}
所以回头再看下1, 3, 5, 7
-
[(id)[NSObject class] isKindOfClass:[NSObject class]];
: 判断NSObject
即与传入类NSObject
。相等
3.[(id)[SATest class] isKindOfClass:[SATest class]]
: 循环判断SATest
的元类
, 根元
, 根
与传入类SATest
。不相等
5.[(id)[NSObject alloc] isKindOfClass:[NSObject class]];
: 判断NSObject
即与传入类NSObject
。相等
7.[(id)[SATest alloc] isKindOfClass:[SATest class]];
: 判断SATest
类即与传入类SATest
类。 相等
知识点6: armv7, arm64, i386 , x86_64解答
-
armv7
|armv7s
|arm64
是ARM处理器的指令集 -
i386
|x86_64
是Mac 处理的指令集。
下面是指令集在设备的使用
arm64
:iPhone5S
之后的iPhone
系列, iPad Air
以及iPad mini2
之后的iPad
系统
armv7s
:iPhone5
|iPhone5C
|iPad4(iPad with Retina Display)
armv7
:iPhone4
|iPhone4S
|iPad
|iPad2
|iPad3
|iPad mini
|iPod Touch 3G
|iPod Touch4
i386
: 是针对intel通用的微处理器32位处理器
x86_64
: 是针对x86架构64位处理器
-
模拟器
32位
处理器测试要i386
的架构 -
模拟器
64位
处理器测试要x86_64
的架构 -
真机
32位
处理器要armv7
或者armv7s
-
真机
64位
处理器要arm64
架构