博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
OC底层知识点
阅读量:6138 次
发布时间:2019-06-21

本文共 4718 字,大约阅读时间需要 15 分钟。

1. OC对象的本质形式 (一个NSObject对象占用多少内存)

  • OC本质底层都是C,C++代码混合实现的--编译汇编代码--机器代码

  • 对象和类结构是基于C和C++中的结构体struct实现的。探究NSObject的本质,OC代码转换为C和C++混合代码。xcode用的编译器前端是clang。

  • 因为1个NSObject对象对应1个结构体内只有1个isa指针,指针在iOS64位系统内占8个字节,因此一个NSObject对象在内存里是占用1个指针的大小。类内部的方法和方法的实现存储空间并不在对象内,obj指针就是isa地址其实就是结构体地址就是NSObject对象的地址。

  • 只要继承自NSObject对象,结构中肯定会有一个isa指针。

2. 实例对象 class类对象 元类对象(类信息存放在什么地方)

  • instance实例对象:alloc出来的。存放成员变量的值,isa。

  • class类对象 :class类型,[实例对像 class],object_getClass(object1),每一个类在内存中有且只有一个class对象,存放对象方法信息,属性信息,成员变量信息(名字等),协议信息,superClass指针,isa等。

  • metaclass元类对象 :也是class类型,每一个类在内存中有且只有一个元类对象,在内存中和类对象结构一样的,但是用途不一样。object_getClass(类对象),object_getClass([NSObject class]);存放有用信息和class类对象不一样,static类型成员变量,属性可能是空的,存放类方法,superClass指针,isa等。

3. isa和superClass底层指针指向

  • instance实例对象:isa指向自己的class类对象
  • class类对象:isa指向自己的metaClass元类对象。class类对象中的superClass指针指向父类的类对象
  • metaClass元类对象:所有元类的isa最终都指向基类( 如NSObject)的metaClass。metaClass元类中的superClass指针指向父类的metaClass元类对象。
  • Tips: 基类的metaClass的superClass指针指向基类的class类对象. (这里之后应该再说一下消息转发机制的)

4. OC消息传递

  • 熟悉runtime的都知道,OC的方法调用其实应该叫消息传递,消息传递是动态绑定的机制来决定需要调用的方法;[person age];会被翻译为objc_msgSend(person, @selector(age));objc_msgSend查找方法时,会先从Person缓存中查找,找到直接返回 (缓存是存在类中的,每个类都有一份方法缓存struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE;)。
  • 找不到,再去 Class 的方法列表中找。在 objc-runtime-new.mm 文件中有一个函数 lookUpImpOrForward,这个函数的作用就是无缓存时去查找方法的实现。lookUpImpOrForward 并不是objc_msgSend 直接调用的,而是通过 _class_lookupMethodAndLoadCache3 方法。
IMP _class_lookupMethodAndLoadCache3(id obj, SEL sel, Class cls){    return lookUpImpOrForward(cls, sel, obj,                              YES/*initialize*/, NO/*cache*/, YES/*resolver*/);}复制代码

lookUpImpOrForward属于源代码层级的了,想要具体了解可以直接

具体查找过程

  • 在当前类中查找实现:先调用了cache_getImp从某个类的cache 属性中获取对应的实现,如果查找到实现,跳转到done。
  • 如果没找到缓存。通过当前类instance实例对象的isa找到类对象(class),接着在当前类的class对象内找到方法列表methodLists找到对应的Method,最后找到method中的IMP,执行具体实现并添加到缓存。
  • 如果在当前类方法列表中没有找到,通过superClass指针找到当前类的父类,在父类中寻找实现。这一操作与上一步基本是一样的,先查找缓存,没找到接着搜索方法列表,添加到缓存。与当前类的区别是,在父类中找到了_objc_msgForward_impcache实现会交给当前类处理。

没找到对应的selector?(方法决议+消息转发)

当前类中和父类中都没有找到对应方法处理,系统会提供三次补救机会

  • 方法决议(method resolve):动态加载,系统就会调用receiver的
    + (BOOL)resolveInstanceMethod:(SEL)sel {}(实例方法) 和
    + (BOOL)resolveClassMethod:(SEL)sel {}(类方法)
