iOS property (property)

Property (property) is used to encapsulate the data in the object, the most commonly used and most convenient variable declaration method in iOS development, allowing us to use dot syntax to access the instance variables of the object. This article mainly introduces the properties of iOS from the implementation principle to the application in detail.

1. What is the attribute

属性 = 成员变量 + set方法 + get方法。
复制代码

When we declare an attribute str, during the compilation phase, the compiler will automatically add an instance variable _str and its access method to the - (void)setStr:(NSString *)strobject - (NSString *)str. Since this process is automatically generated during the compilation phase, we cannot see it during the editing phase. There is a prerequisite for adding instance variables, that is, the object does not have a member variable with the same name, that is, if there is already _str, it will not be added. We can verify this with runtime.

#import "ViewController.h"

#include <objc/runtime.h>

@interface ViewController ()

@property (nonatomic, strong) NSString *str;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    unsigned int count = 0;
    Ivar *varList = class_copyIvarList([self class], &count);
    for (unsigned int i = 0; i < count; i++) {
        const char *varName = ivar_getName(varList[i]);
        printf("成员变量-------%s\n",varName);
    }
    
    Method *methodList = class_copyMethodList([self class], &count);
    for (unsigned int i = 0; i < count; i++) {
        SEL methodName = method_getName(methodList[i]);
        NSLog(@"方法-------%@",NSStringFromSelector(methodName));
    }
}

@end
复制代码

Printed log.

成员变量-------_str
2018-07-13 10:32:09.115049+0800 TestAppIOS[445:214283] 方法-------str
2018-07-13 10:32:09.115161+0800 TestAppIOS[445:214283] 方法-------setStr:
2018-07-13 10:32:09.115233+0800 TestAppIOS[445:214283] 方法-------.cxx_destruct
2018-07-13 10:32:09.115264+0800 TestAppIOS[445:214283] 方法-------viewDidLoad
复制代码

At the same time, we can customize the access method of the property. If we define one, the compiler will still generate another one for us. For example, if we customize the get method, the compiler will generate a set method for us. If we also customize the access method of the property, the compiler will not declare the instance variable for the object. The reason why the compiler can automatically synthesize the access method of the property is because in the formal Objective-C coding style, the access method has strict naming conventions.

2.@synthesize

@property (strong,nonatomic, readwrite) NSString *str;
@synthesize str = mystr;
@dynamic str;
复制代码

The @synthesize keyword has two main functions, and it is rarely used under ARC.

1. Under MRC, @synthesize strin this way, the compiler will automatically synthesize the access method of str. But it is not necessary under ARC, @synthesize strthe compiler will automatically synthesize the access method of str no matter whether you are or not.

2.如果你声明的属性是str,系统自动给你添加的成员变量是_str,如果你对这个变量名字不满,可以这样@synthesize str = mystr;,自己给个名字。这样系统给添加的成员变量就是myStr,而不是_str,但是变量的存取方法没有变化。不过我建议最好不要这么办,因为都按照约定成俗的方式来命名变量,代码的可读性较高,大家都理解,所以我建议大家最好不要用这个关键字。

3.@dynamic

@dynamic 关键字主要是告诉编译器不用为我们自动合成变量的存取方法, 我们会自己实现。即使我们没有实现,编译器也不会警告,因为它相信在运行阶段会实现。如果我们没有实现还调用了,就会报这个错误'-[ViewController setStr:]: unrecognized selector sent to instance 0x10040af10'

@dynamic还有一个应用。 父类

#import <Foundation/Foundation.h>

@interface Father : NSObject

@property (nonatomic, strong) NSString *str;

@end
复制代码

子类

#import "Father.h"

@interface Son : Father

@property (nonatomic, strong) NSString *str;

@end

复制代码

如果我们在子类中重写父类的属性,就会报下面的警告

Auto property synthesis will not synthesize property 'str'; it will be implemented by its superclass, use @dynamic to acknowledge intention
复制代码

