文章首发于个人blog
欢迎指正补充,可联系lionsom_lin@qq.com
原文地址:OC中属性和成员变量(一)概念篇
目录
- 一、什么是成员变量?
- 成员变量的访问权限
- .h文件与.m文件中的@interface声明成员变量的区别?
- .m文件中@implementation声明变量?
- 二、成员变量与实例变量的区别
- 三、属性
- 3.1、@property 历史
- 3.2、@dynamic - 如果我们不想编译器给我们属性进行自动合成
- 3.3、如何自己重写 setter / getter?
- 3.4、self.myTitle 与 _myTitle的区别、本质、哪个更好?
- 四、属性的特质
- 4.1、原子性关键字
- 4.2、读/写权限
- 4.3、内存管理
- 4.4、方法名
- 五、实际使用属性
- 不要在init 和 dealloc 中使用 accessor 即 self.XX
一、什么是成员变量?
成员变量的访问权限
- @public:在任何地方都能直接访问对象的成员变量
- @private:只能在 当前类 的对象方法中直接访问,如果子类要访问需要调用父类的get/set方法
- @protected:可以在 当前类及其子类对象 方法中直接访问,变量默认的访问权限就是 protected
- @package:对于framework内部的类是@protected的权限,对于外部的类是@private,相当于框架级的保护权限,适合使用在静态库.a中。
场景设置
结论:我们在.m里面声明的变量,子类是无法访问的(即使给他@public),也会被认为是@private,所以我们的对外属性都会放到.h去声明,然而由于 _C 变量是 @private ,所以子类还是无法访问的。
外部调用MidStudent
结论:由于我们没有在Student或他的子类里面,所以只能访问.h中@public修饰的变量,也就是name,由于age是@protrcted在外部是不能被访问的!
.h文件与.m文件中的@interface
声明成员变量的区别?
OC中的分类与类扩展
iOS中,在类的源文件(.m)中,@interface部分的作用?
在.m中的@interface部分为 类扩展(class extention)。
其被设计出来就是为了解决两个问题的,
其一,定义类私有方法的地方。
其二,实现public readonly,private readwrite的property(意思是在h头文件中定义一个属性对外是readonly的,但在类的内部希望是可读写的,所以可以在m源文件中的@interface部分重新定义此属性为readwrite,此时此属性对外是只读的,对内是读写的)。
最后,若在此部分申明变量和属性,但申明的变量,属性和方法均为私有的,只能够被当前类访问,相当于private。
.m文件中@implementation声明变量?
- 如果没有
{}
则为全局变量
@implementation Study_Ivar_Student
int ABC = 10;
- 如果有
{}
则为成员变量,但是为私有的
@implementation Study_Ivar_Student
{
@public
NSString * AAA;
}
@interface VS @implementation 声明变量?
@interface中的是成员变量,子类可继承使用,它的存活周期和创建的实体是一样的,在一个控制器中,随控制器的产生和销毁而创建和销毁;
@implementation下定义的是全局变量,如果加了{}则为成员变量,但是为私有的,否则为全局变量。
二、成员变量与实例变量的区别
#import <UIKit/UIKit.h>
@interface Study_IvarVC : UIViewController
{
UIButton *yourButton;
int count;
id data;
}
@property (nonatomic, strong) UIButton *myButton;
@end
上述哪些是实例变量?哪些是成员变量?
在{ } 中所声明的变量都为成员变量。 所以yourButton、count、data都是成员变量。
既然如此,实例变量又是什么意思呢?
实例变量本质上就是成员变量,只是实例是针对类而言,实例是指类的声明。{ }中的yourButton就是实例变量。id 是OC特有的类,本质上讲id等同于(void *)。所以id data属于实例变量。
可以看到在接口 @interface 括号里面的统称为”成员变量”,实例变量是成员变量中的一种!
实例变量的英文翻译是 Instance Variable (object-specific storage)
实例的英文翻译为Instance(manifestation of a class) 说的是“类的表现”,说明实例变量应该是由类定义的变量!
除去基本数据类型int float ....等,其他类型的变量都叫做实例变量。
实例变量+基本数据类型变量=成员变量
三、属性
3.1、@property 历史
iOS5之前,在苹果使用GCC编译器时候,属性的使用方式是:也就是手动声明实例变量,代码如下:
***.h***
@interface ViewController : UIViewController
{
//属性的实例变量
NSString *myTitle;
}
//编译器遇到@property会自动声明对应的setter/getter
@property (copy, nonatomic) NSString *myTitle;
@end
***.m***
// 编译器遇到@synthesize会自动实现setter/getter方法
// 编译器遇到@synthesize回去访问myTitle的同名变量,如果没找到就报错。
@synthesize myTitle;
iOS5之后,现在,苹果将默认编译器从 GCC转换为LLVM(low level virtual machine),从此 不再需要为属性声明实例变量 了。如果LLVM发现一个没有匹配实例变量的属性,它将自动创建一个以下划线开头的实例变量。因此,在这个版本中,我们不再为输出口声明实例变量。
***.h***
@interface ViewController : UIViewController
// 编译器遇到@property会自动声明对应的setter/getter
@property (copy, nonatomic) NSString *myTitle;
@end
不隐藏代码,显示全部代码:
**.h**
#import <UIKit/UIKit.h>
@interface ViewController : UIViewController
{
/*
***被隐藏的代码:***
1.这个默认是@synthesize myTitle = _myTitle;生成的
2.所以如果我们手动设置@synthesize myTitle,那么我们编译器
生成的变量就是NSString *myTitle,相当于@synthesize myTitle = myTitle,
如果设置@synthesize myTitle = youTitle,那么编译器生成的变量就是NSString *youTitle了
这要注意。
*/
NSString *_myTitle;
}
@property (copy, nonatomic) NSString *myTitle;
//***被隐藏的代码***
//编译器遇到@property会自动声明setter/getter方法
- (void)setMyTitle:(NSString *)myTitle;
- (NSString *)myTitle;
@end
**.m**
/*
***被隐藏的代码***
1.@synthesize关键字会自动实现setter/getter的方法
2.@synthesize myTitle = _myTitle指明了属性myTitle的实例变量是_myTitle,setter/getter操作的对象就是_myTitle.
*/
@synthesize myTitle = _myTitle;
- (void)viewDidLoad {
[super viewDidLoad];
_myTitle = @"123";
}
//***被隐藏的代码***
// 由关键字@synthesize自动实现
- (NSString *)myTitle{
return _myTitle;
}
- (void)setMyTitle:(NSString *)myTitle{
_myTitle = myTitle;
}
代码说明:
1.编译器遇到关键字@property,自动声明setter/getter方法。
2.编译器遇到@synthesize,自动实现setter/getter方法。 注:Xcode 现在会默认自动添加 @synthesize 关键字。
3.@synthesize myTitle = _myTitle;为属性myTitle生成了一个实例变量_myTitle,所以我们对属性的操作self.myTitle实质上都是在操作_myTitle变量。
那么问题来了:
1、我们能否认为新编译器LLVM下的@property
== 老编译器GCC的 成员变量+ @property + @synthesize 成员变量
呢?
答案是否定的
。
因为成员变量+ @property + @synthesize 成员变量
的形式,编译器不会帮我们生成_成员变量
,因此不会操作_成员变量
了;
2、同时@synthesize
还有一个作用,可以指定与属性对应的实例变量,
例如@synthesize myString = xxx;
那么self.myString
其实是操作的实例变量xxx
,而非_String
了。
3.2、@dynamic - 如果我们不想编译器给我们属性进行自动合成
- 第一种方法:自己实现存取方法
如果你只实现了其中一个存取方法,那么另一个还是会由编译器来合成。
- 第二种方法:@dynamic关键字
它会告诉编译器:不要自动创建实现属性所用的实例变量,也不要为其创建存取方法。
并且在编译访问属性代码的时,及时没有定义存取方法,也不会报错,因为它相信这些方法在运行期可以找到。
举个例子,比方说如果从CoreData框架中的NSManagedObject类里面继承一个子类,那么就需要在运行期动态创建存取方法。继承NSManagedObject时之所以这么做,是因为子类的某些属性不是实例变量,其数据来源于后端服务器。代码下:
@interface EOCPerson : NSManagedObject
@property NSString * firstName;
@property NSString * lastName;
@end
@implementation EOCPerson
@dynamic firstName , lastName;
@end
3.3、如何自己重写 setter / getter
?
同时重写 setter/getter
的问题:
我们会发现,当我们同时重写setter/getter
时会报错,为什么呢?这是因为当我们同时重写setter/getter
时,编译器自动添加的代码@synthesize myTitle = _myTitle;
失效,就不会自动为我们生成实例变量_myTitle
了,setter/getter
操作的对象就不存在了。所以我们要加上@synthesize myTitle = _myTitle;
,手动指定setter/getter要操作的实例对象是_myTitle
。
***.h***
@interface Study_PropertyVC : UIViewController
@property (copy, nonatomic) NSString *myTitle;
// 重写setter getter
-(NSString *)myTitle;
-(void)setMyTitle:(NSString *)myTitle;
***.m***
@implementation Study_PropertyVC
/*
@synthesize必须重写
*/
@synthesize myTitle = _newtitle;
// 重写setter getter
- (NSString *)myTitle{
return _newtitle;
}
- (void)setMyTitle:(NSString *)myTitle{
_newtitle = myTitle;
}
3.4、self.myTitle 与 _myTitle的区别、本质、哪个更好?
iOS学习——属性引用self.xx与_xx的区别
iOS之self.xxx与_xxx的区别
iOS的self.xxx和_.xxx的区别
OC中属性self.a与_a访问的区别
本质
self.name
是对属性的访问,是调用的name属性
的getter/setter
方法;
_.name
是对局部变量的访问,等价于self->_name
,并不会调用getter/setter
方法;OC中点表达式(.)其实就是调用对象的setter和getter方法的一种快捷方式。
那么使用哪种方式更好呢?
推荐使用
_XX
的理由:
原因一:《Effective OC2.0》中第7条:在对象内部尽量直接访问实例变量;
原因二:不进过OC方法派发,速度更快;
推荐使用
self.XX
的理由:
原因一:它可以兼容 懒加载;
原因二:避免了使用下划线的时候忽视了self这个指针,例如self->_XX
;
原因三:_XX
更容易造成循环引用;
原因四:使用_XX
是获取不到父类的 属性,因为它只是对局部变量的访问;
原因五:self.XX
使用存在内存管理等优势,更加安全可靠。
�综合结论
在写入实例变量的时候,使用
self.XX
方式,通过其 "设置方法" 来设置;而在读取实例变量时候,则使用_XX
方式。此方法既能提高读取速度,又能保证相关属性的"内存管理语义"。
四、属性的特质
按照性质可以分为四类。(原子性/内存管理语义/读写权限/setter/getter别名)
- 原子性: nonatomic、atomic
- 读写权限:readwrite(读写)、readonly(只读)
- 内存管理:assign、strong、 weak、unsafe_unretained、copy
- 方法名:getter=、setter=
可选值: nullable、nonnull、null_resettable、null_unspecified
其中atomic,nonatomic,copy在setter/getter中实现。
而weak和strong等则是直接作用于成员变量上。
4.1、原子性关键字
默认情况下原子性是atomic
,由编译器所合成的方法使用锁机制保证其原子性。如果属性具备 nonatomic
特质,则不使用同步锁。
4.2、读/写权限
默认是 readwrite
具备 readwrite
(读写)特质的属性拥有 setter / getter
方法。编译器会自动为其生成这两个方法。
具备 readonly
(只读)特质的属性仅拥有 getter
方法。但是你可以用此特质把某个属性对外公开为只读属性,然后在实现文件中将其重定义为读写属性。(可查看《Effective Objective-C 2.0》第27条)
4.3、内存管理
OC知识--彻底理解内存管理(MRC、ARC)
iOS中copy,strong,retain,weak和assign的区别
iOS/OS X内存管理(一):基本概念与原理
assign:setter 只会执行针对简单变量,例如int、float、NSInteger、CGFloat、CGRect等;
strong:此特质表明该属性定义了一种"拥有关系"。为这种属性设置新值。设置方法先保留新值,并释放旧值,然后再将新值设置上去。
weak:此特质表明该属性定义了一种"非拥有关系"。为这种属性设置新值时,设置方法既不保留新值,也不释放旧值。此特质同assign类似,然而在属性所指的对象遭到摧毁时,属性值也会清空。
unsafe_unretained:此特质�与assign相同,但它适用于"对象类型",该特质表达一种"非拥有关系",当目标对象遭到摧毁时,属性值不会自动清空,这点与weak区别。
copy:此特质表达的所属关系与strong类似。然而设置方法并不保留新值,而是将其拷贝。
4.4、方法名
*** .h ***
@property (nonatomic, copy, getter=show1 ,setter=show2:) NSString * myName;
-(NSString *)show1;
-(void)show2:(NSString *)myname;
*** .m ***
@implementation XXX
@synthesize myName = _myname;
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
_myname = @"AAAAA";
LXLog(@"自定义属性方法名 = %@",self.myName);
self.myName = @"DDDDD";
LXLog(@"自定义属性方法名 = %@",self.myName);
}
// 重写setter getter
-(NSString *)show1 {
return _myname;
}
-(void)show2:(NSString *)myName {
_myname = [myName copy];
}
输出Log
-[Study_PropertyVC viewDidLoad] [line: 36] 自定义属性方法名 = AAAAA
-[Study_PropertyVC viewDidLoad] [line: 39] 自定义属性方法名 = DDDDD
五、实际使用属性
《Effective Objective-C 2.0》中第6条,第7条,第18条
*** .h ***
@property (nonatomic, copy, readonly) NSString *firstName;
@property (nonatomic, copy, readonly) NSString *lastName;
// 初始化
-(id)initWithFirstName:(NSString *)firstName
lastName:(NSString *)lastName;
*** .m ***
-(id)initWithFirstName:(NSString *)firstName
lastName:(NSString *)lastName {
if (self = [super init]) {
_firstName = [firstName copy];
_lastName = [lastName copy];
}
return self;
}
注意点
1、应尽量使用不可变的对象,也就是说应该把这个类的两个属性都应该设为 『只读』。 (第18条)
2、由于是『只读』,所以不会创建"设置方法",当我们自定义方法的时候,应该保证其属性相关属性声明的特质,也就是_firstName = [firstName copy];
; (第6条)
3、在对象内部尽量直接访问实例变量,这里也就是_firstName
; (第7条)
5.1、不要在init 和 dealloc 中使用 accessor 即 self.XX
唐巧 - 不要在init和dealloc函数中使用accessor
正确实例
-(id)init {
self = [super init];
if (self) {
_count = [[NSNumber alloc] initWithInteger:0];
}
return self;
}
- (void)dealloc {
[_count release];
[super dealloc];
}
终结总结:在init和dealloc中,一定不要使用
self.XX
方式!!
为什么呢?
去 stackoverflow 上搜了一下,比较不靠谱的说法是这样少一次函数调用,更快。比较靠谱的说法是:在 init 和 dealloc 中,对象的存在与否还不确定,所以给对象发消息可能不会成功。