前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >iOS学习——属性引用self.xx与_xx的区别

iOS学习——属性引用self.xx与_xx的区别

作者头像
mukekeheart
发布2018-03-01 10:28:26
8930
发布2018-03-01 10:28:26
举报

 在iOS开发过程中,我们用@proprety声明一个属性后,在代码中我们可以用self.xx与_xx来获取到这个属性。但是一直有一个疑惑,那就是这两个之间有什么区别呢?最初我一直觉得这两个之间没什么区别的,直到有一次,我发现自己明明对声明的属性进行了赋值,但是在使用_xx引用时发现为nil,这才引起我的注意。所以,今天在这里对这个问题进行统一的一个说明和学习。

1 @property 与 @synthesize

  在说self.xx与_xx之前,我们先了解一下@property 以及 @synthesize之间的区别和联系,说到@property 以及 @synthesize,我们就不得不提到iOS中 成员变量和属性 之间的区别和联系了。

  接触iOS的人都知道,@property声明的属性默认会生成一个_类型的成员变量,同时也会生成setter/getter方法。 但这只是在iOS5之后,苹果推出的一个新机制。看老代码时,经常看到一个大括号里面定义了成员变量,同时用了@property声明,而且还在@implementation中使用@synthesize方法,就像下面的代码这样:

代码语言:javascript
复制
@interface ViewController ()
{
   // 1.声明成员变量
    NSString *myString;  
 }
 //2.在用@property
@property(nonatomic, copy) NSString *myString;  
@end

@implementation ViewController
//3.最后在@implementation中用synthesize生成set方法
@synthesize myString;   
@end

其实,发生这种状况根本原因是苹果将默认编译器从GCC转换为LLVM(low level virtual machine),才不再需要为属性声明实例变量了。在没有更改之前,属性的正常写法需要 成员变量 + @property + @synthesize 成员变量 三个步骤。 

如果我们只写成员变量+ @property:

代码语言:javascript
复制
@interface GBViewController :UIViewController
{
    NSString *myString;
}
@property (nonatomic, strong) NSString *myString;
@end
代码语言:javascript
复制
//编译时会报警告:
Autosynthesized property 'myString' will use synthesized instance variable '_myString', not existing instance variable 'myString'

但更换为LLVM之后,编译器在编译过程中发现没有新的实例变量后,就会生成一个下划线开头的实例变量。因此现在我们不必在声明一个实例变量。(注意:==是不必要,不是不可以==) 当然我们也熟知,@property声明的属性不仅仅默认给我们生成一个_类型的成员变量,同时也会生成setter/getter方法。在.m文件中,编译器也会自动的生成一个成员变量_myString。那么在.m文件中可以直接的使用_myString成员变量,也可以通过属性self.myString.都是一样的。注意这里的self.myString其实是调用的myString属性的setter/getter方法

  此外,如果我们再最新的代码中声明一个成员变量,如下代码所示,那么我们只是声明了一个成员变量,并没有setter/getter方法。所以访问成员变量时,可以直接访问name,也可以像C++一样用self->name来访问,但绝对不能用self.name来访问。

代码语言:javascript
复制
@interface MyViewController :UIViewController
{
    NSString *name;
}
@end

从Xcode4.4以后,即iOS的@property已经独揽了@synthesize的功能主要有三个作用:

  1. 生成了成员变量get/set方法的声明
  2. 生成了私有的带下划线的的成员变量因此子类不可以直接访问,但是可以通过get/set方法访问。那么如果想让定义的成员变量让子类直接访问那么只能在.h文件中定义成员变量了,因为它默认是@protected
  3. 生成了get/set方法的实现

值得注意的是:  

  • 如果已经手动实现了get和set方法(两个都实现)的话Xcode不会再自动生成带有下划线的私有成员变量了
  • 因为xCode自动生成成员变量的目的就是为了根据成员变量而生成get/set方法的,但是如果get和set方法缺一个的话都会生成带下划线的变量

2 self.xx与_xx

