整个程序的运行都是建立在runtime的运行时上。
runtime运行时是动态获取代码,也就是程序启动后获取代码。
所以,基于此原理可以做很多操作:
· 给延展类生成属性(动态设置属性,动态获取属性。)
· 方法的交换
· 动态的归档解档
· 字典转模型(如YYModel)
而runtime的运用其实就是一个个方法与属性的运用,只是用的不多。
我这里有简单的Demo,也有复杂的Demo,而Demo里附有我的讲解。
给延展类生成属性
为了给延展类生成属性,需要运用关联对象方法。(<objc/runtime.h>),
objc_get/set。 总共也就几行代码。
关联对象
void objc_setAssociatedObject(id object, void *key, id value, objc_AssociationPolicy policy)
以给定的键和策略为某对象设置关联对象值
id objc_getAssociatedObject(id object,void *key)
根据给定的键从某对象中获取对应的关联对象值
void objc_removeAssociatedObjects(id object)
移除指定对象的全部关联对象
目的是设置一个属性值为某对象,动态与某延展类对象关联。
还可用于给一个对象设置一个block值,在需要的时候,动态获取该对象的该block而后执行。
方法的交换
其实就三行代码,获取A对象的方法选择子a,获取B对象的方法选择子b,然后交换实现子。
//获取两个类的类方法
Method m1 = class_getClassMethod([UIImage class], @selector(imageNamed:));
Method m2 = class_getClassMethod([UIImage class], @selector(AD_imageNamed:));
//开始交换方法的实现
method_exchangeImplementations(m1, m2);
动态的归档解档
也就几行代码,获取对象的所有成员变量。然后归档(encodeObject:forKey:)解档(decodeObjectForKey:)
成员变量获取:class_copyIvarList(class,&int),返回是Ivar类型数值,里面像一个数组一样包含一堆数据,以及每个数据对应的一些数据。
可通过for循环遍历,遍历时可以通过ivar_getName(ivar)获取变量名。
在YYModel中就是用此方法获取到类的变量,进而进行键值匹配
字典转模型(如YYModel)
简单点的动态获取成员变量就可以了。如之前提到的:class_copyIvarList(class,&int)
复杂点的就是动态获取方法的选择子,用于实现属性的getter、setter方法
动态获取类的成员变量,通过偏移量得知成员变量的属性类型。如:字符串,int等。
这里面还会牵扯到元类、isa指针、字典的底层CFMutableDictionary等。
详情可看我的ADModel
objc_msgSend
OC消息传递机制中选择子发送的一种方式。可表示当前对象发送的选择子,且没有结构体返回值。
列如:
((void (*)(id, SEL, bool))(void *) objc_msgSend)((id)model, meta->_setter, num.boolValue);
这里的意思是,通过objc_msgSend给id类型的model对象发送一个选择子meta,选择子调用的方法所需参数为一个bool类型的值num.boolValue。而这个方法无返回值。
通俗点说就是让对象model去执行方法meta->_setter,方法所需参数是num.bollValue
再通俗点描述: ((void (*)(id, SEL, bool))(void *) objc_msgSend) 一位一个无返回值的函数指针,指向id的SEL方法,SEL方法所需参数是bool类型,使用objc_msgSend完成这个id调用SEL方法传递参数bool类型,(void *)objc_msgSend为什么objc_msgSend前加一个(void *)呢?我查了众多资料,众多。最后终于皇天不负有心人有了个结果,是为了避免某些错误,比如model对象的内存被意外侵占了、model对象的isa是一个野指针之类的。要是有大牛能说明白,麻烦跟我说下,谢谢了。
而((id)model, meta->_setter, num.boolValue)则一一对应前面的id,SEL,bool
选择子简单说就是@selector(),OC会提供一张选择子表供其查询,查询得到就去调用(通过实现子IMP),查询不到就添加而后查询对应的实现函数。通过_class_lookupMethodAndLoadCache3(仅提供给派发器用于方法查找的函数),其内部会调用lookUpImpOrForward方法查找,查找之后还会有初始化枷锁缓存之类的操作,详情请自行搜索,就不赘述了。
objc_msgSend,这个函数将消息接收者和方法名作为基础参数。消息发送给一个对象时,objc_msgSend通过对象的isa指针获得类的结构体,先在Cache里找,找到就执行,没找到就在分发列表里查找方法的selector,没找到就通过objc_msgSend结构体中指向父类的指针找到父类,然后在父类分发列表找,直到root class(NSObject)。
在64位下,直接使用objc_msgSend一样会引起崩溃,必须进行一次强转
((void(*)(id, SEL,int))objc_msgSend)(self, @selector(doSomething:), 0);
调用无参数无返回值方法
((void (*)(id, SEL))objc_msgSend)((id)msg, @selector(noArgumentsAndNoReturnValue));
在此方法里会先判断方法是否存在,若不存在返回nil,若存在则保存方法method,与方法操作SEL(method_getName(method)),方法实现IMP(method_getImplementation(method)),方法操作的名字( const char *name = sel_getName(_sel),返回值是一个值不可变的字符指针name,字符指针通常指向一个字符数组的首地址,字符数组类似于字符串。好吧,其实通常来说就是字符串(char [] 或 char * 与 String)。) 。
CFMutableDictionary * cache = CFDictionaryCreateMutable(CFAllocatorGetDefault(), 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
CFMutableDictionary创建中四个参数意义为:
@功能 CFDictionaryCreateMutable创建一个新的词典。
@参数 CFAllocator 分配器应该用于分配CFAllocator字典的内存及其值的存储。这 参数可能为空,在这种情况下,当前的默认值CFAllocator使用。如果这个引用不是一个有效的cfallocator,其行为是未定义的。这里是内存及其值的存储为默认。
@参数 capacity 暗示值得个数,通过0实现可能忽略这个提示,或者可以使用它来优化各种。这里是忽略。
@参数 keyCallBacks 指向CFDictionaryKeyCallBacks结构为这本字典使用回调函数初始化在字典中的每一个键,初始化规则太多而且看的有点迷糊就不多说了,毕竟不敢乱说。意思应该是对键值对中的键进行初始化,并有相应的优化方式。
@参数 valueCallBacks 指向CFDictionaryValueCallBacks结构为这本词典使用回调函数初始化字典中的每一个值。对键值对中的值进行初始化,并有相应地优化方式。
其实按照ADModel中那个格式来就可以。
关于method
取得方法参数和返回值类型(method_getTypeEncoding(method))取得后通过UTF-8编码后保存。
方法的返回值类型(method_copyReturnType(method))取得后通过UTF-8编码后保存。
方法的参数个数(method_getNumberOfArguments(method))若个数大于0,则创建一个可变数组,而后去遍历获取参数类型(method_copyArgumentType(method, i)获取方法的指定位置参数的类型字符串),若获取到的参数类型存在就通过UTF-8编码后获取,不存在就为nil。
getClass
object_getClass(self) 返回对象的类,
objc_getClass(self);返回对象的isa指针,isa是一个指针指向对象自身,确切说是指向其元类,元类指向其元类,继承体系由此构成
iVar
类的成员变量获取:class_copyIvarList(class,&int),返回是Ivar类型数值,里面像一个数组一样包含一堆数据,以及每个数据对应的一些数据。
可通过for循环遍历,遍历时可以通过ivar_getName(ivar)获取变量名。
获取成员变量名(ivar_getName(ivar)),获取得到通过UTF-8编码后保存,在获取成员变量首地址的偏移量(ivar_getOffset(ivar)),runtime会计算ivar的地址偏移来找ivar的最终地址,获取成员变量类型编码(ivar_getTypeEncoding(ivar)),若获取得到通过UTF-8编码后保存。
编码后得到的是一个结构体,其中name与Value值对应如下:
属性类型 name值:T value:变化
编码类型 name值:C(copy) &(strong) W(weak) 空(assign) 等 value:无
非/原子性 name值:空(atomic) N(Nonatomic) value:无
变量名称 name值:V value:变化
属性描述为 T@"NSString",&,V_str 的 str
属性的描述:T 值:@"NSString"
属性的描述:V 值:_str2
为何给nil对象发消息不会崩,给对象发找不到的方法会崩?
给nil发消息,在传递给 objc_msgSend 中 self 参数是 nil,该函数会直接返回0表示空值,代表操作无意义。
给不为空的实例对象发送消息,会通过消息查找机制去依次调用三个系统方法,在最后一个方法里,系统会把消息与对象的相关数据统一处理,然后询问有人能否处理,如果没有,就抛异常代表程序崩溃。
而消息传递时调用的这三个方法也是可以重写的,而后做出一些拦截处理的操作。具体的方法操作,之后的文章中会描述。
程序崩溃不崩溃,全是系统的操作。它想不崩,那可以返回值代表处理错误或干脆没反应。它想崩那就崩。
总结一下空值区别
Nil:对类进行赋空值
nil:对对象进行赋空值
Null:对C指针进行赋空操作,如字符串数组的首地址 char *name = NULL
NSNull:对组合值,如NSArray,Json而言,其内部有值,但值为空
PS: 数组一边遍历,一边操作是会崩的。