@property相关问题

@property 的本质是什么?ivar、getter、setter 是如何生成并添加到这个类中的

@property 的本质是什么?

@property = ivar + getter + setter;

下面解释下:

“属性” (property)有两大概念:ivar(实例变量)、存取方法(access method = getter + setter)

例如下面这个类:

@interface Person : NSObject
@property NSString *firstName;
@property NSString *lastName;
@end

上述代码写出来的类与下面这种写法等效:

@interface Person : NSObject
- (NSString *)firstName;
- (void)setFirstName:(NSString *)firstName;
- (NSString *)lastName;
- (void)setLastName:(NSString *)lastName;
@end		

ivar、getter、setter 是如何生成并添加到这个类中的?

“自动合成”( autosynthesis)

完成属性定义后,编译器会自动编写访问这些属性所需的方法,此过程叫做“自动合成”(autosynthesis)。需要强调的是,这个过程由编译 器在编译期执行,所以编辑器里看不到这些“合成方法”(synthesized method)的源代码。除了生成方法代码 getter、setter 之外,编译器还要自动向类中添加适当类型的实例变量,并且在属性名前面加下划线,以此作为实例变量的名字。在前例中,会生成两个实例变量,其名称分别为 _firstName 与 _lastName。

也可以在类的实现代码里通过 @synthesize 语法来指定实例变量的名字.

@implementation Person
@synthesize firstName = _myFirstName;
@synthesize lastName = _myLastName;
@end	

runtime 如何实现 weak 属性

要实现 weak 属性,首先要搞清楚 weak 属性的特点:

weak 此特质表明该属性定义了一种“非拥有关系” (nonowning relationship)。为这种属性设置新值时,设置方法既不保留新值,也不释放旧值。此特质同 assign 类似, 然而在属性所指的对象遭到摧毁时,属性值也会清空(nil out)。

那么 runtime 如何实现 weak 变量的自动置nil?

runtime 对注册的类, 会进行布局,对于 weak 对象会放入一个 hash 表中。 用 weak 指向的对象内存地址作为 key,当此对象的引用计数为0的时候会 dealloc,假如 weak 指向的对象内存地址是a,那么就会以a为键, 在这个 weak 表中搜索,找到所有以a为键的 weak 对象,从而设置为 nil。


@property中有哪些属性关键字?/ @property 后面可以有哪些修饰符?

属性可以拥有的特质分为四类:

  1. 原子性--- nonatomic 特质

    在默认情况下,由编译器合成的方法会通过锁定机制确保其原子性(atomicity)。如果属性具备 nonatomic 特质,则不使用同步锁。请注意,尽管没有名为“atomic”的特质(如果某属性不具备 nonatomic 特质,那它就是“原子的” ( atomic) ),但是仍然可以在属性特质中写明这一点,编译器不会报错。若是自己定义存取方法,那么就应该遵从与属性特质相符的原子性。

  2. 读/写权限---readwrite(读写)、readonly (只读)

  3. 内存管理语义---assign、strong、 weak、unsafe_unretained、copy

  4. 方法名---getter= 、setter=

getter=的样式:

  @property (nonatomic, getter=isOn) BOOL on;

setter=这种不常用,也不推荐使用。故不在这里给出写法。)

setter=一般用在特殊的情境下,比如:

在数据反序列化、转模型的过程中,服务器返回的字段如果以 init 开头,所以你需要定义一个 init 开头的属性,但默认生成的 setter 与 getter 方法也会以 init 开头,而编译器会把所有以 init 开头的方法当成初始化方法,而初始化方法只能返回 self 类型,因此编译器会报错。

这时你就可以使用下面的方式来避免编译器报错:

@property(nonatomic, strong, getter=p_initBy, setter=setP_initBy:)NSString *initBy;


@synthesize和@dynamic分别有什么作用?

  1. @property有两个对应的词,一个是 @synthesize,一个是 @dynamic。如果 @synthesize和 @dynamic都没写,那么默认的就是@syntheszie var = _var;

  2. @synthesize 的语义是如果你没有手动实现 setter 方法和 getter 方法,那么编译器会自动为你加上这两个方法。

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


ARC下,不显式指定任何属性关键字时,默认的关键字都有哪些?

  1. 对应基本数据类型默认关键字是

atomic,readwrite,assign

  1. 对于普通的 Objective-C 对象

atomic,readwrite,strong


