OC中属性和成员变量(一)概念篇

文章首发于个人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中。

场景设置

Student父类
MidStudent子类

结论:我们在.m里面声明的变量,子类是无法访问的(即使给他@public),也会被认为是@private,所以我们的对外属性都会放到.h去声明,然而由于 _C 变量是 @private ,所以子类还是无法访问的。

外部调用MidStudent

外部创建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下定义的是全局变量,如果加了{}则为成员变量,但是为私有的,否则为全局变量。

二、成员变量与实例变量的区别

iOS 中成员变量、实例变量、属性 三者区别与联系

#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 ....等,其他类型的变量都叫做实例变量。
实例变量+基本数据类型变量=成员变量

三、属性

IOS属性的作用
OC中在.h和.m中声明的属性和成员变量

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 使用存在内存管理等优势,更加安全可靠。

原因四示例图:_myFFF 访问不到父类属性
�综合结论

在写入实例变量的时候,使用 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 中,对象的存在与否还不确定,所以给对象发消息可能不会成功。

参考文档

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,117评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,963评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,897评论 0 240
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,805评论 0 203
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,208评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,535评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,797评论 2 311
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,493评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,215评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,477评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,988评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,325评论 2 252
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,971评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,055评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,807评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,544评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,455评论 2 266

推荐阅读更多精彩内容