目录
1. OC对象的本质
2. OC实例对象、类对象、元类对象分析
3. isa和superclass的分析
4. KVO实现原理
5. 用代码实现一个KVO
- 一点感悟:最近复习一些之前学习过的内容,发现基本都忘记的差不多了,以前也没有写一些文章把学过的总结下来,现在回过头看,又得学习一遍,时间成本确实不小,趁最近业务不是很忙,多总结记录一下哈哈哈。进入正题:
OC对象的本质:
1. OC对象的底层数据结构
- 我们都知道OC是面向对象的语言,底层都是基于C/C++代码实现的,所以OC的面向对象都是基C/C++的数据结构实现的:
- 那么OC的对象、类主要是基于C\C++的什么数据结构实现的?
因为OC大多都有很多属性和方法,C\C++能满足其需求的只有结构体,所以OC的对象、类主要是基于
结构体去实现的。 关于结构体更多知识这里篇文章总结的很棒:iOS中编写高效能结构体的7个要点
2. NSOject的底层实现
- 说到OC对象,就不得不说NSOject了,因为所有OC对象都继承自NSObject,上面也说了对象、类主要是基于结构体去实现的,那么这个结构体里面到底有什么呢,我们将main.m文件转成.cpp文件(前面写的文章有说过iOS autoreleasePool原理总结)
在.cpp文件中发现NSObject的实现就是一个结构体,里面包含了一个结构体指针即isa(isa是Class类型,而Class又是一个结构体类型的指针,所以isa是一个结构体指针)
struct NSObject_IMPL {
Class isa;
};
typedef struct objc_class *Class;
- 继承自NSObject的对象结构大概是什么样的呢,如下我们创建一个继承NSObject的对象Student,添加name和age两个属性
@interface Student : NSObject
@property(nonatomic,assign)int age;
@property(nonatomic,strong)NSString * name;
@end
底层结构如下: 即第一个元素是isa指针,我们可以认为isa的地址即是Student的地址,后面的成员变量会遵循内存对齐的原则逐一分配内存地址。
struct Student_IMPL {
struct NSObject_IMPL NSObject_IVARS;(Class isa;)
int _age;
NSString *_name;
};
总结:
- 因为OC对象都继承自NSObject,而NSObject又包含一个isa,所以我们后面对OC的底层学习,发现结构体内有isa即可判定为OC对象;
- isa为对象的第一个成员,它的地址即为对象的起始地址;
OC实例对象、类对象、元类对象分析
OC对象主要可以分成3种类型:
- instance对象(实例对象)
- class对象(类对象)
- meta-class对象(元类对象)
逐一看看他们到底有有什么作用和区别:
1.instance对象(实例对象):
实例对象就是通过类alloc出来的对象,每次调用alloc都会产生新的instance对象,instance对象在内存中存储的信息包括
isa指针其他成员变量如下创建一个student对象,它的内存信息如图:
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
@interface Student : NSObject
@property(nonatomic,strong)NSString * name;
@property(nonatomic,assign)int age;
@end
@implementation Student
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
Student *student = [[Student alloc] init];
student.age = 20;
student.name = @"lanlin";
}
return 0;
}
2.class对象(类对象):
- 通过实例对象的class方法或者runtime的object_getClass方法可以获取到这个实例的类对象,
每个类在内存中有且只有一个class对象,class对象在内存中存储的信息主要包括:
isa指针superclass指针类的属性信息(@property)类的对象方法信息(instance method)类的协议信息(protocol)类的成员变量信息(ivar)
如下创建一个student对象,它的类对象内存信息如图:
Student *student = [[Student alloc] init];
Class stuClass1 = [student class];
Class stuClass2 = object_getClass(student);//Runtime API
2.meta-class对象(元类对象):
- 通过RuntimeAPIobject_getClass([类名 class])获取,
每个类在内存中有且只有一个meta-class对象,meta-class对象和class对象的内存结构是一样的,但是用途不一样,在内存中存储的信息主要包括:
isa指针superclass指针类的类方法信息(class method)
///获取元类对象
Class objectMetaClass = object_getClass([NSObject class])
///判断是否为元类对象
BOOL isMetaClass = class_isMetaClass(objectMetaClass);
以上三种类型对象的相关特点和作用。
- class、meta-class对象的本质结构都是struct objc_class
通过源码分析:Objc源码下载地址
isa和superclass分析
- 上面分析了OC对象的类型,有几个问题:
- 既然实例对象内存并没有包含对象方法,那么为什么实例对象可以调用到对象方法呢?
- 类对象也没有类方法的信息,但是却可以通过类名调用类方法
- 子类为什么可以调用父类的方法 要回答上面几个问题,就需要了解isa和superclass的作用了。
- 上面也说了isa是结构体指针,只要是OC对象都有isa,作用如下:
- 实例对象的isa指向它的类对象。
当调用对象方法时,实例对象内存并没有包含对象方法,而是通过它内部的isa指针找到它的类对象,从而在类对象中找到对象方法的实现进行调用
- 类对象的isa指向元类对象
当调用类方法时,类对象并没有类方法的信息,而是通过类对象的isa找到元类对象,最后找到类方法的实现进行调用
- superclass指针只会存在类对象和元类对象中,每个类(元类)对象的superclass指针都是指向它的父类,作用如下:
1.当实例对象调用对象方法会通过isa找到它的类对象,然后在类对象找方法实现,然而有时候这个类对象并没方法实现,那么就会通过superclass找到它的父类查找实现方法,一直找到基类。
2.当类调用类方法时会通过isa找到它的元类对象,然后在元类类对象找方法实现,然而有时候这个元类对象并没方法实现,那么就会通过superclass找到它的父元类查找实现方法,一直找到基类。
- 总结:
- 实例对象(instance)的isa指向类对象(class)
- 类对象(class)的isa指向元类对象(meta-class)
- class的superclass指向父类的class,如果没有父类,superclass指针为nil
- meta-class的superclass指向父类的meta-class,基类的meta-class的superclass指向基类的class
- instance调用对象方法的轨迹:isa找到class,方法不存在,就通过superclass找父类
- class调用类方法的轨迹:isa找meta-class,方法不存在,就通过superclass找父类
KVO实现原理
-
定义:KVO的全称是Key-Value Observing,俗称“键值监听”,可以用于监听某个对象属性值的改变。
-
当一个对象的属性被监听时,本质实现如下步骤:
- 会利用Runtime API动态生成一个子类,继承实例对象isa原来指向的类
- 修改实例对象的isa指针,让其指向这个新的子类;
- 当修改实例对象属性时,本质就是调用了对象属性的set方法,会去isa指向的类找实现方法(上面讲过isa指向规则),这时候就是去新子类找set方法实现了;
- 全新的子类会重写set方法,会调用Foundation的_NSSetXXXValueAndNotify函数主要内容:
- 调用willChangeValueForKey: 取旧值(为了在回调中能够提供监听的属性的旧值)
- 调用super的set方法赋值
- 调用didChangeValueForKey: , 内部会触发监听器(Oberser)的监听方法( observeValueForKeyPath:ofObject:change:context:)
- 除了重写set方法,子类还重写了如下几个方法:
- class方法: 前面也说过调用对象[xxxx class],可以得到这个对象isa指向的class,这里重写class方法,当调用[xxxx class],返回动态生成的新class
- dealloc : 因为这个子类是用runtime生成的,系统并未暴露出来,所以释放的时候,要把相关信息还原和清除。
- _isKVOA : 这方法主要返回是否为kvo创建的类,如果是动态生成的子类:return Yes。
用代码实现一个KVO
- 根据上面的实现步骤我们可以通过runtimeAPI实现一下, 创建一个NSObject的分类NSObject+CustomKVO .h文件如下
#import <Foundation/Foundation.h>
@interface NSObject (CustomKVO)
- (void)ll_addObserver:(NSObject *_Nullable)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;
@end
.m文件
#import "NSObject+CustomKVO.h"
#import <objc/message.h>
@implementation NSObject (CustomKVO)
- (void)ll_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context
{
///1.动态创建一个子类
//获取对象名
NSString * oldName = NSStringFromClass([self class]);
//生成新的类名
NSString * newName = [NSString stringWithFormat:@"CustomKVO_%@",oldName];
//分配空间,创建类(仅在 创建之后,注册之前 能够添加成员变量)
Class customClass = objc_allocateClassPair([self class], newName.UTF8String,0);
//注册类(注册后方可使用该类创建对象)
objc_registerClassPair(customClass);
///2.修改对象的isa指针
object_setClass(self, customClass);
///3.重写对象的set方法
NSString * methodName = [NSString stringWithFormat:@"set%@:",keyPath.capitalizedString];//capitalizedString:首字母大写
SEL sel = NSSelectorFromString(methodName);
class_addMethod(customClass, sel, (IMP)setterMethod, "v@:@");
///关联对象
objc_setAssociatedObject(self, (__bridge const void *)@"ObserverKey", observer, OBJC_ASSOCIATION_ASSIGN);
}
//set方法
void setterMethod(id self, SEL _cmd, NSString * name){
id observer = objc_getAssociatedObject(self, (__bridge const void *)@"ObserverKey");
NSString *methodName = NSStringFromSelector(_cmd);
NSString *getterName = getValueKey(methodName);
//1.获取旧值
NSString * oldValue = [self valueForKey:getterName];
//2. 调用父类的set方法
struct objc_super superClass = {
self,
class_getSuperclass([self class])
};
objc_msgSendSuper(&superClass ,_cmd , name);
//3.发出通知
NSDictionary<NSKeyValueChangeKey,id> *changeDict = oldValue ? @{NSKeyValueChangeNewKey : name, NSKeyValueChangeOldKey : oldValue} : @{NSKeyValueChangeNewKey : name};
[observer observeValueForKeyPath:getterName ofObject:self change:changeDict context:NULL];
}
///通过方法名获取属性(字符串处理)
NSString * getValueKey(NSString * setter){
NSRange range = NSMakeRange(3, setter.length - 4);
NSString *key = [setter substringWithRange:range];
NSString *letter = [[key substringToIndex:1] lowercaseString];
key = [key stringByReplacingCharactersInRange:NSMakeRange(0, 1) withString:letter];
return key;
}
@end