runtime

2019-06-11  本文已影响0人  boy丿log

runtime简介

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对象,获取到执行对象和实现方法的所有信息。

消息转发的作用

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具体操作流程。

  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(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。

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 = class_getInstanceMethod([self class], sel_registerName("class_getInstanceMethod")
Method method = class_getClassMethod([self class], @selector(aaa));
    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 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);
}

    //    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能够帮助我们在开发过程中更灵活的掌控代码。

上一篇下一篇

猜你喜欢

热点阅读