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

 在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方法,就像下面的代码这样:

@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:

@interface GBViewController :UIViewController

{

    NSString *myString;

}

@property (nonatomic, strong) NSString *myString;@end

//编译时会报警告: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来访问。

@interface MyViewController :UIViewController

{

    NSString *name;

}@end

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

生成了成员变量get/set方法的声明

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

生成了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中点表达式(.)其实就是调用对象的setter和getter方法的一种快捷方式,self.myString = @"张三";实际就是[self setmyString:@"张三"];

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

#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的初始化,这样也不会影响布局。

- (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);

    }]; 

}

- (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的地方就会报错。




  我的博客即将搬运同步至腾讯云+社区,邀请大家一同入驻:https://cloud.tencent.com/developer/support-plan 

你可能感兴趣的:(iOS学习——属性引用self.xx与_xx的区别)