runtime

objc_object 跟类

struct objc_object {
    Class isa  
};

objc_class

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:类的协议列表

objc_method

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概念

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发消息安全吗?

weak修饰的指针默认值是nil,

在object-c中向nil对象发送消息是安全的。不会崩溃,因为在oc中nil就是0.那函数执行就会读取从函数开始位置偏移值为0的位置,还是函数调用开始的位置,自然什么也不做就直接返回了。

至于要么返回默认的0,要么返回nil对象。要么返回0.0,则根据函数返回值决定。

runtime如何通过selector找到相应的IMP地址?

selector是SEL类型。指的是方法名。

IMP:是方法的具体实现地址,二者是一一对应的关系,

通过selector可以直接找到IMP地址,方法是methodForSelector

多态实现原理:

  • 1.oc中每个类都有自己的名字空间,类中的名字和类外的名字不会冲突,所以不同的类可以有相同的方法名。

  • 2.oc中消息发送[target msg]会自动转化为:objc_msgSend(target, @selector(msg),这里 selector指向的是方法的名字,并不是方法的具体实现,而不同的类的可以有相同的方法名。 故由于oc的类的命名空间和oc的消息发送机制,决定了可以使oc中不同的类响应同一方法。

runtime的消息传递

一个对象的方法调用类似于[obj foo],编译器会知道转化为objc_msgSend(obj, foo), 这就叫runtime的消息发送

runtime的消息机制:

  • 1.对象的方法调用【obj func】会被编译器自动转化为objc_msgSend

  • 2.进入消息传递流程,如果未处理则进入下一步

  • 3.消息分发,

    runtime的消息传递流程是这样的:

  • 第一步通过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,则会进行消息转发。

runtime的消息转发

  • 第一步.添加动态消息解析:实现代理方法+resolveInstanceMethod / + resolveClassMethod

在此方法中可动态添加一个方法来把消息转发出去

  • 第二步. 添加备用接收者:实现代理方法 -forwardingTargetForSelector: 如果没有在第一步添加方法,就会进行第二步。在这里可以返回一个新的对象来处理消息

  • 第三步. .完整消息转发;如果备用接收也返回nil。则进行完整消息转发。分2步进行 3.1.在代理方法 methodSignatureForSelector 获取函数签名

    这里如果返回nil。程序会挂掉,同时程序发送消息doesNotRecognizeSelector

    3.2在代理方法 forwardInvocation获取到函数签名后,runtime机制会自动创建一个NSInvocation对象并发送forwardInvocation消息给目标对象。我们需要在forwardInvocation中激活invoke这个消息

    如果此方法返回nil,Runtime则会发出 -doesNotRecognizeSelector:消息,程序这时也就挂掉了。

runtime可以实现的功能有哪些?

https://www.jianshu.com/p/6ebda3cd8052

  • 动态创建对象(NSClassFromString)

  • 获取/修改/添加 类的方法列表,属性列表,协议列表,

  • 通过属性关联来给分类添加属性。

  • 实现NSCoding的自动归档和自动解档

  • 实现字典和模型(model)的自动转换

  • 方法魔法(Method Swizzling):指的是方法添加和方法交换

    oc语言的Swizzling应该在对象的load方法中进行,应为它是对象创建后最早的方法,而且应该在dispatch_once中完成,因为swizzling 只需要做一次就可

方法交换交换的是方法A和方法B的的IMP指针

使用runtime给分类添加属性

给分类通过属性关联来添加属性,就是使用runtime实现属性的set和get方法,动态添加属性。

objc_setAssociatedObject/objc_getAssociatedObject

因为关联是基于关键字的。所以只要关键字不同,可以让任何两个对象关联起来,使这两个对象有相同的生命周期。

断开某一个 关联 使用objc_setAssociatedObject传入nil值即可。

断开所有链接:objc_removeAssociatedObjects

runtime实现NSCoding的自动归档和自动解档

在归档和解归档对应的方法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);
    }  
}

rumtime实现字典转模型(model)的自动转换

利用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变量的自动置nil?(即weak的实现原理)

Runtime会将注册的类所有被weak修饰的对象放入一个hash表中,表中以weak对象的内存地址为key, 当weak对象的引用计数为0时,会遍历整个hash表,把其中以key为健的所有weak对象设为nil,这样weak修饰的指针自动置为nil。

动态类型:id类型

程序直到运行时才能确定所属的类的类型,在oc中指的是id类型,id是通用的对象类型,本身是一个指针,可以指向任意类型的对象。

id类型和instanceType

都是通用的对象类型指针,可以指向任意类型的对象

区别:instanceType只能作为返回值类型;id可以作为返回值类型,也可以作为参数

动态绑定

  • 动态绑定指在运行时才确定要调用的方法,因为在编译时方法的调用并不和实现绑定在一起,只有在消息发送出来之后,才能确定被调用的是哪个对象的方法。

  • 通过动态类型和动态绑定技术,代码每次执行都可以得到不同的结果。

runtime与动态绑定

  • 因为runtime机制,只有运行时才会根据消息分发机制确定消息的接收者和被调用的方法具体是哪个。

    当我们发送一个消息给一个类的实例,这条消息会在实例所属类的方法列表中查找

当我们发送一个消息给一个类对象,这条消息会在类的元类(meta calass) 的方法列表中查找

能否向编译后的类中增加实例变量?

不能。 编译后,该类已经完成了实例变量的布局,不能再增加实例变量。但可以动态添加属性

@dynamic动态方法解析

  • 动态运行时语言将函数决议推迟到运行期

  • 编译时语言在编译期进行函数决议

元类

元类是对类对象的描述, 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也是一样,只是它指向自己而已

JSRUN前端笔记, 是针对前端工程师开放的一个笔记分享平台,是前端工程师记录重点、分享经验的一个笔记本。JSRUN前端采用的 MarkDown 语法 (极客专用语法), 这里属于IT工程师。