zoukankan      html  css  js  c++  java
  • 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的功能主要有三个作用:

    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,界面上是空白的。

    #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 

  • 相关阅读:
    【Vjudge】P1989Subpalindromes(线段树)
    【Luogu】P3358最长k可重区间集问题(费用流)
    【未有之有】洛森设定随笔
    14-Perl 引用
    13-Perl 子程序(函数)
    12-Perl 时间日期
    11-Perl 运算符
    10-Perl 循环
    9-Perl 条件语句
    8-Perl 哈希
  • 原文地址:https://www.cnblogs.com/mukekeheart/p/8251366.html
Copyright © 2011-2022 走看看