@property & 拷贝

1. @property 后面可以有哪些修饰符?

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

1)原子性:

  • atomic:(默认) 同步锁,会在创建时生成一些额外的代码用于帮助编写多线程程序,这会带来性能问题,通过声明 nonatomic 可以节省这些虽然很小但是不必要额外开销。
  • nonatomic:(一般情况下使用)

2)读写权限:

  • readwrite:读写(setter & getter)
  • readonly:只读(getter)

3)内存管理语义:

  • assign:只针对纯量类型的简单赋值操作。一般用于 ‘基本数据类型’、‘枚举’、‘结构体’ 等非OC对象类型

  • strong:表明一种拥有关系,为这种属性赋新值的时候,设置方法会先保留新值,并释放旧值,然后再将新值设置上去。一般用于OC对象类型(NSArray、NSDate、NSNumber、模型类)

  • weak:表明一种非拥有关系,为这种属性赋新值的时候,设置方法既不保留新值,也不释放旧值。此特质同assign相似,但是当属性所指的对象遭到销毁的时候,属性值会清空(nil out)。一般应用: 代理对象

  • unsafe_unretained:此特质同assign相似,但是适用于对象类型,表明一种非拥有关系,但是当属性所指的对象遭到销毁的时候,属性值不会清空。

  • copy:此特质同strong类似。但是设置方法并不会保留新值,而是将其拷贝。(一般用于NSString&Block)

注:block 使用 copy 是从 MRC 遗留下来的“传统”,在 MRC 中,方法内部的 block 是在栈区的,使用 copy 可以把它放到堆区.在 ARC 中写不写都行:对于 block 使用 copy 还是 strong 效果是一样的,但写上 copy 也无伤大雅,还能时刻提醒我们:编译器自动对 block 进行了 copy 操作。

4)方法名:(自定义存取方法名)
getter =<>
setter =<>

注: 同时重写setter和getter方法的时候,编译器不会自动生成_xxx 实例变量,需要手动添加@synthesize xxx = _xxx;
例子:

  • @property (nonatomic, getter=isOn) BOOL on;
  • setter=一般用在特殊的情境下,比如:
    在数据反序列化、转模型的过程中,服务器返回的字段如果以 init 开头,所以你需要定义一个 init 开头的属性,但默认生成的 setter 与 getter 方法也会以 init 开头,而编译器会把所有以 init 开头的方法当成初始化方法,而初始化方法只能返回 self 类型,因此编译器会报错。

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

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

另外也可以用关键字进行特殊说明,来避免编译器报错:

@property(nonatomic, readwrite, copy, null_resettable) NSString *initBy;

- (NSString *)initBy __attribute__((objc_method_family(none)));

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

  1. 对应基本数据类型默认关键字是
    atomic, readwrite, assign
  2. 对于普通的 Objective-C 对象
    atomic, readwrite, strong

3.什么情况使用 weak 关键字,相比 assign 有什么不同?

  • 表示一种非拥有的关系,一般代理会使用这个;
  • 自身已经对它进行一次强引用,没有必要再强引用一次,此时也会使用 weak,自定义 IBOutlet 控件属性一般也使用 weak。

4.什么时候用 copy 关键字?

一般是 NSString, block, 不可变的集合类时使用

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

调用NSMutableArray方法时出现奔溃。

6.拷贝:

  • 浅拷贝:

指针复制,没有对象的复制(不会重新分配存储空间)

  • 单层深拷贝:
    这是我们经常说的深复制,这里所说的是针对集合类(NSArray,NSDictionary,NSSet),只复制了该集合的最外层,里面的元素没有复制,所以两个集合的地址不一样,但是两个集合的所存储的元素的地址是一样的。

  • 深拷贝:

两个集合的地址不一样,同时两个集合的所存储的元素的地址是不一样的。

从左到右:
@property (nonatomic, copy) NSString *string ;
@property (nonatomic, copy) NSMutableString *mutableString;
@property (nonatomic, mutableCopy) NSString * string ;
@property (nonatomic, mutableCopy) NSMutableString *mutableString;

@property & 拷贝_第1张图片
屏幕快照 2017-11-28 13.25.03.png

注:集合类仿照上面的一样,区别是将非集合类的深复制结果变为单层的深复制即可。

字符串类


@property & 拷贝_第2张图片
屏幕快照 2017-11-28 13.44.23.png

集合类


@property & 拷贝_第3张图片
屏幕快照 2017-11-28 13.38.16.png

下面我们来看一下具体的语义是怎样实现的?

retain:始终执行浅拷贝,引用计数每次加一,返回对象是否可变与被复制的对象一致;
copy 对于不可变为浅复制,引用计数每次加一;可变为深复制,引用计数不变;
mutableCopy 始终执行深拷贝,引用计数不变,始终返回一个可变的对象;

7. 集合的深复制是怎样实现的?

