Gowhich

Durban's Blog

在Objective-C的设计中,一个主要的考虑即为大型代码框架的维护。结构化编程的经验显示,改进代码的一种主要方法即为将其分解为更小的片段。Objective-C借用并扩展了Smalltalk实现中的“分类”概念,用以帮助达到分解代码的目的。[1]

一个分类可以将方法的实现分解进一系列分离的文件。程序员可以将一组相关的方法放进一个分类,使程序更具可读性。举例来讲,可以在字符串类中增加一个名为“拼写检查”的分类,并将拼写检查的相关代码放进这个分类中。

进一步的,分类中的方法是在运行时被加入类中的,这一特性允许程序员向现存的类中增加方法,而无需持有原有的代码,或是重新编译原有的类。例如若系统提供的字符串类的实现中不包含拼写检查的功能,可以增加这样的功能而无需更改原有的字符串类的代码。

在运行时,分类中的方法与类原有的方法并无区别,其代码可以访问包括私有类成员变量在内的所有成员变量。

若分类声明了与类中原有方法同名的函数,则分类中的方法会被调用。因此分类不仅可以增加类的方法,也可以代替原有的方法。这个特性可以用于修正原有代码中的错误,更可以从根本上改变程序中原有类的行为。若两个分类中的方法同名,则被调用的方法是不可预测的。

其它语言也尝试了通过不同方法增加这一语言特性。TOM在这方面走的更远,不仅允许增加方法,更允许增加成员变量。也有其它语言使用面向声明的解决方案,其中最值得注意的是Self语言。

C#与Visual Basic.NET语言以扩展函数的与不完全类的方式实现了类似的功能。Ruby与一些动态语言则以”monkey patch”的名字称呼这种技术。

使用分类的例子

这个例子创建了Integer类,其本身只定义了integer属性,然后增加了两个分类Arithmetic与Display以扩展类的功能。虽然分类可以访问类的私有成员,但通常利用属性的访问方法来访问是一种更好的做法,可以使得分类与原有类更加独立。这是分类的一种典型应用—另外的应用是利用分类来替换原有类中的方法,虽然用分类而不是继承来替换方法不被认为是一种好的做法。

Integer.h
1
2
3
4
5
6
7
8
9
10
11
12

#import <objc/Object.h>

@interface Integer : Object
{
@private
int integer;
}

@property (assign, nonatomic) integer;

@end
Integer.m
1
2
3
4
5
6
7
8

#import "Integer.h"

@implementation Integer

@synthesize integer;

@end
Arithmetic.h
1
2
3
4
5
6
7

#import "Integer.h"

@interface Integer (Arithmetic)
- (id) add: (Integer *) addend;
- (id) sub: (Integer *) subtrahend;
@end
Arithmetic.m
1
2
3
4
5
6
7
8
9
10
11
12
13

#import "Arithmetic.h"
@implementation Integer (Arithmetic)
- (id) add: (Integer *) addend{
self.integer = self.integer + addend.integer;
return self;
}

- (id) sub: (Integer *) subtrahend{
self.integer = self.integer - subtrahend.integer;
return self;
}
@end
Display.h
1
2
3
4
5
6
7
8



#import "Integer.h"
@interface Integer (Display)
- (id) showstars;
- (id) showint;
@end
Display.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

#import "Display.h"
@implementation Integer (Display)
- (id) showstars{
int i, x = self.integer;
for(i=0; i < x; i++)
printf("*");
printf("\n");

return self;
}

- (id) showint{
printf("%d\n", self.integer);

return self;
}
@end
main.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28



#import "Integer.h"
#import "Arithmetic.h"
#import "Display.h"
int
main(void){
Integer *num1 = [Integer new], *num2 = [Integer new];
int x;

printf("Enter an integer: ");
scanf("%d", &x);

num1.integer = x;
[num1 showstars];

printf("Enter an integer: ");
scanf("%d", &x);

num2.integer = x;
[num2 showstars];

[num1 add:num2];
[num1 showint];

return 0;
}

注释

可以利用以下命令来编译:

1
gcc -x objective-c main.m Integer.m Arithmetic.m Display.m -lobjc

