OC: self

为了更好的说明 Objective-C 中的 self,我们先从 Java 的 this 关键字开始来引入话题。

Java 中的 this

在 Java 中 this 关键字表示当前类对象,其只能在类的非静态方法中使用,静态方法和静态的代码块中绝对不能出现 thisthis 只和特定的对象关联,而不和类关联,同一个类的不同对象有不同的 this.

先看一个 Java 示例,能说明上面的问题,示例如下:

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
// 静态代码块中也不可以使用 this
// Error: non-static variable this cannot be referenced from a static context
static {
// this.eat();
}
public void play() {
System.out.println("play()");
// this 调用类方法
this.eat();
// this 调用实例方法
this.finish();
}
public static void eat() {
System.out.println("static eat()");
// 不可以在类方法中使用 this
// Error: non-static variable this cannot be referenced from a static context
// this.play();
// System.out.println(this);
}
public void finish() {
System.out.println("finish()");
}

通过实际的 Java 例子,基本表明了在静态方法和实例方法中 this 的使用场景。

Objective-C 中的 self

Objective-C 中,self 是一个比较特殊的对象,它既可以是实例对象也可以是类对象,有点类似于上面 Java 中的 this 关键字。

下面结合实际例子,来说明 self 这个关键字。

1、实例方法中的 self

实例方法中的 self 可以直接调用实例方法但不可以直接调用类方法,如下示例中,调用实例方法 finish没有问题,而调用类方法 eat 直接报编译错误。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
- (void)play
{
NSLog(@"---------------- '- (void)play' ------------------");
NSLog(@"self: %@, self -> %p", self, self);
// 无法使用 self 来调用类方法
// Error: No visible @interface for 'MZPerson' declares the selector 'eat'
// [self eat];
// 调用实例方法
[self finish];
}
+ (void)eat
{
NSLog(@"---------------- '+ (void)eat' ------------------");
}
- (void)finish
{
NSLog(@"--------------- '- (void)finish' ----------------");
}

我们知道,在实例方法中可以直接通过`[类 类方法] 的方式来调用类方法,那么如果想在实例方法中使用 self 关键字,如何办呢?

很简单,使用 [self class] 即可。

1
2
3
4
5
6
7
- (void)play
{
NSLog(@"---------------- '- (void)play' ------------------");
NSLog(@"self: %@, self -> %p", self, self);
[[self class] eat];
}

关于 class 后续再分享给大家,这里只需要知道可以这么使用就好了。

2、类方法中的 self

这个跟 Java 的 this 有点不一样,上面的 Java 示例中我们可以看到无论是打印 this 还是使用 this 调用方法都不可以,但是在 Objective-C 中却可以使用 self,只是不能使用 self 来调用实例方法和实例变量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
+ (void)eat
{
NSLog(@"---------------- '+ (void)eat' ------------------");
// No known class method for selector 'finish'
// [self finish];
// 调用类方法
[self beat];
// 打印 self
NSLog(@"self: %@", self);
}
+ (void)beat
{
NSLog(@"---------------- '+ (void)beat' ------------------");
}

那么为什么在类方法中可以使用 self 呢?

别着急,接着往下看。

3、实例和类方法中的 self 区别

其实,在类方法中,self 表示当前类对象,在实例方法中 self 表示实例对象,这个是本质区别,务必要理解透彻。

举个例子,如下:

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
- (void)play
{
NSLog(@"---------------- '- (void)play' ------------------");
NSLog(@"self: %@, self -> %p", self, self);
// 打印对应的类地址
NSLog(@"self class: %p", [self class]);
[[self class] eat];
}
+ (void)eat
{
NSLog(@"---------------- '+ (void)eat' ------------------");
// No known class method for selector 'finish'
// [self finish];
// 打印 self 地址
NSLog(@"self: %p", self);
// 调用类方法
[self beat];
}
+ (void)beat
{
NSLog(@"---------------- '+ (void)beat' ------------------");
// 打印 self 地址
NSLog(@"self: %p", self);
}

在实例方法 play 中打印类地址,在类方法 eatbeat 中打印 self 的地址,输出结果是一样的,都是 0x10adb3f98 这个地址。

1
2
3
4
5
6
7
---------------- '- (void)play' ------------------
self: <MZPerson: 0x6000000d8f90>, self -> 0x6000000d8f90
self class: 0x10adb3f98
---------------- '+ (void)eat' ------------------
self: 0x10adb3f98
---------------- '+ (void)beat' ------------------
self: 0x10adb3f98

为了更好的说明,我给大家再举一个形象的例子帮助大家理解。

MZPerson 中声明两个方法,方法同名,一个是实例方法,另一个是类方法,如下:

1
2
3
4
5
6
7
8
9
10
11
@interface MZPerson : NSObject
- (void)play;
+ (void)play;
+ (void)eat;
- (void)finish;
@end
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
@implementation MZPerson
- (void)play
{
NSLog(@"---------------- '- (void)play' ------------------");
}
+ (void)play
{
NSLog(@"---------------- '+ (void)play' ------------------");
}
+ (void)eat
{
NSLog(@"---------------- '+ (void)eat' ------------------");
[self play];
}
- (void)finish
{
NSLog(@"---------------- '- (void)finish' ------------------");
[self play];
}
@end

在类方法 eat 中调用 [self play] 在实例方法 finish 中也调用 [self play],那么结果如何呢?

1
2
3
4
5
---------------- '- (void)finish' ---------------
---------------- '- (void)play' -----------------
---------------- '+ (void)eat' ------------------
---------------- '+ (void)play' -----------------

可以看出符合如期,类和实例方法中的 self 分别代表类本身和实例对象。

self 表示谁,在运行时是由编译器来决定的。

4、每个实例对象的 self 都是不一样的

这个跟 Java 的 this 是一样的,每个类的实例对象对应的 this 都是不一样的,self 亦如此。

下面的例子,分别创建两个 MZPerson 实例对象,然后分别调用play 方法,如下:

1
2
3
4
5
MZPerson *iperson1 = [MZPerson new];
[iperson1 play];
MZPerson *iperson2 = [MZPerson new];
[iperson2 play];

输出结果表明了上面说法的正确性。

1
2
3
4
---------------- '- (void)play' ------------------
self: <MZPerson: 0x600000576ee0>, self -> 0x600000576ee0
---------------- '- (void)play' ------------------
self: <MZPerson: 0x600000576f40>, self -> 0x600000576f40

最后

在继承关系中,使用 self 调用方法时,首先从当前类的方法列表中开始寻找,如果没有再从父类中寻找。

运行时(runtime)会使用 objc_msgSend 向对象发送消息,这个也是调用方法的本质。


扫码关注,你我就各多一个朋友~

坚持原创技术分享!