《招聘一个靠谱的iOS》— Part Ⅰ


本Part整理跟 @property 相关的题目。

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

什么时候用weak关键字:
1.在ARC中,在有可能出现循环引用的时候,往往要通过让其中一端使用weak来解决, 比如:delegate代理属性。


2.自身已经对它进行一次强引用,没有必要再强引用一次,此时也会使用weak,自定义IBOutlet控件属性一般也使用weak;当然,也可以使用strong。在下文也有论述:《IBOutlet连出来的视图属性为什么可以被设置成weak?》


不同点
1.weak 此特质表明该属性定义了一种“非拥有关系” (nonowning relationship)。为这种属性设置新值时,设置方法既不保留新值,也不释放旧值。此特质同assign类似,然而在属性所指的对象遭到摧毁时,属性值也会清空(nil out)。
assign 的 setter 只会执行针对“纯量类型” (scalar type,例如 CGFloat 或NSlnteger 等)的简单赋值操作。


2.assigin 可以用于OC对象与非OC对象,而 weak 必须用于OC对象。注意,用于OC对象时,assignweak 类似,但是在所指向的对象被销毁之后,不会将 property 清空。

怎么用 copy 关键字?

1.copy此特质所表达的 owning 关系与strong类似。然而 setter 并不保留新值,而是将其“拷贝” (copy)。


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


用@property声明 NSString、NSArray、NSDictionary 经常使用copy关键字,是因为他们有对应的可变类型:NSMutableString、NSMutableArray、NSMutableDictionary,他们之间可能进行赋值操作,为确保对象中的字符串值不会无意间变动,应该在设置新属性值时拷贝一份。


2.block也经常使用copy关键字,具体原因见官方文档:Objects Use Properties to Keep Track of Blocks:


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

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

1.添加,删除,修改数组内的元素的时候,程序会因为找不到对应的方法而崩溃。
因为copy就是复制一个不可变NSArray的对象。换言之,就算传入的原对象是 NSMutableArray 类型的,但其 copyToZone: 函数返回的是 NSArray 类型,而系统默认的 setter 是调用对象的 copyToZone: 函数。若要得到 NSMutableArray 类型,则需要自定义 setter,并且在里面调用 mutableCopy 方法。


2.使用了atomic属性会影响性能。
该属性使用了同步锁,会在创建时生成一些额外的代码用于帮助编写多线程程序,这会带来性能问题,通过声明nonatomic可以节省这些虽然很小但是不必要额外开销。

在默认情况下,由编译器所合成的方法会通过锁定机制确保其原子性(atomicity)。如果属性具备nonatomic特质,则不使用同步锁。请注意,尽管没有名为“atomic”的特质(如果某属性不具备nonatomic特质,那它就是“原子的”(atomic))。


在iOS开发中,你会发现,几乎所有属性都声明为nonatomic。


一般情况下并不要求属性必须是“原子的”,因为这并不能保证“线程安全” ( thread safety),若要实现“线程安全”的操作,还需采用更为深层的锁定机制才行。例如,一个线程在连续多次读取某属性值的过程中有别的线程在同时改写该值,那么即便将属性声明为atomic,也还是会读到不同的属性值。


因此,开发iOS程序时一般都会使用nonatomic属性。但是在开发Mac OS X程序时,使用
atomic属性通常都不会有性能瓶颈。

如何让自己的类用 copy 修饰符?如何重写带 copy 关键字的 setter?

1.若想令自己所写的对象具有拷贝功能,则需实现NSCopying协议。如果自定义的对象分为可变版本与不可变版本,那么就要同时实现NSCopyiog与NSMutableCopying协议。


具体步骤:
a. 需声明该类遵从NSCopying协议
b. 实现NSCopying协议。该协议只有一个方法:

1
- (id)copyWithZone: (NSZone*)zone

注意:一提到让自己的类用 copy 修饰符,我们总是想覆盖 copy 方法,其实真正需要实现的却是“copyWithZone”方法。

举个栗子:

1
2
3
4
5
6
7
8
9
// Person.h

@interface Person : NSObject <NSCopying>

@property (copy, nonatomic) NSString *name;

- (instancetype)initWithName:(NSString *)name;

@end

然后实现协议中规定的方法

1
2
3
4
- (id)copyWithZone:(NSZone *)zone {
Person *copy = [[Person allocWithZone:zone] initWithName:_name];
return copy;
}

