# iOS 开发常见问题汇总

# _weakSelf使用场景

_weakSelf使用场景

# @property 和@synthesize 和 @dynamic

@property 的本质:

@property = ivar(实例变量) + getter/setter(存取方法);

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

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

@interface MyViewController :UIViewController
{
    NSString *name;
}
@end

@synthesize

@synthesize 表示如果属性没有手动实现 settergetter 方法,编译器会自动加上这两个方法;

使用场景

  1. 如果你已经手动实现了 setter/getter 方法,或着对 只读对象 实现了 getter 方法,那么自动合成不会产生任何影响。如果你在手动实现需要一个变量,只需声明它就可以了,不需要添加@synthesize 来添加一个别名(尽管可以)。

  2. 当在 protocol 中声明并实现属性时。协议中声明的属性不会自动生成 setter 和 getter,[UIApplicationDelegate window] 就是个典型的例子;

  3. 给变量设置别名: 例如我可以这样写@synthesize age = myAge;,那这样子的话我们去调用的时候 self.age 其实是操作的实例变量 myAge,而不是_age 了。此时使用自动生成的实例变量名将报错,只能使用指定的别名

@dynamic

@dynamic 告诉编译器:属性的 setter 与 getter 方法由用户自己实现,不自动生成。假如一个属性被声明为 @dynamic var,而且你没有提供 @setter 方法和 @getter 方法,编译的时候没问题,但是当程序运行到 instance.var = someVar,由于缺 setter 方法会导致程序崩溃;或者当运行到 someVar = var 时,由于缺 getter 方法同样会导致崩溃。编译时没问题,运行时才执行相应的方法,这就是所谓的动态绑定。

# 属性引用 self.xx 与_xx 的区别

本质上: self.xx 是调用的 xx 属性的 get/set 方法,而_xx 则只是使用成员变量_xx,并不会调用 get/set 方法

两者的更深层次的区别在于,通过存取方法访问比直接访问多做了一些其他的事情(例如内存管理,复制值等),例如如果属性在@property 中属性的修饰符有 retain,那么当使用 self.xx 的时候相应的属性的引用计数器由于生成了 setter 方法而进行加 1 操作,此时的 retaincount 为 2。

比较出错的点: 最容易出问题的地方就是对属性 xx 或成员变量_xx 的初始化的地方和调用时机

例子 1

做懒加载的时候,我们将属性和实例变量的初始化放在重写的 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;
}

例子 2

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


- (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不会再自动生成带有下划线的私有成员变量了

# OC 中 覆盖父类属性会有 Auto property synthesis will not synthesize property 'xxx'的警告

根本原因,protocal 中声明的熟悉,默认不自动生成 getter 和 setter,需要使用 @synthesize var = _var 声明属性,以保证存在 getter 和 setter

情景还原 (opens new window)

# 参考

OS 学习——属性引用 self.xx 与_xx 的区别 (opens new window)

【未经作者允许禁止转载】 Last Updated: 10/14/2021, 11:20:21 AM