-
风格就错题
1.1优化部分
1.2硬伤部分 - 什么情况下使用
weak
关键字,相比assign
有什么不同? - 怎么用
copy
关键字? - 这个写法会有什么问题:
@property(copy) NSMutableArray *array;
- 如何让自己的类用
copy
修饰符?如何重写带copy
关键字的setter
? @property
的本质是什么?ivar
,getter
,setter
是如何生成并添加到这个类中的?@protocol
和category
中如何使用@property
?runtime
如何实现weak
属性?@property
中有哪些属性关键字?@property
后面可以有哪些修饰符?weak
属性需要在dealloc
中置nil么?@synthesize
和@dynamic
分别有什么作用?- ARC下,不显式指定任何属性关键字时,默认的关键字都有哪些?
- 用
@property
声明的NSString
(或NSArray
,NSDictionary
)经常使用copy
关键字,为什么?如果改用strong
关键字,可能造成什么问题? - 对非集合类对象的
copy
操作 - 集合类对象的
copy
与mutableCopy
<h4 id="1">1.语法示例</h4>
修改方法有很多种,现给出一种做示例:
typedef NS_ENUM(NSInteger, JJSex) {
JJSexMan,
JJSexWoman
};
@interface JJUser : NSObject<NSCopying>
@property (nonatomic, readonly, copy) NSString *name;
@property (nonatomic, readonly, assign) NSUInteger age;
@property (nonatomic, readonly, assign) JJSex sex;
- (instancetype)initWithName:(NSString *)name age:(NSUInteger)age sex:(JJSex)sex;
+ (instancetype)userWithName:(NSString *)name age:(NSUInteger)age sex:(JJSex)sex;
@end
下面对具体修改的地方,分两部分做下介绍:硬伤部分 和 优化部分 。因为硬伤部分没什么技术含量,为了节省大家时间,放在后面讲,大神请直接看优化部分。
<h6 id="1.1">优化部分</h6>
1.enum
建议使用NS_ENUM
和NS_OPTIONS
宏来定义枚举类型,参见官方的 Adopting Modern Objective-C 一文:
//定义一个枚举
typedef NS_ENUM(NSInteger, JJSex) {
JJSexMan,
JJSexWoman
};
(仅仅让性别包含男和女可能并不严谨,最严谨的做法可以参考 这里 。)
2.age
属性的类型:应避免使用基本类型,建议使用 Foundation 数据类型,对应关系如下:
int -> NSInteger
unsigned -> NSUInteger
float -> CGFloat
动画时间 -> NSTimeInterval
同时考虑到 age
的特点,应使用 NSUInteger
,而非 int
。 这样做的是基于64-bit
适配考虑,详情可参考出题者的博文《64-bit Tips》。
3.如果工程项目非常庞大,需要拆分成不同的模块,可以在类、typedef
宏命名的时候使用前缀。
4.doLogIn
方法不应写在该类中:
- 虽然
LogIn
的命名不太清晰,但笔者猜测是login
的意思, (勘误:Login
是名词,LogIn
是动词,都表示登陆的意思。见: Log in vs. login )- 登录操作属于业务逻辑,观察类名
UserModel
,以及属性的命名方式,该类应该是一个Model
而不是一个“MVVM
模式下的ViewModel
”:无论是
MVC
模式还是MVVM
模式,业务逻辑都不应当写在Model
里:MVC
应在C
,MVVM
应在VM
。
(如果抛开命名规范,假设该类真的是MVVM
模式里的ViewModel
,那么 UserModel
这个类可能对应的是用户注册页面,如果有特殊的业务需求,比如: -logIn
对应的应当是注册并登录的一个Button
,出现-logIn
方法也可能是合理的。)
5.doLogIn
方法命名不规范:添加了多余的动词前缀。 请牢记:
如果方法表示让对象执行一个动作,使用动词打头来命名,注意不要使用
do
,does
这种多余的关键字,动词本身的暗示就足够了。
应为-logIn
(注意:Login
是名词, LogIn
是动词,都表示登陆。 见 Log in vs. login )
6.-(id)initUserModelWithUserName: (NSString*)name withAge:(int)age;
方法中不要用 with
来连接两个参数: withAge
: 应当换为age
:,age
: 已经足以清晰说明参数的作用,也不建议用 andAge
:通常情况下,即使有类似 withA:withB:
的命名需求,也通常是使用withA:andB:
这种命名,用来表示方法执行了两个相对独立的操作(从设计上来说,这时候也可以拆分成两个独立的方法),它不应该用作阐明有多个参数,比如下面的:
//错误,不要使用"and"来连接参数
- (int)runModalForDirectory:(NSString *)path andFile:(NSString *)name andTypes:(NSArray *)fileTypes;
//错误,不要使用"and"来阐明有多个参数
- (instancetype)initWithName:(CGFloat)width andAge:(CGFloat)height;
//正确,使用"and"来表示两个相对独立的操作
- (BOOL)openFile:(NSString *)fullPath withApplication:(NSString *)appName andDeactivate:(BOOL)flag;
7.由于字符串值可能会改变,所以要把相关属性的“内存管理语义”声明为 copy
。(原因在下文有详细论述:用@property
声明的NSString
(或NSArray
,NSDictionary
)经常使用copy
关键字,为什么?)
8.“性别”(sex
)属性的:该类中只给出了一种“初始化方法” (initializer
)用于设置“姓名”(Name
)和“年龄”(Age
)的初始值,那如何对“性别”(Sex
)初始化?
Objective-C 有
designated
和secondary
初始化方法的观念。designated
初始化方法是提供所有的参数,secondary
初始化方法是一个或多个,并且提供一个或者更多的默认参数来调用designated
初始化方法的初始化方法。举例说明:
@implementation JJUser
- (instancetype)initWithName:(NSString *)name age:(NSUInteger)age sex:(JJSex)sex
{
if(self = [super init]) {
_name = [name copy];
_age = age;
_sex = sex;
}
return self;
}
- (instancetype)initWithName:(NSString *)name age:(NSUInteger)age {
return [self initWithName:name age:age sex:nil];
}
@end
上面的代码中
initWithName:age:sex:
就是designated
初始化方法,另外的是secondary
初始化方法。因为仅仅是调用类实现的designated
初始化方法。
因为出题者没有给出 .m文件,所以有两种猜测:
1:本来打算只设计一个designated
初始化方法,但漏掉了“性别”(sex
)属性。那么最终的修改代码就是上文给出的第一种修改方法。
2:不打算初始时初始化“性别”(sex
)属性,打算后期再修改,如果是这种情况,那么应该把“性别”(sex
)属性设为readwrite
属性,最终给出的修改代码应该是:
typedef NS_ENUM(NSInteger, JJSex) {
JJSexMan,
JJSexWoman
};
@interface JJUser : NSObject<NSCopying>
@property (nonatomic, readonly, copy) NSString *name;
@property (nonatomic, readonly, assign) NSUInteger age;
@property (nonatomic, readwrite, assign) JJSex sex;
- (instancetype)initWithName:(NSString *)name age:(NSUInteger)age sex:(JJSex)sex;
- (instancetype)initWithName:(NSString *)name age:(NSUInteger)age;
+ (instancetype)userWithName:(NSString *)name age:(NSUInteger)age sex:(JJSex)sex;
@end
.h
中暴露designated
初始化方法,是为了方便子类化 (想了解更多,请戳--》 《禅与 Objective-C 编程艺术 (Zen and the Art of the Objective-C Craftsmanship 中文翻译)》。)
-
按照接口设计的惯例,如果设计了“初始化方法” (initializer),也应当搭配一个快捷构造方法。而快捷构造方法的返回值,建议为
instancetype
,为保持一致性,init 方法和快捷构造方法的返回类型最好都用instancetype
。 -
如果基于第一种修改方法:既然该类中已经有一个“初始化方法” (
initializer
),用于设置“姓名”(Name
)、“年龄”(Age
)和“性别”(Sex
)的初始值: 那么在设计对应@property
时就应该尽量使用不可变的对象:其三个属性都应该设为“只读”。用初始化方法设置好属性值之后,就不能再改变了。在本例中,仍需声明属性的“内存管理语义”。于是可以把属性的定义改成这样:@property (nonatomic, readonly, copy) NSString *name;
@property (nonatomic, readonly, assign) NSUInteger age;
@property (nonatomic, readonly, assign) JJSex sex;
由于是只读属性,所以编译器不会为其创建对应的“设置方法”,即便如此,我们还是要写上这些属性的语义,以此表明初始化方法在设置这些属性值时所用的方式。要是不写明语义的话,该类的调用者就不知道初始化方法里会拷贝这些属性,他们有可能会在调用初始化方法之前自行拷贝属性值。这种操作多余而且低效。
9.initUserModelWithUserName
如果改为 initWithName
会更加简洁,而且足够清晰。
10.UserModel
如果改为 User
会更加简洁,而且足够清晰。
11.UserSex
如果改为Sex
会更加简洁,而且足够清晰。
12.第二个 @property
中 assign
和 nonatomic
调换位置。 推荐按照下面的格式来定义属性:
@property (nonatomic, readwrite, copy) NSString *name;
属性的参数应该按照下面的顺序排列: 原子性,读写 和 内存管理。 这样做你的属性更容易修改正确,并且更好阅读。这在《禅与Objective-C编程艺术 >》里有介绍。而且习惯上修改某个属性的修饰符时,一般从属性名从右向左搜索需要修动的修饰符。最可能从最右边开始修改这些属性的修饰符,根据经验这些修饰符被修改的可能性从高到底应为:内存管理 > 读写权限 >原子操作。
<h6 id="1.2">硬伤部分</h6>
1.在-
和(void)
之间应该有一个空格
2.enum
中驼峰命名法和下划线命名法混用错误:枚举类型的命名规则和函数的命名规则相同:命名时使用驼峰命名法,勿使用下划线命名法。
3.enum
左括号前加一个空格,或者将左括号换到下一行
4.enum
右括号后加一个空格
5.UserModel :NSObject
应为UserModel : NSObject
,也就是:右侧少了一个空格。
6.@interface
与 @property
属性声明中间应当间隔一行。
7.两个方法定义之间不需要换行,有时为了区分方法的功能也可间隔一行,但示例代码中间隔了两行。
8.-(id)initUserModelWithUserName: (NSString*)name withAge:(int)age;
方法中方法名与参数之间多了空格。而且 -
与 (id)
之间少了空格。
9.-(id)initUserModelWithUserName: (NSString*)name withAge:(int)age;
方法中方法名与参数之间多了空格:(NSString*)name
前多了空格。
10.-(id)initUserModelWithUserName: (NSString*)name withAge:(int)age;
方法中 (NSString*)name
,应为 (NSString *)name
,少了空格。
11.doLogIn
方法中的 LogIn
命名不清晰:笔者猜测是login
的意思,应该是粗心手误造成的。
(勘误: Login
是名词,LogIn
是动词,都表示登陆的意思。见: Log in vs. login )
<h4 id="2">2.什么情况使用 weak 关键字,相比 assign 有什么不同?</h4>
什么情况使用 weak 关键字?
在 ARC 中,在有可能出现循环引用的时候,往往要通过让其中一端使用 weak
来解决,比如: delegate
代理属性
自身已经对它进行一次强引用,没有必要再强引用一次,此时也会使用 weak
,自定义 IBOutlet
控件属性一般也使用 weak
;当然,也可以使用strong
。在下文也有论述:《IBOutlet连出来的视图属性为什么可以被设置成weak?》
不同点:
-
weak
此特质表明该属性定义了一种“非拥有关系” (nonowning relationship
)。为这种属性设置新值时,设置方法既不保留新值,也不释放旧值。此特质同assign
类似, 然而在属性所指的对象遭到摧毁时,属性值也会清空(nil out)。 而 assign 的“设置方法”只会执行针对“纯量类型” (scalar type
,例如CGFloat
或NSlnteger
等)的简单赋值操作。 -
assign
可以用非 OC 对象,而weak
必须用于 OC 对象
<h4 id="3">3. 怎么用 copy 关键字?</h4>
用途:
-
NSString
、NSArray
、NSDictionary
等等经常使用copy
关键字,是因为他们有对应的可变类型:NSMutableString
、NSMutableArray
、NSMutableDictionary
; -
block
也经常使用copy
关键字,具体原因见官方文档:Objects Use Properties to Keep Track of Blocks:
block
使用 copy
是从 MRC 遗留下来的“传统”,在 MRC 中,方法内部的 block
是在栈区的,使用 copy
可以把它放到堆区.在 ARC 中写不写都行:对于 block
使用 copy
还是 strong
效果是一样的,但写上 copy
也无伤大雅,还能时刻提醒我们:编译器自动对 block
进行了 copy
操作。如果不写 copy
,该类的调用者有可能会忘记或者根本不知道“编译器会自动对 block
进行了 copy
操作”,他们有可能会在调用之前自行拷贝属性值。这种操作多余而低效。你也许会感觉我这种做法有些怪异,不需要写依然写。如果你这样想,其实是你“日用而不知”,你平时开发中是经常在用我说的这种做法的,比如下面的属性不写copy
也行,但是你会选择写还是不写呢?
@property (nonatomic, copy) NSString *userId;
- (instancetype)initWithUserId:(NSString *)userId {
self = [super init];
if (!self) {
return nil;
}
_userId = [userId copy];
return self;
}
下面做下解释: copy
此特质所表达的所属关系与 strong
类似。然而设置方法并不保留新值,而是将其“拷贝” (copy
)。 当属性类型为 NSString
时,经常用此特质来保护其封装性,因为传递给设置方法的新值有可能指向一个 NSMutableString
类的实例。这个类是 NSString
的子类,表示一种可修改其值的字符串,此时若是不拷贝字符串,那么设置完属性之后,字符串的值就可能会在对象不知情的情况下遭人更改。所以,这时就要拷贝一份“不可变” (immutable
)的字符串,确保对象中的字符串值不会无意间变动。只要实现属性所用的对象是“可变的” (mutable
),就应该在设置新属性值时拷贝一份。
用
@property
声明NSString
、NSArray
、NSDictionary
经常使用copy
关键字,是因为他们有对应的可变类型:NSMutableString
、NSMutableArray
、NSMutableDictionary
,他们之间可能进行赋值操作,为确保对象中的字符串值不会无意间变动,应该在设置新属性值时拷贝一份。
该问题在下文中也有论述:用@property
声明的NSString
(或NSArray
,NSDictionary
)经常使用copy
关键字,为什么?如果改用strong
关键字,可能造成什么问题?
<h4 id="4">4. 这个写法会出什么问题: @property (copy) NSMutableArray *array;
</h4>
两个问题:
- 添加,删除,修改数组内的元素的时候,程序会因为找不到对应的方法而崩溃.因为
copy
就是复制一个不可变NSArray
的对象; - 使用了
atomic
属性会严重影响性能 ;
第1条的相关原因在下文中有论述《用@property
声明的NSString
(或NSArray
,NSDictionary
)经常使用 copy
关键字,为什么?如果改用strong
关键字,可能造成什么问题?》 以及上文《怎么用 copy
关键字?》也有论述。
比如下面的代码就会发生崩溃:
.h
文件
@property (nonatomic, copy) NSMutableArray *mutableArray;
.m
文件
NSMutableArray *array = [NSMutableArray arrayWithObjects:@1,@2,nil];
self.mutableArray = array;
[self.mutableArray removeObjectAtIndex:0];
接下来就会奔溃:
-[__NSArrayI removeObjectAtIndex:]: unrecognized selector sent to instance 0x7fcd1bc30460
第2条原因,如下:
该属性使用了同步锁,会在创建时生成一些额外的代码用于帮助编写多线程程序,这会带来性能问题,通过声明 nonatomic 可以节省这些虽然很小但是不必要额外开销。
在默认情况下,由编译器所合成的方法会通过锁定机制确保其原子性(atomicity
)。如果属性具备 nonatomic
特质,则不使用同步锁。请注意,尽管没有名为“atomic”的特质(如果某属性不具备 nonatomic
特质,那它就是“原子的”(atomic
))。
在iOS开发中,你会发现,几乎所有属性都声明为 nonatomic
。
一般情况下并不要求属性必须是“原子的”,因为这并不能保证“线程安全” ( thread safety
),若要实现“线程安全”的操作,还需采用更为深层的锁定机制才行。例如,一个线程在连续多次读取某属性值的过程中有别的线程在同时改写该值,那么即便将属性声明为 atomic
,也还是会读到不同的属性值。
因此,开发iOS程序时一般都会使用 nonatomic
属性。但是在开发 Mac OS X 程序时,使用 atomic 属性通常都不会有性能瓶颈。
<h4 id="5">5. 如何让自己的类用 copy
修饰符?如何重写带 copy
关键字的 setter
?</h4>
若想令自己所写的对象具有拷贝功能,则需实现
NSCopying
协议。如果自定义的对象分为可变版本与不可变版本,那么就要同时实现NSCopying
与NSMutableCopying
协议。
具体步骤:
-
需声明该类遵从
NSCopying
协议 -
实现
NSCopying
协议。该协议只有一个方法:- (id)copyWithZone:(NSZone *)zone;
注意:一提到让自己的类用 copy
修饰符,我们总是想覆写copy
方法,其实真正需要实现的却是 “copyWithZone
” 方法。
以第一题的代码为例:
.h
文件
typedef NS_ENUM(NSInteger, JJSex) {
JJSexMan,
JJSexWoman
};
@interface JJUser : NSObject<NSCopying>
@property (nonatomic, readonly, copy) NSString *name;
@property (nonatomic, readonly, assign) NSUInteger age;
@property (nonatomic, readonly, assign) JJSex sex;
- (instancetype)initWithName:(NSString *)name age:(NSUInteger)age sex:(JJSex)sex;
+ (instancetype)userWithName:(NSString *)name age:(NSUInteger)age sex:(JJSex)sex;
@end
然后实现协议中规定的方法:
- (id)copyWithZone:(NSZone *)zone {
JJUser *copy = [[[self class] allocWithZone:zone] initWithName:_name age:_age sex:_sex];
return copy;
}
但在实际的项目中,不可能这么简单,遇到更复杂一点,比如类对象中的数据结构可能并未在初始化方法中设置好,需要另行设置。举个例子,假如 CYLUser 中含有一个数组,与其他 CYLUser 对象建立或解除朋友关系的那些方法都需要操作这个数组。那么在这种情况下,你得把这个包含朋友对象的数组也一并拷贝过来。下面列出了实现此功能所需的全部代码:
.h
文件
typedef NS_ENUM(NSInteger, JJSex) {
JJSexMan,
JJSexWoman
};
@interface JJUser : NSObject<NSCopying>
@property (nonatomic, readonly, copy) NSString *name;
@property (nonatomic, readonly, assign) NSUInteger age;
@property (nonatomic, readonly, assign) JJSex sex;
- (instancetype)initWithName:(NSString *)name age:(NSUInteger)age sex:(JJSex)sex;
+ (instancetype)userWithName:(NSString *)name age:(NSUInteger)age sex:(JJSex)sex;
- (void)addFriend:(JJUser *)user;
- (void)removeFriend:(JJUser *)user;
@end
.m
文件
@implementation JJUser {
NSMutableSet *_friends;
}
- (void)setName:(NSString *)name {
_name = [name copy];
}
- (instancetype)initWithName:(NSString *)name
age:(NSUInteger)age
sex:(JJSex)sex {
if(self = [super init]) {
_name = [name copy];
_age = age;
_sex = sex;
_friends = [[NSMutableSet alloc] init];
}
return self;
}
- (void)addFriend:(JJUser *)user {
[_friends addObject:user];
}
- (void)removeFriend:(JJUser *)user {
[_friends removeObject:user];
}
- (id)copyWithZone:(NSZone *)zone {
JJUser *copy = [[[self class] allocWithZone:zone] initWithName:_name age:_age sex:_sex];
copy->_friends = [_friends mutableCopy];
return copy;
}
- (id)deepCopy {
CYLUser *copy = [[[self class] alloc] initWithName:_name age:_age sex:_sex];
copy->_friends = [[NSMutableSet alloc] initWithSet:_friends copyItems:YES];
return copy;
}
@end
以上做法能满足基本的需求,但是也有缺陷:
如果你所写的对象需要深拷贝,那么可考虑新增一个专门执行深拷贝的方法。
【注:深浅拷贝的概念,在下文中有介绍,详见下文的:用
@property
声明的NSString
(或NSArray
,NSDictionary
)经常使用copy
关键字,为什么?如果改用strong
关键字,可能造成什么问题?】
在例子中,存放朋友对象的 set
是用 “copyWithZone:
” 方法来拷贝的,这种浅拷贝方式不会逐个复制 set
中的元素。若需要深拷贝的话,则可像下面这样,编写一个专供深拷贝所用的方法:
- (id)deepCopy {
CYLUser *copy = [[[self class] alloc] initWithName:_name age:_age sex:_sex];
copy->_friends = [[NSMutableSet alloc] initWithSet:_friends copyItems:YES];
return copy;
}
至于如何重写带 copy
关键字的 setter
这个问题,
如果抛开本例来回答的话,如下:
- (void)setName:(NSString *)name {
//[_name release];
_name = [name copy];
}
不过也有争议,有人说“苹果如果像下面这样干,是不是效率会高一些?”
- (void)setName:(NSString *)name {
if (_name != name) {
//[_name release];//MRC
_name = [name copy];
}
}
这样真得高效吗?不见得!这种写法“看上去很美、很合理”,但在实际开发中,它更像下图里的做法:
如果使用了属性的话,那么编译器就会自动编写访问属性所需的方法,此过程叫做“自动合成”( auto synthesis
)。需要强调的是,这个过程由编译器在编译期执行,所以编辑器里看不到这些“合成方法” (synthesized method
)的源代码。除了生成方法代码之外,编译器还要自动向类中添加适当类型的实例变量,并且在属性名前面加下划线,以此作为实例变量的名字。
@interface JJPerson : NSObject
@property NSString *firstName;
@property NSString *lastName;
@end
在上例中,会生成两个实例变量,其名称分别为 _firstName 与 _lastName。也可以在类的实现代码里通过 @synthesize 语法来指定实例变量的名字:
@implementation JJPerson
@synthesize firstName = _myFirstName;
@synthesize lastName = _myLastName;
@end
上述语法会将生成的实例变量命名为 _myFirstName
与 _myLastName
,而不再使用默认的名字。一般情况下无须修改默认的实例变量名,但是如果你不喜欢以下划线来命名实例变量,那么可以用这个办法将其改为自己想要的名字。笔者还是推荐使用默认的命名方案,因为如果所有人都坚持这套方案,那么写出来的代码大家都能看得懂。
总结下 @synthesize
合成实例变量的规则,有以下几点:
-
如果指定了成员变量的名称,会生成一个指定的名称的成员变量,
-
如果这个成员已经存在了就不再生成了.
-
如果是
@synthesize foo
; 还会生成一个名称为foo
的成员变量,也就是说:
如果没有指定成员变量的名称会自动生成一个属性同名的成员变量, -
如果是
@synthesize foo = _foo
; 就不会生成成员变量了.
假如 property
名为 foo
,存在一个名为 _foo
的实例变量,那么还会自动合成新变量么? 不会。如下图:
<h4 id="15">15. 在有了自动合成属性实例变量之后,@synthesize
还有哪些使用场景?</h4>
回答这个问题前,我们要搞清楚一个问题,什么情况下不会autosynthesis
(自动合成)?
- 同时重写了
setter
和getter
时 - 重写了只读属性的
getter
时 - 使用了
@dynamic
时 - 在
@protocol
中定义的所有属性 - 在
category
中定义的所有属性 - 重载的属性
当你在子类中重载了父类中的属性,你必须 使用 @synthesize
来手动合成ivar
。
除了后三条,对其他几个我们可以总结出一个规律:当你想手动管理 @property
的所有内容时,你就会尝试通过实现 @property
的所有“存取方法”(the accessor methods)或者使用 @dynamic
来达到这个目的,这时编译器就会认为你打算手动管理 @property
,于是编译器就禁用了 autosynthesis
(自动合成)。
因为有了 autosynthesis
(自动合成),大部分开发者已经习惯不去手动定义ivar,而是依赖于 autosynthesis
(自动合成),但是一旦你需要使用ivar
,而 autosynthesis
(自动合成)又失效了,如果不去手动定义ivar
,那么你就得借助 @synthesize
来手动合成 ivar
。
其实,@synthesize
语法还有一个应用场景,但是不太建议大家使用:
可以在类的实现代码里通过 @synthesize
语法来指定实例变量的名字:
@implementation CYLPerson
@synthesize firstName = _myFirstName;
@synthesize lastName = _myLastName;
@end
上述语法会将生成的实例变量命名为 _myFirstName
与 _myLastName
,而不再使用默认的名字。一般情况下无须修改默认的实例变量名,但是如果你不喜欢以下划线来命名实例变量,那么可以用这个办法将其改为自己想要的名字。笔者还是推荐使用默认的命名方案,因为如果所有人都坚持这套方案,那么写出来的代码大家都能看得懂。
举例说明:应用场景:
@import Foundation;
@interface CYLObject : NSObject
@property (nonatomic, copy) NSString *title;
@end
@implementation CYLObject {
// NSString *_title;
}
//@synthesize title = _title;
- (instancetype)init
{
self = [super init];
if (self) {
_title = @"微博@iOS程序犭袁";
}
return self;
}
- (NSString *)title {
return _title;
}
- (void)setTitle:(NSString *)title {
_title = [title copy];
}
@end
结果编译器报错:
当你同时重写了 setter
和 getter
时,系统就不会生成 ivar
(实例变量/成员变量)。这时候有两种选择:
- 要么如第14行:手动创建
ivar
- 要么如第17行:使用
@synthesize foo = _foo;
关联@property
与ivar
。
更多信息,请戳- 》 When should I use @synthesize explicitly?
网友评论