mirror of
https://github.com/NohamR/knowledge-kit.git
synced 2026-05-25 04:17:17 +00:00
feature: App 逆向防护
This commit is contained in:
@@ -75,7 +75,7 @@ Person 类存在3个 BOOL 属性:
|
||||
|
||||
上 Demo
|
||||
|
||||
<img src="./../assets/BOOLPropertyImplementedByBitOperator.png" style="zoom:25%">
|
||||
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/BOOLPropertyImplementedByBitOperator.png" style="zoom:25%">
|
||||
|
||||
|
||||
|
||||
@@ -85,7 +85,7 @@ Person 类存在3个 BOOL 属性:
|
||||
|
||||
新方案采用:**结构体的位域能力**,限定单个成员变量所占用的内存。代码如下:
|
||||
|
||||
<img src="./../assets/BOOLProptertImpledByStructureBitDomain.png" style="zoom:25%" />
|
||||
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/BOOLProptertImpledByStructureBitDomain.png" style="zoom:25%" />
|
||||
|
||||
|
||||
|
||||
@@ -97,7 +97,7 @@ Person 类存在3个 BOOL 属性:
|
||||
|
||||
虽然上述方式都可以实现存储 Person 类3个属性的目的,但是还有第三种方案,参考 iOS 系统设计,采用 Union 实现。代码如下
|
||||
|
||||
<img src="./../assets/BOOLPropertyImplementedByUnion.png" style="zoom:25%" />
|
||||
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/BOOLPropertyImplementedByUnion.png" style="zoom:25%" />
|
||||
|
||||
分析:
|
||||
|
||||
@@ -168,7 +168,7 @@ union {
|
||||
|
||||
与一个不是3个数之一的数按位与,得到的结果为`0b0000 0000`。利用这个特性我们可以判断传递来的参数是不是包含了某个值
|
||||
|
||||
<img src="./../assets/BitOperateorOnEnumDemo.png" style="zoom:25%" />
|
||||
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/BitOperateorOnEnumDemo.png" style="zoom:25%" />
|
||||
|
||||
有了上面的铺垫,就可以更好的查看 runtime 中 isa 的定义了。
|
||||
|
||||
@@ -273,7 +273,7 @@ isa 在 arm64 之后必须通过 `ISA_MASK` 去查询 class(类对象、元类
|
||||
|
||||
`0x0000000ffffffff8ULL` 用程序员模式打开计算器
|
||||
|
||||
<img src="./../assets/objc-isa-mask.png" style="zoom:30%">
|
||||
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/objc-isa-mask.png" style="zoom:30%">
|
||||
|
||||
|
||||
|
||||
@@ -283,7 +283,7 @@ extra_rc、has_sidetable_rc、deallocating、weakly_referenced、magic、shiftcl
|
||||
|
||||
知道结构体可以指定存储大小这个功能后,可以看到 `isa_t` 联合体与 `ISA_MASK` 按位与之后的地址,其实就是类真实的地址信息(可能是类对象、也有可能是元类对象)
|
||||
|
||||
<img src="./../assets/objc-isa.png" style="zoom:30%" />
|
||||
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/objc-isa.png" style="zoom:30%" />
|
||||
|
||||
|
||||
|
||||
@@ -423,7 +423,7 @@ struct class_ro_t {
|
||||
|
||||
具体关系整理如下图
|
||||
|
||||
<img src="./../assets/runtime-class.png" style="zoom:50%" />
|
||||
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/runtime-class.png" style="zoom:50%" />
|
||||
|
||||
|
||||
|
||||
@@ -435,7 +435,7 @@ struct class_ro_t {
|
||||
|
||||
|
||||
|
||||
<img src="./../assets/runtime-class-rw-t.png" style="zoom:40%" />
|
||||
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/runtime-class-rw-t.png" style="zoom:40%" />
|
||||
|
||||
比如访问 method 的过程
|
||||
|
||||
@@ -453,7 +453,7 @@ struct class_ro_t {
|
||||
|
||||
- `class_ro_t` 里面的 baseMethodList、baseProtocols、ivars、baseProperties 是一维数组,是只读的,包含了类的(原始信息)初始内容
|
||||
|
||||
<img src="./../assets/runtime-class-ro-t.png" style="zoom:50%" />
|
||||
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/runtime-class-ro-t.png" style="zoom:50%" />
|
||||
|
||||
|
||||
|
||||
@@ -1240,7 +1240,7 @@ v
|
||||
|
||||
可以对照下面的表格进行查看:
|
||||
|
||||

|
||||

|
||||
|
||||
```objectivec
|
||||
- (int)calcuate:(int)baseHeight heigith:(float)height;
|
||||
@@ -1513,7 +1513,7 @@ bucket_t goodStudentSayBucket = buckets[(uintptr_t)@selector(personSay) & cache.
|
||||
NSLog(@"%s %p", goodStudentSayBucket._key, goodStudentSayBucket._imp);
|
||||
```
|
||||
|
||||
<img src="./../assets/runtime-method-find.png" style="zoom:25%">
|
||||
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/runtime-method-find.png" style="zoom:25%">
|
||||
|
||||
|
||||
|
||||
@@ -1544,7 +1544,7 @@ static inline mask_t cache_hash(cache_key_t key, mask_t mask)
|
||||
|
||||
## Runtime - objc_msgSend
|
||||
|
||||
<img src="./../assets/MethodCallIsObjcMsgSend.png" style="zoom:20%" />
|
||||
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/MethodCallIsObjcMsgSend.png" style="zoom:20%" />
|
||||
|
||||
```c++
|
||||
Person *p = [[Person alloc] init];
|
||||
@@ -2117,7 +2117,7 @@ if (imp) goto done;
|
||||
|
||||
上面的流程是整个 `objc_msgSend` 的消息发送阶段的整个流程。可以用下图表示
|
||||
|
||||
<img src="./../assets/runtime-objc_msgSend-messageSend.png" style="zoom:60%" />
|
||||
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/runtime-objc_msgSend-messageSend.png" style="zoom:60%" />
|
||||
|
||||
|
||||
|
||||
@@ -2262,7 +2262,7 @@ SEL_resolveClassMethod, sel);`
|
||||
|
||||
完整流程如下
|
||||
|
||||
<img src="./../assets/runtime-objc_msgSend-ResolveMethod.png" style="zoom:40%" />
|
||||
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/runtime-objc_msgSend-ResolveMethod.png" style="zoom:40%" />
|
||||
|
||||
|
||||
|
||||
@@ -2307,7 +2307,7 @@ Person *person = [[Person alloc] init];
|
||||
|
||||
知道 `objc_msgSend` 的流程,我们尝试给它修正下
|
||||
|
||||
<img src="./../assets/objc_msgSend_fix1.png" style="zoom:25%">
|
||||
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/objc_msgSend_fix1.png" style="zoom:25%">
|
||||
|
||||
方法1,增加一个兜底方法,然后利用 `class_addMethod` 动态增加方法实现
|
||||
|
||||
@@ -2341,7 +2341,7 @@ class_addMethod(self, sel, method_getImplementation(method), method_getTypeEncod
|
||||
|
||||
方法3,也可以添加 c 语言方法
|
||||
|
||||
<img src="./../assets/objc_msgSend_fix2.png" style="zoom:25%">
|
||||
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/objc_msgSend_fix2.png" style="zoom:25%">
|
||||
|
||||
c 函数即函数地址,只不过传递给 class_addMethod 的时候需要转换为函数参数加 `(IMP)cfuntionResolver`,手动添加方法签名。
|
||||
|
||||
@@ -2454,7 +2454,7 @@ void *_objc_forward_handler = (void*)objc_defaultForwardHandler;
|
||||
|
||||
为什么是 `__forwarding__` 方法。我们可以根据 Xcode 崩溃窥探一二
|
||||
|
||||

|
||||

|
||||
|
||||
```c
|
||||
int __forwarding__(void *frameStackPointer, int isStret) {
|
||||
@@ -2498,7 +2498,7 @@ int __forwarding__(void *frameStackPointer, int isStret) {
|
||||
|
||||
完整流程如下
|
||||
|
||||
<img src="./../assets/runtime-forwarding.png" style="zoom:40%" />
|
||||
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/runtime-forwarding.png" style="zoom:40%" />
|
||||
|
||||
|
||||
|
||||
@@ -2532,13 +2532,13 @@ int __forwarding__(void *frameStackPointer, int isStret) {
|
||||
|
||||
Person 类不存在对象方法 makeliving ,PersonHelper 类存在。
|
||||
|
||||
<img src="./../assets/RuntimeCallUnKnownMethod.png" style="zoom:25%" />
|
||||
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/RuntimeCallUnKnownMethod.png" style="zoom:25%" />
|
||||
|
||||
调用对象不存在的方法,则会抛出错误。同时在 Runtime 动态消息解析阶段,`resolveInstanceMethod` 没有处理对象方法,所以会报错
|
||||
|
||||
方法1:因为动态消息解析没有处理,则会开始走消息转发阶段。消息转发首先会调用 `- (id)forwardingTargetForSelector:(SEL)aSelector` 方法。(如果是对象方法则调用 `- (id)forwardingTargetForSelector:(SEL)aSelector`,如果是类方法则调用 `+ (id)forwardingTargetForSelector:(SEL)aSelector`)
|
||||
|
||||
<img src="./../assets/RuntimeMethodForwardingDemo1.png" style="zoom:30%" />
|
||||
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/RuntimeMethodForwardingDemo1.png" style="zoom:30%" />
|
||||
|
||||
方法2:如果消息转发里,`forwardingTargetForSelector` 返回了 nil,则开始调用方法签名 `methodSignatureForSelector` 方法和 `forwardInvocation` 方法
|
||||
|
||||
@@ -2568,13 +2568,13 @@ Person 类不存在对象方法 makeliving ,PersonHelper 类存在。
|
||||
|
||||
注意:`methodSignatureForSelector` 如果返回 nil,则 `forwardInvocation` 不会执行
|
||||
|
||||
<img src="./../assets/RuntimeMethodForwardingDemo2.png" style="zoom:30%" />
|
||||
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/RuntimeMethodForwardingDemo2.png" style="zoom:30%" />
|
||||
|
||||
|
||||
|
||||
上述方式不够优雅,针对方法签名的获取太被动,方法改变了,方法签名处需要调整,很麻烦。
|
||||
|
||||
<img src="./../assets/RuntimeMethodForwardingWithMethodSignature.png" style="zoom:30%" />
|
||||
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/RuntimeMethodForwardingWithMethodSignature.png" style="zoom:30%" />
|
||||
|
||||
|
||||
|
||||
@@ -2601,11 +2601,11 @@ Person 类不存在对象方法 makeliving ,PersonHelper 类存在。
|
||||
}
|
||||
```
|
||||
|
||||
<img src="./../assets/RuntimeMethodForwardingByClassMethod.png" style="zoom:30%"/>
|
||||
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/RuntimeMethodForwardingByClassMethod.png" style="zoom:30%"/>
|
||||
|
||||
|
||||
|
||||
<img src="./../assets/RuntimeMethodForwardingByClassMethod2.png" style="zoom:30%"/>
|
||||
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/RuntimeMethodForwardingByClassMethod2.png" style="zoom:30%"/>
|
||||
|
||||
|
||||
|
||||
@@ -2632,7 +2632,7 @@ OC 中方法调用的本质就是利用 runtime 发消息,发消息也给 rece
|
||||
|
||||
3. 如果在当前类的方法列表中没有找对方法实现,则根据类对象的 superclass 在父类的类对象的方法列表中继续查找
|
||||
|
||||
<img src="./../assets/MethodCacheLookUpProcess.png" style="zoom:30%" />
|
||||
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/MethodCacheLookUpProcess.png" style="zoom:30%" />
|
||||
|
||||
先根据 superclass 找到父类,然后在父类中也按照上面3点进行递归查找,即:
|
||||
|
||||
@@ -2757,7 +2757,7 @@ objc_msgSendSuper(arg, sel_registerName("class"))
|
||||
|
||||
我们对 iOS 项目`[super viewDidLoad]` 下符号断点,发现`objc_msgSendSuper2`
|
||||
|
||||

|
||||

|
||||
|
||||
查看 objc4 源代码发现是一段汇编实现。
|
||||
|
||||
@@ -2845,7 +2845,7 @@ call - 调用函数
|
||||
|
||||
也可以在 Xcode 上可视化面板操作,路径为:菜单栏 `Product -> Perform Action -> Assemble "ViewController.m"`
|
||||
|
||||
<img src="./../assets/XcodeAssembleClass.png" style="zoom:30%">
|
||||
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/XcodeAssembleClass.png" style="zoom:30%">
|
||||
|
||||
|
||||
|
||||
@@ -2871,7 +2871,7 @@ call - 调用函数
|
||||
|
||||
Demo1
|
||||
|
||||
<img src="./../assets/RuntimeIsKindOfClassDemo1.png" style="zoom:40%" />
|
||||
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/RuntimeIsKindOfClassDemo1.png" style="zoom:40%" />
|
||||
|
||||
|
||||
|
||||
@@ -2899,7 +2899,7 @@ Demo1
|
||||
|
||||
Demo2
|
||||
|
||||
<img src="./../assets/RuntimeIsKindOfClassDemo2.png" style="zoom:30%" />
|
||||
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/RuntimeIsKindOfClassDemo2.png" style="zoom:30%" />
|
||||
|
||||
下面面2个判断都是调用类方法的 `isMemberOfClass` 、`isKindOfClass`
|
||||
|
||||
@@ -2917,7 +2917,7 @@ Demo2
|
||||
|
||||
可以看到 `+(BOOL)isMemberOfClass:(Class)cls` 方法内部就是对当前类获取类对象,然后与传递进来的 cls 判断是否相等。由于是 `[Student isMemberOfClass:[Student class]])` `Student` 类调用类方法 `+isMemberOfClass` 所以类对象的类对象也就是元类对象,cls 参数也就是 `[Student class]` 是一个类对象,元类对象等于类对象吗?显然不是
|
||||
|
||||
想让判断成立,可以改为 `[Student isMemberOfClass:object_getClass([Student class])]` 或者 `[[Student **class**] isMemberOfClass:object_getClass([Student class])]`
|
||||
想让判断成立,可以改为 `[Student isMemberOfClass:object_getClass([Student class])]` 或者 `[[Student class] isMemberOfClass:object_getClass([Student class])]`
|
||||
|
||||
`+(BOOL)isKindOfClass:(Class)cls` 同理分析。作用是当前类的元类,是否是右边传入对象的元类或者元类的子类。
|
||||
|
||||
@@ -2958,7 +2958,7 @@ QA:
|
||||
|
||||
综合练习:
|
||||
|
||||
<img src="./../assets/RuntimeIsKindOfClassUnitDemo.png" style="zoom:30%" />
|
||||
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/RuntimeIsKindOfClassUnitDemo.png" style="zoom:30%" />
|
||||
|
||||
|
||||
|
||||
@@ -2966,16 +2966,12 @@ QA:
|
||||
|
||||
## Runtime 刁钻题
|
||||
|
||||
能否向编译后的类,添加实例变量?class_ro_t 不可以添加。但可以向动态创建的类添加。
|
||||
|
||||
|
||||
### NSObject 的内存布局、isa、对象属性访问原理
|
||||
|
||||
> 这道题目设计:super 调用的本质、函数栈空间向下增长、runtime 消息调用本质(isa)、访问对象的成员变量(找到 isa,约过前面的8字节,按照成员变量的大小,去找成员)
|
||||
>
|
||||
> 因为实例对象里存的就是:isa + 各个成员变量的值
|
||||
|
||||
|
||||
|
||||
```objective-c
|
||||
@interface Person : NSObject
|
||||
@property (nonatomic, copy) NSString *name;
|
||||
@@ -2999,22 +2995,20 @@ QA:
|
||||
|
||||
程序运行什么结果?
|
||||
|
||||
<img src="./../assets/RuntimeQA1.png" style="zoom:30%">
|
||||
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/RuntimeQA1.png" style="zoom:30%">
|
||||
|
||||
为什么会方法调用成功?为什么 name 打印出为 @"杭城小刘"
|
||||
为什么会方法调用成功?为什么 name 打印出为 @"杭城小刘"。我们来分析下:
|
||||
|
||||
我们来分析下:
|
||||
|
||||
1.**方法调用本质就是寻找 isa 进行消息发送**
|
||||
第一、**方法调用本质就是寻找 isa 进行消息发送**
|
||||
|
||||
```objective-c
|
||||
Person *person = [[Person alloc] init];
|
||||
[person sayHi];
|
||||
```
|
||||
|
||||
`[[Person alloc] init]`在内存中分配一块内存,然后 isa 指向这块内存,然后 person 指针,指向结构体,结构体的第一个成员。
|
||||
`[[Person alloc] init]` 在内存中分配一块内存,然后 isa 指向这块内存,然后 person 指针,指向结构体,结构体的第一个成员。
|
||||
|
||||
2.**栈空间数据内存向下生长。第一个变量地址高,其次降低。且每个变量的内存地址是连续的。**
|
||||
第二、**栈空间数据内存向下生长。第一个变量地址高,其次降低。且每个变量的内存地址是连续的。**
|
||||
|
||||
这个流程其实和上面的代码一样的。所以可以正常调用
|
||||
|
||||
@@ -3029,9 +3023,9 @@ void test () {
|
||||
|
||||
方法内的变量存储在栈上,堆向上增长,栈向下增长。
|
||||
|
||||

|
||||

|
||||
|
||||
3.**实例对象的本质就是一个结构体,存储所有成员变量(isa 是一个特殊成员变量,其他的成员变量,这里就是 _name),`sayHi` 方法内部的 self 就是 obj,找成员变量的本质就是找内存地址的过程(此时就是偏移8个字节)**
|
||||
第三、**实例对象的本质就是一个结构体,存储所有成员变量(isa 是一个特殊成员变量,其他的成员变量,这里就是 _name),`sayHi` 方法内部的 self 就是 obj,找成员变量的本质就是找内存地址的过程,本质就是对象地址 + Offset。此时就是 isa 地址 + 8字节偏移量)**
|
||||
|
||||
上面代码可以类比类调用方法的流程。 obj 指针指向 Person 这块内存,给类对象发送 `sayHi` 消息也就是通过 obj 指针找到 isa,恰好 obj 指针指向的地址就是类对象的类结构体的地址,结构体成员变量第一个就是 isa 指针,结构体的其他成员变量就是类的其他属性,这里也就是 `_name`,所以我们给自定义的指针 `void *p` 调用 sayHi 方法,系统 runtime 在打印 name 的时候,会在 p 附近(下8个字节,因为 isa 是指针,长度为8)找 `_name` 属性,此时也就找到了 temp 字符串。
|
||||
|
||||
@@ -3046,17 +3040,17 @@ struct Person_IMPL {
|
||||
|
||||
再看一个变体1
|
||||
|
||||
<img src="./../assets/RuntimeQA2.png" style="zoom:30%">
|
||||
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/RuntimeQA2.png" style="zoom:30%">
|
||||
|
||||
打印输出是因为 `*p` 类似 isa 指针。本身占用8字节空间,然后访问 `self->_name` 就是 `base + 8 = isa地址 + 8 ` 出的内存就是 name,`*p` 是在栈中,加8,就是向上声明的变量,当前情况下 `Address(*p) + 8` 就是 temp 变量。所以输出 `<NSObject: 0x****>`
|
||||
|
||||
|
||||
|
||||
再看一个变体2
|
||||
|
||||
<img src="./../assets/RuntimeQA3.png" style="zoom:30%">
|
||||
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/RuntimeQA3.png" style="zoom:30%">
|
||||
|
||||
|
||||
|
||||
搞懂的小伙伴不迷惑了。没搞懂其实就是没搞懂**栈地址由高到低,向下生长** 和 `super` 调用的本质。
|
||||
分析: 搞懂的小伙伴不迷惑了。没搞懂其实就是没搞懂**栈地址由高到低,向下生长** 和 `super` 调用的本质。
|
||||
|
||||
再强调一句,根据指针寻找成员变量 _name 的过程其实就是根据内存偏移找对象的过程。在变体2中,isa 地址就是 class 的地址,所以按照`地址 +8` 的策略,其实前一个局部变量。
|
||||
|
||||
@@ -3069,7 +3063,11 @@ objc_msgSendSuper(arg, sel_registerName("viewDidLoad"));
|
||||
|
||||
所以此时的“前一个局部变量” 也就是结构体 `objc_super` 类型的 arg。arg 是一个结构体,结构体第一个成员变量就是 self,所以“前一个局部变量” 也就是 self(ViewController)
|
||||
|
||||

|
||||
结构体有2个成员变量,顺序越高的成员变量地址越高。所以在栈上,struct 中第一个成员变量地址更低。在通过 isa 内存偏移的时候,优先找到 self。所以会输出 ViewController
|
||||
|
||||
|
||||
|
||||

|
||||
|
||||
可能会疑问,知道了 super 调用本质,知道了会产生一个局部变量的结构体,但是结构体里面2个成员变量,找属性的时候,isa 下的8个字节会命中哪个?
|
||||
|
||||
@@ -3079,12 +3077,44 @@ objc_msgSendSuper(arg, sel_registerName("viewDidLoad"));
|
||||
|
||||
|
||||
|
||||
<img src="./../assets/RuntimeQA.png" style="zoom: 80%">
|
||||
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/RuntimeQA.png" style="zoom: 80%">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
### runtime 对象方法、类方法的查找过程熟悉吗?
|
||||
|
||||
下面的代码会 crash 吗?
|
||||
|
||||
```objective-c
|
||||
id rs = [NSObject valueForKey:@"isa"];
|
||||
NSLog(@"%@", rs);
|
||||
```
|
||||
|
||||
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/RuntimeCallValueForKeyOnObject.png" style="zoom:50%" />
|
||||
|
||||
不会 Crash。输出的是 NSObject 的元类对象。因为 NSObject 调用的是类方法,先去元类对象的方法列表中查找,发现没有 `valueForKey` 方法。则继续从 NSObject 元类对象的父类对象,也就是 NSObject 的类对象上查找 `valueForKey` 方法。
|
||||
|
||||
因为分类 `NSObject(NSKeyValueCoding)` 实现了 `-valueForKey` 方法。所以不会 crash,在类对象方法中,访问 isa,也就是获取类对象的 isa,也就是 NSObject 的元类对象。
|
||||
|
||||
|
||||
|
||||
查看了 objc 源码,会发现很多 NSObject 的基础方法:`+ (id)init`、`- (id)init` 等均有 `+` 、`-` 方法。
|
||||
|
||||
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/NSObjectImpleByClassAndInstance.png" style="zoom:30%" />
|
||||
|
||||
为什么这么设计?猜测是为了代码的健壮。
|
||||
|
||||
- 因为存在继承关系,所有的对象的基类需要有个源头。即使是 NSObject 也是如此。
|
||||
- 调用对象方法的本质就是根据对象的 isa 找到类对象,然后从方法缓存中去查找方法实现,有就调用,没有就从类对象的方法列表中查找,找到则写入方法缓存并调用,没有则根据 superclass 的类对象继续查找...,一直找到 NSObject 还是找不到,则走 Runtime 消息转发流程。
|
||||
- 调用类方法的本质是根据对象的 isa 找到类对象,再根据类对象的 isa 找到元类对象,从元类对象的方法方法列表中查找方法实现,如果没有则继续向上查找,直到找到基类的元类对象,也就是 NSObject,如果找不到则走消息转发流程
|
||||
- 但是 Apple 的设计是为了 NSObject 元类对象的父类也要有个东西去接着,于是就让 NSObject 的类对象来充当 。
|
||||
|
||||
这也就是为什么 NSObject 子类对象调用 `+` 类方法不 crash 的原因。
|
||||
|
||||
|
||||
|
||||
## 应用场景
|
||||
|
||||
### 统计 App 中未响应的方法。给 NSObject 添加分类 `NSObject+ExceptionHunter.m`,利用 NSProxy 实现
|
||||
@@ -3121,7 +3151,7 @@ Person *p = [Person new];
|
||||
object_setClass(p, [Student class]);
|
||||
```
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
|
||||
@@ -3150,7 +3180,7 @@ void createClass (void) {
|
||||
}
|
||||
```
|
||||
|
||||

|
||||

|
||||
|
||||
runtime 中 copy、create 等出来的内存,不使用的时候需要手动释放`objc_disposeClassPair(newClass>)`
|
||||
|
||||
@@ -3229,7 +3259,7 @@ free(properties);
|
||||
|
||||
|
||||
|
||||
<img src="./../assets/RuntimeDicToObject.png" style="zoom:30%" />
|
||||
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/RuntimeDicToObject.png" style="zoom:30%" />
|
||||
|
||||
不够健壮体现在:
|
||||
|
||||
@@ -3369,3 +3399,7 @@ OC 是一门动态性很强的编程语言,允许很多操作推迟到程序
|
||||
- 无痕埋点
|
||||
|
||||
- 热修复
|
||||
|
||||
可以认为 oc 中对象上一个指向 CLassObject 地址的变量 `id obj = &ClassObject`
|
||||
|
||||
而对象的实例变量 `void *ivar = &obj + offset(N*size)` 。根据 isa 也就是对象的基地址,然后偏移访问 ivar。
|
||||
|
||||
Reference in New Issue
Block a user