用@property声明的NSString(或NSArray,NSDictionary)经常使用copy关键字,为什么?如果改用strong关键字,可能造成什么问题?

  1. 因为父类指针可以指向子类对象,使用 copy 的目的是为了让本对象的属性不受外界影响,使用 copy 无论给我传入是一个可变对象还是不可对象,我本身持有的就是一个不可变的副本.
    如果我们使用是 strong ,那么这个属性就有可能指向一个可变对象,如果这个可变对象在外部被修改了,那么会影响该属性.

  2. copy 此特质所表达的所属关系与 strong 类似。然而设置方法并不保留新值,而是将其“拷贝” (copy)。 当属性类型为 NSString 时,经常用此特质来保护其封装性,因为传递给设置方法的新值有可能指向一个 NSMutableString 类的实例。这个类是 NSString 的子类,表示一种可修改其值的字符串,此时若是不拷贝字符串,那么设置完属性之后,字符串的值就可能会在对象不知情的情况下遭人更改。所以,这时就要拷贝一份“不可变” (immutable)的字符串,确保对象中的字符串值不会无意间变动。只要实现属性所用的对象是“可变的” (mutable),就应该在设置新属性值时拷贝一份。

例如:定义一个以 strong 修饰的 array:

@property (nonatomic ,readwrite, strong) NSArray *array;

然后进行下面的操作:

NSMutableArray *mutableArray = [[NSMutableArray alloc] init];
NSArray *array = @[ @1, @2, @3, @4 ];
self.array = mutableArray;
[mutableArray removeAllObjects];;
NSLog(@"%@",self.array);

[mutableArray addObjectsFromArray:array];
self.array = [mutableArray copy];
[mutableArray removeAllObjects];;
NSLog(@"%@",self.array);

打印结果如下所示:

2015-09-27 19:10:32.523 CYLArrayCopyDmo[10681:713670] (
)
2015-09-27 19:10:32.524 CYLArrayCopyDmo[10681:713670] (
1,
2,
3,
4
)

在有了自动合成属性实例变量之后,@synthesize还有哪些使用场景?

回答这个问题前,我们要搞清楚一个问题,什么情况下不会autosynthesis(自动合成)?

  1. 同时重写了 setter 和 getter 时

  2. 重写了只读属性的 getter 时

  3. 使用了 @dynamic 时

  4. 在 @protocol 中定义的所有属性

  5. 在 category 中定义的所有属性

  6. 重载的属性

    当你在子类中重载了父类中的属性,你必须 使用 @synthesize 来手动合成ivar。

除了后三条,对其他几个我们可以总结出一个规律:当你想手动管理 @property 的所有内容时,你就会尝试通过实现 @property 的所有“存取方法”(the accessor methods)或者使用 @dynamic 来达到这个目的,这时编译器就会认为你打算手动管理 @property,于是编译器就禁用了 autosynthesis(自动合成)。

readwrite,readonly,assign,retain,copy,nonatomic 属性的作用

readwrite,readonly

  • 设置可供访问级别

assign(默认)

  • setter方法直接赋值,不更改索引计数(即不进行retain操作)
  • 为了解决原类型与环循引用问题
  • 对基础数据类型 (NSInteger)和C数据类型(int, float, double, char, 等)

retain

  • setter方法对参数进行release旧值再retain新值

  • 释放旧的对象,将旧对象的值赋予输入对象,再提高输入对象的索引计数为1

  • 此属性只能用于Objective-C对象类型,而不能用于Core Foundation对象。(原因很明显,retain会增加对象的引用计数,而基本数据类型或者Core Foundation对象都没有引用计数)。

      注意: 把对象添加到数组中时,引用计数将增加对象的引用次数+1。
    

assign与retain:

  • 假设你用malloc分配了一块内存,并且把它的地址赋值给了指针a,后来你希望指针b也共享这块内存,于是你又把a赋值给 (assign)了b。此时a和b指向同一块内存,请问当a不再需要这块内存,能否直接释放它?答案是否定的,因为a并不知道b是否还在使用这块内存,如 果a释放了,那么b在使用这块内存的时候会引起程序crash掉。
  • 了解到上面中assign的问题,那么如何解决?最简单的一个方法就是使用引用计数(reference counting),还是上面的那个例子,我们给那块内存设一个引用计数,当内存被分配并且赋值给a时,引用计数是1。当把a赋值给b时引用计数增加到 2。这时如果a不再使用这块内存,它只需要把引用计数减1,表明自己不再拥有这块内存。b不再使用这块内存时也把引用计数减1。当引用计数变为0的时候, 代表该内存不再被任何指针所引用,系统可以把它直接释放掉。
    总结:上面两点其实就是assign和retain的区别,assign就是直接赋值,从而可能引起1中的问题,当数据为int, float等原生类型时,可以使用assign。retain就如2中所述,使用了引用计数,retain引起引用计数加1, release引起引用计数减1,当引用计数为0时,dealloc函数被调用,内存被回收。