在编译时间,可以利用省略#import "Arithmetic.h"[num1 add:num2]命令,以及Arithmetic.m文件来实验。程序仍然可以运行,这表明了允许动态的、按需的加载分类;若不需要某一分类提供的功能,可以简单的不编译之。

源自:维基百科

动态类型

类似于Smalltalk,Objective-C具备动态类型: 即消息可以发送给任何对象实体,无论该对象实体的公开接口中有没有对应的方法。在C++这种静态类型的语言里,不可能对一个(void*)指针调用任何方 法,编译器会挡下该调用行为。但在Objective-C中,你可以对id发送任何信息(id很像void*,但是被严格限制只能使用在对象上),编译器 仅会发出“该对象可能无法回应信息”的警告,程序同样可以通过编译,而实际发生的事则取决于运行期该对象的真正形态,若该对象的确可以回应消息,则依旧运 行对应的方法。

这种特性可以增加语言的灵活性,因为它允许对象“捕捉”消息,再将消息转送到另一个可以正确处理该消息的对象,形同消息“转发”给另一个对象。

一个对象收到信息之后,他有三种处理信息的可能手段,第一是回应该消息并运行方法,若无法回应,则可以转发消息给其他对象,若以上两者均无,就要处 理无法回应而抛出的例外。只要进行三者之其一,该消息就算完成任务而被丢弃。若对“nil”(空对象指针)发送消息,该消息通常会被忽略,取决于编译器选 项可能会抛出例外。

虽然Objective-C具备动态类型的能力,但编译期的静态类型检查依旧可以应用到变量上。以下三种声明在运行时效力是完全相同的,但是三种声明提供了一个比一个更明显的类型信息,附加的类型信息让编译器在编译时可以检查变量类型,并对类型不符的变量提出警告。

下面三个方法,差异仅在于参数的形态:

1
- setMyValue:(id) foo;

id形态表示参数“foo”可以是任何类的实例。

1
- setMyValue:(id <aProtocol>) foo;

id <aProtocol>表示“foo”可以是任何类的实例,但必须采纳“aProtocol”协议。

1
- setMyValue:(NSNumber*) foo;

该声明表示“foo”必须是“NSNumber”的实例。

动态类型是一种强大的特性。在缺少泛型的静态类型语言(如Java 5以前的版本)中实现容器类时,程序员需要写一种针对通用类型对象的容器类,然后在通用类型和实际类型中不停的强制类型转换。无论如何,类型转换会破坏静态类型,例如写入一个“整数”而将其读取为“字符串”会产生运行时错误。这样的问题被泛型解决,但容器类需要其内容对象的类型一致,而对于动态类型语言则完全没有这方面的问题。

[资料:维基百科]

Objective-C允许对一个对象发送消息,不管它是否能够响应之。除了响应或丢弃消息以外,对象也可以将消息转发到可以响应该消息的对象。转发可以用于简化特定的设计模式,例如观测器模式或代理模式。

Objective-C运行时在Object中定义了一对方法:

A.转发方法:

1
2
- (retval_t) forward:(SEL) sel :(arglist_t) args; // with GCC
- (id) forward:(SEL) sel :(marg_list) args; // with NeXT/Apple systems

B.响应方法:

1
2
- (retval_t) performv:(SEL) sel :(arglist_t) args;  // with GCC
- (id) performv:(SEL) sel :(marg_list) args; // with NeXT/Apple systems

希望实现转发的对象只需用新的方法覆盖以上方法来定义其转发行为。无需重写响应方法performv::,由于该方法只是单纯的对响应对象发送消息并传递参数。其中,SEL类型是Objective-C中消息的类型。

例子

这里包括了一个演示转发的基本概念的程序示例。(代码来源:维基百科Objective-C)

Forwarder.h
1
2
3
4
5
6
7
8
9
#import <objc/Object.h>

@interface Forwarder : Object{
id recipient; //该对象是我们希望转发到的对象。
}

@property (assign, nonatomic) id recipient;

@end
Forwarder.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#import "Forwarder.h"

@implementation Forwarder

@synthesize recipient;

- (retval_t) forward: (SEL) sel : (arglist_t) args
{
/*
*检查转发对象是否响应该消息。
*若转发对象不响应该消息,则不会转发,而产生一个错误。
*/
if([recipient respondsTo:sel])
return [recipient performv: sel : args];
else
return [self error:"Recipient does not respond"];
}
Recipient.h
1
2
3
4
5
6
7

