OC底层知识点

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

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

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

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

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

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

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

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

  • 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属于源代码层级的了,想要具体了解能够直接 draveness.me/messagespa

具体查找过程

  • 在当前类中查找实现:先调用了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:),若是没找到依然会抛出异常错误。
相关文章
相关标签/搜索