copy

  • 对 NSString

       此属性只对那些实行了NSCopying协议的对象类型有效。 
    
    • 当属性类型为 NSString 时,经常用此特质来保护其封装性,因为传递给设置方法的新值有可能指向一个 NSMutableString 类的实例。这个类是 NSString 的子类,表示一种可修改其值的字符串,此时若是不拷贝字符串,那么设置完属性之后,字符串的值就可能会在对象不知情的情况下遭人更改。所以,这时就要拷贝一份“不可变” (immutable)的字符串,确保对象中的字符串值不会无意间变动。只要实现属性所用的对象是“可变的” (mutable),就应该在设置新属性值时拷贝一份。
  • 建立一个索引计数为1的对象,然后释放旧对象

  • setter方法进行Copy操作,与retain处理流程一样,先旧值release,再 Copy出新的对象,retainCount为1。

  • 这是为了减少对上下文的依赖而引入的机制

  • copy与retain的区别:

    • copy是创建一个新对象,retain是创建一个指针,引用对象计数加1。
    • retain 是指针拷贝(指针相同,内容相同),copy 是内容拷贝(指针不同,内容相同)。

nonatomic,

  • 非原子性访问,不加同步,多线程并发访问会提高性能。注意,如果不加此属性,则默认是两个访问方法都为原子型事务访问。锁被加到所属对象实例级

atomic(默认)

  • 为setter方法加锁(默认就是atomic)而这种机制是耗费系统资源的,

  • 如果有多个线程同时调用setter的话,不会出现某一个线程执行setter全部语句之前,另一个线程开始执行setter情况,相当于函数头尾加了锁一样。

      @synchronized(self) { 
      	_age = age;
      }
    

assign vs weak

修饰对象

  • assign适用于基本数据类型,“纯量类型” (scalar type,例如 CGFloat 或 NSlnteger 等)
  • weak是适用于NSObject对象,并且是一个弱引用。

指针地址改变

  • assign其实也可以用来修饰对象,那么我们为什么不用它呢?因为被assign修饰的对象在释放之后,指针的地址还是存在的,也就是说指针并没有被置为nil。如果在后续的内存分配中,刚好分到了这块地址,程序就会崩溃掉。
  • 而weak修饰的对象在释放之后,指针地址会被置为nil。所以现在一般弱引用就是用weak。
  • assigin 可以用非 OC 对象,而 weak 必须用于 OC 对象

这个写法会出什么问题: @property (copy) NSMutableArray *array;

两个问题:

  1. 添加,删除,修改数组内的元素的时候,程序会因为找不到对应的方法而崩溃.因为 copy 就是复制一个不可变 NSArray 的对象;

  2. 使用了 atomic 属性会严重影响性能 ;

__block vs __weak

在block中修改变量

  • Blocks可以访问局部变量,但是不能修改,如果修改局部变量,需要加__block
  • _ _block:使用__block修饰的变量在block代码快中会被retain(ARC下,MRC下不会retain)
  • _ _weak:使用__weak修饰的变量不会在block代码块中被retain

避免循环引用

  • 要避免block出现循环引用 __weak typedof(self)weakSelf = self;
  • 为什么不用__block 是因为通过引用来访问self的实例变量 ,self被retain,block也是一个强引用,引起循环引用,用__week是弱引用,当self释放时,weakSelf已经等于nil。

使用atomic一定是线程安全的吗

不是的

  • atomic仅限于getter,setter时的线程安全。
  • 在一个线程执行setter方法的时候,会涉及到字符串拷贝,另一个线程去读取,很可能读到一半的数据,也就是garbage数据。
  • 比如@property(atomic,strong)NSMutableArray *arr;如果一个线程循环读数据,一个线程循环写数据,肯定会产生内存问题。因为它和setter,getter没有关系。

retain cycle例子

block中的循环引用:一个viewController

@property (nonatomic,strong)HttpRequestHandler * handler;
@property (nonatomic,strong)NSData          *data;
_handler = [httpRequestHandler sharedManager];
[ downloadData:^(id responseData){
    _data = responseData;
}];

self 拥有_handler, _handler 拥有block, block拥有self(因为使用了self的_data属性,block会copy 一份self)

vc异步的网络请求,成功后的block调用vc,如果此时,用户已经不用此vc了,vc还是没有释放。

posted @ 2016-03-21 14:47  孙焱焱  阅读(433)  评论(0编辑  收藏  举报