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
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等。编码
- 熟悉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。methodLists
找到对应的Method,最后找到method中的IMP,执行具体实现并添加到缓存。_objc_msgForward_impcache
实现会交给当前类处理。当前类中和父类中都没有找到对应方法处理,系统会提供三次补救机会翻译
+ (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。若是实现了这个方法,系统就会从新启动一次消息发送。指针
- (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
NSKVONotifying_class
。
willChangeValueForKey:
原来的seter方法setClass:
didChangeValueForkey:
,(这个方法内部又会调用监听器方法observeValueForKeyPath:ofObject:change:context:
)
在
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
类对象。KVC的全称是Key-Value Coding,俗称“键值编码”,能够经过一个Key来访问某个属性
- (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;
setKey:``,_setKey:
顺序寻找方法。若是有找到方法存在会传递参数直接调用这个方法。accessInstanceVariablesDirectly
方法的返回值。若是返回值为NO,不容许直接返回成员变量,调用setValue:forUndefineKey:
并抛出异常。若是容许会去访问成员变量,若是找到了成员变量会直接赋值(依然会触发KVO,内部作了willChangeValueForKey:
和didChangeValueForkey:
),若是没找到依然会抛出异常错误。