因为我们同时在父类和子类中同时声明了str的属性,系统就不知道该在哪里(父类Father还是子类Son?)自动合成str的存取方法,系统默认是在父类中声明,因为子类可以调用。不过,系统希望我们显式的声明这一点,这样有利于提高代码的可读性。

#import "Son.h"

@implementation Son

@dynamic str;

@end
复制代码

4.属性关键字

属性关键字主要解决3个问题,数据安全,定义读写权限,内存管理。

4.1 读写权限:readonly,readwrite

属性默认都是readwrite的,表示可读可写,set/get方法编译器都会自动合成。如果用readonly修成属性,表示该属性是只读的,编译器只会自动合成get方法,不会合成set方法。另外,我们可以在.h文件中用readonly中修饰属性,在.m文件类扩展中用readwrite修饰同一个属性,这样来防止外界修改该属性。

4.2 原子性:nonatomic,atomic

在并发编程中,如果某操作是具有整体性的,也就是说,系统其它部分无法观察到其中间步骤生成的临时结果,而只能看到操作前与操作后的结果。那么该操作就是原子的,或者该操作具有原子性。在默认情况下,由编译器合成的set方法通过同步锁的机制保证其原子性(get方法没有加锁)。atomic修饰的属性的存取方法是线程安全的,nonatomic修饰的属性不具有原子性,不使用同步锁。自己定义属性的存取方法的时候,也应该遵守与属性特质相符的原子性。

// atomic原子性的实现
- (void)setStr:(NSString *)str{
     @synchronized(self) {
         _str = str;
     }
}
复制代码

由于atomic具有一定的性能开销,所以我们最好把所有的属性都用nonatomic修饰。而且原子性也不能保证线程安全,因为如果绕过set方法给属性赋值,就是线程不安全的了。属性没有atomic关键字,不过你用atomic修饰属性,编译器也不会报错。

4.3 内存管理语义:strong,weak,assign,copy,unsafe_unretained

这些关键字仅会影响属性的set方法,编译器根据不同的关键字生成不同的代码。当我们自定义set方法的时候,也应该符合属性所具备的特质。除了assign可以用来修饰基本数据类型外,其他的关键字都只能用来修饰对象。

**strong:**表示一种“拥有关系”。为属性设置新值的时候,设置方法会先保留新值(新值的引用计数加一),并释放旧值(旧值的引用计数减一),然后将新值赋值上去。相当于MRC下的retain。

在.h文件中,尽量不要用strong修饰属性,尤其是集合。如果这个集合是可变的,多个线程同时修改这个集合,很容易崩溃。

**weak:**表示一种“非拥有关系”。用weak修饰属性的时候,为属性设置新值的时候,设置方法既不会保留新值(新值的引用计数加一),也不会释放旧值(旧值的引用计数减一)。当属性所指的对象释放的时候,属性也会被置为nil。用于修饰UI控件,代理(delegate)。

assign:可以同时用来修饰基本数据(NSInteger,CGFloat等)类型和对象。当assign用来修饰对象的时候,和weak类似。唯一的区别就是当属性所指的对象释放的时候,属性不会被置为nil,这就会产生野指针。

copy:修饰的属性设置新值的时候,当新值是不可变的,和strong是一模一样的。当新值是可变的(开头是NSMutable),设置方法不会保留新值(新值的引用计数加一),而是对新值copy一份,不会影响新值的引用计数。copy常用来修饰NSString,因为当新值是可变的,防止属性在不知不觉中被修改。

unsafe_unretained:用来修饰属性的时候,和assing修饰对象的时候是一模一样的。为属性设置新值的时候,设置方法既不会保留新值(新值的引用计数加一),也不会释放旧值(旧值的引用计数减一)。唯一的区别就是当属性所指的对象释放的时候,属性不会被置为nil,这就会产生野指针,所以是不安全的。

おすすめ

転載: juejin.im/post/7225856176130031675