runtime简介
- 运行时(Runtime)是指将数据类型的确定由编译时推迟到了运行时
- Runtime是一套比较底层的纯C语言API, 属于1个C语言库, 包含了很多底层的C语言API
- 平时编写的OC代码,在程序运行过程中,其实最终会转换成Runtime的C语言代码,Runtime是Object-C的幕后工作者
- Object-C需要Runtime来创建类和对象,进行消息发送和转发
作用
- 在程序运行过程中,动态的创建类,动态添加、修改这个类的属性和方法;
- 遍历一个类中所有的成员变量、属性、以及所有方法
- 消息传递、转发
类和对象
struct objc_object {
private:
isa_t isa;
public:
// ISA() assumes this is NOT a tagged pointer object
Class ISA();
// getIsa() allows this to be a tagged pointer object
Class getIsa();
}
struct objc_class : objc_object {
// Class ISA;
Class superclass;
cache_t cache; // formerly cache pointer and vtable
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
}
可以看出类是继承于对象的,多了一个superClass,缓存和bits,对象是一个结构体,拥有一个isa指针。
对象
对象拥有一个isa,它是一个联合体(union)
union isa_t {
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls;
uintptr_t bits;
#if defined(ISA_BITFIELD)
struct {
ISA_BITFIELD; // defined in isa.h
};
#endif
};
# define ISA_BITFIELD
uintptr_t nonpointer : 1; //0,代表普通指针,存储着class、metaclass对象的内存地址,1,代表优化过,使用位域存储更多的信息。 用来区分isa存的是union还是地址
uintptr_t has_assoc : 1; //是否有设置过关联对象,如果没有,释放更快
uintptr_t has_cxx_dtor : 1;//是否有c++的析构函数
uintptr_t shiftcls : 44; //存储着class、metaclass对象的内存地址
uintptr_t magic : 6; //用来分辨对象是否未完成初始化
uintptr_t weakly_referenced : 1; //是否被弱引用
uintptr_t deallocating : 1; //是否正在释放
uintptr_t has_sidetable_rc : 1; //如果为1,则引用技术存储在sidetable
uintptr_t extra_rc : 8//引用计数器
# define RC_ONE (1ULL<<56)
# define RC_HALF (1ULL<<7)
类
struct objc_class : objc_object {
Class superclass;
cache_t cache;
class_data_bits_t bits;
...
}
这里的class_data_bits_t存储了类对象的一些flag和 class_rw_t。 class_rw_t是一个结构体:
uint32_t flags;
uint32_t version;
const class_ro_t *ro;//只读属性,存放只读的协议、属性等
method_array_t methods;//方法列表
property_array_t properties;//属性列表
protocol_array_t protocols;//协议列表
Class firstSubclass;
Class nextSiblingClass;
char *demangledName;
其实一开始类的方法,属性,成员变量属性协议等等都是存放在class_ro_t中的,当程序运行的时候,需要将分类中的列表跟类初始的列表合并在一起的时,就会将class_ro_t中的列表和分类中的列表合并起来存放在class_rw_t中,也就是说class_rw_t中有部分列表是从class_ro_t里面拿出来的。并且最终和分类的方法合并。
cache_t的实现可以看这个文章探寻Runtime本质(二)
引用:
当第一次使用方法时,消息机制通过isa找到方法之后,会对方法以SEL为keyIMP为value的方式缓存在cache的_buckets中,当第一次存储的时候,会创建具有4个空间的散列表,并将_mask的值置为散列表的长度减一,之后通过SEL & mask计算出方法存储的下标值,并将方法存储在散列表中。举个例子,如果计算出下标值为3,那么就将方法直接存储在下标为3的空间中,前面的空间会留空。
当散列表中存储的方法占据散列表长度超过3/4的时候,散列表会进行扩容操作,将创建一个新的散列表并且空间扩容至原来空间的两倍,并重置_mask的值,最后释放旧的散列表,此时再有方法要进行缓存的话,就需要重新通过SEL & mask计算出下标值之后在按照下标进行存储了。
如果一个类中方法很多,其中很可能会出现多个方法的SEL & mask得到的值为同一个下标值,那么会调用cache_next函数往下标值-1位去进行存储,如果下标值-1位空间中有存储方法,并且key不与要存储的key相同,那么再到前面一位进行比较,直到找到一位空间没有存储方法或者key与要存储的key相同为止,如果到下标0的话就会到下标为_mask的空间也就是最大空间处进行比较。
当要查找方法时,并不需要遍历散列表,同样通过SEL & mask计算出下标值,直接去下标值的空间取值即可,同上,如果下标值中存储的key与要查找的key不相同,就去前面一位查找。这样虽然占用了少量控件,但是大大节省了时间,也就是说其实apple是使用空间换取了存取的时间。
元件
imp
imp指的是方法的具体实现,是.m文件中的方法实现。imp相关的方法:
- 设置
/**
设置block的实现。block的第一个参数为执行imp的对象,之后的参数是imp需要传入的参数
id _block = ^ (id obj, NSInteger a,NSInteger b){
NSLog(@"%ld %ld",a,b);
};
*/
OBJC_EXPORT IMP _Nonnull
imp_implementationWithBlock(id _Nonnull block);
//获取imp的block
OBJC_EXPORT id _Nullable
imp_getBlock(IMP _Nonnull anImp);
//移除imp的block
OBJC_EXPORT BOOL
imp_removeBlock(IMP _Nonnull anImp);
- 获取
//这个方法不能区分类方法或对象方法
Imp imp = class_getMethodImplementation(class,sel);
/**
还有一种方法是通过method获取imp,mehtod的获取可以区分类和对象,然后通过method获取imp
*/
Mehtod method = class_getInstanceMethod(class,sel);
Mehtod method = class_getClassMethod(class,sel);
Imp imp = method_getImplementation(method)
- 调用
((void (*) (id,SEL,NSInteger,NSInteger))imp)(self,NULL,1,2);
或者
int (*impyFunct)(id, SEL, int, int) = (void*) imp_implementationWithBlock(impyBlock);
impyBlock(nil, 20, 22);
值得注意的是_objc_msgForward这个imp指针,
当获取到的imp为NULL并不代表方法未实现,只能说当前方法未重写父类方法,只有当imp为_objc_msgForward时才代表没有实现这个方法,这时会走消息转发流程。
消息转发
消息转发是指OC方法调用时没有获取到实现方法,而进行的一系列保护操作,一般有3个步骤。
第一步,类方法调用,如果是实例方法,调用:
+ (BOOL)resolveInstanceMethod:(SEL)sel;
如果是类方法:
+ (BOOL)resolveClassMethod:(SEL)sel;
这时你可以通过sel给类动态添加方法。返回值为YES,这时runtime停止消息转发,尝试执行未找到的方法,若此时仍没有实现,则crash。为NO则继续接下来的第二步。
+ (BOOL)resolveClassMethod:(SEL)sel { NSLog(@"%s",__func__); if (sel == @selector(number)) { return class_addMethod(object_getClass(self), sel, (IMP)number, "v@:"); } return [super resolveClassMethod:sel]; }
第二步,重定向,这个时候,会走
- (id)forwardingTargetForSelector:(SEL)aSelector;
允许你传一个id对象,如果返回nil,继续向下执行第三步,如果返回的对象实现了该方法,则调用,如果没实现,crash。
- (id)forwardingTargetForSelector:(SEL)aSelector {
NSLog(@"消息重定向 %s",__func__);
if (aSelector == @selector(run))
{
return [Monkey new];
}
return [super forwardingTargetForSelector:aSelector];
}
第三步,消息转发,会调用两个方法,
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;
- (void)forwardInvocation:(NSInvocation *)anInvocation
上一个方法可以创建方法签名信息,如果返回为空或不实现会crash,下一个方法可以获取到NSIvocation对象,获取到执行对象和实现方法的所有信息。
消息转发的作用
- 实现多继承
- @dynamic
Aspects、Rac、JSPath等都是使用了这一原理。
SEL
SEL是对象用来查询method的“键值”。关于SEL的方法:
//如果sel不存在则创建
sel_registerName("sel")
//sel不存在返回nil
sel_getUid("sel")
//获取sel的c字符串
sel_getName("sel")
//sel是否有效
sel_isMapped("sel")
//两个sel是否相同
sel_isEqual(sel1,sel2)
Method
method实际上是一个结构体
typedef struct method_t *Method;
struct method_t {
SEL name;//key值
const char *types;//返回的类型编码
MethodListIMP imp;//imp指针
struct SortBySELAddress :
public std::binary_function<const method_t&,
const method_t&, bool>
{
bool operator() (const method_t& lhs,
const method_t& rhs)
{ return lhs.name < rhs.name; }
};
};
关于类型编码,官方文档
方法:
//获取名字
method_getName(method)
//获取imp指针
method_getImplementation(method)
//获取编码
method_getTypeEncoding(method)
//获取参数数量
method_getNumberOfArguments(method)
//获取返回的类型编码,必须自己释放
method_copyReturnType(method)
//获取参数的类型编码,必须自己释放
method_copyArgumentType(method)
//获取返回的类型编码
method_getReturnType(method)
//获取参数的类型编码
method_getArgumentType(method)
//重新设置method的方法实现
method_setImplementation(method)
//交换两个method的实现
method_exchangeImplementations(method1, method2)
关于method_setImplementation和method_exchangeImplementations
前一个方法主要是通过block来替换原方法实现,如Aspects和RAC都是用了这种交换方法,不需要额外的增加内存,但难度较高。
后一个方法需要重新写一个方法,额外增加了内存开销,但相对来说使用简单,不需要了解imp具体操作流程。
- method_setImplementation示例
static void (*aaaImp)(__unsafe_unretained id , SEL);
__block void (*oldImp) (__unsafe_unretained id , SEL) = NULL;
SEL deallocSelector = sel_registerName("aaa");
//如果之前imp不为空,证明之前已方法交换过,
if (aaaImp == NULL) {
//获取对象的实例方法(非类方法)
Method deallocMethod = class_getInstanceMethod([self class], deallocSelector);
//获得老的imp指针
aaaImp = oldImp = (__typeof__(oldImp))method_getImplementation(deallocMethod);
//设置新的imp指针
method_setImplementation(deallocMethod, imp_implementationWithBlock(^(){
if (oldImp == NULL) {//如果当前类没有实现imp,则由父类类执行方法,否则当前类执行方法。
struct objc_super superInfo = {
.receiver = self,
.super_class = class_getSuperclass([self class])
};
void (*msgSend)(struct objc_super *, SEL) = (__typeof__(msgSend))objc_msgSendSuper;
msgSend(&superInfo, deallocSelector);
}else{
oldImp(self,deallocSelector);
}
NSLog(@"ddd");
}));
}
- method_exchangeImplementations示例
method_exchangeImplementations(class_getInstanceMethod([self class], @selector(aaa)), class_getInstanceMethod([self class], @selector(bbb)));
Ivar
实例变量,它是一个结构体:
struct ivar_t {
int32_t *offset;
const char *name;//名字
const char *type;//类型编码
uint32_t alignment_raw;
uint32_t size;
uint32_t alignment() const {
if (alignment_raw == ~(uint32_t)0) return 1U << WORD_SHIFT;
return 1 << alignment_raw;
}
};
方法不多:
//获取名字
ivar_getName(ivar)
//获取内存偏移量
ivar_getOffset(ivar)
//获取内存编码
ivar_getTypeEncoding(ivar)
Protocol
协议对象,继承于object_object.
struct protocol_t : objc_object {
const char *mangledName;
struct protocol_list_t *protocols;
method_list_t *instanceMethods;
method_list_t *classMethods;
method_list_t *optionalInstanceMethods;
method_list_t *optionalClassMethods;
property_list_t *instanceProperties;
uint32_t size; // sizeof(protocol_t)
uint32_t flags;
// Fields below this point are not always present on disk.
const char **_extendedMethodTypes;
const char *_demangledName;
property_list_t *_classProperties;
}
没有成员变量,只有方法。
方法:
//通过字符串获取协议
objc_getProtocol("DPCommonCollectionViewProtocol")
//协议p2添加到p1
protocol_addProtocol(p1, p2)
//协议p1是否遵守p2
protocol_conformsToProtocol(p1, p2)
//是否相等
protocol_isEqual(p1, p2)
//获取名字
protocol_getName(p1)
struct objc_method_description {
SEL _Nullable name;
char * _Nullable types;
};
//获得方法描述信息
protocol_getMethodDescription(p1,sel,required,isInstanceMehtod)
//获得属性
protocol_getProperty(p1,name,required,isInstanceProperty)
property
typedef struct property_t *objc_property_t;
struct property_t {
const char *name;
const char *attributes;
};
方法:
//获取属性名
property_getName(p1)
//获取属性对象的属性
property_getAttributes(p1)
//const char *propertyType = property_copyAttributeValue(property, "T");
//属性名
//const char *property_Value = property_copyAttributeValue(property, "V");
//NSLog(@"property_name:%s \n property_attr:%s \n propertyType:%s \n property_Value:%s",property_name, property_attr, propertyType,property_Value);
const char *property_Value = property_copyAttributeValue([self property], "W");
NSLog(@"%s",property_Value);
属性类型 name值:T value:变化
编码类型 name值:C(copy) &(strong) W(weak) 空(assign) 等 value:无
非/原子性 name值:空(atomic) N(Nonatomic) value:无
变量名称 name值:V value:变化
property_getAttributes函数返回objc_property_attribute_t结构体列表,objc_property_attribute_t结构体包含name和value。
typedef struct {
const char * _Nonnull name; /**< The name of the attribute */
const char * _Nonnull value; /**< The value of the attribute (usually empty) */
} objc_property_attribute_t;
object
object最重要的就是获取isa指针:
isa
isa包含了指向父类或元类内存地址。主要是通过object_getClass(objec),来使用的。
object的其他方法:
//更换isa指针类
object_setClass(self, [DPRuntimeSimple class])
//根据ivar获取实例id对象
object_getIvar(obj, ivar)
//设置成员的布局
object_setIvar(obj, ivar, value)
object_setIvarWithStrongDefault( obj, ivar, value)
object_setIvar比\c object_setInstanceVariable(如果是Ivar)更快,使用内存管理
后面我就要仔细说说object_setIvar和object_setIvarWithStrongDefault,这两个函数都和内存管理有关系。先说下它们的共同点,如果内存管理属于已知的内存管理方式(成员变量或属性属于ARC,strong或者weak),它们都没有区别。不同点就是如果是属于未知的内存管理方式,object_setIvar会把该实例变量被分配为unsafe_unretain,而object_setIvarWithStrongDefault会把该实例变量被分配为strong。
首先我们要清楚3个概念,strong,weak和unsafe_unretain。
strong是强引用指向并拥有那个对象,根据retainCount是否为0来确定是否释放内存
weak是弱引用指向但并不拥有那个对象。释放空间时会自动将指针设置成nil。
unsafe_unretain和weak类似,只是释放空间时不会将指针设置成nil,所以会有野指针的危害。
所以,在ARC下,这两个方法的作用几乎一模一样。
Class
先说下class方法,后面会讲应用
- 设置版本号
int version = class_getVersion([self class]);
NSLog(@"%d", version);
class_setVersion([self class], 100);
version = class_getVersion([self class]);
- 获取实例大小
size_t size = class_getInstanceSize([self class]);
NSLog(@"%ld",size);
- 根据名字获取实例变量
Ivar ivar = class_getInstanceVariable([self class], "_ivar");
// self.aa = 100;
NSLog(@"%s %ld %s", ivar_getName(ivar),ivar_getOffset(ivar), ivar_getTypeEncoding(ivar));
NSInteger val;
val = ((NSInteger (*)(id, Ivar))object_getIvar)(self, ivar);
NSLog(@"%ld",val);
这里需要注意的是,object_getIvar只能返回id对象,如果要返回基本数据类型的话需要强转,不然会crash。
- class_getClassVariable
Ivar ivar = class_getClassVariable([self class], "aaa");
NSLog(@"%s %ld %s", ivar_getName(ivar),ivar_getOffset(ivar), ivar_getTypeEncoding(ivar));
- 获取所有成员变量
- (void)class_copyIvarList{
unsigned int count;
Ivar *aa = class_copyIvarList([self class], &count);
for (int i = 0; i < count; i++) {
Ivar ivar = aa[i];
NSLog(@"%s %ld %s", ivar_getName(ivar),ivar_getOffset(ivar), ivar_getTypeEncoding(ivar));
}
}
- 获取一个对象method
Method method = class_getInstanceMethod([self class], sel_registerName("class_getInstanceMethod")
- 获取一个类method
Method method = class_getClassMethod([self class], @selector(aaa));
- 获取imp
Method imageNamedMethod = class_getClassMethod(objc_getClass("UIImage"), @selector(imageNamed:));
Method ln_imageNamedMethod = class_getClassMethod(objc_getClass("UIImage"), @selector(ln_imageNamed:));
IMP imageNameIMP = class_getMethodImplementation(objc_getClass("UIImage"), @selector(imageNamed:));
IMP ln_imageNameIMP = class_getMethodImplementation(objc_getClass("UIImage"), @selector(ln_imageNamed:));
NSLog(@"imageNameIMP = %p %p",imageNameIMP,method_getImplementation(imageNamedMethod));
NSLog(@"ln_imageNameIMP = %p %p",ln_imageNameIMP, method_getImplementation(ln_imageNamedMethod));
// ((id (*) (id, SEL,id))imageNameIMP)(objc_getMetaClass("UIImage"),@selector(imageNamed:),@"111");
((id (*) (id, SEL,id))imageNameIMP)([self class],@selector(aa:),@"111");
- 是否包含一个方法
class_respondsToSelector(cls, sel)
- 是否遵循协议
class_conformsToProtocol(cls, pro)
- 获取一个属性
class_getProperty(cls, “c”)
- 获取类布局类型,资料
const uint8_t*a = class_getIvarLayout([self class]);
NSLog(@"%s",a);
如:
interface Foo : NSObject {
__strong id ivar0;
__weak id ivar1;
__weak id ivar2;
}
@end
详情查看
则储存 strong ivar 的 ivarLayout 的值为 0x012000
储存 weak ivar 的 weakIvarLayout 的值为 0x1200
1.前两位 01 表示有 0 个非 strong 对象和 1 个 strong 对象
2.之后两位 20 表示有 2 个非 strong 对象和 0 个 strong 对象
3.最后两位 00 为结束符,就像 cstring 的 \0 一样
- 添加方法,如果已添加,会返回失败
class_addMethod(clas, name, imp, "c")
- 重新设置一个方法的imp
IMP a = class_replaceMethod(clas, name, imp, "c")
- 添加实例
Class class = objc_allocateClassPair(NSObject.class, "Sark", 0);
class_addIvar(class, "_gayFriend", sizeof(id), log2(sizeof(id)), @encode(id));
class_addIvar(class, "_girlFriend", sizeof(id), log2(sizeof(id)), @encode(id));
class_addIvar(class, "_company", sizeof(id), log2(sizeof(id)), @encode(id));
class_setIvarLayout(class, (const uint8_t *)"\x01\x12"); // <--- new
class_setWeakIvarLayout(class, (const uint8_t *)"\x11\x10"); // <--- new
objc_registerClassPair(class);
添加实例只能在创建类之后,注册类之前。
- 动态添加属性
objc_property_attribute_t types = { "T", "@\"NSString\"" };
objc_property_attribute_t ownership = { "C", "" };
objc_property_attribute_t backIvar = { "V", "_privateName" };
objc_property_attribute_t attrs[] = { types, ownership, backIvar};
class_addProperty([SomeClass class], "name", attrs, 3);
- 动态替换属性
NSString *propertyName = @"delegate1";
Class targetClass = [self class];
objc_property_attribute_t type = { "T", [[NSString stringWithFormat:@"@\"%@\"",NSStringFromClass([NSString class])] UTF8String] }; //type
objc_property_attribute_t ownership0 = { "C", "" }; // C = copy
objc_property_attribute_t ownership = { "N", "" }; //N = nonatomic
objc_property_attribute_t backingivar = { "V", [[NSString stringWithFormat:@"_%@", propertyName] UTF8String] }; //variable name
objc_property_attribute_t attrs[] = { type, ownership0, ownership, backingivar };
class_replaceProperty([self class], "delegate1", attrs, 3);
NSLog(@"%@",self.delegate1);
- 创建一个实例
id a = class_createInstance([self class], 0);
- 返回类起源的动态库名称
class_getName([NSObject class])
objc
objc跟object名字有点像,但作用就大不一样了。objc方法是负责runtime的全局方法。
- 操作类
//获取类
objc_getClass("UITableViewController")
//获取元类
objc_getMetaClass("UITableViewController");
//objc_getClass与这个函数的不同之处在于,如果这个类不是已注册,\c objc_getClass调用类处理程序回调,然后检查第二个的时候查看这个类是否注册了。此函数不调用类处理程序回调
objc_lookUpClass("");
//与objc_getClass相同但是如果没有类的时候会终止进程
objc_getRequiredClass("UITableViewController");
//获取类的数量
int newNumClasses = objc_getClassList(NULL, 0)
//获取所有类
Class *classes = (Class *)malloc(sizeof(Class) * (newNumClasses + 1)); // 2
newNumClasses = objc_getClassList(classes, newNumClasses);
//获取所有类,outcount为类数量
unsigned int outCount = 0;
Class *classLiset =objc_copyClassList(&outCount);
//创建类和元类
Class class = objc_allocateClassPair([NSObject class], "AA", 0);
//注册,未注册的不能通过NSClassFromString获取class
objc_registerClassPair(class);
//objc_disposeClassPair只能销毁由objc_allocateClassPair创建的类,当有实例存在或者它的子类存在时,调用这个函数会抛出异常。 objc_disposeClassPair(class);
- 协议
//获取协议
Protocol*protocol = objc_getProtocol("UITableViewDelegate");
//获取所有协议
Protocol * __unsafe_unretained *list = objc_copyProtocolList(&count);
for (int i = 0; i < count; i++) {
NSLog(@"%s",protocol_getName(list[i]));
}
//创建注册协议
Protocol*a = objc_allocateProtocol("aaaaaaa");
objc_registerProtocol(a);
- 动态库
//获取所有动态库,及获取动态库名字
const char **list = objc_copyImageNames(&count);
for (int i = 0; i < count; i++) {
unsigned int count1 = 0;
const char **images = objc_copyClassNamesForImage(list[i], &count1);
for (int j = 0; j < count1; j++) {
NSLog(@"className:::::%s",images[j]);
}
free(images);
}
free(list);
- 消息
/**
消息转发,用来实现_objc_msgForward
*/
- (void)objc_setForwardHandler{
// objc_setForwardHandler(fwd, fwd)
}
/**
主动报错, Terminating app due to uncaught exception 'NSGenericException', reason: '*** Collection <DPObjcSimple: 0x6000017dfcc0> was mutated while being enumerated.
*/
- (void)objc_enumerationMutation{
objc_enumerationMutation(self);
}
void (aaa)(){
NSLog(@"失败了");
};
/**
捕获异常
*/
- (void)objc_setEnumerationMutationHandler{
objc_setEnumerationMutationHandler(aaa);
objc_enumerationMutation(self);
}
- weak指针
该函数加载一个弱指针引用的对象,并在对其做retain和autoreleasing操作后返回它。这样,对象就可以在调用者使用它时保持足够长的生命周期。该函数典型的用法是在任何有使用__weak变量的表达式中使用。
// objc_loadWeak(self);
// 该函数的典型用法是用于__weak变量做为赋值对象时。
// objc_storeWeak(self, self);
- 动态关联
objc_setAssociatedObject(obj, key, value, policy)
objc_getAssociatedObject(objc,key)
objc_removeAssociatedObjects(obj)
动态关联不支持KVO,KVO的支持与否与包不包含这个对象无关,KVO重写了对象的set和get方法。
消息发送流程
objc_msgSend
objc_msgSend发送消息与imp类似:
((void (*) (id,SEL,NSInteger,NSInteger))imp)(self,NULL,1,2);
((void(*)(id,SEL))objc_msgSend)(self,@selector(log));
不一样的是objc_msgSend的第二个参数为selector。
objc_msgSendSuper
struct objc_super superInfo = {
.receiver = self,
.super_class = class_getSuperclass(object_getClass(self))
};
((void(*)(struct objc_super *,SEL))objc_msgSendSuper)(&superInfo,@selector(log));
向父类发送消息
method_invoke
((void (*)(id, Method))method_invoke)(self,class_getInstanceMethod([self class], @selector(log)));
根据method调用方法。
_objc_msgForward
消息转发。
super与self的区别
当调用super的时候,实际上调用的是objc_msgSendSuper方法,这个方法的第一个参数是一个结构体
struct objc_super superInfo = {
.receiver = self,
.super_class = class_getSuperclass(object_getClass(self))
};
它有两个值,一个是receiver,它指的是消息处理的对象,第二个是superclass,指的是调用的父类。
即调用super的时候,调用者依然是self,但是调用的方法确是从父类中查询的。
self方法调用的是objc_msgSend。
class和object_getClass
class是类实现的方法,一般是返回本类,如[super class]、[[super class] class]返回的都是当前类的对象。
而object_getClass获取的则是isa指针。这里要说一下类和元类。我们可以发现,类的创建方法名为objc_allocateClassPair,一对......这里的一对指的是在创建类的时候自动创建了元类,类用来存储属性和方法,而类方法(+)和属性则由元类来存储。这里需要了解下:
class和isa的关系
需要注意的是,NSObjec的元类的父类为NSObject类对象,isa指针指向自己。NSObject类的父类为nil。
这里有道面试题,来考下你:
@implementation NSObject (n)
- (void)foo {
NSLog(@"IMP: -[NSObject (Sark) foo]");
}
@end
定义这个方法后,调用方法
[NSObject foo];
会crash吗?
接下来说一些runtime的具体应用场景
监听方法的参数,以及改变执行的顺序。
将需要监听的方法的imp设为_objc_msgForward,然后走消息转发最后一步。方法交换对象的forwardInvocation:方法,自己拿到invocation后进行解析和调用。具体可以看Aspects和rac。
自己实现KVO
void *Shine_KVO = &Shine_KVO;
static dispatch_once_t predicate;
@implementation DPRuntimeKVO
//根据keypath 获取set方法
-(SEL)getNewSelector:(NSString *)selectorName
{
NSString *firstChar = [selectorName substringToIndex:1];
NSString *upFirst = [firstChar uppercaseString];
NSString *otherChar = [selectorName substringFromIndex:1];
NSString *newSelectorName = [NSString stringWithFormat:@"set%@%@:",upFirst,otherChar];
return NSSelectorFromString(newSelectorName);
}
//根据set方法 获取keypath
-(NSString *)getKeypath:(SEL)selector
{
NSString *selectorName = NSStringFromSelector(selector);
selectorName = [selectorName substringFromIndex:3];
NSString *firstChar = [selectorName substringToIndex:1];
NSString *lowFirst = [firstChar lowercaseString];
NSString *otherChar = [selectorName substringFromIndex:1];
NSString *newChar = [NSString stringWithFormat:@"%@%@",lowFirst,otherChar];
return [newChar substringToIndex:newChar.length-1];
}
- (void)shine_observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change{
NSLog(@"%@",[object valueForKey:keyPath]);
}
void setObj(id sf,SEL cd,id value){
NSString *keypath = [sf getKeypath:cd];
id oldValue = [sf valueForKey:keypath];
NSMutableDictionary *change = @{}.mutableCopy;
if (oldValue != nil) {
change[@"old"] = oldValue;
}
if (value != nil) {
change[@"new"] = value;
}
//取出当前类
id class = [sf class];
NSLog(@"%@ %@", class, class_getSuperclass(class));
//指向父类
object_setClass(sf, class_getSuperclass(class));
//向父类发送消息
((void (*)(id, SEL, id))objc_msgSend)(sf, cd ,value);
// //获取动态绑定对象
id observe = objc_getAssociatedObject(sf, &Shine_KVO);
// //监听回调
((void(*)(id,SEL,id,id,id))objc_msgSend)(observe, @selector(shine_observeValueForKeyPath:ofObject:change:),keypath,sf,change);
//修改指向
object_setClass(sf, class);
}
-(void)shine_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath
{
// 1.动态生成一个派生类
static Class newClass;
dispatch_once(&predicate, ^{
NSString * oldClassName = NSStringFromClass([self class]);
NSString *newClassName = [NSString stringWithFormat:@"Shine_%@",oldClassName];
//创建派生类
newClass = objc_allocateClassPair([self class], [newClassName UTF8String], 0);
//注册派生类
objc_registerClassPair(newClass);
//修改被观察者的isa指针,指向自定义的类
object_setClass(self, newClass);
//动态绑定
objc_setAssociatedObject(self, &Shine_KVO, observer, OBJC_ASSOCIATION_ASSIGN);
});
//2.生成新的set方法
SEL selector = [self getNewSelector:keyPath];
//3.添加新的set方法
class_addMethod(newClass, selector, (IMP)setObj, "v@:@");
}
- (void)dealloc
{
predicate = 0;
objc_disposeClassPair([self class]);
}
动态创建类并添加到ARC
参考,sunny的博客。详情查看
static void fixup_class_arc(Class class) {
struct {
Class isa;
Class superclass;
struct {
void *_buckets;
#if __LP64__
uint32_t _mask;
uint32_t _occupied;
#else
uint16_t _mask;
uint16_t _occupied;
#endif
} cache;
uintptr_t bits;
} *objcClass = (__bridge typeof(objcClass))class;
#if !__LP64__
#define FAST_DATA_MASK 0xfffffffcUL
#else
#define FAST_DATA_MASK 0x00007ffffffffff8UL
#endif
struct {
uint32_t flags;
uint32_t version;
struct {
uint32_t flags;
} *ro;
} *objcRWClass = (typeof(objcRWClass))(objcClass->bits & FAST_DATA_MASK);
#define RO_IS_ARR 1<<7
objcRWClass->ro->flags |= RO_IS_ARR;
}
调用
Class class = objc_allocateClassPair(NSObject.class, "Sark", 0);
class_addIvar(class, "_gayFriend", sizeof(id), log2(sizeof(id)), @encode(id));
class_addIvar(class, "_girlFriend", sizeof(id), log2(sizeof(id)), @encode(id));
class_addIvar(class, "_company", sizeof(id), log2(sizeof(id)), @encode(id));
class_setIvarLayout(class, (const uint8_t *)"\x01\x12"); // <--- new
class_setWeakIvarLayout(class, (const uint8_t *)"\x11\x10"); // <--- new
objc_registerClassPair(class);
fixup_class_arc(class);
字典转模型
参考YYModel。
关联weak对象
runtime动态关联属性有
typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
OBJC_ASSOCIATION_ASSIGN = 0,
OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, /
OBJC_ASSOCIATION_COPY_NONATOMIC = 3,
OBJC_ASSOCIATION_RETAIN = 01401,
OBJC_ASSOCIATION_COPY = 01403
};
并没有weak选项,我们关联assign类型,但是属性销毁而指针没有销毁的时候会造成野指针,那么如何设置一个weak对象呢?
方案一、在关联的对象销毁时将引用的对象指针置nil。为了不重写对象的dealloc方法,你可以写个NSObject分类,
就像DPRouter里面的,
- (void)dp_addDellocTask:(dp_deallocTask)task;
在设置时,调用引用对象的这个方法。
方案二、自己新建一个类,拥有一个weak对象,用strong类型设置一个这个类的关联,然后weak属性设置成需要引用的值。
就像这样
static void *kdpActionBlock = & kdpActionBlock;
static void *kDPCategoryActionViewController = &kDPCategoryActionViewController;
@interface UIAlertActionWithController : NSObject
@property (nonatomic, weak) UIAlertController * alertViewController;
@end
@implementation UIAlertActionWithController
@end
@implementation UIAlertAction (DPCategory)
- (void)setAlertViewController:(UIAlertActionWithController *)model{
objc_setAssociatedObject(self, kDPCategoryActionViewController, model, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (UIAlertActionWithController *)alertViewActionWithController{
return objc_getAssociatedObject(self, kDPCategoryActionViewController);
}
- (UIAlertController *)alertViewController{
return [self alertViewActionWithController].alertViewController;
}
github项目的UIAlertController分类。
总结
runtime能够帮助我们在开发过程中更灵活的掌控代码。
网友评论