runtime简介

因为objective-c是一门动静语言,也就是说只有编译器是不够的,还须要一个运行时零碎(runtime system)来执行编译后的代码。这是整个objective-c运行框架的一块基石。runtime简称运行时。其中最次要的就是音讯机制。对于编译期语言,会在编译的时候决定调用哪个函数。对于OC的函数,是动静调用的,在编译的时候并不能决定真正调用哪个函数,只有在运行时才会依据函数的名称找到对应的函数来调用。

runtime的作用

Objc 在三种层面上与 Runtime 零碎进行交互:   1.  通过 Objective-C 源代码   2.  通过 Foundation 框架的 NSObject 类定义的办法   3.  通过对 Runtime 库函数的间接调用

runtime源码

苹果和GNU各自保护一个开源的runtime版本,这两个版本之间都在致力的保持一致。

都是运行时的头文件,其中次要应用的函数定义在message.h和runtime.h这两个文件中。

通过 Foundation 框架的 NSObject 类定义的办法

Cocoa 程序中绝大部分类都是 NSObject 类的子类,所以都继承了 NSObject 的行为。(NSProxy 类是个例外,它是个形象超类)
  • -class办法返回对象的类;
  • -isKindOfClass:-isMemberOfClass: 办法查看对象是否存在于指定的类的继承体系中(是否是其子类或者父类或者以后类的成员变量);
  • -respondsToSelector: 查看对象是否响应指定的音讯;
  • -conformsToProtocol:查看对象是否实现了指定协定类的办法;
  • -methodForSelector: 返回指定办法实现的地址。

通过对 Runtime 库函数的间接调用

Runtime 零碎是具备公共接口的动静共享库。头文件寄存于/usr/include/objc目录下,这意味着咱们应用时只须要引入objc/Runtime.h头文件即可。

许多函数能够让你应用纯 C 代码来实现 Objc 中同样的性能。除非是写一些 Objc 与其余语言的桥接或是底层的 debug 工作,你在写 Objc 代码时个别不会用到这些 C 语言函数。对于公共接口都有哪些,前面会讲到。我将会参考苹果官网的 API 文档。

Runtime的术语的数据结构

SEL

它是selector在 Objc 中的示意(Swift 中是 Selector 类)。selector 是办法选择器,其实作用就和名字一样,日常生活中,咱们通过人名分别谁是谁,留神 Objc 在雷同的类中不会有命名雷同的两个办法。selector 对办法名进行包装,以便找到对应的办法实现。它的数据结构是:

typedef struct objc_selector *SEL;

咱们能够看出它是个映射到办法的 C 字符串,你能够通过 Objc 编译器器命令@selector() 或者 Runtime 零碎的 sel_registerName 函数来获取一个 SEL 类型的办法选择器。

id

id 是一个参数类型,它是指向某个类的实例的指针。定义如下:

typedef struct objc_object *id;struct objc_object { Class isa; };

以上定义,看到 objc_object 构造体蕴含一个 isa 指针,依据 isa 指针就能够找到对象所属的类。

Class

typedef struct objc_class *Class;

Class 其实是指向 objc_class 构造体的指针。objc_class 的数据结构如下:

struct objc_class {    Class isa  OBJC_ISA_AVAILABILITY;#if !__OBJC2__    Class super_class                                        OBJC2_UNAVAILABLE;    const char *name                                         OBJC2_UNAVAILABLE;    long version                                             OBJC2_UNAVAILABLE;    long info                                                OBJC2_UNAVAILABLE;    long instance_size                                       OBJC2_UNAVAILABLE;    struct objc_ivar_list *ivars                             OBJC2_UNAVAILABLE;    struct objc_method_list **methodLists                    OBJC2_UNAVAILABLE;    struct objc_cache *cache                                 OBJC2_UNAVAILABLE;    struct objc_protocol_list *protocols                     OBJC2_UNAVAILABLE;#endif} OBJC2_UNAVAILABLE;

objc_class 能够看到,一个运行时类中关联了它的父类指针、类名、成员变量、办法、缓存以及从属的协定。

其中 objc_ivar_listobjc_method_list 别离是成员变量列表和办法列表:

// 成员变量列表struct objc_ivar_list {    int ivar_count                                           OBJC2_UNAVAILABLE;#ifdef __LP64__    int space                                                OBJC2_UNAVAILABLE;#endif    /* variable length structure */    struct objc_ivar ivar_list[1]                            OBJC2_UNAVAILABLE;}                                                            OBJC2_UNAVAILABLE;// 办法列表struct objc_method_list {    struct objc_method_list *obsolete                        OBJC2_UNAVAILABLE;    int method_count                                         OBJC2_UNAVAILABLE;#ifdef __LP64__    int space                                                OBJC2_UNAVAILABLE;#endif    /* variable length structure */    struct objc_method method_list[1]                        OBJC2_UNAVAILABLE;}

Method

Method 代表类中某个办法的类型

typedef struct objc_method *Method;struct objc_method {    SEL method_name                                          OBJC2_UNAVAILABLE;    char *method_types                                       OBJC2_UNAVAILABLE;    IMP method_imp                                           OBJC2_UNAVAILABLE;}

objc_method 存储了办法名,办法类型和办法实现:

