前篇说到咱们经过ObjC的Category特性给平常工做增长便捷的实现,这一篇则要从语言设计角度,跟你们分享一些思考。segmentfault
ObjC的@interface设计,跟Java和C#真的很像,但又略有不一样,相比之下Java和C#则像是一个模子刻出来的。ObjC的特色十分明显,首先是通常不用写@private
和@public
来区分私有变量,大部分ObjC开发者甚至都不知道还有这两个关键字,其实Cocoa源代码中也基本没有使用过这种设计,即便ObjC是支持的。设计模式
<!--more-->安全
在@interface 中使用 @private和@public框架
@interface Student : NSObject { @private NSString* _name; @public NSNumber* _age; int _height; } @end
如上代码中,Student
有一个私有变量_name
,和两个共有变量_age
、_height
,但在@interface
中声明变量,必定不是Cocoa设计者的初衷,这里有两个方面的考虑。atom
其一,把内部变量直接暴露在外,会下降整个框架的稳定性,由于增长不一样模块之间的耦合,下降了每一个类的内聚性。
其二,内部变量的变量名,很容易跟局部变量变量名产生冲突。上例中我给每个变量名前加了下划线,就是为了防止这个问题发生。线程
因此纵观Cocoa框架的头文件设计,基本没有这样的代码,由于设计者提供了更好的实现方式,就是你们用的更多的@property
关键字。设计
若是用@property声明上面的类,你们都很熟悉code
@interface Student : NSObject @property (nonatomic, strong) NSString* name; @property (nonatomic, strong) NSNumber* age; @property (nonatomic, assign) NSInteger height; @end
@property这个设计真的颇有意思,首先咱们再也不区分私有公有属性,由于只要写在.h
里面的@property,咱们都默认是共有的,私有的@property能够写在.m
文件里。对象
其次,配合写在@implementation里面的@synthesize关键字,能够自动生成setter和getter方法,而如今@synthesize关键字均可以省略,除了个别状况有修改内部变量名称的需求。blog
@implementation Student @synthesize name = _name; @synthesize age = __age; @end
上面的@synthesize,第一个是能够省略的,在不写的状况下,编译预处理会自动给添加@synthesize代码,因此即便没有合成(synthesize)height属性,咱们依然实现了它的setter和getter方法
//这两个方法能够重写 - (void)setHeight:(NSInteger)height { _height = height; } - (NSInteger)height { return _height; }
在setter和getter方法均重写的状况下,@synthesize须要手动添加。
@synthesize height = _height;
setter
和getter
的设计的确值得琢磨,咱们主要从如下几点分析:
例如上例中的内部变量_name
,外部类是没法操做的,只能经过set和get接口来发消息:
Student* s = [[Student alloc] init]; [s setName:@"Tom"]; [s name];
这也是很实用的一个点,由于ObjC的消息设计机制,致使ObjC很难在初始化(init)方法中传入过多参数(题外话,我给ObjC扩展过依赖注入,详见iOS实现依赖注入)。所以新实例的默认属性,放在什么位置实现合适,是你们必定遇到过的问题。
例如最多见的UIViewController
,代码初始化走init
方法,而经过storyboard实力化则走initWithCoder
方法,一些容器属性,经过getter方法初始化,则可避免第一次调用还没有初始化形成的问题。
早期的Cocoa在若是给nil
发消息,是会引发异常的,如今的版本给没有alloc的对象发消息再也不抛异常,以致于某些时候属性没有初始化形成的问题变得更隐蔽,然而重写getter方法能够有效避免这个问题,例如:
//班级类 @interface XXClass : NSObject @property (nonatomic, strong) NSMutableArray* students; //学生 @end @implementation //实现getter方法,在内部变量_students没初始化的状况下将其初始化 - (NSMutableArray *)students { if (!_students) { _students = [NSMutableArray array]; } return _students; } @end
如此一来,不管在任什么时候候,第一次发送[self students]
消息的时候,内部变量_students
都会初始化。
在这里要另外注明一点,在类的内部,不要在setter和getter方法外,直接使用内部变量,遵照这一条会收益不少。
这里要说的就是@property的灵活性了,你们知道@property拥有一系列的修饰词,除了经常使用的nonatomic(非原子化,线程安全)
,strong(强引用类型)
,weak(弱引用类型)
,assign(赋值,用于非对象属性)
之外,还有readonly(只读)
和readwrite(可读写)
两个影响setter和getter方法的属性,readonly
修饰的属性,只有getter方法而没有setter方法。
readwrite
则是一个看起来无关紧要的修饰词,由于默认就是可读写。然而它其实有个专门设计的用法,就是在.h中的interface中被readonly
修饰的属性,能够在这个类的其余类别(category)或者匿名类别中从新声明这个属性时,修改其读写限制,例如
//班级类 @interface XXClass : NSObject @property (nonatomic, strong, readonly) NSMutableArray* students; //学生 @end
//匿名类别 @interface XXClass() @property (nonatomic, strong, readwrite) NSMutableArray* students; @end
这样一来,由于匿名类别通常写在.m文件里(基本没见过写在.h文件里的),因此外部是不能调用students
属性的setter方法,而XXClass
类内部则可使用。
还有一种常见状况是用setter和getter来模拟属性(@property),例如:
//班级类 @interface XXClass : NSObject @property (nonatomic, strong, readonly) NSMutableArray* students; //学生 @property (nonatomic, assign, readonly) NSUInteger studentsCount; //学生数量 @end
- (NSUInteger)studentsCount { return self.students.count; }
这里的studentsCount
是没有内部变量的,经过getter方法伪形成属性接口。
这一篇是ObjC的接口设计模式的一部分,写的比较详细是帮助新手入门,给有经验的朋友带来一些思考,并引出接下来的内容。