但在实际的项目中,不可能这么简单,遇到更复杂一点,比如类对象中的数据结构可能并未在初始化方法中设置好,需要另行设置。再举个栗子,假如 Person 中含有一个数组,与其他 Person 对象建立或解除朋友关系的那些方法都需要操作这个数组。那么在这种情况下,你得把这个包含朋友对象的数组也一并拷贝过来。下面列出了实现此功能所需的全部代码:

1
2
3
4
5
6
7
8
9
10
11
12
// Person.h

@interface Person : NSObject <NSCopying>

@property (copy, nonatomic) NSString *name;

- (instancetype)initWithName:(NSString *)name;

- (void)addFriend:(Person *)user; //添加盆友
- (void)removeFriend:(Person *)user; //删除盆友

@end

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
Person.m

@implementation Person {
NSMutableSet *_friends;
}

- (instancetype)initWithName:(NSString *)name {
if (self = [super init]) {
_name = [name copy];
_friends = [[NSMutableSet alloc] init];
}

return self;
}

- (void)addFriend:(Person *)user {
[_friends addObject:user];
}

- (void)removeFriend:(Person *)user {
[_friends removeObject:user];
}

- (id)copyWithZone:(NSZone *)zone {
Person *copy = [[Person allocWithZone:zone] initWithName:_name];
copy->_friends = [_friends mutableCopy];
return copy;
}


2.怎么重写带 copy 关键字的 setter

1
2
3
- (void)setName:(NSString *)name {
_name = [name copy];
}

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

1.@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;


例如下面类:

1
2
3
4
@interface Person : NSObject 
@property NSString *firstName;
@property NSString *lastName;
@end

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

1
2
3
4
5
6
@interface Person : NSObject 
- (NSString *)firstName;
- (void)setFirstName:(NSString *)firstName;
- (NSString *)lastName;
- (void)setLastName:(NSString *)lastName;
@end


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


“自动合成”(autosynthesis)


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

1
2
3
4
@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 方法中从偏移量开始取值,为了能够读取正确字节数,系统对象偏移量的指针类型进行了类型强转。

@protocol 和 category 中如何使用 @property

1.在 protocol 中使用 property 只会生成 setter 和 getter 方法声明,我们使用属性的目的,是希望遵守协议的对象能实现该属性。换言之,对于 protocol 中声明的属性,如果未在类中实现其 getter 与 setter,在 app 使用到这些属性的地方会因为找不到方法而崩溃。


2.category 使用 @property 也是只会生成setter和getter方法的声明,如果我们真的需要给category增加属性的实现,需要借助于运行时的两个函数:objc_setAssociatedObjectobjc_getAssociatedObject。用刚刚的 Person 作为例子,给它增加一个名为 MetaHuman 的 category:

1
2
3
4
5
6
7
#import "Person.h"

@interface Person (MetaHuman)

@property (copy, nonatomic) NSString *skillName;

@end

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#import "Person+MetaHuman.h"
#import <objc/runtime.h>

static NSString *key = @"skillName";

@implementation Person (MetaHuman)

@dynamic skillName;

- (void)setSkillName:(NSString *)skillName {
objc_setAssociatedObject(self, (__bridge const void *)(key), skillName, OBJC_ASSOCIATION_COPY_NONATOMIC);
}

- (NSString *)skillName {
return objc_getAssociatedObject(self, (__bridge const void *)(key));
}
@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。


设计一个函数(伪代码)来表示上述机制,objc_storeWeak(&a, b):


objc_storeWeak 函数把第二个参数—赋值对象 b 的内存地址作为键值 key,将第一个参数(weak 修饰的属性变量 a)的内存地址 &a 作为value,注册到 weak 表中。如果第二个参数 b 为 nil,那么把变量 a 的内存地址 &a 从weak表中删除。注意区分变量的内存地址变量指向的对象的内存地址


你可以把 objc_storeWeak(&a, b) 理解为:objc_storeWeak(value, key),并且当 key 变 nil,将 value 置 nil。


而如果a是由assign修饰的,则:
在b非nil时,a和b指向同一个内存地址,在b变nil时,a还是指向该内存地址,变野指针。此时向a发送消息极易崩溃。


下面我们将基于 objc_storeWeak(&a, b) 函数,使用伪代码模拟“runtime如何实现weak属性”:

