案例
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
| //这是一个推荐的写法
@interface CYLUser : NSObject<NSCopying>
@property (nonatomic, readonly, copy) NSString *name;
@property (nonatomic, readonly, assign) NSUInteger age;
@property (nonatomic, readwrite, assign) CYLSex sex;
- (instancetype)initWithName:(NSString *)name age:(NSUInteger)age sex:(CYLSex)sex;
- (instancetype)initWithName:(NSString *)name age:(NSUInteger)age;
+ (instancetype)userWithName:(NSString *)name age:(NSUInteger)age sex:(CYLSex)sex;
@end
|
应避免使用基本类型
建议使用 Foundation 数据类型 这样做的是基于64-bit 适配考虑
1 2 3 4 5 6 7 8 9
| int -> NSInteger
unsigned -> NSUInteger
float -> CGFloat
动画时间 -> NSTimeInterval
|
补充链接:64位的跟进
方法命名
1 2 3
| -(id)initUserModelWithUserName: (NSString*)name withAge:(int)age;
|
方法中尽量不要用 with 来连接两个参数: withAge: 应当换为age:,age: 已经足以清晰说明参数的作用,**也不建议用 andAge: **
通常情况下,即使有类似 withA:withB: 的命名需求,也通常是使用withA:andB:
这种命名,用来表示方法执行了两个相对独立的操作(从设计上来说,这时候也可以拆分成两个独立的方法),它不应该用作阐明有多个参数,比如下面的:
1 2 3 4 5 6 7 8 9 10 11 12 13
| //错误,不要使用"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;
|
OC中的@property详解
属性捕获了对象的状态。它们反映了对象的固有属性(intrinsic attributes)以及对象与其他对象之间的关系。属性(property)提供了一种安全、便捷的方式来与这些属性(attribute)交互,而不需要手动编写一系列的访问方法,如果需要的话可以自定义getter和setter方法来覆盖编译器自动生成的相关方法。
– Apple Official Property Introduction
好处
自动合成getter和setter方法。当声明一个属性(property)的时候编译器默认情况下会自动生成相关的getter和setter方法
更好的声明一组方法。因为访问方法的命名约定,可以很清晰的看出getter和setter的用处。
属性(property)关键词能够传递出相关行为的额外信息。属性提供了一些可能会使用的特性来进行声明,包括assign(对比 copy),weak,strong,atomic(对比 nonatomic),readwrite,readonly,unsafe_unretained,retain等。
@property指示符
atomic/nonatomic
指定合成存取方法是否为原子操作,可以理解为是否线程安全
但在iOS上即时使用atomic也不一定是线程安全的,要保证线程安全需要使用锁机制,后面再研究
可以发现几乎所有代码的属性设置都会使用nonatomic,这样能够提高访问性能,在iOS中使用锁机制的开销较大,会损耗性能。
所谓原子操作是指不会被线程调度机制打断的操作;这种操作一旦开始,就一直运行到结束,中间不会有任何 context switch(切换到另一个进程)
readwrite/readonly
readwrite是编译器的默认选项,表示自动生成getter和setter,如果需要getter和setter不写即可。
readonly表示只合成getter而不合成setter。
assign
assign表示对属性只进行简单的赋值操作,不更改所赋的新值的引用计数,也不改变旧值的引用计数,常用于标量类型,如NSInteger,NSUInteger,CGFloat,NSTimeInterval等。
assign也可以修饰对象如NSString等类型对象,上面说过使用assign修饰不会更改所赋的新值的引用计数,也不改变旧值的引用计数,如果当所赋的新值引用计数为0对象被销毁时属性并不知道,编译器不会将该属性置为nil,指针仍旧指向之前被销毁的内存,这时访问该属性会产生野指针错误并崩溃,因此使用assign修饰的类型一定要为标量类型。
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 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73
| @interface Person : NSObject
@property (nonatomic, assign) NSString* name;
@property (nonatomic, assign) NSUInteger age;
@end
@implementation Person
@synthesize name = _name;
@synthesize age = _age;
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *p = [[Person alloc] init];
//这里使用NSMutableString而不使用NSString是因为NSString会缓存字符串,后面置空的时候实际没有被销毁
NSMutableString *s = [[NSMutableString alloc] initWithString:@"Jiaming Chen"];
//设置p.name不会增加s的引用计数,只是单纯将s指向的地址赋给p.name
p.name = s;
//输出两个变量的内存地址,可以看出是一致的
NSLog(@"%p %p", p.name, s);
//这里可以正常访问name
NSLog(@"%@ %ld", p.name, p.age);
//将上述字符串置空,引用计数为0,对象被销毁
s = nil;
//查看其地址时仍然可以访问到,表示其仍然指向那一块内存
NSLog(@"%p", p.name);
//访问内容时发生野指针错误,程序崩溃。因为对象已经被销毁
NSLog(@"%@ %ld", p.name, p.age);
}
return 0;
}
|
weak/strong
使用weak修饰的时候同样不会增加所赋的新值的引用计数,也不减少旧值的引用计数,但当该值被销毁时,weak修饰的属性会被自动赋值为nil,这样就可以避免野指针错误。
strong表示属性对所赋的值持有强引用表示一种“拥有关系”(owning relationship),会先保留新值即增加新值的引用计数,然后再释放旧值即减少旧值的引用计数。只能修饰对象。如果对一些对象需要保持强引用则使用strong。strong修饰可变对象
weak表示对所赋的值对象持有弱引用表示一种“非拥有关系”(nonowning relationship),对新值不会增加引用计数,也不会减少旧值的引用计数。所赋的值在引用计数为0被销毁后,weak修饰的属性会被自动置为nil能够有效防止野指针错误.
weak常用在修饰delegate等防止循环引用的场景。
unsafe_unretained
使用unsafe_unretained修饰时效果与assign相同,不会增加引用计数,当所赋的值被销毁时不会被置为nil可能会发生野指针错误。unsafe_unretained与assign的区别在于,unsafe_unretained只能修饰对象,不能修饰标量类型,而assign两者均可修饰。
- copy
copy修饰的属性会在内存里拷贝一份对象,两个指针指向不同的内存地址。他修饰不可变对象。
一般用来修饰有对应可变类型子类的对象。如:NSString/NSMutableString,NSArray/NSMutableArray,NSDictionary/NSMutableDictionary等。
为确保这些不可变对象因为可变子类对象影响,需要copy一份备份,如果不使用copy修饰,使用strong或assign等修饰则会因为多态导致属性值被修改。
这里的copy还牵扯到NSCopying和NSMutableCopying协议.
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 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
| @interface Person : NSObject
//使用strong修饰NSString
@property (nonatomic, strong) NSString* name;
@property (nonatomic, assign) NSUInteger age;
@end
@implementation Person
@synthesize name = _name;
@synthesize age = _age;
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *p = [[Person alloc] init];
NSMutableString *s = [[NSMutableString alloc] initWithString:@"Jiaming Chen"];
//将可变字符串赋值给p.name
p.name = s;
//输出的地址和内容均一致
NSLog(@"%p %p %@ %@", p.name, s, p.name, s);
//修改可变字符串s
[s appendString:@" is a good guy"];
//再次输出p.name被影响
NSLog(@"%p %p %@ %@", p.name, s, p.name, s);
}
return 0;
}
|
对于可变对象类型,如NSMutableString、NSMutableArray等则不可以使用copy修饰,因为Foundation框架提供的这些类都实现了NSCopying协议,使用copy方法返回的都是不可变对象,如果使用copy修饰符在对可变对象赋值时则会获取一个不可变对象,接下来如果对这个对象进行可变对象的操作则会产生异常,因为OC没有提供mutableCopy修饰符,对于可变对象使用strong修饰符即可。
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 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
| @interface Person : NSObject
//使用copy修饰NSMutableString
@property (nonatomic, copy) NSMutableString* name;
@property (nonatomic, assign) NSUInteger age;
@end
@implementation Person
@synthesize name = _name;
@synthesize age = _age;
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *p = [[Person alloc] init];
NSMutableString *s = [[NSMutableString alloc] initWithString:@"Jiaming Chen"];
//将可变字符串赋值给p.name
p.name = s;
//输出的地址不一致,内容一致 在进行赋值的时候会通过copy方法获取一个不可变对象,因此p.name的地址和s的地址不同
NSLog(@"%p %p %@ %@", p.name, s, p.name, s);
//修改p.name,此时抛出异常
[p.name appendString:@" is a good guy."];
}
return 0;
}
|
所以,针对不可变对象使用copy修饰,针对可变对象使用strong修饰。
retain
在ARC环境下使用较少,在MRC下使用效果与strong一致。
初始化方法
Objective-C 有 designated 和 secondary 初始化方法的观念。 designated 初始化方法是提供所有的参数,secondary 初始化方法是一个或多个,并且提供一个或者更多的默认参数来调用 designated 初始化方法的初始化方法.
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 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| @implementation CYLUser
//指定初始化方法 designated initializer
- (instancetype)initWithName:(NSString *)name
age:(NSUInteger)age
sex:(CYLSex)sex {
if(self = [super init]) {
_name = [name copy];
_age = age;
_sex = sex;
}
return self;
}
//间接初始化方法 secondary initializer
- (instancetype)initWithName:(NSString *)name
age:(NSUInteger)age {
return [self initWithName:name age:age sex:nil];
}
@end
|
暴露 designated 初始化方法,也是为了方便子类化
注意点
在初始化中第二个方法并没有选择传入参数,不打算初始时初始化“性别”(sex)属性,打算后期再修改,如果是这种情况,那么应该把“性别”(sex)属性设为 readwrite 属性
但是就于指定初始化方法 designated initializer的出现,那么在设计对应 @property 时就应该尽量使用不可变的对象
本文引用大量的文章,仅做收集整理理解
《招聘一个靠谱的iOS》面试题参考答案(上)
iOS @property探究(一): 基础详解