iOS创建对象的不同姿势详解
前言
在写iOS代码的时候,怎么样去new一个新对象出来,都有一些讲究在里面。使用不同的姿势去创建对象,对后期维护所造成的影响会存在细微的差别。
init创建
在之前一篇分析iOS代码耦合的文章中,提到过当我们给一个对象的property赋值的时候,通过init方法传入参数来初始化property会让我们的代码更可靠。
有些人在定义带property的class的时候,会这样定义:
@interfaceUser:NSObject @property(nonatomic,strong)NSNumber*userID; @end
使用的时候如下:
User*user=[[Useralloc]init]; user.userID=@1000;
尤其是在定义model的时候,很容易写出这种,先init,而后挨个给property赋值的代码。这种代码的问题在于property对于外部是可写的,property处于随时可能变化的状态。之前不少篇文章中都强调过immutable的重要性,同样对于一个class,我们也应该优先考虑设计成immutable的。
initWith创建
如果将property都设置成readonly的,或者不暴露property,property的赋值都通过initWith的方式来初始化,就可以得到一个具备immutable的class定义了,具体到上面的例子代码如下:
//User.h @interfaceUser:NSObject @property(nonatomic,strong,readonly)NSNumber*userID; -(instancetype)initWithUserID:(NSNumber*)uid; @end //User.m @implementationUser -(instancetype)initWithUserID:(NSNumber*)uid{ self=[superinit]; if(!self){ returnnil; } _userID=uid; returnself; } @end
userID在.h文件当中是readonly的,userID只有一次被赋值的机会,即在User的initWith方法中。这种方式的好处是一旦User对象创建完毕之后,就处于immutable的状态,property都是不可修改的,安全可靠。
Designatedinitializer
Apple为了方便开发者使用init方法,引入了一种名为designatedinitializer的pattern。主要用来管理当一个class拥有多个property需要赋值的场景。比如上面我们的User类:
@interfaceUser:NSObject @property(nonatomic,strong,readonly)NSNumber*userID; @property(nonatomic,strong,readonly)NSString*userName; @property(nonatomic,strong,readonly)NSString*signature; @end
有些场景需要初始化userID和userName,而有些场景只需要初始化userID和signature,所以我们需要提供多个initWith方法给不同的场景使用。为了管理initWith方法,Apple将init方法分为两种类型:designatedinitializer和convenienceinitializer(又叫secondaryinitializer)。
designatedinitializer只有一个,它会为class当中每个property都提供一个初始值,是最完整的initWith方法。convenienceinitializer则可以有很多个,它可以选择只初始化部分的property。convenienceinitializer最后到会调用到designatedinitializer,所以designatedinitializer也可以叫做finalinitializer。
无论我们定义何种类型的class,给class中的每个property都赋予一个初始值是个很好的习惯,可以避免掉一些意外的bug产生,这也是designatedinitializer的重要职责。
在实际的项目当中,一个class的property数目可能会随着业务的增长而增加,最后的结果就是会生成越来越多的convenienceinitializer。上述的User类,如果是3个property,极端的情况下最多可以有7个init方法。Peak君在阅读代码的时候,也确实看到过有些class定义了一连串整整齐齐摆放的init方法,代码虽然看着规范,但显得啰嗦,而且每次需要肉眼搜索适合的init方法。
其实我们还可以用另一种姿势来init我们的对象。
Builderpattern
最初是在学习Android的时候,发现这个builderpattern也可以用来构建对象,而且可以很好的解决init方法过多难以管理的问题。先来看下如何实现,顾名思义,builderpattern使用另一个名为builder的类来创建我们的目标对象,还是上面的例子,代码如下:
//UserBuilder.h @interfaceUserBuilder:NSObject @property(nonatomic,strong,readonly)NSNumber*userID; @property(nonatomic,strong,readonly)NSString*userName; @property(nonatomic,strong,readonly)NSString*signature; -(UserBuilder*)userID:(NSNumber*)userID; -(UserBuilder*)userName:(NSString*)userName; -(UserBuilder*)signature:(NSString*)signature; @end //UserBuilder.m @implementationUserBuilder -(UserBuilder*)userID:(NSNumber*)userID{ _userID=userID; returnself; } -(UserBuilder*)userName:(NSString*)userName{ _userName=userName; returnself; } -(UserBuilder*)signature:(NSString*)signature{ _signature=signature; returnself; } @end
接下来User的init方法从Builder中获取property的初始值:
//User.h @interfaceUser:NSObject @property(nonatomic,strong,readonly)NSNumber*userID; @property(nonatomic,strong,readonly)NSString*userName; @property(nonatomic,strong,readonly)NSString*signature; -(instancetype)initWithUserBuilder:(UserBuilder*)builder; @end //User.m @implementationUser -(instancetype)initWithUserBuilder:(UserBuilder*)builder{ self=[superinit]; if(!self){ returnnil; } _userID=builder.userID; _userName=builder.userName; _signature=builder.signature; returnself; } @end
如果要创建User对象,则按照这种方式:
UserBuilder*builder=[[[[UserBuildernew]userName:@"peak"]userID:@1000]signature:@"roll"]; User*user=[[Useralloc]initWithUserBuilder:builder];
这样我们避免了书写多个init方法,同样User对象也是immutable的,也做到了只在init方法中做一次赋值操作,每个场景都可以按照自己的需求初始化部分property,当然最后我们需要在initWithUserBuilder中为每一个property赋值,initWithUserBuilder扮演的角色类似于designatedinitializer。
追求代码美感的同学可能发现了,UserBuilder的创建语法很丑陋,多个[]套嵌使用。为了让代码更好看一些,我们也可以使用block来创建:
User*user=[UseruserWithBlock:^(UserBuilder*builder){ builder.userName=@"peak"; builder.userID=@1000; builder.signature=YES; }];
builderpattern在Android平台使用的比较多,我在iOS平台上鲜少有看到使用的场景。builderpattern的不足之处也比较明显,需要另外定义一个builder类,多写一些代码(property基本都重复写了一遍)。个人觉得,在property数量较多,初始化的场景也比较多的时候,在iOS上使用builderpattern也会是个不错的方案。
designatedinitializervsbuilderpattern,这二者之间的不同其实很好的体现了语言本身的差异性。学习过java的同学就能明白,在java的世界中,一切都是可以被封装成对象的,使用java的时候,经常要定义各式各样的辅助类来完成某个任务,好处是封装度高,类职责划分粒度小,缺点是类太多,有时候会为了封装而封装,某些场景代码反而不够直观。
经读者反馈,原来这篇文章的主题已经被写过了。看过之后发现比我写的更全面,推荐大家阅读,传送门。
总结
以上就是这篇文章的全部内容了,本文简单梳理了下创建对象的不同姿势,希望对大家有些帮助。如果有疑问大家可以留言交流。