#import <objc/Object.h>

// A simple Recipient object.
@interface Recipient : Object
- (id) hello;
@end
Recipient.m
1
2
3
4
5
6
7
8
9
10
11
12

#import "Recipient.h"

@implementation Recipient

- (id) hello{
printf("Recipient says hello!\n");

return self;
}

@end
main.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

#import "Forwarder.h"
#import "Recipient.h"

int main(void){
Forwarder *forwarder = [Forwarder new];
Recipient *recipient = [Recipient new];

forwarder.recipient = recipient; //Set the recipient.
/*
*转发者不响应hello消息!该消息将被转发到转发对象。
* (若转发对象响应该消息)
*/
[forwarder hello];

return 0;
}

脚注

利用GCC编译时,编译器报告:

1
2
3
4
$ gcc -x objective-c -Wno-import Forwarder.m Recipient.m main.m -lobjc
main.m: In function `main':
main.m:12: warning: `Forwarder' does not respond to `hello'

如前文所提到的,编译器报告Forwarder类不响应hello消息。在这种情况下,由于实现了转发,可以忽略这个警告。 运行该程序产生如下输出:

1
2
$ ./a.out
Recipient says hello!

我是打算使用一下最新版本的Golang,然后使用下Hugo,结果安装的时候提示要安装上一个版本,我感觉没啥问题,我就安装了要求安装的版本,结果还是有问题
继续安装要求安装的版本
结果就是一个套着一个,我服了

这个机制头一次见到

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
$ gvm install go1.24.3
Updating Go source...
Installing go1.24.3...
* Compiling...
ERROR: Failed to compile. Check the logs at /home/durban/.gvm/logs/go-go1.24.3-compile.log
ERROR: Failed to use installed version

# durban @ durban-amd-workspace in ~/Downloads/ubuntu_software [17:13:01] C:130
$ tail -f -n 100 ~/.gvm/logs/go-go1.24.3-compile.log
Building Go cmd/dist using /home/durban/.gvm/gos/go1.14.15. (go1.14.15 linux/amd64)
can't load package: package ./cmd/dist: found packages main (build.go) and building_Go_requires_Go_1_22_6_or_later (notgo122.go) in /home/durban/.gvm/gos/go1.24.3/src/cmd/dist

# durban @ durban-amd-workspace in ~/Downloads/ubuntu_software [17:13:25] C:130
$ gvm install go1.22.6
Installing go1.22.6...
* Compiling...
ERROR: Failed to compile. Check the logs at /home/durban/.gvm/logs/go-go1.22.6-compile.log
ERROR: Failed to use installed version

# durban @ durban-amd-workspace in ~/Downloads/ubuntu_software [17:13:35] C:1
$ tail -f -n 100 ~/.gvm/logs/go-go1.22.6-compile.log
Building Go cmd/dist using /home/durban/.gvm/gos/go1.14.15. (go1.14.15 linux/amd64)
can't load package: package ./cmd/dist: found packages main (build.go) and building_Go_requires_Go_1_20_6_or_later (notgo120.go) in /home/durban/.gvm/gos/go1.22.6/src/cmd/dist

# durban @ durban-amd-workspace in ~/Downloads/ubuntu_software [17:13:49] C:130
$ gvm install go1.20.6
Installing go1.20.6...
* Compiling...
ERROR: Failed to compile. Check the logs at /home/durban/.gvm/logs/go-go1.20.6-compile.log
ERROR: Failed to use installed version

# durban @ durban-amd-workspace in ~/Downloads/ubuntu_software [17:13:53] C:1
$ tail -f -n 100 ~/.gvm/logs/go-go1.20.6-compile.log
Building Go cmd/dist using /home/durban/.gvm/gos/go1.14.15. (go1.14.15 linux/amd64)
can't load package: package ./cmd/dist: found packages main (build.go) and building_Go_requires_Go_1_17_13_or_later (notgo117.go) in /home/durban/.gvm/gos/go1.20.6/src/cmd/dist

