定义
OC提供了一个self关键字,self关键字总是指向调用该方法的对象。self关键字最大的作用是让类中的一个方法访问该类的另一个方法或成员变量。
事例
我们先来看一个例子:
我们先定义一个FKDog类,这个FKDog对象的run方法需要调用它的jump方法,那么应该如何实现呢。
#import<Foundation/Foundation.h>
@interface FKDog : NSObject
//定义一个jump方法
-(void) jump;
//定义一个run方法
-(void) run;
@end
//定义接口部分
下面是FKDog的实现部分:
#import"FKDog.h"
@implementation FKDog
//实现一个jump方法
-(void) jump
{
NSLog(@"正在执行jump方法");
}
//实现一个run方法,run方法需要借助jump方法
-(void) run
{
FKDog* d = [[FKDog alloc] init];
[d jump]
NSLog(@"正在执行run方法");
}
@end
这种定义FKDog类的方法,确实可以实现在run方法中调用jump方法,下面再提供一个程序来创建FKDog对象,并调用该对象的run方法。
程序清单:codes/05/5.1/FKDogTest.m
#import<Foundation/Foundation.h>
#import"FKDog.h"
int main(int argc , char * argv[])
{
@autoreleasepool{
FKDog* dog = [[FKDog alloc] init];
[dog run];
}
}
在上面的程序中,一共产生了两个FKDog对象,在实现部分,FKDog类的run方法中,创建了一个FKDog对象,并使用名为d的指针变量来指向该FKDog对象;在main()函数中,程序再次创建了一个FKDog对象,并使用名为dog的指针变量来指向该FKDog对象。
其实,我们在run方法中调用jump方法时一定需要一个FKDog对象,但不是非要重新创建一个FKDog对象。因为当程序调用run方法时,一定会提供一个FKDog对象,这样就可以直接使用已经存在的FKDog对象,而无需重新创建FKDog对象。
所以,我们需要在run方法中获得调用该方法的对象,通过self关键字就可以满足这个要求。
self关键字的用法
self关键字的理解
self总是代表当前类的对象,当self出现在某个方法体中时,它代表的对象是不确定的,但它的类型是确定的,它所代表的对象只是当前类的实例;当这个方法被调用时,它所代表的对象才被确定下来:谁在调用这个方法,self就代表谁。由此可见,self不能出现在类方法中。因为类方法的调用者时类本身,而不是对象,如果在类方法中使用self关键字,这个self关键字就不能确定代表谁了。
self关键字的使用
首先,我们将前面定义的Dog类的实现部分改成如下形式:
#import "FKDog.h"
@implementation FKDog
//实现一个jump方法
-(void) jump
{
NSLog(@"正在执行jump方法");
}
//实现一个run方法,run方法需要借助jump方法
-(void) run
{
[self jump];
NSLog(@"正在执行run方法");
}
从前一种FKDog类定义来看,在FKDog对象的run方法内重新创建了一个新的FKDog对象,并调用了新创建的FKDog的jump方法,这意味着一个FKDog对象的run方法依赖于另一个FKDog对象的jump方法,这不符合逻辑。而第二种方法,当一个FKDog对象调用run方法时,run方法依赖于它自己的jump方法,显而易见,更加合理。
self关键字的拓展
当局部变量和成员变量重名的情况下,局部变量会隐藏成员变量。为了在方法中强行引用成员变量,也可以使用self关键字进行区分。例如下面定义了FKWolf类的接口部分。
#import<Foundation/Foundation.h>
@interface FKWolf : NSObject
{
NSString* _name;
int _age;
}
//定义一个setName :ageAge方法
-(void) setName: (NSString*) _name andAge : (int) _age;
//定义一个info方法
-(void)info;
@end
上面FKWolf类接口部分定义了一个setName: andAge: 方法,接下来会定义实现部分为该方法提供实现,在实现方法,我们故意让形参与成员变量重名,然后通过self强行指定访问成员变量。
#import"FKWolf.h"
@implementation FKWolf
//定义一个setName:andAge方法
-(void)setName: (NSString*)_name andAge:(int)_age
{
//当局部变量隐藏成员变量时
//可用self代表调用该方法的对象,这样即可为调用该方法的成员变量赋值
self->_name = _name;
self->_age = _age;
}
//定义一个info方法
-(void) info
{
NSLog(@"我的名字是%@, 年龄是%d岁“, _name, _age);
}
@end
int main(int argc , char * argv[])
{
@autoreleasepool{
FKWolf* w = [[FKWolf alloc] init];
[w setName: @"灰太狼" andAge :8];
[w info];
}
}
在上面程序的setName:andAge:方法中,由于_name、_age形参隐藏了_name、_age成员变量,因此编译器会提示警告,但由于程序中使用了self->_name、self->_age来指定为调用该方法的FKWolf对象的_name、_age成员变量赋值,这样就可以把调用该方法时传入的参数赋值给__name、_age两个成员变量。
self关键字的返回值问题
因为当self作为对象的默认引用使用时,程序可以像访问普通指针变量一样访问这个self引用,甚至可以把self当成普通方法的返回值。
#import<Foundation/Foundation.h>
@interface ReturnSelf : NSObject
{
@public //用于暴露位于它下面的所有成员变量
int _age;
}
- (ReturnSelf*) grow;
@end
@implementation ReturnSelf
-(ReturnSelf *) grow
{
_age++;
//return self, 返回调用该方法的对象
return self;
}
@end
int main(int argc, char * argv[])
{
@autoreleasepool{
ReturnSelf* rt = [[ReturnSelf alloc] init];
//可以联系调用同一个方法;
[[rt grow] grow] grow];
NSLog(@"rt的_age成员变量的值是:%d", rt->_age);
}
}
从上面的程序可以看出,如果某个方法中把self作为返回值,则可以连续调用同一个方法,从而使代码更简单。但是,这种把self作为返回值的方法容易造成实际意义的模糊,例如上面的grow方法,用于表示对象的生长,即_age成员变量的值加1,实际上是不需要有返回值的,它只是表示一种状态,一种生长状态。
id类型
定义
OC定义了一个id类型,这个id类型可以代表所有对象的类型。所以id类型可以说是一个万能指针。当我们通过id类型的变量来调用方法时,OC将会执行动态绑定。所谓动态绑定,是指:OC将会跟踪对象所属的类,它会在运行时判断该对象所属的类,并在运行时确定需要动态调用的方法,而不是在编译时确定要调用的方法。
id类型使用事例
如下main()函数中将会定义一个id类型的变量。
#import<Foundation/Foundation.h>
#import"FKPerson.h"
int main(int argc , char * argv[])
{
@autoreleasepool{
//定义id类型的变量,并将FKPerson对象赋给该变量
id p = [[FKPerson alloc] init];
//使用p变量调用say:方法。
//程序将在运行时执行动态绑定,因此实际执行FKPerson对象的say:方法
[p say: @"你好,疯狂iOS讲义“];
}
}
上面的程序定义了一个id类型的变量,并将一个FKPerson对象赋给了id类型的变量p,接下来通过该id类型的变量就可以调用say:方法了,程序将在运行时动态检测该变量所指的对象的实际类型为FKPerson,因此,将会动态绑定到执行FKPerson对象的say:方法。