@property
@property关键字可以自动生成某个成员变量的setter和getter方法的声明
@synthesize关键字会指定一个以下划线 ( _ ) 为前缀,加上属性名的成员变量。 并且由编译器自动进行该属性setter和getter方法的实现
@dynamic关键字,编译器就不会为上面这个类自动合成存取方法或实例变量
xcode4.5之后@property已经会自动为我们提供@synthesize方法
所以一般情况下无需对属性添加 @synthesize ,但一些特殊情形仍然需要,例如protocol中声明的属性。
我们在协议中使用@property声明一个属性,在某个类中遵循这个协议,这时就会报警告,我们必须使用@synthesize来获取这个属性的成员变量,并且得到其set/get的实现函数
同样还有Category
Category能否一样使用@synthesize来合成成员变量以及实现setget方法呢?
并不行
分类不是类,所以我们使用@synthesize也没用,我们可以声明属性,但是不能声明成员变量
从底层结构来说
分类是在运行时去把分类中的方法添加到类的方法列表里,类的底层其实是结构体,分类可以添加属性,不能添加成员变量,因为结构体声明后不能加成员,而属性是结构体里的一个列表(rw:property_array_t / ro: property_list_t),就是可以加的。
正确的做法是我们通过手动的实现set/get,通过关联对象绑定属性值到分类上。
默认设置
- 基本数据类型:atomic,readwrite,assign
- 对象类型:atomic, readwrite,strong
关键字
关键字 | 解释 |
---|
atomic | 原子性访问 | nonatomic | 非原子性访问,多线程并发访问会提高性能 | readwrite | 此标记说明属性会被当成读写的,也就是默认属性 | readonly | 此标记说明属性只可以读,也就是不能设置,可以获取 | strong | ARC默认strong,相当于retain | weak | 1. 只能修饰对象类型;2. ARC 下才能使用;3. 修饰弱引用,不增加对象引用计数,主要可以用于避免循环引用;4. weak 修饰的对象在被释放之后,会自动将指针置为 nil,不会产生悬垂指针 | assign | 1. 既可以修饰基本数据类型,也可以修饰对象类型;2. setter 方法的实现是直接赋值,一般用于基本数据类型 ;3. 修饰基本数据类型,如 NSInteger、BOOL、int、float 等;4. 修饰对象类型时,不增加其引用计数;5. 会产生悬垂指针(悬垂指针:assign 修饰的对象在被释放之后,指针仍然指向原对象地址,该指针变为悬垂指针。这时候如果继续通过该指针访问原对象的话,就可能导致程序崩溃)。 | unsafe_unretain | 与weak类似,但是在销毁时不自动清空,容易形成野指针 | copy | 与strong类似,设置方法会拷贝一份副本一般用于修饰字符串和集合类的不可变变量,Block也是用copy修饰 |
老图新看
原子相关操作
什么是原子性? 并发编程中确保其操作具备整体性,系统其他部分无法观察到中间步骤,只能看到操作前后的结果
atomic
- 默认的原子操作关键字,atomic也就代表其具有原子性。
- 属性关键字会给该property的getter和setter方法加锁,但不能保证线程一定安全并且会带来更多损耗。
atomic加锁只是保证setter和getter的有序性(这里对有序性的理解应该是线程A进行写操作,这时其他线程的读或者写操作会因为该操作而等待)
这样对于单线程来说没啥问题,但是多线程就不能保证线程安全了
比如:线程1调用了A的setter方法,当还没调用完时,线程2调用A的getter方法,但这时候线程2的调用要等线程1的完成才能进行。 但是线程1调用了getter,线程2调用了setter,线程3调用了setter。那么这时候线程1调用getter后得出的值是不确定的,有可能是线程2操作后的值,也可能是线程3操作后的值
在举个例子,一个线程正在进行set/get操作,另一个线程直接强制release,这个时候程序就会crush
这也能看出来其是读写安全 但并不是线程安全
nonatomic
- 非原子性的,不会给setter,getter方法加锁 不保证setter和getter的完整性
- 由于锁定机制开销较大,一般情况下并不要求属性必须是“原子的”,因为这并不能保证“线程安全” ( thread
safety),若要实现“线程安全”的操作,还需采用更为深层的锁定机制才行。例如,一个线程 在连续多次读取某属性值的过程中有别的线程在同时改写该值,那么即便将属性声明为 atomic,也还是会读到不同的属性值。 - 因此,nonatomic的线程不安全,但访问速度快,而atomic由于加速,访问速度会比nonatomic的要慢,但不一定是线程安全的
读写权限
读写权限不写时默认为 readwrite
- readwrite:属性拥有setter方法和getter方法
- readonly:仅有get方法
setter相关修饰符
retain和strong
- strong是对象的默认内存管理关键字,表明该属性定义了一种“持有关系”
- 这两个都是强引用 ,除了某些情况下不一样,其他情况是通用的
- 无论ARC还是MRC,在修饰block属性时,strong修饰block相当于copy,而retain修饰block相当于assign,这样block会出现提前被释放掉的问题
- strong的源码部分我们已经在ARC部分讲解过了
需要注意的就是最后会调用storeStrong,相当于对对象发送release消息 非自己生成并持有的类型会有objc_autoreleasedReturnValue / objc_retainAutoreleaseReturnValue这两兄弟进行对自动释放池的一次优化操作使对象不进池,直接持有。
assign、weak、unsafe_unretain
- 相同之处:都不是强引用
- 不同之处:
-
- weak引用的OC对象被销毁时,指针会自动清空,不再只想销毁的对象,不会产生野指针错误;
-
- unsafe_unretain和assign修饰对象时在引用的OC对象被销毁时,指针不会清空。
-
- assign修饰基本数据类型,内存在栈上由系统自动回收,不会造成野指针的问题。
嗯…assgin怎么看感觉都和unsafe_unretain一样 搜一下
很清晰啊 就Assign和weak来说,仅仅是会不会留下野指针的问题 就assign和unsafe_untained来说,二者用法相同 在iOS4或更低版本,使用unsafe,其他就用assign
strong、copy
如果属性声明中指定了copy特性,合成方法会使用类的copy方法,这里注意:属性并没有mutableCopy特性。即使是可变的实例变量,也是使用copy特性,正如方法 copyWithZone:的执行结果。所以,按照约定会生成一个对象的不可变副本。
- 相同之处:用于修饰标识拥有关系的对象
- 不同之处:strong的赋值是多个指针指向同一个地址,而copy的复制就是每次会在内存中复制一份对象,指针指向不同的地址。 所有对于不可变对象我们应该使用copy修饰,为确保对象中的字符串值不会无意变动,应该在设置新属性时拷贝一份
回顾一下深浅拷贝的知识
深拷贝就是内容拷贝,浅拷贝就是指针拷贝。
深拷贝就是拷贝出和原来仅仅是值一样,但是内存地址完全不一样的新的对象,创建后和原对象没有任何关系。浅拷贝就是拷贝指向原来对象的指针,使原对象的引用计数+1,可以理解为创建了一个指向原对象的新指针而已,并没有创建一个全新的对象。
我们做个测试 分别对.m文件中的不可变字符串类型的name属性使用copy和strong
NSMutableString *otherName = [[NSMutableString alloc] initWithString:@"Jack"];
Person *person = [[Person alloc] init];
person.name = otherName;
person.age = 23;
[otherName appendString:@" and Mary"];
NSLog(@"person.name = %@",person.name);
copy 的结果
strong的结果
- 因为otherName是可变的,person.name属性是copy,所以创建了新的字符串,属于深拷贝,内容拷贝,我们拷贝出来了一个对象,后面的赋值操作都是针对新建的对象进行操作,而我们实际的调用还是原本的对象。所以值并不会改变。
- 如果设置为strong,strong会持有原来的对象,使原来的对象引用计数+1,其实就是浅拷贝、指针拷贝。这时我们进行操作,更改其值就使本对象发生了改变。
我们既然创建的是不可变类型,那我们就不希望其发生改变,所以这个时候我们就应该使用copy关键字,strong会使其在外部被修改时发生改变
那么对于可变类型字符串 ,我们使用copy时,按约定会生成一个对象的不可变副本,此时我们进行增删改操作就会因为找不到对象而崩溃
|