发消息
概述
oc
里面的发消息,实际上就是在调用那个对象的一个方法。- 但是,这种方式与其他语言中的“函数调用”或“方法调用”有所不同。
Objective-C
使用了一个叫做“消息传递”(message passing
)的机制。
- 实例:
- 在
Objective-C
中,这种语法[receiver message]
表示向receiver
对象发送一个message
消息。 - 这个“消息传递”实际上是通过
Objective-C
的运行时系统(runtime
)实现的,它会查找并执行与该消息对应的方法。
- 在
1 |
[receiver message] |
OC运行时库
Objective-C
的运行时(runtime
)是一个用C
语言编写的库,它为Objective-C
提供了动态特性的底层支持。
消息传递机制
- 选择器
- 消息的名称被表示为选择器。
- 选择器是一个在编译时和运行时都有的表示方法名的类型。
- 方法查找
- 当给对象发送消息时,编译器会将
message
转化为一个选择器(selector
)。这样运行时系统就可以识别和处理它。 - 这个选择器,类型为
SEL
,是对应方法名的一个表示。 - 现在,当
objc_msgSend
在运行时库被调用时,它的任务就是找到receiver
对象的类中与该选择器@selector(message)
对应的方法的实现。步骤如下: objc_msgSend
查看receiver
的类的方法列表,试图找到与@selector(message)
匹配的条目。- 如果在当前类中找不到,
objc_msgSend
将查看receiver
的父类的方法列表,继续寻找匹配的条目。 - 这个查找过程在继承链上继续,直到找到与选择器匹配的方法,或者直到达到基类 (通常是
NSObject
)。 - 如果最终在整个继承链上都找不到匹配的方法,运行时会触发动态方法解析和消息转发的机制。
- 当给对象发送消息时,编译器会将
1 2 3 4 5 |
[receiver message]; // 编译器在编译时会将这转换为类似以下的 C 函数调用: // @selector(message) 是一个选择器,它是 Objective-C 方法名 message 的一个编译时表示。 objc_msgSend(receiver, @selector(message)); |
- 动态方法解析
- 如果运行时系统在类中找不到相应的方法,它会调用
+resolveInstanceMethod:
或+resolveClassMethod:
,允许程序员动态地为该选择器提供一个方法实现。 - 如果对象是一个实例,而且其类没有实现所调用的方法,那么运行时系统会自动调用这个类的
+resolveInstanceMethod:
方法。
方法的参数是一个选择器,表示未找到的方法。
如果你在这个方法中动态地为该选择器提供了一个实现,那么运行时系统会使用这个新的实现来响应原始的消息调用。
为了为一个选择器动态地添加方法实现,你可能会使用函数如class_addMethod
。 - 如果发送的消息是给类对象的(即是调用类方法),而不是实例对象,那么运行时系统会调用
+resolveClassMethod:
。
同样,如果你在这里为该选择器提供了一个实现,运行时系统会使用这个新的实现。
- 如果运行时系统在类中找不到相应的方法,它会调用
1 2 3 4 5 6 7 8 9 10 11 |
+ (BOOL)resolveInstanceMethod:(SEL)sel { if (sel == @selector(myDynamicMethod)) { class_addMethod([self class], sel, (IMP)dynamicMethodImplementation, "v@:"); return YES; } return [super resolveInstanceMethod:sel]; } void dynamicMethodImplementation(id self, SEL _cmd) { NSLog(@"This is a dynamically added method"); } |
1 2 3 4 5 6 7 8 9 10 11 12 |
+ (BOOL)resolveClassMethod:(SEL)sel { if (sel == @selector(myDynamicClassMethod)) { Class metaClass = objc_getMetaClass(class_getName(self)); class_addMethod(metaClass, sel, (IMP)dynamicClassMethodImplementation, "v@:"); return YES; } return [super resolveClassMethod:sel]; } void dynamicClassMethodImplementation(id self, SEL _cmd) { NSLog(@"This is a dynamically added class method"); } |
-
转发
-
如果方法还没有被解析,运行时提供了消息转发机制。
-
首先,通过
-forwardingTargetForSelector:
尝试找到一个可以响应该消息的对象。
这允许开发者返回另一个对象,作为消息的新接收者。这是一个简单且快速的消息转发方式。 -
如果没有适当的对象来转发消息,运行时系统会进入完整的消息转发流程。
这涉及到
-methodSignatureForSelector:
和-forwardInvocation:
。开发者可以在
-forwardInvocation:
中决定如何处理或转发这个消息。
-
-
调用
- 一旦找到方法的实现,运行时系统会使用动态函数调用来执行该方法。
- 这通常涉及到将控制权传递给一个函数指针,该指针指向所找到的方法的实现。
-
错误抛出
- 如果消息仍然没有被处理,那么运行时系统将抛出一个
NSInvalidArgumentException
,提示 “unrecognized selector sent to instance
”。
- 如果消息仍然没有被处理,那么运行时系统将抛出一个
消息传递机制的特点
- 这种基于消息传递的方法调用机制相对较慢,因为它涉及到运行时查找。
- 但是,
Objective-C
的运行时使用了各种缓存策略,特别是方法缓存(method cache
),来加速这个过程,从而使得常见的消息发送非常快。
OC运行时库
使用
- 导入
<objc/runtime.h>
和/或<objc/message.h>
头文件
消息发送
- 当你在
Objective-C
中向一个对象发送消息,如[object message]
,这实际上被转换成一个C
函数调用,如objc_msgSend(object, @selector(message))
。- 这个函数负责在对象的类中查找方法并执行它。
类和对象的管理
- 运行时提供了许多函数来操作和管理类和对象。
- 例如,
class_addMethod
允许你动态地为类添加方法 objc_allocateClassPair
和objc_disposeClassPair
允许你动态地创建和销毁类。
- 例如,
方法、属性和协议的查询和操作
- 使用如
class_getInstanceMethod
、class_copyPropertyList
和class_copyProtocolList
这样的函数,你可以查询类的方法、属性和实现的协议。
方法替换和交换
- 运行时允许你替换或交换方法的实现,这在许多高级技术中,如方法切面(
method swizzling
)是非常有用的。
关联对象
- 这是一种将值与对象关联的方式,不需要修改类的定义。
objc_setAssociatedObject
和objc_getAssociatedObject
- 这在
Objective-C
的分类中非常有用,因为分类不允许添加实例变量。
动态加载
- 运行时支持动态加载代码,允许你在运行时加载和链接新的类和方法。
类型编码
- 运行时提供了对
Objective-C
的类型系统的底层表示的访问,允许你查询和操作这些类型信息。
类对象相关
类对象
- 在
Objective-C
中,每个类都有一个与之关联的“类对象”。这个类对象代表了类本身,而不是类的实例。 - 当你向一个类名发送消息,如
[SomeClass someClassMethod]
,实际上你是向这个类的类对象发送消息。 - 但是如果
SomeClass
是一个实例,向一个实例发送消息,消息说被发送给了实例对象。
1 2 3 4 |
[MyClass someClassMethod]; // 发送给 MyClass 的类对象 MyClass *instance = [[MyClass alloc] init]; [instance someInstanceMethod]; // 发送给 instance 这个实例对象 |
self
self
在Objective-C
中是一个特殊的变量。- 在实例方法中,
self
指向调用该方法的实例对象。但在类方法中,self
指向类对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
@interface MyClass : NSObject + (void)classMethod; - (void)instanceMethod; @end @implementation MyClass + (void)classMethod { NSLog(@"%@", self); // 这里的self指向类对象 } - (void)instanceMethod { NSLog(@"%@", self); // 这里的self指向实例对象 } @end |
- 在
Objective-C
中,self
的解析是在运行时进行的,而不是在编译时。- 这意味着,当你在一个类方法中使用
self
时,它会引用当前正在执行该类方法的类对象。
- 这意味着,当你在一个类方法中使用
- 这种设计的特点:
- 通用性:由于
self
在类方法中指向类对象,你可以在子类中重写类方法,并使用self
调用该类方法时动态地引用正确的类。 - 动态性:利用
self
在类方法中的这种动态性,类簇(如NSArray
、NSString
等)在内部可以根据条件返回不同的私有子类的实例。 - 注意点:尽管
self
在类方法中指向类对象,但这不意味着你可以访问实例变量或实例方法,因为它们与实例关联,而不是与类关联。
- 通用性:由于
内存布局
- 在
Objective-C
中,类和对象都有各自的内存布局。 - 类对象内存布局:
Objective-C
中的每一个类都有一个与之对应的类对象。- 类对象存储了关于该类的元数据,如方法列表、属性列表、协议列表和父类指针等。
- 类对象的布局如下:
isa
指针: 指向元类 (metaclass
)。superclass
指针: 指向父类的类对象。- 类的实例变量大小和布局信息(虽然类对象不存储实例变量的值(实例对象做这个),但它们确实知道每个实例变量的大小和它们在实例对象中的位置)。
- 类的属性列表:为类定义的属性的列表。
- 类的方法列表:这包含了该类的所有方法的列表,包括它们的选择器(方法名)和方法的实际实现的地址。
- 协议列表:如果类实现了任何协议,它们将被列在这里。
- 缓存: 这是一个优化,用于缓存最近调用的方法,以提高消息派发的速度。
- 实例对象内存布局:
isa
指针: 指向元类 (metaclass
)
这是每个Objective-C
对象的第一个成员变量
它是一个指向对象所属类的类对象的指针。
这个isa
指针使得对象知道自己是什么类型的,也使得对象能够在收到消息时查找到正确的方法实现。- 实例变量:
一个对象的实例变量跟随其isa
指针。
这些变量是在类的定义中声明的,并为该类的每个对象分配存储空间。
它们的布局顺序和声明顺序以及继承关系有关:
基类的实例变量首先被放置。
子类的实例变量跟在基类的实例变量之后。 - 对于具有属性的对象:
属性本身并不存储在对象的内存布局中。
如果属性有一个对应的实例变量(这是默认的情况,除非你特意指定了@synthesize
或@dynamic
),那么这个实例变量就会被放在对象的内存布局中。 - 关于内存对齐:
在实际的内存布局中,可能会有一些内存对齐的填充,这是为了满足特定的CPU
和操作系统的要求,以使内存访问更加高效。 - 关于关联对象:
Objective-C
提供了一种方法,允许在运行时将任意值与对象关联,而不需要修改类的定义。
但这些关联对象并不直接存储在对象的标准内存布局中。它们是通过一个特殊的运行时机制存储和检索的。
具有属性的对象
- 在
Objective-C
中,属性(properties
)是一个方便的方式来声明类的数据成员以及关于这些数据成员访问的合同(如读写权限、线程安全性等)。- 属性的概念有助于提高代码的清晰度和可维护性。
- 注意点:
- 默认情况下,编译器会为每个属性生成一个名为
_propertyName
(在这种情况下是_name
)的实例变量。
这是属性值的实际存储位置。 - 除了实例变量之外,编译器还会为属性自动生成
getter
和setter
方法,除非你声明属性为readonly
(只会生成getter
)或者你使用@synthesize
或@dynamic
为属性提供自定义的实现或行为。 - @synthesize 和 @dynamic:
@synthesize
: 用于指定一个不同于默认的实例变量名称。
@dynamic
: 告诉编译器不要为该属性生成默认的getter
和setter
。这通常用于你想在运行时动态地生成这些方法的情况。
- 默认情况下,编译器会为每个属性生成一个名为
1 2 3 |
@interface MyClass : NSObject @property (nonatomic, strong) NSString *name; @end |
内存布局图示
- 示例类:
- 基于64位系统显示下面的图示
1 2 3 4 5 |
@interface Person : NSObject @property (nonatomic, strong) NSString *name; @property (nonatomic, assign) NSInteger age; @property (nonatomic, strong) Person *bestFriend; @end |
- 类对象的内存布局
- 并没有显示具体准确的大小,是因为类对象的具体大小取决于诸多因素,如方法数量、属性数量等(下图示例)
- 方法描述占16个字节,其中,选择器占8个字节,方法实现的地址占8个字节
1 2 3 4 5 6 7 8 9 10 11 12 13 |
----------------------------------- | isa pointer (8 bytes) | |---------------------------------| | Class specific data: | | + Class name (8 bytes) | | + Superclass pointer (8 bytes)| | + Class method list | | - Method 1 (16 bytes) | | - Method 2 (16 bytes) | | + Class property list | | - Property (8 bytes) | | ... | ----------------------------------- |
- 类的实例的内存布局
1 2 3 4 5 6 7 8 9 10 11 12 |
--------------------------------- | isa pointer (8 bytes) | |------------------------------ | | _name (NSString* pointer) | | (8 bytes) | |------------------------------ | | _age (NSInteger) | | (8 bytes) | |------------------------------ | | _bestFriend (Person* pointer) | | (8 bytes) | --------------------------------- |
self和C++的this
- 定义和基础用法:
self
在Objective-C
中是一个隐藏的参数,传递给每一个实例方法(与super
不同)。this
在C++
中是一个指向对象实例的隐式指针,用于非静态成员函数。
- 可修改性:
- 在
C++
中,你可以使用this
指针修改它指向的对象,但你不能更改this
指针本身。 - 在
Objective-C
中,self
是一个可以修改的变量。这意味着你可以将其指向另一个对象。
- 在
- 类型和指针:
- 在
C++
中,this
是一个指针,因此必须使用->
来访问成员,而不是.
。例如:this->memberVar
。 - 在
Objective-C
中,self
是一个对象,所以你可以像处理任何其他对象一样使用它。
- 在
- 上下文的使用:
- 在
C++
中,this
主要在类内部使用,尤其是当成员函数需要明确引用类的实例时。 - 在
Objective-C
中,self
也用于发送消息给同一个对象。例如,[self someMethod]
。
- 在
- 关于类方法:
- 在
C++
的静态成员函数中不能使用this
指针,因为它们没有与具体实例关联。 - 在
Objective-C
中的类方法(使用+
定义)里,self
指的是类对象本身,而不是实例。
- 在
元类
- 在
Objective-C
中,你可以向对象发送消息,但类本身也是对象。- 所以,当你向类(而不是类的实例)发送消息时,你实际上是在与元类交互。
- 类也是对象:
- 在
Objective-C
中,当你定义一个类,运行时系统还会为你创建一个相关的元类。 - 这个元类描述了类本身的行为,而不是实例的行为。
- 在
- 类方法与实例方法:
- 类对象存储类方法的信息,而类的实例对象存储实例方法的信息。元类是用来存储类方法的。
- 当你调用一个类方法时,你实际上是向类对象的元类发送消息。
- 元类的结构:
- 元类也有自己的
isa
指针,它指向自己的元类。 - 这构成了一个链条,这条链条最终在根元类处结束。
- 根元类的
isa
指针指向自己,形成一个闭环。
- 元类也有自己的
- 如何找到元类:
- 在
Objective-C
运行时,你可以使用object_getClass()
函数来获取一个类的元类。 - 如果再次调用这个函数,就可以得到元元类(在这种情境下,所有元类的元类其实都是根元类)。
- 在
- 元类与类的继承:
- 当一个子类继承了父类,它会继承父类的实例方法、属性、和其他特性。
- 同时,子类的元类也会继承父类的元类的所有类方法。
- 元类的用途:
- 虽然元类的概念初看起来可能比较晦涩,但它为
Objective-C
提供了动态性和灵活性。 - 例如,它使得方法的动态添加、方法交换、类别、关联对象等功能成为可能。
- 虽然元类的概念初看起来可能比较晦涩,但它为
协议
简述
- 在 Objective-C 中,协议是一种强大的工具,用于定义一个方法的集合,这些方法可以由任何类实现。
- 协议为不同的类提供了一种方式来保证它们提供某些功能或行为,而不必关心这些类的继承关系。
定义协议
- 使用
@protocol
关键字定义协议。在协议内部,你可以列出一系列的方法声明。
1 2 3 4 5 |
@protocol MyProtocol - (void)requiredMethod; @optional - (void)optionalMethod; @end |
实现协议
- 一旦协议被定义,任何类都可以选择实现它。
- 在类的头文件中,使用尖括号
<...>
来指明类遵循哪些协议。
1 2 |
@interface MyClass : NSObject <MyProtocol> @end |
@required
和 @optional
指令
- 默认情况下,协议中定义的方法是必需的,这意味着任何遵循该协议的类都必须实现这些方法。
- 你可以使用
@optional
指令来声明一些可选的方法。这意味着遵循该协议的类可以选择是否实现这些方法。
查询协议的遵循
- 可以使用
conformsToProtocol:
方法来检查一个对象是否遵循某个特定的协议。
1 2 3 |
if ([someObject conformsToProtocol:@protocol(MyProtocol)]) { // the object conforms to the MyProtocol protocol } |
协议可以继承其他协议
- 就像类可以继承其他类一样,协议也可以从其他协议继承。
- 这意味着一个协议可以添加到另一个协议已经定义的方法集合中。
1 2 3 |
@protocol AnotherProtocol <MyProtocol> - (void)anotherMethod; @end |
多协议遵循
- 一个类可以遵循多个协议,只需在类的声明中使用逗号分隔每个协议名。
1 2 |
@interface MyClass : NSObject <MyProtocol, AnotherProtocol> @end |
本文为原创文章,版权归Aet所有,欢迎分享本文,转载请保留出处!
你可能也喜欢
- ♥ Objective-C学习记述一09/26
- ♥ KWP2000协议04/14
- ♥ Windows消息处理机制04/29
- ♥ HTTP 协议10/22
- ♥ Spy++相关08/18
- ♥ Chromium:鼠标事件的生成与处理07/19