void myMethod(id self, SEL _cmd,NSString *nub) {NSLog(@"ifelseagexx%@",nub);};+ (BOOL)resolveInstanceMethod:(SEL)sel{    NSLog(@"Method Resolution");    if (sel == @selector(age)) { // 方法没有被实现        class_addMethod([self class], sel, imp_implementationWithBlock(^() {            // 实现方法的代码写在这里                    }), "v@:");                return YES;    }    return [super resolveInstanceMethod:sel];}复制代码

我们只需要在resolveInstanceMethod:方法中,利用class_addMethod 方法,将未实现的 age绑定到(IMP)myMethod 上。这样就能完成转发,最后返回YES。如果实现了这个方法,系统就会重新启动一次消息发送。

  • 在缓存、当前类、父类以及 resolveInstanceMethod: 都没有解决实现查找的问题时,执行第二次:
- (id)forwardingTargetForSelector:(SEL)aSelector {}复制代码

确定是哪个对象处理(找到该对象的方法名与消息中的选择器的方法名一致的方法并调用)这个消息。使用场景一般是将 A 类的某个方法,转发到 B 类的实现中去。

  • forwardingTargetForSelector:如果实现这个方法时,返回值为nil或者self即代表不处理消息。执行第三次:
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {};- (void)forwardInvocation:(NSInvocation *)anInvocation {};复制代码

第一个要求返回一个方法签名,第二个方法转发具体的实现。二者相互依赖,只有返回了正确的方法签名,才会执行第二个方法。

这次的转发作用和第二次的比较类似,都是将 A 类的某个方法,转发到 B 类的实现中去。不同的是,第三次的转发相对于第二次更加灵活,forwardingTargetForSelector: 只能固定的转发到一个对象;forwardInvocation: 可以让我们转发到多个对象中去。

tips:如果传递走到最后都没有处理,系统就会崩溃并报错:unrecognized selector sent to instance 0x7fea0ac2b0a0

5. KVO的本质

  • 当一个对象使用了KVO监听,系统会修改这个对象的isa指针,改为指向一个全新的通过runtime动态创建的子类NSKVONotifying_class
  • 子类拥有自己的set方法实现:

willChangeValueForKey:

原来的seter方法setClass:
didChangeValueForkey:,(这个方法内部又会调用监听器方法observeValueForKeyPath:ofObject:change:context:)

  • 手动触发KVO

addObserver:selector:name:object:后手动调用willChangeValueForKey:didChangeValueForkey:,可以实现不改变属性值手动触发监听KVO方法。注意:必须will和did一起成对调用,猜测可能didChangeValueForkey在触发监听方法的时候会检测will方法有没有被调用,进而成功触发observeValueForKeyPath:ofObject:change:context:

  • 动态创建的子类NSKVONotifying_class中其实除了会重写原类的setClass:方法(不会重写get方法)外,通过class_copyMethodList()可以发现,class() dealloc _isKVOA会新出现在NSKVONotifying_class类对象的方法里。因为子类重写了KVO的class方法,[object class]获取的类对象还是原类对象,object_getClass(object)获取到的是NSKVONotifying_class类对象。

6. KVC的本质

KVC的全称是Key-Value Coding,俗称“键值编码”,可以通过一个Key来访问某个属性

  • 常见的API:
    - (void)setValue:(nullable id)value forKey:(NSString *)key;
    - (nullable id)valueForKey:(NSString *)key;
    - (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath;
    - (nullable id)valueForKeyPath:(NSString *)keyPath;
  • kvc赋值属于runtime层级的直接赋值,通过KVC修改属性也会触发KVO。
    • 首先会按照,setKey:``,_setKey:顺序寻找方法。如果有找到方法存在会传递参数直接调用这个方法。
    • 如果没有找到方法,会查看accessInstanceVariablesDirectly方法的返回值。如果返回值为NO,不允许直接返回成员变量,调用setValue:forUndefineKey:并抛出异常。如果允许会去访问成员变量,如果找到了成员变量会直接赋值(依然会触发KVO,内部做了willChangeValueForKey:didChangeValueForkey:),如果没找到依然会抛出异常错误。

转载于:https://juejin.im/post/5aa25ae0f265da238f122526

你可能感兴趣的文章
linux下安装telnet(centos7)
查看>>
HTML基础标签大全
查看>>
[转] thrift的使用介绍
查看>>
Django如何安装指定版本
查看>>
Network | 协议栈
查看>>
HTTP中Get与Post、ViewState 原理
查看>>
修改PYTHONPATH的一种方法(在Window平台和Ubuntu下都有效)
查看>>
正则表达式
查看>>
U3D MemoryProfiler
查看>>
JVM调优
查看>>
NYOJ 298 点的变换
查看>>
Scrapy基础(五) ------css选择器基础
查看>>
表达式与运算符
查看>>
jquery使用js的一些疼处
查看>>
django中视图处理请求方式(FBV、CBV)
查看>>
IOC的概念及原理
查看>>
iOS页面传值方式
查看>>
WebSocket简单使用
查看>>
java学习笔记-集合
查看>>
linux下载相关
查看>>