由于objective-c是一门动态语言,也就是说只有编译器是不够的,还须要一个运行时系统(runtime system)来执行编译后的代码。这是整个objective-c运行框架的一块基石。 runtime简称运行时。其中最主要的就是消息机制。对于编译期语言,会在编译的时候决定调用哪一个函数。对于OC的函数,是动态调用的,在编译的时候并不能决定真正调用哪一个函数,只有在运行时才会根据函数的名称找到对应的函数来调用。
Objc 在三种层面上与 Runtime 系统进行交互: 1. 经过 Objective-C 源代码 2. 经过 Foundation 框架的 NSObject 类定义的方法 3. 经过对 Runtime 库函数的直接调用
苹果和GNU各自维护一个开源的runtime版本,这两个版本之间都在努力的保持一致。
都是运行时的头文件,其中主要使用的函数定义在message.h和runtime.h这两个文件中。
Cocoa 程序中绝大部分类都是 NSObject 类的子类,因此都继承了 NSObject 的行为。(NSProxy 类是个例外,它是个抽象超类)
-class
方法返回对象的类;-isKindOfClass:
和 -isMemberOfClass:
方法检查对象是否存在于指定的类的继承体系中(是不是其子类或者父类或者当前类的成员变量);-respondsToSelector:
检查对象可否响应指定的消息;-conformsToProtocol:
检查对象是否实现了指定协议类的方法;-methodForSelector:
返回指定方法实现的地址。Runtime 系统是具备公共接口的动态共享库。头文件存放于/usr/include/objc目录下,这意味着咱们使用时只须要引入objc/Runtime.h
头文件便可。c++
许多函数可让你使用纯 C 代码来实现 Objc 中一样的功能。除非是写一些 Objc 与其余语言的桥接或是底层的 debug 工做,你在写 Objc 代码时通常不会用到这些 C 语言函数。对于公共接口都有哪些,后面会讲到。我将会参考苹果官方的 API 文档。objective-c
它是selector
在 Objc 中的表示(Swift 中是 Selector 类)。selector 是方法选择器,其实做用就和名字同样,平常生活中,咱们经过人名辨别谁是谁,注意 Objc 在相同的类中不会有命名相同的两个方法。selector 对方法名进行包装,以便找到对应的方法实现。它的数据结构是:数组
typedef struct objc_selector *SEL;
咱们能够看出它是个映射到方法的 C 字符串,你能够经过 Objc 编译器器命令@selector()
或者 Runtime 系统的 sel_registerName
函数来获取一个 SEL
类型的方法选择器。缓存
id 是一个参数类型,它是指向某个类的实例的指针。定义以下:数据结构
typedef struct objc_object *id; struct objc_object { Class isa; };
以上定义,看到 objc_object
结构体包含一个 isa 指针,根据 isa 指针就能够找到对象所属的类。框架
typedef struct objc_class *Class;
Class
实际上是指向 objc_class
结构体的指针。objc_class
的数据结构以下:iphone
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_list
和 objc_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 表明类中某个方法的类型优化
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
是表示成员变量的类型。
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在objc.h中的定义是:
typedef id (*IMP)(id, SEL, ...);
它就是一个函数指针,这是由编译器生成的。当你发起一个 ObjC 消息以后,最终它会执行的那段代码,就是由这个函数指针指定的。而 IMP
这个函数指针就指向了这个方法的实现。
若是获得了执行某个实例某个方法的入口,咱们就能够绕开消息传递阶段,直接执行方法,这在后面 Cache
中会提到。
你会发现 IMP
指向的方法与 objc_msgSend
函数类型相同,参数都包含 id
和 SEL
类型。每一个方法名都对应一个 SEL
类型的方法选择器,而每一个实例对象中的 SEL
对应的方法实现确定是惟一的,经过一组 id
和 SEL
参数就能肯定惟一的方法实现地址。
而一个肯定的方法也只有惟一的一组 id
和 SEL
参数。
Cache 定义以下:
typedef struct objc_cache *Cache struct 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 同样。
typedef struct objc_property *Property; typedef struct objc_property *objc_property_t;//这个更经常使用
能够经过class_copyPropertyList
和 protocol_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
从不返回数据,而是你的方法在运行时实现被调用后才会返回数据。下面详细叙述消息发送的步骤(以下图):
经过上图能够看出,一个实例对象`struct objc_object`的isa指针指向它的`struct objc_class`类对象,类对象的isa指针指向它的元类;`super_class`指针指向了父类的`类对象`,而`元类`的`super_class`指针指向了父类的`元类`。
方法调用的本质,就是让对象发送消息。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 // 定义关联的key static 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
// 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; }
1.咱们发现。load 的加载比main 还要早,因此若是咱们再load方法里面作了耗时的操做,那么必定会影响程序的启动时间,因此在load里面必定不要写耗时的代码。
2.不要在load里面取加载对象,由于咱们再load调用的时候根本就不肯定咱们的对象是否已经初始化了,因此不要去作对象的初始化
分类中的同名方法,源码中是按照逆序加载的,也就是说后编译的分类方法会覆盖前面全部的同名的方法,分类还有一个特性就是,无论把声明写在主类仍是分类,只要分类中实现了就能够找到。
+initialize本质为objc/_msgSend,若是子类没有实现initialize则会去父类查找,若是分类中实现,那么会覆盖主类,和runtime消息转发逻辑同样
1.initialize 会在类第一次接收到消息的时候调用 2.先调用父类的 initialize,而后调用子类。 3.initialize 是经过 objc_msgSend 调用的 4.若是子类没有实现 initialize,会调用父类的initialize(父类可能被调用屡次) 5.若是分类实现了initialize,会覆盖本类的initialize方法