struct objc_object {
Class isa
};
struct objc_class {
Class isa;
Class super_class ;
const char *name ;
long version ;// 类的版本信息,初始化默认为0,可以通过
runtime函数 class_setVersion和
class_getVersion进行 修改、读取
long info;
long instance_size ;
struct objc_ivar_list *ivars; // ivars类的实例变量列表
struct objc_method_list **methodLists ; // methodLists类的方法列表
struct objc_cache *cache; // cache最近使用的方法列表
struct objc_protocol_list *protocols; // 类需要遵守的协议列表
}
isa指针,指向类自身,super_class:父类 ivars:实例变量数组;methodLists;cache;protpcols:类的协议列表
struct objc_method {
SEL method_name //方法名称
char *method_types //方法参数和返回类型的描述字符串
IMP method_imp //方法的具体实现指针,执行方法的具体实现
}
isa指针:指向类本身.根据它可以找到对象所属的类
元类:即是描述类对象的类,也是类对象所属的类.一个类只有一个元类,表达了类对象本书所具有的元数据.比如类方法,是结构体objc_class中isa指向的类
类对象:是由程序员定义并在运行时由编译器创建的,它没有自己的实例变量,这里需要注意的是类的成员变量和实例方法列表是属于实例对象的,但其存储于类对象当中的
实例对象:是我们对类对象alloc或者new操作时所创建的,在这个过程中会拷贝实例所属类的成员变量,但并不拷贝类定义的方法。调用实例方法时,系统会根据实例的isa指针去类的方法列表及父类的方法列表中寻找与消息对应的selector指向的方法
SEL:方法选择器.存储的是方法的名称.是一个映射到方法的c字符串
IMP:方法的具体实现指针
objc_msgSend中隐藏的指针是: self指针和cmd指针,
self指针指向当前方法的对象指针
cmd指针指向方法的具体实现
super 是一个关键字,接收到super的消息会创建一个objc_super的结构体,结构体的receive(接受者)依然是self,所以NSStringFormClass([super class])依然是当前对象
类实例的isa指针指向类自身,类自身的isa指针指向类的元类。
NSObject的isa指针指向根元类,这个根元类的isa指针指向它自己,这样isa指针就形成一个闭环。
oc的跟类是NSobject,NSobject是只有一个Class类型的结构体
superClass是一层层往上继承的, NSObject元类的superClass是NSObject,NSObject的superClass是nil.
runtime 又叫运行时,是将一些决定从编译期推迟到代码运行期来处理,在oc中的一切都被设计成了对象,所以runtime的机制就是说:
只有在运行时才能真正确定类的相关信息。故利用runtime机制我们可以在运行时获取并修改类的各种信息。比如方法列表,属性列表,添加方法,属性等。
runtime由一套比较底层的c语言的API来实现,我们平时编写的OC代码,程序在运行过程中,实际最终都转化成runtime的c语言代码,所以说runtime的oc的幕后工作者
NSLog(@"______%@",NSStringFromClass([self class]));
NSLog(@"______%@",NSStringFromClass([super class]));
NSLog(@"______%@",NSStringFromClass([self.superclass class]));
答案: self ,self super
super 是一个关键字,接收到super的消息会创建一个objc_super的结构体,结构体的receive(接受者)依然是self,所以NSStringFormClass([super class])依然是当前对象
BOOL res1 = [(id)[NSObject class] isKindOfClass:[NSObject class]];
BOOL res2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]];
BOOL res3 = [(id)[UIViewController class] isKindOfClass:[UIViewController class]];
BOOL res4 = [(id)[UIViewController class] isMemberOfClass:[UIViewController class]]; NSLog(@"%d**%d**%d**%d",res1,res2,res3,res4); //YES/NO/NO/NO
https://www.jianshu.com/p/e8524e99f3e7
在isKindOfClass中有一个循环,先判断class是否等于meta class,不等就继续循环判断是否等于meta class的super class,不等再继续取super class,如此循环下去。
由类的基础层次知:加入a是NSObject的子类。则a的元类(metaclass)是AMetaClass。AMetaClass的super类是NSObjectMetaclass ,NSObjectMetaclass的super类是NSObjectClass。NSObjectClass的superClass是nil
isMemberOfClass的源码实现是拿到自己的isa指针和自己比较,是否相等。
UIViewController的isa指针是UIviewControllerMetaClass,与UIviewController不同,
NSObject的isa指针是NSObjectMetaclass与NSObject不同,但NSObjectMetaClass的superclass就是NSobjectMetaClass自身,所以返回YES
weak修饰的指针默认值是nil,
在object-c中向nil对象发送消息是安全的。不会崩溃,因为在oc中nil就是0.那函数执行就会读取从函数开始位置偏移值为0的位置,还是函数调用开始的位置,自然什么也不做就直接返回了。
至于要么返回默认的0,要么返回nil对象。要么返回0.0,则根据函数返回值决定。
selector是SEL类型。指的是方法名。
IMP:是方法的具体实现地址,二者是一一对应的关系,
通过selector可以直接找到IMP地址,方法是methodForSelector
1.oc中每个类都有自己的名字空间,类中的名字和类外的名字不会冲突,所以不同的类可以有相同的方法名。
2.oc中消息发送[target msg]会自动转化为:objc_msgSend(target, @selector(msg),这里 selector指向的是方法的名字,并不是方法的具体实现,而不同的类的可以有相同的方法名。 故由于oc的类的命名空间和oc的消息发送机制,决定了可以使oc中不同的类响应同一方法。
一个对象的方法调用类似于[obj foo],编译器会知道转化为objc_msgSend(obj, foo), 这就叫runtime的消息发送
1.对象的方法调用【obj func】会被编译器自动转化为objc_msgSend
2.进入消息传递流程,如果未处理则进入下一步
3.消息分发,
第一步通过obj的isa指针找到它的类(class );
第二步先在所属类class的方法缓存(objc_cache)中查找foo,找到就执行,找不到则去所属类class的方法列表(method list)中 继续寻找foo。此时找到fool,则就转去它的实现IMP来执行。同时会以方法名(method_name)为key,方法实现IMP指针(method_IMP)为value存入class的方法缓存(objc_cache)列表中。
第三步,如果第二步找不到fool。则继续在class的superclass中执行第二步,以此类推,直到查找到跟类NSobject依然找不到方法fool,则会进行消息转发。
在此方法中可动态添加一个方法来把消息转发出去
第二步. 添加备用接收者:实现代理方法 -forwardingTargetForSelector: 如果没有在第一步添加方法,就会进行第二步。在这里可以返回一个新的对象来处理消息
第三步. .完整消息转发;如果备用接收也返回nil。则进行完整消息转发。分2步进行 3.1.在代理方法 methodSignatureForSelector 获取函数签名
这里如果返回nil。程序会挂掉,同时程序发送消息doesNotRecognizeSelector
3.2在代理方法 forwardInvocation获取到函数签名后,runtime机制会自动创建一个NSInvocation对象并发送forwardInvocation消息给目标对象。我们需要在forwardInvocation中激活invoke这个消息
如果此方法返回nil,Runtime则会发出 -doesNotRecognizeSelector:消息,程序这时也就挂掉了。
https://www.jianshu.com/p/6ebda3cd8052
动态创建对象(NSClassFromString)
获取/修改/添加 类的方法列表,属性列表,协议列表,
通过属性关联来给分类添加属性。
实现NSCoding的自动归档和自动解档
实现字典和模型(model)的自动转换
方法魔法(Method Swizzling):指的是方法添加和方法交换
oc语言的Swizzling应该在对象的load方法中进行,应为它是对象创建后最早的方法,而且应该在dispatch_once中完成,因为swizzling 只需要做一次就可
方法交换交换的是方法A和方法B的的IMP指针
给分类通过属性关联来添加属性,就是使用runtime实现属性的set和get方法,动态添加属性。
objc_setAssociatedObject/objc_getAssociatedObject
因为关联是基于关键字的。所以只要关键字不同,可以让任何两个对象关联起来,使这两个对象有相同的生命周期。
断开某一个 关联 使用objc_setAssociatedObject传入nil值即可。
断开所有链接:objc_removeAssociatedObjects
在归档和解归档对应的方法initWithCoder和encodeWithData 中利用runtime提供的获取对象所有属性的方法,对所有获取到的属性进行encode 和decode操作
- (id)initWithCoder:(NSCoder *)aDecoder{
if (self = [super init]) {
Class c = self.class;
// 截取类和父类的成员变量
while (c && c != [NSObject class]) {
unsigned int count = 0;
Ivar *ivars = class_copyIvarList(c, &count);
for (int i = 0; i < count; i++) {
NSString *key = [NSString stringWithUTF8String:ivar_getName(ivars[i])];
id value = [aDecoder decodeObjectForKey:key];
[self setValue:value forKey:key];
}
// 获得c的父类
c = [c superclass];
free(ivar);
}
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)aCoder
{
Class c = self.class;
// 截取类和父类的成员变量
while (c && c != [NSObject class]) {
unsigned int count = 0;
Ivar *ivars = class_copyIvarList(c, &count);
for (int i = 0; i < count; i++) {
Ivar ivar = ivars[i];
NSString *key = [NSString stringWithUTF8String:ivar_getName(ivar)];
id value = [self valueForKey:key];
[aCoder encodeObject:value forKey:key];
}
c = [c superclass];
// 释放内存
free(ivar);
}
}
利用runtime遍历model自身的所有属性,如果属性在josn的字典中有值.则从json的字典中获取值赋值给此属性,一般此方法写到NSObject的分类中即可. 与YYModel的区别是YYModel可以直接嵌套解析,这个不行
//字典转模型
- (instancetype)initWithDict:(NSDictionary *)dict
{
self = [super init];
if (self)
{
// 获取类的属性及属性对应的类型
NSMutableArray *keys = [NSMutableArray array];
NSMutableArray *attributes = [NSMutableArray array];
unsigned int outCount;
objc_property_t *properties = class_copyPropertyList([self class], &outCount);
for (int i = 0; i < outCount; i ++)
{
objc_property_t property = properties[i];
// 通过property_getName函数获得属性的名字
NSString *propertyName = [NSString stringWithCString:property_getName(property) encoding:NSUTF8StringEncoding];
[keys addObject:propertyName];
// 通过property_getAttributes函数可以获得属性的名字和@encode编码
NSString *propertyAttribute = [NSString stringWithCString:property_getAttributes(property) encoding:NSUTF8StringEncoding];
[attributes addObject:propertyAttribute];
}
// 立即释放properties指向的内存
free(properties);
// 根据类型给属性赋值
for (NSString *key in keys)
{
id value = [dict valueForKey:key];
if (value == nil)
{
continue;
}
[self setValue:value forKey:key];
}
}
return self;
}
Runtime会将注册的类所有被weak修饰的对象放入一个hash表中,表中以weak对象的内存地址为key, 当weak对象的引用计数为0时,会遍历整个hash表,把其中以key为健的所有weak对象设为nil,这样weak修饰的指针自动置为nil。
程序直到运行时才能确定所属的类的类型,在oc中指的是id类型,id是通用的对象类型,本身是一个指针,可以指向任意类型的对象。
都是通用的对象类型指针,可以指向任意类型的对象
区别:instanceType只能作为返回值类型;id可以作为返回值类型,也可以作为参数
动态绑定指在运行时才确定要调用的方法,因为在编译时方法的调用并不和实现绑定在一起,只有在消息发送出来之后,才能确定被调用的是哪个对象的方法。
通过动态类型和动态绑定技术,代码每次执行都可以得到不同的结果。
当我们发送一个消息给一个类的实例,这条消息会在实例所属类的方法列表中查找
当我们发送一个消息给一个类对象,这条消息会在类的元类(meta calass) 的方法列表中查找
不能。 编译后,该类已经完成了实例变量的布局,不能再增加实例变量。但可以动态添加属性
动态运行时语言将函数决议推迟到运行期
编译时语言在编译期进行函数决议
元类是对类对象的描述, 1)NSObject的superclass是nil。 2)每一个类对象都有一个isa指针,这个isa指针的指向该类对象的元类,所以实例变量的isa指向类对象,而类对象的isa指针指向其元类
假设A类继承自B类,B类继承自NSObject
A便是图中的Subclass,B便是图中的Superclass,NSObject便是Root class
A *a = [A new];
其实A和a一样,也是对象,A称为类对象,a称为实例对象,每一个类都是它的元类的对象,就像类是普通实例对象的描述一样。
每一个类里面声明的类方法,其本质就是把该类方法放到元类的方法列表上面,所以类在调用类方法时,可以想象成是元类的对象在调用一个实例方法。
A的父类是B,所以A的元类的父类是B的元类,B的父类是NSObject,NSObject的父类是nil,B元类的父类是NSObject的元类;特别注意的一点,NSObject的元类的父类是NSObject,NSObject的isa指针又指向NSObject的元类,所以在NSObject里面的所有方法,NSObject的元类也都拥有,
1、所以用NSObject 调用任意NSObject里面的实例方法都是可以成功的, 2、这也就解释了上面的声明里面是+(void)run;类方法,实现里面是-(void)run{ NSLog(@"run.....");}实例方法,调用却不会崩溃。
类和元类是一个闭环,实例指向类,类指向元类,元类指向跟元类,跟元类指向自身,根元类的父类是NSObject
元类是 Class 对象的类。每个类(Class)都有自己独一无二的元类(每个类都有自己第一无二的方法列表)。这意味着所有的类对象都不同。
NSObject里面的所有实例方法,任意类都可以通过类方法调用。
所有的meta-class使用基类的meta-class作为自己的基类,对于顶层基类的meta-class也是一样,只是它指向自己而已