# durban @ durban-amd-workspace in ~/Downloads/ubuntu_software [17:14:02] C:130
$ gvm install go1.17.13
Installing go1.17.13...
* Compiling...
go1.17.13 successfully installed!

笑死人

这个安装逻辑就是按照这个顺序依次安装

1
2
3
4
gvm install go1.17.13
gvm install go1.20.6
gvm install go1.22.6
gvm install go1.24.3

为了体验下新版本我这是招谁了

Objective-C中创建对象的方法

Objective-C 创建对象需通过 alloc 以及 init。alloc的作用是分配内存,init 则是初始化对象。 init 与 alloc 都是定义在 NSObject 里的方法,父对象收到这两个信息并做出正确回应后,新对象才创建完毕。以下为范例:

1
MyObject * my = [[MyObject alloc] init];

在 Objective-C 2.0 里,若创建对象不需要参数,则可直接使用 new

1
MyObject * my = [MyObject new];

这仅仅是语法上的精简,效果完全相同。

若要自己定义初始化的过程,可以重写 init 方法,来添加额外的工作。(用途类似 C++ 的构造函数 constructor)

1
2
3
4
5
6
- (id) init {
if ( self=[super init] ) { // 必须调用父类的 init
// do something here ...
}
return self;
}

协议是一组尚未实现的方法列表,任何的类均可采纳该协议并给出方法的具体实现。

Objective-C在NeXT时期曾经试图引入多重继承的概念,但由于协议的出现而没有实现。协议的功能类似于C++中的多重抽象基类继承或是Java与C#语言中的“接口”。在Objective-C中,包括两种定义协议的方式:由编译器保证的“正式协议”,以及为特定目的设定的“非正式协议”。

非正式协议为一个可以选择性实现的一系列方法列表。非正式协议虽名为协议,但实际上是挂于NSObject上的未实现分类 (Unimplemented Category)的一种称谓,Objetive-C语言机制上并没有非正式协议这种东西,OSX 10.6版本之后由于正式协议也可以通过@optional关键字达成相同功用,所以非正式协议已经被废弃不再使用。

正式协议则类似于Java中的”接口”,它是一系列方法的列表,任何类都可以声明自身实现了某个协议。在Objective-C 2.0之前,一个类必须实现它声明符合的协议中的所有方法,否则编译器会报告一个错误,表明这个类没有实现它声明符合的协议中的全部方法。 Objective-C 2.0版本允许标记协议中某些方法为可选的(Optional),这样编译器就不会强制实现这些可选的方法。

协议经常应用于Cocoa 中的委托及事件触发。例如文本框类通常会包括一个委托 (delegate)对象,该对象可以实现一个协议,该协议中可能包含一个实现文字输入的自动完成方法。若这个委托对象实现了这个方法,那么文本框类就会在适当的时候触发自动完成事件,并调用这个方法用于自动完成功能。

Objective-C中协议的概念与Java中接口的概念并不完全相同,即一个类可以在不声明它符合某个协议的情况下,实现这个协议所包含的方 法,也即实质上符合这个协议,而这种差别对外部代码而言是不可见的。正式协议的声明不提供实现,它只是简单地表明符合该协议的类实现了该协议的方法,保证 调用端可以安全调用方法。

语法

协议以关键字@protocol作为区段起始,@end退出,中间为方法列表。

1
2
3
4
@protocol Locking
- (void)lock;
- (void)unlock;
@end

这是一个协议的例子,多线程编程中经常要确保一份共享资源同时只有一个线程可以使用,会在使用前给该资源挂上锁 ,以上即为一个表明有“锁”的概念的协议,协议中有两个方法,只有名称但尚未实现。

下面的SomeClass宣称他采纳了Locking协议:

1
2
3
4
5
6
7
8
9
10
11
12
@interface SomeClass : SomeSuperClass <Locking>
@end
一旦SomeClass表明他采纳了Locking协议,SomeClass就有义务实现Locking协议中的两个方法。

@implementation SomeClass
- (void)lock {
// 实现lock方法...
}
- (void)unlock {
// 实现unlock方法...
}
@end

由于SomeClass已经确实遵从了Locking协议,故调用端可以安全的发送lock或unlock信息给SomeClass实体变量,不需担心他没有办法回应信息。