1
2
3
4
5
6
id obj1;
objc_initWeak(&obj1, obj);

// obj引用计数变为0,变量作用域结束

objc_destroyWeak(&obj1);

通过 objc_initWeak 函数初始化 weak property obj1 ,在变量作用域结束时通过 objc_destoryWeak 函数释放 obj1。


其中,objc_initWeak 函数的实现是这样的:在将 weak property obj1 初始化为 nil 后,会将 obj 作为参数,调用 objc_storeWeak 函数。

1
2
3
4
// objc_initWeak 内部原理

obj1 = 0
obj_storeWeak(&obj1, obj);

而 obj_destroyWeak 的工作原理是将 nil 作为参数,调用 objc_storeWeak 函数:objc_storeWeak(&obj1, 0);


于是前面的代码等价于下面代码

1
2
3
4
5
id obj1;
obj1 = 0;
objc_storeWeak(&obj1, obj);
// obj的引用计数变为0,被置nil
objc_storeWeak(&obj1, 0);

objc_storeWeak 函数把赋值对象 obj 的内存地址作为键值,将 weak property obj1 的内存地址注册到 weak 表中。如果obj 为 nil,那么把变量 obj1 的地址从 weak 表中删除,在后面的相关一题会详解。


使用伪代码是为了方便理解,下面我们“真枪实弹”地实现下: 如何让不使用weak修饰的@property,拥有weak的效果。


首先看 setter

1
2
3
4
5
6
7
- (void)setObject:(NSObject *)object
{
objc_setAssociatedObject(self, "object", object, OBJC_ASSOCIATION_ASSIGN);
[object cyl_runAtDealloc:^{
_object = nil;
}];
}

这里重点是,如何实现这个 cyl_runAtDealloc: 函数,思路如下

1
2
3
4
5
6
7
id objectToBeDeallocated; //要销毁的目标对象
id objectWeWantToBeReleasedWhenThatHappens; //可以理解为一个“事件”:当上面的目标对象销毁时,同时要发生的“事件”。

objc_setAssociatedObject(objectToBeDeallocted,
someUniqueKey,
objectWeWantToBeReleasedWhenThatHappens,
OBJC_ASSOCIATION_RETAIN);

下面开始实现这个 cyl_runAtDealloc 方法,实现过程分为两部分:
第一部分:创建一个类,可以理解为一个“事件”:当目标对象销毁时,同时要发生的“事件”。借助 block 执行“事件”

1
2
3
4
5
6
7
8
9
10
// .h文件
// 这个类,可以理解为一个“事件”:当目标对象销毁时,同时要发生的“事件”。借助block执行“事件”。

typedef void (^voidBlock)(void);

@interface CYLBlockExecutor : NSObject

- (id)initWithBlock:(voidBlock)block;

@end

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// .m文件
#import "CYLBlockExecutor.h"

@interface CYLBlockExecutor() {
voidBlock _block;
}

@implementation CYLBlockExecutor

- (id)initWithBlock:(voidBlock)aBlock
{
self = [super init];

if (self) {
_block = [aBlock copy];
}

return self;
}

- (void)dealloc
{
_block ? _block() : nil;
}

@end

第二部分:*核心代码:利用 runtime 实现 cyl_runAtDealloc 方法。这里利用了 Category 来实现:

1
2
3
4
5
6
7
8
9
10
11
12
// CYLNSObject+RunAtDealloc.h文件
// 利用runtime实现cyl_runAtDealloc方法

#import "CYLBlockExecutor.h"

const void *runAtDeallocBlockKey = &runAtDeallocBlockKey;

@interface NSObject (CYLRunAtDealloc)

- (void)cyl_runAtDealloc:(voidBlock)block;

@end

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// CYLNSObject+RunAtDealloc.m文件
// 利用runtime实现cyl_runAtDealloc方法

#import "CYLNSObject+RunAtDealloc.h"
#import "CYLBlockExecutor.h"

@implementation NSObject (CYLRunAtDealloc)

- (void)cyl_runAtDealloc:(voidBlock)block
{
if (block) {
CYLBlockExecutor *executor = [[CYLBlockExecutor alloc] initWithBlock:block];

objc_setAssociatedObject(self,
runAtDeallocBlockKey,
executor,
OBJC_ASSOCIATION_RETAIN);
}
}

@end

对上面的原理感兴趣,可移步到Fun With the Objective-C Runtime: Run Code at Deallocation of Any Object

