这是我参与「第四届青训营 」笔记创作活动的第1天
前言
本文主要解决如下几个问题
- 成员变量和实例变量的区别
- 成员变量的访问
- 成员变量的作用域范围
- 属性的作用
- @syntesize关键字的作用
成员变量和实例变量
首先要区分一下什么是成员变量、什么是实例变量。下面这张图定义了一个MyClass类,在@Interface括号里定义的变量统称为成员变量(Member variable declarations)。
实例变量是成员变量的一种,它指的是那些由类定义的变量,除了类定义的变量,还有基本数据类型定义的变量,也就是说,成员变量 = 实例变量+基本数据类型变量。对应于图中的例子,name就是实例变量。
总结一下,成员变量是定义在{}中的变量,如果成员变量的数据类型是一个类,则称这个变量是实例变量。
在下文中我统一用成员变量去描述,偶尔在截图中会出现实例变量的字眼,你要明白他们俩可以被认为是同一个东西。
成员变量的访问
首先我们声明一个Person类,其中有两个成员变量name和age和一个初始化类的方法以及一个打印成员变量的方法print
// Person.h
@interface Person : NSObject {
NSInteger age;
NSString * name;
}
- (instancetype)initWithName:(NSString*)name age:(NSInteger)age;
- (void)print;
@end
// Person.m
@implementation Person
- (instancetype)initWithName:(NSString*)name age:(NSInteger)age{
if(self = [super init]){
self->name = name;
self->age = age;
}
return self;
}
- (void)print{
NSLog(@"名字:%@ 年龄:%ld",self->name,self->age);
}
@end
我们直接实例化Person类并且调用print方法
Person* person = [[Person alloc]initWithName:@"小明" age:18];
[person print];
// 输出
// 名字:小明 年龄:18
从这个例子中可以看到对于成员变量的访问是使用→箭头符号的。
但当我们想要去直接修改成员变量时,会被编译器告知无法修改
这意味着成员变量是存在「作用域限定符」,包括protected、public、private。如果一个成员变量没有任何的作用域限定,默认是@protected
- @private : 作用范围只能在自身类
- @protected : 作用范围在自身类和继承自己的类
- @public :作用范围在任何地方
更具体可以参考苹果的官方文档
因此如果希望修改上述实例的name成员变量的值,需要添@public标识符,值得注意的是,@public之后的所有成员变量都会变成public,即age也会变成public。
// Person.h
@interface Person : NSObject {
@public
NSInteger age;
NSString * name;
}
// mian.h
Person* person = [[Person alloc]initWithName:@"小明" age:18];
[person print];
person->name = @"小红";
[person print];
// 名字:小红 年龄:18
总结一下,如果我们定义了成员变量, 可以用→箭头符号去访问,成员变量是有作用域的,默认是protected,意味着默认的成员变量只能在类以及子类中进行访问和修改,在外部访问需要改成public
属性
我们知道可以用@property来声明一个叫做「属性」的东西,早OC1.0时期,声明了属性的同时必须声明一个与之对应的成员变量
@interface MyViewController :UIViewController
{
UIButton *myButton;
}
@property (nonatomic, retain) UIButton *myButton;
@end
这实在是太啰嗦了,在OC2.0时期使用@property时将自动创建一个以下划线开头的成员变量。在下面我们定义了一个PersonProperty类,它使用Property关键字定义属性。
@interface PersonProperty : NSObject
@property (nonatomic,copy) NSString* name;
@property (nonatomic,assign) NSInteger age;
- (instancetype)initWithName:(NSString*)name age:(NSInteger)age;
- (void)print;
@end
可以看到虽然我们没有显示定义成员变量,但编译器自动为我们生成了「同名带下划线」的成员变量。我们同样可以使用箭头符号→去访问。当然也可以省去self→,直接使用「_变量名」去访问。
编译器自动生成的成员变量是带下划线的,如果不喜欢这个带下划线的变量,我们可以使@synthesize自己指定生成的成员变量的名字。比如下面的例子中我们将name属性自动生成的_name成员变量换成了「我是个奇怪的名」。
提两点
- @syntesize必须放在implementation中使用
- 系统自动生成带下划线的成员变量,等价于用了 @synthesize name = _name; 只是系统隐藏了
- 如果直接使用@synthesize name;_name成员变量会被替换成name
@implementation PersonProperty
@synthesize name = 我是个奇怪的名;
- (instancetype)initWithName:(NSString*)name age:(NSInteger)age{
if(self = [super init]){
self->_age = age;
self->我是个奇怪的名 = name;
}
return self;
}
@end
使用property定义属性后最大的好处是更快速地为实例变量创建存取器,并可以使用点语法使用存取器。点语法的本质是调用setter和getter方法。在property之前,如果要使用存取器,对于每一个成员变量需要如下操作:
-
在.h中文件中声明setter和getter方法
-
在.m文件中实现setter和getter方法
-
在main函数中使用使用消息语法调用setter和getter
我们修改了Person类,来看下面的代码。
// .h
// 定义两个成员变量
// 手动创建存取器
@interface Person : NSObject {
@public
NSInteger age;
NSString * name;
}
// setter
- (void)setAge:(NSInteger)age;
// getter
- (NSInteger)age;
// setter
- (void)setName:(NSString*)name;
// getter
- (NSString*)name;
- (instancetype)initWithName:(NSString*)name age:(NSInteger)age;
- (void)print;
@end
// ========================
// .m
// 实现存取器
@implementation Person
- (instancetype)initWithName:(NSString*)name age:(NSInteger)age{
if(self = [super init]){
self->name = name;
self->age = age;
}
return self;
}
- (void)setAge:(NSInteger)age{
self->age = age;
}
- (NSInteger)age{
return self->age;
}
- (void)setName:(NSString *)name{
self->name = name;
}
- (NSString *)name{
return self->name;
}
@end
//========================
//main.m
//消息语法调用setter和getter
Person* person = [[Person alloc]initWithName:@"小明" age:18];
[person setName:@"小白"];
[person setAge:22];
NSLog(@"名字:%@,年龄:%ld",[person name],[person age]);
// 名字:小白,年龄:22
这个代码量实在是太大了。而@property会自动实现存取器。 可以看到我们使用点语法来给name和age属性赋值,其本质是调用了setAge和setName方法,而且从自动补全中看到依然可以使用消息语法,说明编译器确实自动实现了两个成员变量的存取器,并且隐藏了他们的代码。
// PersonProperty.m
- (void)print{
NSLog(@"名字:%@ 年龄:%ld",self.name,self.age);
}
总结
本文主要是初步介绍了property。首先本文区分了成员变量和实例变量的区别,实例变量是成员变量的一部分。我们可以使用箭头符号去访问成员变量,成员变量有自己的作用域范围,用不同的关键词修饰。property属性定义的时候会自动创建带下划线的成员变量,为成员变量自动创建存取器,并且可以使用点语法访问。