插件是另一个使用抽象定义的例子,可以在不关心插件的实现的情况下定义其希望的行为。

Objective-C 类型要求区分接口(interface)与实现(implementation)为两个程序区块,这是强制性的。

类型的接口通常放置于头文件内,依C语言的惯例以.h作为扩展名;类型的实现则放于以.m为扩展名。

Interface

接口区段里头清楚定义了类型的名称,实体变量(instance variable),以及方法。 以关键字@interface作为区段起头,@end退出区段。

1
2
3
4
5
6
7
8
9
10
@interface MyObject : NSObject {
int memberVar1; // 实体变量
id memberVar2;
}
+(return_type) class_method; // 类别分类

-(return_type) instance_method1; // 实体方法
-(return_type) instance_method2: (int) p1;
-(return_type) instance_method3: (int) p1 andPar: (int) p2;
@end

方法前面的+/-号代表方法的类型:加号(+)代表类型方法(class method),不需要实体就可以调用,近于C++的静态成员函数(static member function)。减号(-)即是一般的实体方法(instance method)。 这里提供了一份意义相近的C++语法对照,如下:

1
2
3
4
5
6
7
8
9
10
11
12
class MyObject : public NSObject {
protected:
int memberVar1; // 实体变量
void * memberVar2;

public:
static return_type class_method(); // 类别方法

return_type instance_method1(); // 实体方法
return_type instance_method2( int p1 );
return_type instance_method3( int p1, int p2 );
}

Objective-C定义一个新的方法时,名称内的冒号(:)代表参数传递,不同于C语言以数学函数的括号来传递参数。Objective-C方法的参数也不必全部都附缀于方法名称的尾端,也可以夹杂于名称中间,提高程序可读性。以一个设置颜色RGB值的方法为例:

1
2
3
- (void) setColorToRed: (float)red Green: (float)green Blue:(float)blue; /* 声明方法 */

[myColor setColorToRed: 1.0 Green: 0.8 Blue: 0.2]; /* 调用方法 */

这个方法的全名是setColorToRed:Green:Blue:。每个冒号后面都带着一个形态为float的参数,分别代表红,绿,蓝三色。

Implementation

实现区段则撰写方法实际运行的程序。以关键字@implementation作为区段起头,@end结尾。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@implementation MyObject {
int memberVar3; //私有实体变量
}

+(return_type) class_method {
.... //method implementation
}
-(return_type) instance_method1 {
....
}
-(return_type) instance_method2: (int) p1 {
....
}
-(return_type) instance_method3: (int) p1 andPar: (int) p2 {
....
}
@end

值得一提的是不只interface区段开头可以声明实体变量,implementation区段开头也可以声明实体变量,两者的差别在于成员访问权限, 声明于interface区段内的实体变量默认权限为protected,声明于implementation区段的实体变量则默认为private,基 于面向对象的封装原则,仅供类型内部使用的变量请尽可能声明于implementation区段(.m档)内,不需要曝露于interface(.h档) 中。

关于ARC的介绍文章网上已经很多,苹果的官方文档也不少。担心使用ARC会带来问题的同学主要的理由有以下5点:

1.担心这个技术方案不靠谱。苹果大多数时候的技术方案都是比较靠谱的,但也有一些技术方案有很多坑,例如storyboard。

2.原有的项目在非ARC环境下运行良好,担心迁移成本或引入新的问题。

3.苹果以前手工管理内存需要非常小心,稍微不注意应用程序就崩溃了。有过这段经历的iOS开发老手,心里上还是觉得自己手工管理内存更踏实一些。

4.使用ARC需要了解ARC的一些细节,还需要引入_bridge等新的关键字,学习成本还是有的。

5.以为ARC只能支持iOS5.0以上(这是非常大的误解)。

对于上面提到5点问题,我认为相应的回答如下:

1.ARC是WWDC2011大会时提出的技术,离现在已经快2年了,而且苹果现在将MacOS上的垃圾回收机制废弃(Deprecated),采用ARC替代,无疑证明了ARC是成熟的了。

2.确实有一些迁移成本,但苹果在Xcode中专门集成了迁移工具,成本已经非常小了。如下图就是Xcode集成的将非ARC工程转换成ARC工程的工具。另外,为了兼容第三方的非ARC开源库,你也可以在工程中随意使用编译参数:-fno-objc-arc ,这个参数允许对部分文件关闭ARC。

