详解
静态类型语言与动态类型语言
-
静态类型语言:变量的数据类型在编译时就可以确定的语言,多数静态类型的语言要求在使用变量之前必须声明类型。函数的调用在编译的时候会决定调用哪个函数。C,C++,Java,C# 都属于静态类型语言。
-
动态类型语言:变量的数据类型在运行时确定的语言,变量在使用之前不需要变量类型声明,通常的变量类型是被赋值的那个变量的类型。Python,ruby,OC,js这些都是动态类型的语言。
OC是一门动态语言,它不仅需要一个编译器,也需要一个运行时系统,其幕后最大的功臣就是runtime,runtime是基于C和混编来写的,其中最主要的是消息机制。在编译的时候并不能决定真正调用哪个函数,只有在真正运行的时候才会根据函数的名称找到对应的函数来调用。
Runtime相关的头文件
usr/include/objc文件夹下面有这样几个文件 主要使用的函数定义在message.h和runtime.h这两个文件中,在message.h中主要包含了一些向对象发送消息的函数,这是OC对象方法调用的底层实现。runtime.h是运行时最重要的文件,其中包含了对运行时进行操作的方法。 使用时,需要导入文件,导入如:
#import <objc/runtime.h>
#import <objc/message.h>
对象(object),类(class),方法(method)这几个的结构体:
对象
它的isa
指针指向类对象,
struct objc_object {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
};
类
它实际上是一个指向objc_class
结构体的指针,第一个成员变量也是isa
指针,这就说明了Class
本身其实也是一个对象,因此我们称之为类对象,类对象在编译期产生用于创建实例对象,是单例。
struct objc_class {
// `isa`指针 类对象的`isa`指针指向元类(`metaclass`),元类中保存了创建类对象以及类方法所需的所有信息
元类的`isa`指针又指向了自己。
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
//父类的指针 `super_class`指针指向了父类的类对象,
元类的`super_class`指针指向了父类的元类
Class _Nullable super_class OBJC2_UNAVAILABLE;
// 类的名字
const char * _Nonnull name OBJC2_UNAVAILABLE;
// 版本
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
// 实例大小
long instance_size OBJC2_UNAVAILABLE;
// 实例变量列表
struct objc_ivar_list * _Nullable ivars OBJC2_UNAVAILABLE;
// 方法列表
struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE;
// 缓存
struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE;
// 遵守的协议列表
struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
#### 方法列表
struct objc_method_list {
struct objc_method_list * _Nullable 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;
}
#### 方法
struct objc_method {
// 方法名
SEL _Nonnull method_name OBJC2_UNAVAILABLE;
// 方法类型
char * _Nullable method_types OBJC2_UNAVAILABLE;
// 方法实现
IMP _Nonnull method_imp OBJC2_UNAVAILABLE;
}
函数的定义
- 对对象进行操作的方法一般以
object_
开头 - 对类进行操作的方法一般以
class_
开头 - 对类或对象的方法进行操作的方法一般以
method_
开头 - 对成员变量进行操作的方法一般以
ivar_
开头 - 对属性进行操作的方法一般以
property_开头
开头 - 对协议进行操作的方法一般以
protocol_
开头
类缓存(objc_cache)
当Objective-C
运行时通过跟踪它的isa
指针检查对象时,它可以找到一个实现许多方法的对象。然而,你可能只调用它们的一小部分,并且每次查找时,搜索所有选择器的类分派表没有意义。所以类实现一个缓存,每当你搜索一个类分派表,并找到相应的选择器,它把它放入它的缓存。所以当objc_msgSend
查找一个类的选择器,它首先搜索类缓存。这是基于这样的理论:如果你在类上调用一个消息,你可能以后再次调用该消息。
为了加速消息分发, 系统会对方法和对应的地址进行缓存,就放在上述的objc_cache
,所以在实际运行中,大部分常用的方法都是会被缓存起来的,Runtime
系统实际上非常快,接近直接执行内存地址的程序速度。
分类(objc_category)
从定义中看出,category
可以:添加实例方法(instanceMethods),类方法(classMethods),协议(protocols)和实例属性(instanceProperties)。不可以:不能够添加实例变量
虽然category可以添加实例属性@property,但是它既不会生成新的实例变量,也不会生成相应的setter、getter方法。也就是添加了其实也没啥作用。在编译的时候调用是没有问题的,编译器不会报错;但是在运行时的时候就会崩溃。因为找不到getter/setter方法。想使用就需要自己去关联对象实现,关联对象也是不会自动生成新的实例变量的,这是因为在clas里的Ivarlist、IvarLayout大小在编译器都确定了,不能修改。
Category
是表示一个指向分类的结构体的指针,其定义如下:
struct category_t {
// 是指 class_name 而不是 category_name。
const char *name;
// 要扩展的类对象,编译期间是不会定义的,而是在Runtime阶段通过name对 应到对应的类对象。
classref_t cls;
// 实例方法
struct method_list_t *instanceMethods;
// 类方法
struct method_list_t *classMethods;
// 协议
struct protocol_list_t *protocols;
// 属性
struct property_list_t *instanceProperties;
};
使用场景
runtime消息转发
当我们调用一个方法时,其实就是给对象发送一个消息,runtime会在相关的类对象中搜索方法列表,如果找不到则会沿着继承树向上一直搜索知道继承树根部(通常为NSObject
),如果还没有找到,runtime会给最后的三次补救机会。如果未做补救那就会报unrecognized selector
错
- 动态方法解析
- 指定对象去执行
- 消息重定向,这个消息转发出去
Runtime应用
Runtime
简直就是做大型框架的利器。它的应用场景非常多,下面就介绍一些常见的应用场景。
- 关联对象(Objective-C Associated Objects)给分类增加属性
@property (nonatomic, strong, nullable) UIImage *mt_emptyDataImag;
static char const * const kEmptyDataImag = "mt_emptyDataImag";
- (UIImage *)mt_emptyDataImag
{
return objc_getAssociatedObject(self, kEmptyDataImag);
}
- (void)setMt_emptyDataImag:(UIImage *)mt_emptyDataImag
{
objc_setAssociatedObject(self, kEmptyDataImag, mt_emptyDataImag, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- 黑魔法(Method Swizzling)方法替换(NSArray越界处理,URL转义处理,VC全屏present处理,埋点等)
@implementation UIViewController (FullModal)
+ (void)load{
SEL originalSel = @selector(presentViewController:animated:completion:);
SEL overrideSel = @selector(override_presentViewController:animated:completion:);
Method originalMet = class_getInstanceMethod(self.class, originalSel);
Method overrideMet = class_getInstanceMethod(self.class, overrideSel);
method_exchangeImplementations(originalMet, overrideMet);
}
#pragma mark - Swizzling
- (void)override_presentViewController:(UIViewController *)viewControllerToPresent animated: (BOOL)flag completion:(void (^ __nullable)(void))completion{
if(@available(iOS 13.0, *)){
if (viewControllerToPresent.modalPresentationStyle == UIModalPresentationPageSheet){
viewControllerToPresent.modalPresentationStyle = UIModalPresentationFullScreen;
}
}
[self override_presentViewController:viewControllerToPresent animated:flag completion:completion];
}
- 消息转发处理crash
// 动态解析
+ (BOOL)resolveInstanceMethod:(SEL)sel
// 指定对象执行
- (id)forwardingTargetForSelector:(SEL)aSelector
//重定向转发
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
- (void)forwardInvocation:(NSInvocation *)anInvocation
- 实现NSCoding的自动归档和自动解档
- (id)initWithCoder:(NSCoder *)aDecoder
{
if (self = [super init]) {
unsigned int count = 0;
Ivar *ivars = class_copyIvarList([self class], &count);
for (int i = 0; i < count; i++) {
Ivar ivar = ivars[i];
const char *name = ivar_getName(ivar);
NSString *key = [NSString stringWithUTF8String:name];
id value = [aDecoder decodeObjectForKey:key];
[self setValue:value forKey:key];
}
free(ivars);
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)aCoder
{
unsigned int count = 0;
Ivar *ivars = class_copyIvarList([self class], &count);
for (int i = 0; i < count; i++) {
Ivar ivar = ivars[i];
const char *name = ivar_getName(ivar);
NSString *key = [NSString stringWithUTF8String:name];
id value = [self valueForKey:key];
[aCoder encodeObject:value forKey:key];
}
free(ivars);
}
- 实现字典和模型的自动转换(MJExtension)