前言
如果你在浏览器中搜索Runtime
会出现多如牛毛文章在介绍它,但是大多数文章都是讲的Runtime
有多少用法云云...,也就是说单单是告诉你如何调用API
,能达到什么效果。很多大神都会告诉你iOS
进阶,Runtime
学习是必备的,但是我们进阶就是为了知道如何调用API
吗?当然不是,我们知道OC是一门面向对象编程的语言,无时无刻不在和Class(类)
,Object(实例对象)
打交道,然而类中又包含了Ivar(成员变量)
,Method(方法)
,Protocol(协议)
,所以这遍文章就要来揭一揭Runtime
与它们之间的面纱。
本篇文章目录
一. Runtime简介
二. Class/Category/Extension(类/分类/扩展)
2.1、 Class的结构
2.2、Runtime中对类Class的操作
三. Object (对象)是如何初始化的?
3.1 + alloc方法分析
3.2 - init方法分析
3.3 对象操作相关的Runtime API
四. Ivar/Property(成员变量/属性)
4.1 property组成
4.2@synthesize和@dynamic的作用
4.3@synthesize的使用场景
4.4@property中有哪些属性关键字
4.5 Ivar/Property在runtime中的一些应用
4.6 Category中使用Property
五. Method/SEL/IMP (方法/方法名称/方法实现)
5.1 Method结构
5.2 消息(Message)的发送
5.3 消息(Message)的转发
5.4 Runtime相关的操作
六. Protocol(协议)
6.1 协议是什么?
6.2 如何写一个协议
6.3 协议中的方法由谁来实现
6.4协议的作用
6.5 Runtime中关于Protocol的API
一.Runtime简介
Objective-C 是基于 C 的,它为 C 添加了面向对象的特性。它将很多静态语言在编译和链接时期做的事放到了 runtime 运行时来处理,可以说 runtime 是我们 Objective-C 幕后工作者。(Objective-c面向对象, 在运行时处理其他语言编译和链接时期做的事),OC中对象的类型和对象所执行的方法都是在运行时阶段进行查找并确认的,这种机制被称为动态绑定。
二.Class介绍
本文过长,关于Category/Extension
的介绍放在深入理解Objective-C:Category
2.1Class
类的结构
我们都知道OC中NSObject是大部分类的基类,而在NSObject头文件中的结构如下:
NSObject.png@interface NSObject <NSObject> {
Class isa OBJC_ISA_AVAILABILITY;
}
每一个继承自NSObject
的类都包含Class isa
的成员变量,这个isa
的类为Class
的结构如下:
typedef struct objc_class *Class;
Class
是一个objc_class
的结构体:
在Objc2.0
之前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;
Objc2.0
之后objc_class
结构:
struct objc_object {
private:
isa_t isa;
}
struct objc_class : objc_object {
Class superclass;
cache_t cache; // formerly cache pointer and vtable
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
}
union isa_t {
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls;
uintptr_t bits;
}
Objc2.0
之后objc_class
的结构体继承自objc_object
,事实上Object(实例对象)
比如:Student *Bob
Bob这个学生对象就是一个objc_object
结构体,而我们常用的id
对象也是objc_object
结构体
typedef struct objc_object *id;
那么我们可以理解为我们所说的类就是objc_class
结构体,实例对象就是objc_object
结构体,而objc_class
继承自objc_object
,那么类也是一个对象。
objc_class
包含以下结构:
- 1.
isa_t isa
继承自objc_object
- 2.
Class superclass
- 3.
cache_t cache
- 4.
class_data_bits_t bits
1.isa_t isa
指向元类的指针
2.Class superclass
指向当前类的父类
对应关系的图如下图,下图很好的描述了对象,类,元类之间的关系:
图中实线是 super_class
指针,虚线是isa
指针。
对象的 isa
指向当前类,类中存储了对象的所有实例方法,成员变量等,类的 isa
指向meta-class(元类)
,meta-class
存储着一个类的所有类方法。Meta class
的isa
指针都指向Root class (meta)
即NSObject
的Meta class
元类,Root class (meta)
的 isa
指向了自己。
对象的实例方法调用时,通过对象的
isa
找到对应的类,再从类的class_data_bits_t bits
中查找对应的方法。
类对象的类方法调用时,通过类的isa
找到对应的元类,再从元类的class_data_bits_t bits
中查找对应的方法。
Root class (class)
其实就是NSObject
,NSObject
是没有超类的,所以Root class(class)
的superclass
指向nil
。
Root class(meta)
的superclass
指向Root class(class)
,也就是NSObject
,形成一个回路。
从 NSObject 的初始化了解 isa可以让你深入了解isa
3.cache_t cache
缓存经常调用的方法,当调用方法时,优先在Cache查找,如果没有找到,再到methodLists查找
4.class_data_bits_t bits
存储类的方法、属性和遵循的协议等信息的地方
深入解析 ObjC 中方法的结构深入了解class_data_bits_t
以上就是关于类Class
的结构介绍,接下来说一说在Runtime
中对类的一些操作。
2.2Runtime
中对类Class
的操作
runtime
中类的操作方法大部分是以class_
为前缀,比如
class_getProperty
,class_getClassMethod
等。
类名Class Name
// 获取类名
const char *class_getName(Class cls)
父类super_class
// 获取类的父类
Class class_getSuperclass ( Class cls );
元类meta_class
// 判断给定的Class是否是一个元类
BOOL class_isMetaClass ( Class cls );
成员变量Ivar
// 获取类中指定名称实例成员变量的信息
Ivar class_getInstanceVariable ( Class cls, const char *name );
// 获取类成员变量的信息
Ivar class_getClassVariable ( Class cls, const char *name );
// 获取整个成员变量列表
Ivar * class_copyIvarList ( Class cls, unsigned int *outCount );
// 添加成员变量
BOOL class_addIvar ( Class cls, const char *name, size_t size, uint8_t alignment, const char *types );
1.class_copyIvarList
可以获得成员变量数组,数组中的成员变量信息都是objc_ivar
结构体的指针。outCount
指针返回数组的大小。在结束时须使用free()
来释放数组。
2.class_addIvar
需要注意的是OC不支持向已存在的类中添加Ivar
实例变量,因此除非通过运行时来创建的类,其他类中都不能添加Ivar
,在运行时添加Ivar
,class_addIvar
方法只能在objc_allocateClassPair
函数与objc_registerClassPair
之间调用。
举个栗子🌰:
unsigned int outCount;
Ivar * ivarList = class_copyIvarList([UIView class], &outCount);
for (int i = 0; i < outCount; i++) {
Ivar ivar = ivarList[i];
NSLog(@"%s",ivar_getName(ivar));
NSLog(@"%@",[NSString stringWithUTF8String:ivar_getName(ivar)]);
}
free(ivarList);
2017-07-19 16:07:06.385 Runtime[3016:281958] _constraintsExceptingSubviewAutoresizingConstraints
2017-07-19 16:07:06.385 Runtime[3016:281958] _cachedTraitCollection
2017-07-19 16:07:06.386 Runtime[3016:281958] _layer
2017-07-19 16:07:06.386 Runtime[3016:281958] _layerRetained
2017-07-19 16:07:06.386 Runtime[3016:281958] _gestureInfo
2017-07-19 16:07:06.386 Runtime[3016:281958] _gestureRecognizers
2017-07-19 16:07:06.387 Runtime[3016:281958] _window
2017-07-19 16:07:06.387 Runtime[3016:281958] _subviewCache
2017-07-19 16:07:06.387 Runtime[3016:281958] _templateLayoutView
2017-07-19 16:07:06.387 Runtime[3016:281958] _charge
2017-07-19 16:07:06.387 Runtime[3016:281958] _tag
2017-07-19 16:07:06.388 Runtime[3016:281958] _viewDelegate
.........
属性Property
// 获取指定的属性
objc_property_t class_getProperty ( Class cls, const char *name );
// 获取属性列表
objc_property_t * class_copyPropertyList ( Class cls, unsigned int *outCount );
// 为类添加属性
BOOL class_addProperty ( Class cls, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount );
// 替换类的属性
void class_replaceProperty ( Class cls, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount );
方法Method
// 添加方法
BOOL class_addMethod ( Class cls, SEL name, IMP imp, const char *types );
// 获取实例方法
Method class_getInstanceMethod ( Class cls, SEL name );
// 获取类方法
Method class_getClassMethod ( Class cls, SEL name );
// 获取所有方法的数组
Method * class_copyMethodList ( Class cls, unsigned int *outCount );
// 替代方法的实现
IMP class_replaceMethod ( Class cls, SEL name, IMP imp, const char *types );
// 返回方法的具体实现
IMP class_getMethodImplementation ( Class cls, SEL name );
IMP class_getMethodImplementation_stret ( Class cls, SEL name );
// 类实例是否响应指定的selector
BOOL class_respondsToSelector ( Class cls, SEL sel );
class_addMethod
的实现会覆盖父类的方法实现,但不会取代本类中已存在的实现,如果本类中包含一个同名的实现,则函数会返回NO。如果要修改已存在实现,可以使用class_replaceMethod
或method_setImplementation
。
举两个栗子🌰🌰:
Method *methodList = class_copyMethodList([UIView class], &outCount);
for (int i = 0; i < outCount; i++) {
Method method = methodList[i];
NSLog(@"%@",NSStringFromSelector( method_getName(method)));
}
free(methodList);
2017-07-19 16:07:06.831 Runtime[3016:281958] convertPoint:fromView:
2017-07-19 16:07:06.831 Runtime[3016:281958] subviews
2017-07-19 16:07:06.832 Runtime[3016:281958] setOpaque:
2017-07-19 16:07:06.832 Runtime[3016:281958] addGestureRecognizer:
2017-07-19 16:07:06.832 Runtime[3016:281958] removeGestureRecognizer:
2017-07-19 16:07:06.832 Runtime[3016:281958] addSubview:
2017-07-19 16:07:06.833 Runtime[3016:281958] sizeThatFits:
2017-07-19 16:07:06.833 Runtime[3016:281958] nextResponder
2017-07-19 16:07:06.833 Runtime[3016:281958] becomeFirstResponder
2017-07-19 16:07:06.834 Runtime[3016:281958] convertRect:fromView:
2017-07-19 16:07:06.834 Runtime[3016:281958] convertPoint:toView:
2017-07-19 16:07:06.835 Runtime[3016:281958] drawRect:
2017-07-19 16:07:06.835 Runtime[3016:281958] setFrameOrigin:
2017-07-19 16:07:06.835 Runtime[3016:281958] isHiddenOrHasHiddenAncestor
2017-07-19 16:07:06.836 Runtime[3016:281958] willRemoveSubview:
2017-07-19 16:07:06.836 Runtime[3016:281958] setAutoresizingMask:
2017-07-19 16:07:06.837 Runtime[3016:281958] charge
2017-07-19 16:07:06.837 Runtime[3016:281958] setCharge:
2017-07-19 16:07:06.838 Runtime[3016:281958] origin
2017-07-19 16:07:06.838 Runtime[3016:281958] setOrigin:
......
@interface Bob : NSObject
@end
@implementation Bob
@end
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
// 将要添加method实例方法的SEL
SEL methodSEL = @selector(name);
// 根据SEL获取Method
Method methodName = class_getInstanceMethod([self class], methodSEL);
// 根据SEL获取方法(Method)的实现
IMP methodIMP = class_getMethodImplementation([self class], methodSEL);
// Method Encoding
const char *methodEncoding = method_getTypeEncoding(methodName);
// 1.当类中没有name方法时添加方法
BOOL isSuccess = class_addMethod([Bob class], methodSEL, methodIMP, methodEncoding);
// 2.当类中有name方法时,使用class_replaceMethod
//IMP replacedMethodIMP = class_replaceMethod([Bob class], methodSEL, methodIMP, methodEncoding);
// 3.当类中有name方法时,使用method_setImplementation
//Method BobMethodName = class_getInstanceMethod([Bob class], methodSEL);
//IMP BobMethodIMP = class_getMethodImplementation([Bob class], methodSEL);
method_setImplementation(BobMethodName, methodIMP);
if (isSuccess) {
[[Bob new] performSelector:@selector(name)];
}
}
- (void)name{
NSLog(@"My name is Bob");
}
@end
// 三种方法都可以实现,打印如下
Runtime[2779:265550] My name is Bob
协议protocol
// 添加协议
BOOL class_addProtocol ( Class cls, Protocol *protocol );
// 返回类是否实现指定的协议
BOOL class_conformsToProtocol ( Class cls, Protocol *protocol );
// 返回类实现的协议列表
Protocol * class_copyProtocolList ( Class cls, unsigned int *outCount );
对象Object
创建
id class_createInstance(Class cls, size_t extraBytes)
把对象Object
创建放在最后是因为接下来要说Object
对象,当然对象的创建也是必须要说的。
三. Object 对象
关于对象在Class
中已经基本介绍清楚了,我们常见的对象是一个objc_object
结构体
struct objc_object {
private:
isa_t isa;
}
isa
指向对象Object
所属的类Class
,实例对象Object
的方法,成员变量都保存在对象的类Class
中.
那么问题来了,对象的创建[[NSObject alloc] init]
是如何完成的呢?
3.1 + alloc
方法分析
+ alloc
的实现
+ (id)alloc {
return _objc_rootAlloc(self);
}
// alloc 方法的实现真的是非常的简单, 它直接调用了另一个私有方法 id _objc_rootAlloc(Class cls)
id _objc_rootAlloc(Class cls) {
return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/);
}
static id callAlloc(Class cls, bool checkNil, bool allocWithZone=false) {
id obj = class_createInstance(cls, 0);
return obj;
}
id class_createInstance(Class cls, size_t extraBytes) {
return _class_createInstanceFromZone(cls, extraBytes, nil);
}
// 对象初始化中最重要的操作都在 _class_createInstanceFromZone 方法中执行:
static id _class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone, bool cxxConstruct = true, size_t *outAllocatedSize = nil) {
size_t size = cls->instanceSize(extraBytes);
id obj = (id)calloc(1, size);
if (!obj) return nil;
obj->initInstanceIsa(cls, hasCxxDtor);
return obj;
}
// 在使用 calloc 为对象分配一块内存空间之前,我们要先获取对象在内存的大小:
size_t instanceSize(size_t extraBytes) {
size_t size = alignedInstanceSize() + extraBytes;
if (size < 16) size = 16;
return size;
}
uint32_t alignedInstanceSize() {
return word_align(unalignedInstanceSize());
}
//实例大小 instanceSize 会存储在类的 isa_t 结构体中,然后经过对齐最后返回,在获取对象大小之后,直接调用 calloc 函数就可以为对象分配内存空间了
uint32_t unalignedInstanceSize() {
assert(isRealized());
return data()->ro->instanceSize;
}
isa
的初始化
// 在对象的初始化过程中除了使用 calloc 来分配内存之外,还需要根据类初始化 isa_t 结构体:
inline void objc_object::initIsa(Class cls, bool indexed, bool hasCxxDtor) {
if (!indexed) {
isa.cls = cls;
} else {
isa.bits = ISA_MAGIC_VALUE;
isa.has_cxx_dtor = hasCxxDtor;
isa.shiftcls = (uintptr_t)cls >> 3;
}
}
3.2 - init
方法分析
// NSObject 的 - init 方法只是调用了 _objc_rootInit 并返回了当前对象:
- (id)init {
return _objc_rootInit(self);
}
id _objc_rootInit(id obj) {
return obj;
}
总的来说,在 iOS 中一个对象的初始化过程只是分配内存空间、然后初始化 isa_t
结构体。
3.3 对象操作相关的Runtime API
/************** object_ *************/
// 获取对象的类名
const char * object_getClassName ( id obj );
// 拷贝一个对象并返回拷贝好的对象
id object_copy(id obj, size_t size)
// 释放指定对象占用的内存
id object_dispose(id obj)
// 获取对象的类
Class object_getClass(id obj)
// 设置对象的类
Class object_setClass(id obj, Class cls)
// 判断某个对象是不是一个类
BOOL object_isClass(id obj)
// 获取指定对象,指定实例变量的值(对象)
id object_getIvar(id obj, Ivar ivar)
// 设置对象中实例变量的值
void object_setIvar(id obj, Ivar ivar, id value)
// 修改类实例的实例变量的值
Ivar object_setInstanceVariable(id obj, const char *name, void *value)
// 获取对象实例变量的值
Ivar object_getInstanceVariable(id obj, const char *name, void **outValue)
四. Ivar/Property
成员变量/属性
4.1 property
组成
Ivar
成员变量与Property
属性的关系如下:
@property = ivar + getter + setter
即属性property
是由成员变量ivar
和getter 、setter
存取方法组成的。
Objective-C
对象通常会把其所需要的数据保存为各种实例变量。实例变量一般通过“存取方法”(access method
)来访问。其中,“获取方法” (getter
)用于读取变量值,而“设置方法” (setter
)用于写入变量值。这个概念已经定型,并且经由“属性”这一特性而成为Objective-C 2.0
的一部分。 而在正规的Objective-C
编码风格中,存取方法有着严格的命名规范。 正因为有了这种严格的命名规范,所以 Objective-C
这门语言才能根据名称自动创建出存取方法。
property
通过自动合成(autosynthesis
)来生成ivar、getter 、setter
并添加到类中。
完成属性定义后,编译器会自动编写访问这些属性所需的方法,此过程叫做“自动合成”( autosynthesis
)。需要强调的是,这个过程由编译 器在编译期执行,所以编辑器里看不到这些“合成方法”(synthesized method
)的源代码。除了生成方法代码 getter、setter
之外,编译器还要自动向类中添加适当类型的实例变量,并且在属性名前面加下划线,以此作为实例变量的名字。也可以在类的实现代码里通过 @synthesize
语法来指定实例变量的名字。
@implementation Bob
@synthesize bookName = _BobBookName;
@synthesize age = _BobAge;
@end
4.2@synthesize
和@dynamic
的作用
1)@property
有两个对应的词,一个是@synthesize
,一个是@dynamic
。如果@synthesize
和@dynamic
都没写,那么默认的就是@syntheszie var = _var;
2)@synthesize
的语义是如果你没有手动实现setter
方法和getter
方法,那么编译器会自动为你加上这两个方法。
3)@dynamic
告诉编译器:属性的setter
与getter
方法由用户自己实现,不自动生成。(当然对于readonly
的属性只需提供getter
即可)。假如一个属性被声明为@dynamic var
,然后你没有提供@setter
方法和@getter
方法,编译的时候没问题,但是当程序运行到instance.var = someVar
,由于缺setter
方法会导致程序崩溃;或者当运行到 someVar = var
时,由于缺getter
方法同样会导致崩溃。编译时没问题,运行时才执行相应的方法,这就是所谓的动态绑定。
4.3 有了autosynthesis
(自动合成),@synthesize
的使用场景
@synthesize
只有在不会自动合成的时候使用,以下情况@property
不会自动合成:
- 1.同时重写了
setter
和getter
时 - 2.重写了只读属性的
getter
时 - 3.使用了
@dynamic
时 - 4.在
@protocol
中定义的所有属性 - 5.在
category
中定义的所有属性 - 6.在子类中重载了父类中的属性
除了后三条,对其他几个我们可以总结出一个规律:当你想手动管理@property
的所有内容时,你就会尝试通过实现@property
的所有“存取方法”)或者使用@dynamic
来达到这个目的,这时编译器就会认为你打算手动管理@property
,于是编译器就禁用了autosynthesis
(自动合成)。
因为有了autosynthesis
(自动合成),大部分开发者已经习惯不去手动定义ivar,而是依赖于autosynthesis
(自动合成),但是一旦你需要使用ivar
,而autosynthesis
(自动合成)又失效了,如果不去手动定义ivar
,那么你就得借助@synthesize
来手动合成ivar
。
4.4 @property
中有哪些属性关键字
1.原子、非原子性(nonatomic
、 atomic
)
2.读写、只读(readwrite
、readonly
)
3.内存管理(assign、strong、 weak、unsafe_unretained、copy
)
4.方法名(@property (nonatomic, getter=isNewVersion) BOOL newVersion;
)
5.是否可以为空 (nonnull,null_resettable,nullable
)
在默认情况下,由编译器合成的方法会通过锁定机制确保其原子性(atomic
), nonatomic
则不使用同步锁,但是需要注意的是atomic
只能保证存数据的set
方法加锁,并不能保证直接使用成员变量取值的安全性。
copy
关键字使用在block、NSString、NSArray、NSDictionary
等中,block
中的copy
将block
由栈中拷贝到堆中,而NSString、NSArray、NSDictionary
中的copy
可以保证数据不被更改,因为你不能保证传入的是一个可变的数据(NSMutable...
)
weak
此特质表明该属性定义了一种“非拥有关系” (nonowning relationship
)。为这种属性设置新值时,设置方法既不保留新值,也不释放旧值。此特质同assign
类似, 然而在属性所指的对象遭到摧毁时,属性值也会清空(nil out
)。 而 assign 的“设置方法”只会执行针对“纯量类型” (scalar type
,例如 CGFloat
或 NSlnteger
等)的简单赋值操作。
assigin
可以用非OC
对象,而weak
必须用于OC
对象
4.5 Ivar/Property
在runtime
中的一些应用
Ivar
的一些操作
// 获取成员变量名
const char *ivar_getName(Ivar v)
// 获取成员变量类型编码
const char *ivar_getTypeEncoding(Ivar v)
// 获取成员变量的偏移量
ptrdiff_t ivar_getOffset(Ivar v)
Property
的一些操作
// 获取属性名
const char *property_getName(objc_property_t property)
// 获取属性特性描述字符串
const char *property_getAttributes(objc_property_t property)
// 获取属性中指定的特性
objc_property_attribute_t *property_copyAttributeList(objc_property_t property, unsigned int *outCount)
// 获取属性的特性列表
char *property_copyAttributeValue(objc_property_t property, const char *attributeName)
4.6 Category
中使用Property
我们都知道Category
使用 Property
不会将它添加到类中的,这些将会在深入理解Objective-C:Category中介绍。我们可以通过属性关联的方式来使Category
可以使用Property
@interface Student (Name)
/* name */
@property (nonatomic, strong) NSString *name;
@end
static char nameKey;
#pragma mark - - 动态添加属性
- (void)setName:(NSString *)name{
return objc_setAssociatedObject(self, & nameKey, name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (NSString *)name{
return objc_getAssociatedObject(self, & nameKey);
}
五.Method/SEL/IMP
(方法/方法名称/方法实现)
5.1 Method
结构
方法分为2种,实例方法(Instance Method
)与类方法(Class Method
)
实例方法(Instance Method
)保存在对象(Instance
)的类(Class
)中
类方法(Class Method
)保存在元类(meta-class
)中
Class
结构:
struct objc_object {
private:
isa_t isa;
}
struct objc_class : objc_object {
Class superclass;
cache_t cache;
class_data_bits_t bits;
}
class_data_bits_t bits
中保存了Ivar、Property、Method、Protocol
等
同样Method
也是一个结构体,它的结构如下:
struct method_t {
SEL name;
const char *types;
IMP imp;
};
SEL name
方法名字 ,@selector()
用于表示运行时方 法的名字。Objective-C在编译时,会依据每一个方法的名字、参数序列,生成一个唯一的整型标识(Int类型的地址),这个标识就是SEL。本质上,SEL只是一个指向方法的指针(准确的说,只是一个根据方法名hash化了的KEY值,能唯一代表一个方法),它的存在只是为了加快方法的查询速度。
const char *types
类型编码,你可以通过官方文档来了解它
IMP imp
方法的实现,IMP
是一个函数指针,指向方法实现的首地址
5.2 消息(Message
)的发送
如果你稍微了解一点Runtime
,那么你肯定知道,在 Objective-C
中,所有的消息传递中的“消息“都会被转换成一个 selector
作为objc_msgSend
函数的参数,
[object speak] -> objc_msgSend(object, @selector(speak))
消息发送的流程:
1.检测这个 selector是不是要忽略的。
2.检查target是不是为nil。如果这里有相应的nil的处理函数,就跳转到相应的函数中。如果没有处理nil的函数,就自动清理现场并返回。这一点就是为何在OC中给nil发送消息不会崩溃的原因。
3.确定不是给nil发消息之后,在该class的缓存中查找方法对应的IMP实现。如果找到,就跳转进去执行。如果没有找到,就在方法分发表里面继续查找,一直找到NSObject为止。
4.如果还没有找到,那就需要开始消息转发阶段了。至此,发送消息Messaging阶段完成。这一阶段主要完成的是通过select()快速查找IMP的过程。
5.3 消息(Message
)的转发
我们都知道调用一个没有实现的方法时,会crash,在程序crash之前还会执行消息转发,那么我们来看看消息转发的机制:
- 消息转发第一步:
+(BOOL)resolveInstanceMethod:(SEL)sel
,当调用一个方法,但没有实现时,消息会通过上面方法寻找是否能找到实现。
void functionForMethod1(id self, SEL _cmd) {
NSLog(@"%@, %p", self, _cmd);
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
NSString *selectorString = NSStringFromSelector(sel);
if ([selectorString isEqualToString:@"method1"]) {
class_addMethod(self.class, @selector(method1), (IMP)functionForMethod1, "@:");
}
return [super resolveInstanceMethod:sel];
}
- 如果上一步没有实现,那么进入
-(id)forwardingTargetForSelector:(SEL)aSelector
,这一步是替消息找备援接收者,如果这一步返回的是nil,那么补救措施就完全的失效了,Runtime系统会向对象发送methodSignatureForSelector:
消息.
-(id)forwardingTargetForSelector:(SEL)aSelector{
Class class = NSClassFromString(@"BLView");
UIView *view = class.new;
if (aSelector == NSSelectorFromString(@"calculate")) {
return vc;
}
return nil;
}
-
-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
取到返回的方法签名用于生成NSInvocation
对象。为接下来的完整的消息转发生成一个NSMethodSignature
对象。NSMethodSignature
对象会被包装成NSInvocation
对象,forwardInvocation:
方法里就可以对NSInvocation
进行处理了。 -
-(void)forwardInvocation:(NSInvocation *)anInvocation
- (void)forwardInvocation:(NSInvocation *)anInvocation{
if ([someOtherObject respondsToSelector:
[anInvocation selector]])
[anInvocation invokeWithTarget:someOtherObject];
else
[super forwardInvocation:anInvocation];
}
消息发送与转发.jpg
5.4 Runtime
相关的操作
Method
// 调用指定方法的实现
id method_invoke ( id receiver, Method m, ... );
// 调用返回一个数据结构的方法的实现
void method_invoke_stret ( id receiver, Method m, ... );
// 获取方法名
SEL method_getName ( Method m );
// 返回方法的实现
IMP method_getImplementation ( Method m );
// 获取描述方法参数和返回值类型的字符串
const char * method_getTypeEncoding ( Method m );
// 获取方法的返回值类型的字符串
char * method_copyReturnType ( Method m );
// 获取方法的指定位置参数的类型字符串
char * method_copyArgumentType ( Method m, unsigned int index );
// 通过引用返回方法的返回值类型字符串
void method_getReturnType ( Method m, char *dst, size_t dst_len );
// 返回方法的参数的个数
unsigned int method_getNumberOfArguments ( Method m );
// 通过引用返回方法指定位置参数的类型字符串
void method_getArgumentType ( Method m, unsigned int index, char *dst, size_t dst_len );
// 返回指定方法的方法描述结构体
struct objc_method_description * method_getDescription ( Method m );
// 设置方法的实现
IMP method_setImplementation ( Method m, IMP imp );
// 交换两个方法的实现
void method_exchangeImplementations ( Method m1, Method m2 );
关于方法交换举个例子🌰:
@implementation NSObject (Swizzling)
+ (void)swizzleMethod:(SEL)originalSelector swizzledSelector:(SEL)swizzledSelector {
Class class = [self class];
//原有方法
Method originalMethod = class_getInstanceMethod(class, originalSelector);
//替换原有方法的新方法
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
//先尝试給源SEL添加IMP,这里是为了避免源SEL没有实现IMP的情况
BOOL didAddMethod = class_addMethod(class,originalSelector,
method_getImplementation(swizzledMethod),
method_getTypeEncoding(swizzledMethod));
if (didAddMethod) {//添加成功:说明源SEL没有实现IMP,将源SEL的IMP替换到交换SEL的IMP
class_replaceMethod(class,swizzledSelector,
method_getImplementation(originalMethod),
method_getTypeEncoding(originalMethod));
} else {//添加失败:说明源SEL已经有IMP,直接将两个SEL的IMP交换即可
method_exchangeImplementations(originalMethod, swizzledMethod);
}
}
@end
需要注意的是方法的交换需要写在方法所在类的+(void)load;
中
#import "NSArray+safe.h"
#import <objc/message.h>
#import "NSObject+Swizzling.h"
@implementation NSArray (safe)
+ (void)load{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
@autoreleasepool {
[objc_getClass("__NSArray0") swizzleMethod:@selector(objectAtIndex:) swizzledSelector:@selector(emptyObjectIndex:)];
[objc_getClass("__NSArrayI") swizzleMethod:@selector(objectAtIndex:) swizzledSelector:@selector(arrObjectIndex:)];
[objc_getClass("__NSArrayM") swizzleMethod:@selector(objectAtIndex:) swizzledSelector:@selector(mutableObjectIndex:)];
[objc_getClass("__NSArrayM") swizzleMethod:@selector(insertObject:atIndex:) swizzledSelector:@selector(mutableInsertObject:atIndex:)];
[objc_getClass("__NSArrayM") swizzleMethod:@selector(integerValue) swizzledSelector:@selector(replace_integerValue)];
}
});
}
- (id)emptyObjectIndex:(NSInteger)index{
return nil;
}
- (id)arrObjectIndex:(NSInteger)index{
if (index >= self.count || index < 0) {
return nil;
}
return [self arrObjectIndex:index];
}
- (id)mutableObjectIndex:(NSInteger)index{
if (index >= self.count || index < 0) {
return nil;
}
return [self mutableObjectIndex:index];
}
- (void)mutableInsertObject:(id)object atIndex:(NSUInteger)index{
if (object) {
[self mutableInsertObject:object atIndex:index];
}
}
- (NSInteger)replace_integerValue {
return 0;
}
@end
SEL
// 返回给定选择器指定的方法的名称
const char * sel_getName ( SEL sel );
// 在Objective-C Runtime系统中注册一个方法,将方法名映射到一个选择器,并返回这个选择器
SEL sel_registerName ( const char *str );
// 在Objective-C Runtime系统中注册一个方法
SEL sel_getUid ( const char *str );
// 比较两个选择器
BOOL sel_isEqual ( SEL lhs, SEL rhs );
IMP
// 创建一个指针函数的指针,该函数调用时会调用特定的block
IMP imp_implementationWithBlock ( id block );
// 返回与IMP(使用imp_implementationWithBlock创建的)相关的block
id imp_getBlock ( IMP anImp );
// 解除block与IMP(使用imp_implementationWithBlock创建的)的关联关系,并释放block的拷贝
BOOL imp_removeBlock ( IMP anImp );
@interface MyRuntimeBlock : NSObject
@end
@implementation MyRuntimeBlock
@end
// 测试代码
IMP imp = imp_implementationWithBlock(^(id obj, NSString *str) {
NSLog(@"%@", str);
});
class_addMethod(MyRuntimeBlock.class, @selector(testBlock:), imp, "v@:@");
MyRuntimeBlock *runtime = [[MyRuntimeBlock alloc] init];
[runtime performSelector:@selector(testBlock:) withObject:@"hello world!"];
六、Protocol
6.1 协议是什么?
协议声明了任何类都能够选择实现的程序接口。协议能够使两个不同继承树上的类相互交流并完成特定的目的,因此它提供了除继承外的另一种选择。任何能够为其他类提供有用行为的类都能够声明接口来匿名的传达这个行为。任何其他类都能够选择遵守这个协议并实现其中的一个或多个方法,从而利用这个行为。如果协议遵守者实现了协议中的方法,那么声明协议的类就能够通过遵守者调用协议中的方法。
6.2 如何写一个协议
协议中能够声明方法,以及属性,协议的继承。
Protocol
与Category
相同,属性是不会添加到类中的,我们需要使用objc_setAssociatedObject 、objc_getAssociatedObject
关联属性。
@required
表示必须实现,@optional
代表可选
@protocol UITableViewDataSource<NSObject>
@required
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section;
// Row display. Implementers should *always* try to reuse cells by setting each cell's reuseIdentifier and querying for available reusable cells with dequeueReusableCellWithIdentifier:
// Cell gets various attributes set automatically based on table (separators) and data source (accessory views, editing controls)
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;
@optional
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView; // Default is 1 if not implemented
- (nullable NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section; // fixed font style. use custom view (UILabel) if you want something different
- (nullable NSString *)tableView:(UITableView *)tableView titleForFooterInSection:(NSInteger)section;
@end
6.3 协议中的方法由谁来实现
实现:遵守协议者及其子类
调用:遵守协议者、其子类、id <协议名>
6.4协议的作用
某一个类需要委托其他类处理某些事件,最具代表性性的便是UITableView的那些代理方法。这些方法其实还是代理的方法,只不过定义的地方可能会在委托者类中,通过调用这些方法,可以:将委托者中的数据传递给代理;将代理的数据传递给委托者;将委托者的事件抛给代理去处理...
给某几个特定的类添加统一的接口,这些接口是从这些类中抽象出的共同的行为,这样便可以减少重复的代码。
6.5 Runtime
中关于Protocol
的API
// 返回指定的协议
Protocol * objc_getProtocol ( const char *name );
// 获取运行时所知道的所有协议的数组
Protocol ** objc_copyProtocolList ( unsigned int *outCount );
// 创建新的协议实例
Protocol * objc_allocateProtocol ( const char *name );
// 在运行时中注册新创建的协议
void objc_registerProtocol ( Protocol *proto );
// 为协议添加方法
void protocol_addMethodDescription ( Protocol *proto, SEL name, const char *types, BOOL isRequiredMethod, BOOL isInstanceMethod );
// 添加一个已注册的协议到协议中
void protocol_addProtocol ( Protocol *proto, Protocol *addition );
// 为协议添加属性
void protocol_addProperty ( Protocol *proto, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount, BOOL isRequiredProperty, BOOL isInstanceProperty );
// 返回协议名
const char * protocol_getName ( Protocol *p );
// 测试两个协议是否相等
BOOL protocol_isEqual ( Protocol *proto, Protocol *other );
// 获取协议中指定条件的方法的方法描述数组
struct objc_method_description * protocol_copyMethodDescriptionList ( Protocol *p, BOOL isRequiredMethod, BOOL isInstanceMethod, unsigned int *outCount );
// 获取协议中指定方法的方法描述
struct objc_method_description protocol_getMethodDescription ( Protocol *p, SEL aSel, BOOL isRequiredMethod, BOOL isInstanceMethod );
// 获取协议中的属性列表
objc_property_t * protocol_copyPropertyList ( Protocol *proto, unsigned int *outCount );
// 获取协议的指定属性
objc_property_t protocol_getProperty ( Protocol *proto, const char *name, BOOL isRequiredProperty, BOOL isInstanceProperty );
// 获取协议采用的协议
Protocol ** protocol_copyProtocolList ( Protocol *proto, unsigned int *outCount );
// 查看协议是否采用了另一个协议
BOOL protocol_conformsToProtocol ( Protocol *proto, Protocol *other );
总而言之
Runtime
的所有知识基本都围绕两个中心
(1)类的各个方面(Class、Object、Ivar、Property、Method、Protocol)的动态配置。
(2)消息传递,Message Send
& Message Forward
.
网友评论