3.手工管理内存虽然踏实,但是泄露很容易发生。常常开发完成后,需要使用Instruments来检测泄露。但用了ARC后,基本不会出现泄露了,我在 开发粉笔网iPhone客户端时,由于使用了ARC,花三个月开发完的应用,用instruments检测后,没有发现任何内存泄漏问题。这在没有使用 ARC的工程中是不可想象的。

4.确实有学习成本。但是非常值得学习,能省不少开发精力。

5.虽然ARC是与iOS5一同推出,但是由于ARC的实现机制是在编译期完成,所以使用ARC之后App仍然可以支持iOS4.3。稍微需要注意的是, 如果要在ARC开启的情况下支持iOS4.3,需要将weak关键字换成 __unsafe_unretained,另外还有一些细节需要处理

所以,希望大家都能在项目中使用ARC,一旦你感受到它带来的好处,你就离不开它了。它也能让你从繁琐的内存管理代码中解放出来,将精力更多关注于代码结构、设计模式而不是底层的内存管理。

小插件解决大问题,做网站,最喜欢使用这种东西,不需要重写代码,直接调用,传递参数,就得到想要的模块,yii也有次功能,看代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$this->beginWidget('CBootStrapPortlet', [
'title' => '博文分类', //导航标题
'htmlOptions' => ['class' => 'nav nav-pills nav-stacked'], //样式定义
'tagName' => 'ul', //
'decorationCssClass' => 'active',
'titleCssClass' => '',
'contentCssClass' => '',

]);

$this->widget('CBootStrapMenu', [
'items' => $this->blogType,
'htmlOptions' => ['class' => ''],
]);

$this->endWidget();

上面的代码是我的列表的小插件,样式我已经定义好了,只要给变量,传递参数就好了s

Objective-C最大的特色是承自Smalltalk的信息传递模型(message passing),与今日主流的C++差异甚大。Objective-C里,与其说对象互相调用方法,不如说对象之间互相传递信息更为精确。此二种风格的差异主要在于程序如何看待调用方法/传送信息这个动作。C++里类型与方法的关系非常严格清楚,一个方法必定属于一个类型,而且在编译时(compile time)就已经紧密绑定,你不可能去调用一个不存在类型里的方法。但在Objective-C,类型与信息的关系比较松散,调用方法视为对对象发送信息,所有方法都被视为对信息的回应。所有信息处理直到运行时(runtime)才会动态决定,并交由类型自行决定如何处理收到的信息。也就是说,一个类型不保证一定会回应收到的信息,如果类型收到了一个无法处理的信息,程序只会抛出一个Exception,不会出错或当掉。

C++里,送一个信息给对象(或者说调用一个方法)的语法如下:

1
obj->method(argument);

Objective-C则写成:

1
[obj method: argument];

此二者并不仅仅是语法上的差异,还有基本行为上的不同。

这里以一个汽车类(car class)的简单例子来解释Objective-C的信息传递特性:

1
[car fly];

典型的C++意义解读是“调用car类型的fly方法”。若car类型里头没有定义fly方法,那编译肯定不会通过。但是Objective-C里,我们应当解读为“发提交一个fly的信息给car对象”,fly是信息,而car是信息的接收者。car收到信息后会决定如何回应这个信息,若car类型内定义有fly方法就运行此段程序,若car内不存在fly方法,这里不会产生编译错误,它仅仅是抛出Exception。

此二种风格各有优劣。C++的编译期绑定使得函数调用非常快速,强制要求所有的方法都必须有对应的动作。缺点是不支持动态绑定(除非手动加上 virtual关键字)。Objective-C天生即是动态绑定,运行期才处理信息,允许传送未知信息给对象。可以送信息给整个对象集合而不需要一一检 查每个对象的型态,天生具备消息转送机制。同时空对象nil也可以接受信息,但是默认不做事,所以送信息给nil也不用担心程序崩溃。

Objective-C的方法调用因为运行期才动态解析信息,一开始信息比C++ virtual成员函数调用速度慢上三倍。但经由IMP高速缓存改善,目前已经比C++的virtual function快上50%。

0%