Jonathan's Studio

OC-声明小记(一)

字数统计: 2.5k阅读时长: 9 min
2020/03/06

案例

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

好处
  1. 自动合成getter和setter方法。当声明一个属性(property)的时候编译器默认情况下会自动生成相关的getter和setter方法

  2. 更好的声明一组方法。因为访问方法的命名约定,可以很清晰的看出getter和setter的用处。

  3. 属性(property)关键词能够传递出相关行为的额外信息。属性提供了一些可能会使用的特性来进行声明,包括assign(对比 copy),weak,strong,atomic(对比 nonatomic),readwrite,readonly,unsafe_unretained,retain等。

@property指示符
  1. atomic/nonatomic

    1. 指定合成存取方法是否为原子操作,可以理解为是否线程安全

    2. 但在iOS上即时使用atomic也不一定是线程安全的,要保证线程安全需要使用锁机制,后面再研究

    3. 可以发现几乎所有代码的属性设置都会使用nonatomic,这样能够提高访问性能,在iOS中使用锁机制的开销较大,会损耗性能。

所谓原子操作是指不会被线程调度机制打断的操作;这种操作一旦开始,就一直运行到结束,中间不会有任何 context switch(切换到另一个进程)

  1. readwrite/readonly

    1. readwrite是编译器的默认选项,表示自动生成getter和setter,如果需要getter和setter不写即可。

    2. readonly表示只合成getter而不合成setter。

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

}





  1. weak/strong

    1. 使用weak修饰的时候同样不会增加所赋的新值的引用计数,也不减少旧值的引用计数,但当该值被销毁时,weak修饰的属性会被自动赋值为nil,这样就可以避免野指针错误。

    2. strong表示属性对所赋的值持有强引用表示一种“拥有关系”(owning relationship),会先保留新值即增加新值的引用计数,然后再释放旧值即减少旧值的引用计数。只能修饰对象。如果对一些对象需要保持强引用则使用strong。strong修饰可变对象

    3. weak表示对所赋的值对象持有弱引用表示一种“非拥有关系”(nonowning relationship),对新值不会增加引用计数,也不会减少旧值的引用计数。所赋的值在引用计数为0被销毁后,weak修饰的属性会被自动置为nil能够有效防止野指针错误.

    4. weak常用在修饰delegate等防止循环引用的场景。

  1. unsafe_unretained

    使用unsafe_unretained修饰时效果与assign相同,不会增加引用计数,当所赋的值被销毁时不会被置为nil可能会发生野指针错误。unsafe_unretained与assign的区别在于,unsafe_unretained只能修饰对象,不能修饰标量类型,而assign两者均可修饰。

  1. 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修饰。

  1. 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 初始化方法,也是为了方便子类化

注意点
  1. 在初始化中第二个方法并没有选择传入参数,不打算初始时初始化“性别”(sex)属性,打算后期再修改,如果是这种情况,那么应该把“性别”(sex)属性设为 readwrite 属性

  2. 但是就于指定初始化方法 designated initializer的出现,那么在设计对应 @property 时就应该尽量使用不可变的对象

本文引用大量的文章,仅做收集整理理解

《招聘一个靠谱的iOS》面试题参考答案(上)

iOS @property探究(一): 基础详解

CATALOG
  1. 1. 案例
    1. 1.1. 应避免使用基本类型
    2. 1.2. 方法命名
    3. 1.3. OC中的@property详解
      1. 1.3.1. 好处
      2. 1.3.2. @property指示符
    4. 1.4. 初始化方法
      1. 1.4.1. 注意点