  • 办法名类型为 SEL
  • 办法类型 method_types 是个 char 指针,存储办法的参数类型和返回值类型
  • method_imp 指向了办法的实现,实质是一个函数指针

Ivar

Ivar 是示意成员变量的类型。

typedef struct objc_ivar *Ivar;struct objc_ivar {    char *ivar_name                                          OBJC2_UNAVAILABLE;    char *ivar_type                                          OBJC2_UNAVAILABLE;    int ivar_offset                                          OBJC2_UNAVAILABLE;#ifdef __LP64__    int space                                                OBJC2_UNAVAILABLE;#endif}

其中 ivar_offset 是基地址偏移字节

IMP

IMP在objc.h中的定义是:

typedef id (*IMP)(id, SEL, ...);

它就是一个函数指针,这是由编译器生成的。当你发动一个 ObjC 音讯之后,最终它会执行的那段代码,就是由这个函数指针指定的。而 IMP 这个函数指针就指向了这个办法的实现。

如果失去了执行某个实例某个办法的入口,咱们就能够绕开消息传递阶段,间接执行办法,这在前面 Cache 中会提到。

你会发现 IMP 指向的办法与 objc_msgSend 函数类型雷同,参数都蕴含 idSEL 类型。每个办法名都对应一个 SEL 类型的办法选择器,而每个实例对象中的 SEL 对应的办法实现必定是惟一的,通过一组 idSEL 参数就能确定惟一的办法实现地址。

而一个确定的办法也只有惟一的一组 idSEL 参数。

Cache

Cache 定义如下:

typedef struct objc_cache *Cachestruct objc_cache {    unsigned int mask /* total = mask + 1 */                 OBJC2_UNAVAILABLE;    unsigned int occupied                                    OBJC2_UNAVAILABLE;    Method buckets[1]                                        OBJC2_UNAVAILABLE;};

Cache 为办法调用的性能进行优化,每当实例对象接管到一个音讯时,它不会间接在 isa 指针指向的类的办法列表中遍历查找可能响应的办法,因为每次都要查找效率太低了,而是优先在 Cache 中查找。

Runtime 零碎会把被调用的办法存到 Cache 中,如果一个办法被调用,那么它有可能今后还会被调用,下次查找的时候就会效率更高。就像计算机组成原理中 CPU 绕过主存先拜访 Cache 一样。

Property

typedef struct objc_property *Property;typedef struct objc_property *objc_property_t;//这个更罕用

能够通过class_copyPropertyListprotocol_copyPropertyList 办法获取类和协定中的属性:

objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount)objc_property_t *protocol_copyPropertyList(Protocol *proto, unsigned int *outCount)
#import <Foundation/Foundation.h>@interface Person : NSObject/** 姓名 */@property (strong, nonatomic) NSString *name;/** age */@property (assign, nonatomic) int age;/** weight */@property (assign, nonatomic) double weight;@end

以上是一个 Person 类,有3个属性。让咱们用上述办法获取类的运行时属性。