集合的深复制有两种方法。可以用 initWithArray:copyItems: 将第二个参数设置为YES即可深复制,如

NSDictionary shallowCopyDict = [[NSDictionary alloc]     initWithDictionary:someDictionary copyItems:YES];

如果你用这种方法深复制,集合里的每个对象都会收到 copyWithZone: 消息。如果集合里的对象遵循 NSCopying 协议,那么对象就会被深复制到新的集合。如果对象没有遵循 NSCopying 协议,而尝试用这种方法进行深复制,会在运行时出错。
copyWithZone: 这种拷贝方式只能够提供一层内存拷贝(one-level-deep copy),而非真正的深复制。

第二个方法是将集合进行归档(archive),然后解档(unarchive),如:

NSArray *trueDeepCopyArray = [NSKeyedUnarchiver unarchiveObjectWithData:[NSKeyedArchiver archivedDataWithRootObject:oldArray]];

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

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

9. 不同内存管理语义下内存的处理方式?

@property (nonatomic,关键字) NSObject *a;
当使用了不同的关键字后自动实现的set方法:

//assign
-(void)setA:(int)a{
        _a=a;
}

//retain
-(void)setA:(Car *)a{
       if(_a!=a){
           [_a release];
           _a=[a retain];
       }
}

//copy
-(void)setA:(NSString *)a{
       if(_a!=a){
          [_a release];
          _a=[a copy];
       }
}

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

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

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

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

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

@property 的本质是什么?

@property = ivar + getter + setter;

下面解释下:

“属性” (property)有两大概念:ivar(实例变量)、存取方法(access method = getter + setter)。
“属性” (property)作为 Objective-C 的一项特性,主要的作用就在于封装对象中的数据。 Objective-C 对象通常会把其所需要的数据保存为各种实例变量。实例变量一般通过“存取方法”(access method)来访问。其中,“获取方法” (getter)用于读取变量值,而“设置方法” (setter)用于写入变量值。这个概念已经定型,并且经由“属性”这一特性而成为 Objective-C 2.0 的一部分。 而在正规的 Objective-C 编码风格中,存取方法有着严格的命名规范。 正因为有了这种严格的命名规范,所以 Objective-C 这门语言才能根据名称自动创建出存取方法。其实也可以把属性当做一种关键字,其表示:

编译器会自动写出一套存取方法,用以访问给定类型中具有给定名称的变量。 所以你也可以这么说:
@property = 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

更新:

property在runtime中是objc_property_t定义如下:

typedef struct objc_property *objc_property_t;
而objc_property是一个结构体,包括name和attributes,定义如下:

struct property_t {
    const char *name;
    const char *attributes;
};
而attributes本质是objc_property_attribute_t,定义了property的一些属性,定义如下:

/// Defines a property attribute
typedef struct {
    const char *name;           /**< The name of the attribute */
    const char *value;          /**< The value of the attribute (usually empty) */
} objc_property_attribute_t;

而attributes的具体内容是什么呢?其实,包括:类型,原子性,内存语义和对应的实例变量。

例如:我们定义一个string的property@property (nonatomic, copy) NSString *string;,通过 property_getAttributes(property)获取到attributes并打印出来之后的结果为T@"NSString",C,N,V_string

其中T就代表类型,可参阅Type Encodings,C就代表Copy,N代表nonatomic,V就代表对于的实例变量。

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

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

@implementation Person
@synthesize firstName = _myFirstName;
@synthesize lastName = _myLastName;
@end
我为了搞清属性是怎么实现的,曾经反编译过相关的代码,他大致生成了五个东西

  1. OBJC_IVAR_$类名$属性名称 :该属性的“偏移量” (offset),这个偏移量是“硬编码” (hardcode),表示该变量距离存放对象的内存区域的起始地址有多远。
  2. setter 与 getter 方法对应的实现函数
  3. ivar_list :成员变量列表
  4. method_list :方法列表
  5. prop_list :属性列表

也就是说我们每次在增加一个属性,系统都会在 ivar_list 中添加一个成员变量的描述,在 method_list 中增加 setter 与 getter 方法的描述,在属性列表中增加一个属性的描述,然后计算该属性在对象中的偏移量,然后给出 setter 与 getter 方法对应的实现,在 setter 方法中从偏移量的位置开始赋值,在 getter 方法中从偏移量开始取值,为了能够读取正确字节数,系统对象偏移量的指针类型进行了类型强转.

链接:
https://www.zybuluo.com/MicroCai/note/50592
http://www.jianshu.com/p/a1111480e4a7
https://github.com/ChenYilong/iOSInterviewQuestions/blob/master/01《招聘一个靠谱的iOS》面试题参考答案/《招聘一个靠谱的iOS》面试题参考答案(上).md#4-这个写法会出什么问题-property-copy-nsmutablearray-array

你可能感兴趣的:(@property & 拷贝)