上面我们说到了属性与成员变量、@property 以及 @synthesize之间的联系与区别。同时,我们提到了self.xx和_xx的一点区别,其中self.xx是调用的xx属性的get/set方法,而_xx则只是使用成员变量_xx,并不会调用get/set方法。两者的更深层次的区别在于,通过存取方法访问比直接访问多做了一些其他的事情(例如内存管理,复制值等),例如如果属性在@property中属性的修饰符有retain,那么当使用self.xx的时候相应的属性的引用计数器由于生成了setter方法而进行加1操作,此时的retaincount为2。

  • 扩展:很多人觉得OC中的点语法比较奇怪,实际是OC设计人员有意为之。
  • 点表达式(.)看起来与C语言中的结构体访问以及java语言汇总的对象访问有点类似,如果点表达式出现在等号  左边,调用该属性名称的setter方法。如果点表达式出现在右边,调用该属性名称的getter方法。
  • OC中点表达式(.)其实就是调用对象的settergetter方法的一种快捷方式,self.myString = @"张三";实际就是[self setmyString:@"张三"];

最后说一下容易出现的问题的地方,根据我个人的经验,最容易出问题的地方就是对属性xx或成员变量_xx的初始化的地方和调用时机,直接通过例子来看,我们将属性和实例变量的初始化放在重写的get方法中,于是我们在 - (void)viewDidLoad 中使用_invoiceInfoImageView来进行布局时,实际上因为在这之前也没有调用invoiceInfoImageView的get方法,所以此时invoiceInfoImageView的值其实为nil,界面上是空白的。

代码语言:javascript
复制
#import "InvoiceTitleInfoViewController.h"

@interface InvoiceTitleInfoViewController ()

//定义属性invoiceInfoImageView
@property (strong, nonatomic) UIImageView *invoiceInfoImageView;

@end

@implementation InvoiceTitleInfoViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    self.title = @"发票抬头";
    
    //用_xx来调用实例变量_invoiceInfoImageView,此时由于没有调用get方法进行初始化,因此此时_invoiceInfoImageView的值为nil
    [self.view addSubview:_invoiceInfoImageView];
    WEAKSELF
    [_invoiceInfoImageView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.top.mas_equalTo(weakSelf.view).mas_offset(0.0f);
        make.left.mas_equalTo(weakSelf.view).mas_offset(15.0f);
        make.right.bottom.mas_equalTo(weakSelf.view).mas_offset(-15.0f);
    }];  
}

//初始化属性invoiceInfoImageView,其实这就是invoiceInfoImageView的get方法
- (UIImageView *)invoiceInfoImageView{
    if (!_invoiceInfoImageView) {
        _invoiceInfoImageView = [[UIImageView alloc] init];
        _invoiceInfoImageView.image = [UIImage imageNamed:@"invoice_title_info"];
    }
    return _invoiceInfoImageView;
}

   如果我们在 使用self.xx来调用变量,则会调用invoiceInfoImageView的get方法,进行初始化,界面布局将会显示我们想要的图片。此外,如果我们再使用_xx之前用self.xx调用过变量invoiceInfoImageView,则同样会调用其get方法从而触发invoiceInfoImageView的初始化,这样也不会影响布局。

代码语言:javascript
复制
- (void)viewDidLoad {
    [super viewDidLoad];
    self.title = @"发票抬头";
    
    //用self.xx来调用invoiceInfoImageView
    [self.view addSubview:self.invoiceInfoImageView];
    WEAKSELF
    [self.invoiceInfoImageView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.top.mas_equalTo(weakSelf.view).mas_offset(0.0f);
        make.left.mas_equalTo(weakSelf.view).mas_offset(15.0f);
        make.right.bottom.mas_equalTo(weakSelf.view).mas_offset(-15.0f);
    }];  
}
代码语言:javascript
复制
- (void)viewDidLoad {
    [super viewDidLoad];
    self.title = @"发票抬头";
    
    //先用self.invoiceInfoImageView触发get方法进行初始化,这样_invoiceInfoImageView的值被初始化后不为nil
    self.invoiceInfoImageView;
    [self.view addSubview:_invoiceInfoImageView];
    WEAKSELF
    [_invoiceInfoImageView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.top.mas_equalTo(weakSelf.view).mas_offset(0.0f);
        make.left.mas_equalTo(weakSelf.view).mas_offset(15.0f);
        make.right.bottom.mas_equalTo(weakSelf.view).mas_offset(-15.0f);
    }]; 
}

  还有一点值得注意的就是我们前面提到过的,如果我们同时手动重写了一个属性的get和set方法的话,Xcode不会再自动生成带有下划线的私有成员变量了。如下图所示,在我们只定义了get方法时一切都没有问题,但是一旦我们又重写set方法,会发现用到_xx的地方就会报错。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2018-01-09 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1 @property 与 @synthesize
  • 2 self.xx与_xx
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档