@property中有哪些属性关键字?/ @property 后面可以有哪些修饰符?
  1. 原子性:nonatomic 特质

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

  1. 读/写权限:readwrite(读写)、readonly (只读)

  2. 内存管理语义:assign、strong、 weak、unsafe_unretained、copy

  3. 方法名:getter= 、setter=

  4. 不常用的:nonnull,null_resettable,nullable

weak属性需要在dealloc中置nil么?

不需要。在ARC环境无论是强指针还是弱指针都无需在 dealloc 设置为 nil , ARC 会自动帮我们处理。即便是编译器不帮我们做这些,weak也不需要在 dealloc 中置nil。


正如上面一题 runtime 如何实现 weak 属性 中提到的:在属性所指的对象遭到摧毁时,属性值也会清空(nil out)。

@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
  2. 对于普通的 Objective-C 对象:atomic,readwrite,strong

参考链接:
Objective-C ARC: strong vs retain and weak vs assign
Variable property attributes or Modifiers in iOS

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


1.因为父类指针可以指向子类对象,使用 copy 的目的是为了让本对象的属性不受外界影响,使用 copy 无论给我传入是一个可变对象还是不可对象,我本身持有的就是一个不可变的副本.


2.如果我们使用是 strong ,那么这个属性就有可能指向一个可变对象,如果这个可变对象在外部被修改了,那么会影响该属性.


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


另外需要知道两种情况:

  1. 对非集合类对象的 copy 与 mutableCopy 操作;
  2. 对集合类对象的 copy 与 mutableCopy 操作。


1.对非集合类对象的copy操作:


在非集合类对象中:对 immutable 对象进行 copy 操作,是指针复制,mutableCopy 操作时内容复制;对 mutable 对象进行 copy 和 mutableCopy 都是内容复制。用代码简单表示如下:

  • [immutableObject copy] // 浅复制
  • [immutableObject mutableCopy] //深复制
  • [mutableObject copy] //深复制
  • [mutableObject mutableCopy] //深复制


2.集合类对象的copy与mutableCopy


集合类对象是指 NSArray、NSDictionary、NSSet … 之类的对象。下面先看集合类 immutable 对象使用 copy 和 mutableCopy 的一个例子:

1
2
3
NSArray *array = @[@[@"a", @"b"], @[@"c", @"d"]];
NSArray *copyArray = [array copy];
NSMutableArray *mCopyArray = [array mutableCopy];

查看内容,可以看到 copyArray 和 array 的地址是一样的,而 mCopyArray 和 array 的地址是不同的。说明 copy 操作进行了指针拷贝,mutableCopy 进行了内容拷贝。但需要强调的是:此处的内容拷贝,仅仅是拷贝 array 这个对象,array 集合内部的元素仍然是指针拷贝。这和上面的非集合 immutable 对象的拷贝还是挺相似的,那么mutable对象的拷贝会不会类似呢?我们继续往下,看 mutable 对象拷贝的例子:

1
2
3
NSMutableArray *array = [NSMutableArray arrayWithObjects:[NSMutableString stringWithString:@"a"],@"b",@"c",nil];
NSArray *copyArray = [array copy];
NSMutableArray *mCopyArray = [array mutableCopy];

查看内存,如我们所料,copyArray、mCopyArray和 array 的内存地址都不一样,说明 copyArray、mCopyArray 都对 array 进行了内容拷贝。同样,我们可以得出结论:


在集合类对象中,对 immutable 对象进行 copy,是指针复制, mutableCopy 是内容复制;对 mutable 对象进行 copy 和 mutableCopy 都是内容复制。但是:集合对象的内容复制仅限于对象本身,对象元素仍然是指针复制。用代码简单表示如下:

  • [immutableObject copy] // 浅复制
  • [immutableObject mutableCopy] //单层深复制
  • [mutableObject copy] //单层深复制
  • [mutableObject mutableCopy] //单层深复制

这个代码结论和非集合类的非常相似。


参考链接:iOS 集合的深复制与浅复制

@synthesize合成实例变量的规则是什么?假如property名为foo,存在一个名为_foo的实例变量,那么还会自动合成新变量么?

在回答之前先说明下一个概念:实例变量 = 成员变量 = ivar。这些说法,笔者下文中,可能都会用到,指的是一个东西。