    unsigned int outCount = 0;    objc_property_t *properties = class_copyPropertyList([Person class], &outCount);    NSLog(@"%d", outCount);    for (NSInteger i = 0; i < outCount; i++) {        NSString *name = @(property_getName(properties[i]));        NSString *attributes = @(property_getAttributes(properties[i]));        NSLog(@"%@--------%@", name, attributes);    }

音讯

一些 Runtime 术语讲完了,接下来就要说到音讯了。领会苹果官网文档中的 messages aren’t bound to method implementations until Runtime。音讯直到运行时才会与办法实现进行绑定。

这里要分明一点,objc_msgSend 办法看清来如同返回了数据,其实objc_msgSend 从不返回数据,而是你的办法在运行时实现被调用后才会返回数据。上面具体叙述音讯发送的步骤(如下图):

深刻代码了解instance、class object、metaclass

通过上图能够看出,一个实例对象`struct objc_object`的isa指针指向它的`struct objc_class`类对象,类对象的isa指针指向它的元类;`super_class`指针指向了父类的`类对象`,而`元类`的`super_class`指针指向了父类的`元类`。

runtime的利用

发送音讯

办法调用的实质,就是让对象发送音讯。objc\_msgSend,只有对象能力发送音讯,因而以objc结尾。应用音讯机制前提,必须导入#import <objc/message.h>音讯机制简略应用:
// 创立person对象    Person *p = [[Person alloc] init];    // 调用对象办法    [p eat];    // 实质:让对象发送音讯    objc_msgSend(p, @selector(eat));    // 调用类办法的形式:两种    // 第一种通过类名调用    [Person eat];    // 第二种通过类对象调用    [[Person class] eat];    // 用类名调用类办法,底层会主动把类名转换成类对象调用    // 实质:让类对象发送音讯    objc_msgSend([Person class], @selector(eat));
咱们能够通过clang来查看代码生成的CPP代码。例如:clang 将oc main.m文件转成c++ main\_cpp文件代码:  

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main_cpp.cpp

替换办法

替换两个办法的实现个别写在类的load办法外面,因为load办法会在程序运行前加载一次,而initialize办法会在类或者子类在 第一次应用的时候调用,当有分类的时候会调用屡次。
@implementation ViewController- (void)viewDidLoad {    [super viewDidLoad];    // Do any additional setup after loading the view, typically from a nib.    // 需要:给imageNamed办法提供性能,每次加载图片就判断下图片是否加载胜利。    // 步骤一:先搞个分类,定义一个能加载图片并且能打印的办法+ (instancetype)imageWithName:(NSString *)name;    // 步骤二:替换imageNamed和imageWithName的实现,就能调用imageWithName,间接调用imageWithName的实现。    UIImage *image = [UIImage imageNamed:@"123"];}@end@implementation UIImage (Image)// 加载分类到内存的时候调用+ (void)load{    // 替换办法    // 获取imageWithName办法地址    Method imageWithName = class_getClassMethod(self, @selector(imageWithName:));    // 获取imageWithName办法地址    Method imageName = class_getClassMethod(self, @selector(imageNamed:));    // 替换办法地址,相当于替换实现形式    method_exchangeImplementations(imageWithName, imageName);}// 不能在分类中重写零碎办法imageNamed,因为会把零碎的性能给笼罩掉,而且分类中不能调用super.// 既能加载图片又能打印+ (instancetype)imageWithName:(NSString *)name{    // 这里调用imageWithName,相当于调用imageName    UIImage *image = [self imageWithName:name];    if (image == nil) {        NSLog(@"加载空的图片");    }    return image;}@end

类/对象的关联对象

应用形式一:给分类增加属性
@implementation ViewController- (void)viewDidLoad {    [super viewDidLoad];    // Do any additional setup after loading the view, typically from a nib.    // 给零碎NSObject类动静增加属性name    NSObject *objc = [[NSObject alloc] init];    objc.name = @"小码哥";    NSLog(@"%@",objc.name);}@end// 定义关联的keystatic const char *key = "name";@implementation NSObject (Property)- (NSString *)name{    // 依据关联的key,获取关联的值。    return objc_getAssociatedObject(self, key);}- (void)setName:(NSString *)name{    // 第一个参数:给哪个对象增加关联    // 第二个参数:关联的key,通过这个key获取    // 第三个参数:关联的value    // 第四个参数:关联的策略    objc_setAssociatedObject(self, key, name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);}@end
应用形式二:给对象增加关联对象。
/** *  删除点击 *  @param recId        购物车ID */- (void)shopCartCell:(BSShopCartCell *)shopCartCell didDeleteClickedAtRecId:(NSString *)recId{    UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"" message:@"确认要删除这个宝贝" delegate:self cancelButtonTitle:@"勾销" otherButtonTitles:@"确定", nil];        // 传递多参数    objc_setAssociatedObject(alert, "suppliers_id", @"1", OBJC_ASSOCIATION_RETAIN_NONATOMIC);    objc_setAssociatedObject(alert, "warehouse_id", @"2", OBJC_ASSOCIATION_RETAIN_NONATOMIC);        alert.tag = [recId intValue];    [alert show];}/** *  确定删除操作 */- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex {    if (buttonIndex == 1) {                NSString *warehouse_id = objc_getAssociatedObject(alertView, "warehouse_id");        NSString *suppliers_id = objc_getAssociatedObject(alertView, "suppliers_id");        NSString *recId = [NSString stringWithFormat:@"%ld",(long)alertView.tag];    }}

 动静增加办法

简略应用:
@implementation ViewController- (void)viewDidLoad {    [super viewDidLoad];    // Do any additional setup after loading the view, typically from a nib.    Person *p = [[Person alloc] init];    // 默认person,没有实现eat办法,能够通过performSelector调用,然而会报错。    // 动静增加办法就不会报错    [p performSelector:@selector(eat)];}@end@implementation Person// void(*)()// 默认办法都有两个隐式参数,void eat(id self,SEL sel){    NSLog(@"%@ %@",self,NSStringFromSelector(sel));}// 当一个对象调用未实现的办法,会调用这个办法解决,并且会把对应的办法列表传过来.// 刚好能够用来判断,未实现的办法是不是咱们想要动静增加的办法+ (BOOL)resolveInstanceMethod:(SEL)sel{    if (sel == @selector(eat)) {        // 动静增加eat办法        // 第一个参数:给哪个类增加办法        // 第二个参数:增加办法的办法编号        // 第三个参数:增加办法的函数实现(函数地址)        // 第四个参数:函数的类型,(返回值+参数类型) v:void @:对象->self :示意SEL->_cmd        class_addMethod(self, @selector(eat), eat, "v@:");    }    return [super resolveInstanceMethod:sel];}@end

字典转模型KVC实现

// Ivar:成员变量 以下划线结尾// Property:属性+ (instancetype)modelWithDict:(NSDictionary *)dict{    id objc = [[self alloc] init];        // runtime:依据模型中属性,去字典中取出对应的value给模型属性赋值    // 1.获取模型中所有成员变量 key    // 获取哪个类的成员变量    // count:成员变量个数    unsigned int count = 0;    // 获取成员变量数组    Ivar *ivarList = class_copyIvarList(self, &count);        // 遍历所有成员变量    for (int i = 0; i < count; i++) {        // 获取成员变量        Ivar ivar = ivarList[i];                // 获取成员变量名字        NSString *ivarName = [NSString stringWithUTF8String:ivar_getName(ivar)];        // 获取成员变量类型        NSString *ivarType = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)];        // @\"User\" -> User        ivarType = [ivarType stringByReplacingOccurrencesOfString:@"\"" withString:@""];        ivarType = [ivarType stringByReplacingOccurrencesOfString:@"@" withString:@""];        // 获取key        NSString *key = [ivarName substringFromIndex:1];                // 去字典中查找对应value        // key:user  value:NSDictionary                id value = dict[key];                // 二级转换:判断下value是否是字典,如果是,字典转换层对应的模型        // 并且是自定义对象才须要转换        if ([value isKindOfClass:[NSDictionary class]] && ![ivarType hasPrefix:@"NS"]) {            // 字典转换成模型 userDict => User模型            // 转换成哪个模型            // 获取类            Class modelClass = NSClassFromString(ivarType);                        value = [modelClass modelWithDict:value];        }                // 给模型中属性赋值        if (value) {            [objc setValue:value forKey:key];        }    }            return objc;}

+ load 和 + initialize 原理解说

+load 总结

  • load 办法调用在main之前,并且不须要咱们初始化,程序启动就会把所有文件加载
  • 主类的调用优先于分类,分类的调动优先于以后类优先于分类
  • 主类和分类的调用程序跟编译程序无关
  • 分类之间加载,也就是平级之前加载取决于编译程序,谁先编译就先加载谁

注意事项

1.咱们发现。load 的加载比main 还要早,所以如果咱们再load办法外面做了耗时的操作,那么肯定会影响程序的启动工夫,所以在load外面肯定不要写耗时的代码。
2.不要在load外面取加载对象,因为咱们再load调用的时候基本就不确定咱们的对象是否曾经初始化了,所以不要去做对象的初始化

调用程序延长(category)

分类中的同名办法,源码中是依照逆序加载的,也就是说后编译的分类办法会笼罩后面所有的同名的办法,分类还有一个个性就是,不论把申明写在主类还是分类,只有分类中实现了就能够找到。

+ initialize

+initialize实质为objc/_msgSend,如果子类没有实现initialize则会去父类查找,如果分类中实现,那么会笼罩主类,和runtime音讯转发逻辑一样

initialize总结

1.initialize 会在类第一次接管到音讯的时候调用
2.先调用父类的 initialize,而后调用子类。
3.initialize 是通过 objc_msgSend 调用的
4.如果子类没有实现 initialize,会调用父类的initialize(父类可能被调用屡次)
5.如果分类实现了initialize,会笼罩本类的initialize办法