正如Apple官方文档 You Can Customize Synthesized Instance Variable Names所说:
image
如果使用了属性的话,那么编译器就会自动编写访问属性所需的方法,此过程叫做“自动合成”( auto synthesis)。需要强调的是,这个过程由编译器在编译期执行,所以编辑器里看不到这些“合成方法” (synthesized method)的源代码。除了生成方法代码之外,编译器还要自动向类中添加适当类型的实例变量,并且在属性名前面加下划线,以此作为实例变量的名字。

1
2
3
4
@interface CYLPerson : NSObject 
@property NSString *firstName;
@property NSString *lastName;
@end

在上例中,会生成两个实例变量,其名称分别为 _firstName 与 _lastName。也可以在类的实现代码里通过 @synthesize 语法来指定实例变量的名字:

1
2
3
4
@implementation CYLPerson 
@synthesize firstName = _myFirstName;
@synthesize lastName = _myLastName;
@end

上述语法会将生成的实例变量命名为 _myFirstName 与 _myLastName ,而不再使用默认的名字。一般情况下无须修改默认的实例变量名,但是如果你不喜欢以下划线来命名实例变量,那么可以用这个办法将其改为自己想要的名字。笔者还是推荐使用默认的命名方案,因为如果所有人都坚持这套方案,那么写出来的代码大家都能看得懂。

总结下 @synthesize 合成实例变量的规则,有以下几点:

  1. 如果指定了成员变量的名称,会生成一个指定的名称的成员变量,
  2. 如果这个成员已经存在了就不再生成了.
  3. 如果是 @synthesize foo; 还会生成一个名称为foo的成员变量,也就是说:如果没有指定成员变量的名称,便会自动生成一个属性同名的成员变量。
  4. 如果是 @synthesize foo = _foo; 就不会生成成员变量了.


所以,假如 property 名为 foo,存在一个名为 _foo 的实例变量,那么还会自动合成新变量么? 不会。如下图:
image
注意上面 another synthesized property,也就是说编译器认为 property object 已经 synthesized

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

在Xcode4.4之后,编译器会自动帮你完成 @synthesize 操作:XCode4.4 release note:The compiler automatically calls @synthesize by default for unimplemented @properties(default 是 @synthesize propertyName = _propertyName)。这就是所说的自动合成。


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

  1. 同时重写了 setter 和 getter 时
  2. 重写了只读属性的 getter 时
  3. 使用了 @dynamic 时
  4. 在 @protocol 中定义的所有属性
  5. 在 category 中定义的所有属性
  6. 重载的属性:当你在子类中重载了父类中的属性,你必须 使用 @synthesize 来手动合成 ivar。


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


因为有了 autosynthesis(自动合成),大部分开发者已经习惯不去手动定义ivar,而是依赖于 autosynthesis(自动合成),但是一旦你需要使用ivar,而 autosynthesis(自动合成)又失效了,如果不去手动定义ivar,那么你就得借助 @synthesize 来手动合成 ivar。


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

1
2
3
4
@implementation CYLPerson 
@synthesize firstName = _myFirstName;
@synthesize lastName = _myLastName;
@end


上述语法会将生成的实例变量命名为 _myFirstName 与 _myLastName,而不再使用默认的名字。一般情况下无须修改默认的实例变量名,但是如果你不喜欢以下划线来命名实例变量,那么可以用这个办法将其改为自己想要的名字。笔者还是推荐使用默认的命名方案,因为如果所有人都坚持这套方案,那么写出来的代码大家都能看得懂。


举个例子,还是我们的 person 类:

1
2
3
4
5
6
7
8
9
//  Person.h

#import <Foundation/Foundation.h>

@interface Person : NSObject

@property (copy, nonatomic) NSString *name;

@end

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//  Person.m
// 只实现setter

#import "Person.h"


@implementation Person

- (void)setName:(NSString *)name
{
_name = [name copy];
}

@end

这样没问题。但是如果连同 getter 也一并实现了的话,就会报错:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//  Person.m

#import "Person.h"

@implementation Person

- (void)setName:(NSString *)name
{
_name = [name copy];
}

- (NSString *)name
{
return _name;
}

@end

编译器报错:
image


这是由于上面所提到过的,这里实现了 @property 的所有 accessor methods,这时编译器就会认为你打算手动管理 @property,于是编译器就禁用了 autosynthesis(自动合成)。



以上