iOS中创建Model的最佳实践记录
前言
作为一个优秀的程序员,或者想成为优秀的程序员,最基本的你得有MVC编程思想,那么你就要对JSON获取的数据建Model,将service和controller层都分离,从而做到低耦合。现在有很多利用runtime能快速的将json数据转为一个Model。但是我在做项目的时候,发现创建Model(特别是属性特多的)写属性代码很浪费时间,降低了编程效率。后来我自己就写了个好玩的能省去时间创建Model的一个方法,下面话不多说了,来一起看看详细的介绍吧
ImmutableModel
我们以UserModle为例,我们可以像这样创建:
publicclassUserModel:NSObject{ publicvaruserId:NSNumber publicvarname:String? publicvaremail:String? publicvarage:Int? publicvaraddress:String? init(userId:NSNumber){ self.userId=userId super.init() } }
用的时候可以像这样:
letuserModel=UserModel(userId:1) user.email="335050309@qq.com" user.name="roy" user.age=27 user.address="上海市杨浦区"
这样创建一个User对象好处是弹性很大,我可以随意选择设定某个property的值,但是背后同样带有很大的缺点,就是这个Model变得异常开放,不安分,这种Model我们一般叫MutableModel。有的时候我们需要MutableModel,但大部分的时候出于数据安全和解耦考虑我们不希望创建的property在外部可以随意改变,在初始化后不可变的Model叫做ImmutableModel,在开发中我的建议尽量使用ImmutableModel。我们通过把property设置成readonly,在Swift可以用let或者private(set)。也就是这样:
publicclassUserModel:NSObject{ publicletuserId:NSNumber publicprivate(set)varname:String? publicprivate(set)varemail:String? publicprivate(set)varage:Int? publicprivate(set)varaddress:String? }
那么怎么写初始化方法呢?
Initializermappingargumentstoproperties
当我们把property设置成readonly后,我们只能在init的时候赋值,这个时候就变成这样:
publicclassUser:NSObject{ publicvaruserId:NSNumber publicvarname:String? publicvaremail:String? publicvarage:Int? publicvaraddress:String? init(userId:NSNumber,name:String?,email:String,age:Int,address:String){ self.userId=userId super.init() self.name=name self.email=email self.age=age self.address=address } }
使用的时候就变成这样:
letuser=User.init(userId:1,name:"335050309@qq.com",email:"roy",age:27,address:"上海市杨浦区")
这样创建Model安全可靠,大多数时候是有效的,但是也有一些缺点:
- 如果property很多,init方法就有很多形参,然后变得又臭又长。
- 有的时候我们只需要Model的某些property,这样我们可能为各个不同的需求写不同的init方法,最终让UserModel变得很庞大。
Initializertakingdictionary
初始化的时候注入一个字典,就是下面的样子:
publicclassUserModel:NSObject{ publicletuserId:NSNumber publicprivate(set)varname:String? publicprivate(set)varemail:String? publicprivate(set)varage:Int? publicprivate(set)varaddress:String? init(dic:NSDictionary){ self.userId=(dic["userId"]as?NSNumber)! super.init() self.name=dic["name"]as?String self.email=dic["email"]as?String self.age=dic["age"]as?Int self.address=dic["address"]as?String } }
很显然这解决上一种第一个缺点,但是还是有一个不足之处:
- 如果字典没有某个属性对应的key的时候会崩溃,编译器并不能帮助我们排查这种运行时的崩溃。
- 不能很好的满足某些时候只需要Model的某些property的需求。
Mutablesubclass
我们看看ImprovingImmutableObjectInitializationinObjective-C关于这个是怎么描述的
Weendupunsatisfiedandcontinueourquestforthebestwaytoinitializeimmutableobjects.Cocoaisavastland,sowecan–andshould–stealsomeoftheideasusedbyAppleinitsframeworks.WecancreateamutablesubclassofReminderclasswhichredefinesallpropertiesasreadwrite:
@interfaceMutableReminder:Reminder@property(nonatomic,copy,readwrite)NSString*title; @property(nonatomic,strong,readwrite)NSDate*date; @property(nonatomic,assign,readwrite)BOOLshowsAlert; @end
AppleusesthisapproachforexampleinNSParagraphStyleandNSMutableParagraphStyle.Wemovebetweenmutableandimmutablecounterpartswith-copyand-mutableCopy.Themostcommoncasematchesourexample:abaseclassisimmutableanditssubclassismutable.
Themaindisadvantageofthiswayisthatweendupwithtwiceasmanyclasses.What'smore,mutablesubclassesoftenexistonlyasawaytoinitializeandmodifytheirimmutableversions.Manybugscanbecausedbyusingamutablesubclassbyaccident.Forexample,amentalburdenshowsinsettingupproperties.Wehavetoalwayscheckifamutablesubclassexists,andifsousecopymodifierinsteadofstrongforthebaseclass.
大致意思是创建一个可变子类,它将所有属性重新定义为readwrite。这种方式的主要缺点是我们最终得到两倍的类。而且,可变子类通常仅作为初始化和修改其不可变版本的方式存在。偶然使用可变子类可能会导致许多错误。例如,在设置属性时会出现心理负担。我们必须始终检查是否存在可变子类。
还有一点这种方式只能在Objective-C中使用。
Builderpattern
Builderpattern模式需要我们使用一个Builder来创建目标对象,目标对象的property依旧是readonly,但是Builder的对应property却可以选择为readwrite。依旧用UserModel为例,我们需要为其进行适当的改造,改造之后:
typealiasUserModelBuilderBlock=(UserModelBuilder)->UserModelBuilder publicclassUserModel:NSObject{ publicletuserId:NSNumber publicprivate(set)varname:String? publicprivate(set)varemail:String? publicprivate(set)varage:Int? publicprivate(set)varaddress:String? init(userId:NSNumber){ self.userId=userId super.init() } convenienceinit(userId:NSNumber,withblock:UserModelBuilderBlock){ letuserModelBuilder=block(UserModelBuilder.init(userId:userId)) self.init(userId:userModelBuilder.userId) self.email=userModelBuilder.email self.name=userModelBuilder.name self.age=userModelBuilder.age self.address=userModelBuilder.address } }
之后是对应的Builder
classUserModelBuilder:NSObject{ publicletuserId:NSNumber publicvarname:String? publicvaremail:String? publicvarage:Int? publicvaraddress:String? init(userId:NSNumber){ self.userId=userId super.init() } }
然后可以像下面这样使用:
letuserModle=UserModel(userId:1){(builder)->UserModelBuilderin builder.email="335050309@qq.com" builder.name="roy" builder.age=27 builder.address="上海市杨浦区" returnbuilder }
这种方式虽然我们需要为Model再创建一个Builder,略显啰嗦和复杂,但是当property较多,对Model的需求又比较复杂的时候这又确实是一种值得推荐的方式。
以上全是Swift的代码实现,下面我再贴上对应的OC代码
#import@interfaceRUserModelBuilder:NSObject @property(nonatomic,strong,readwrite,nonnull)NSNumber*userId; @property(nonatomic,copy,readwrite,nullable)NSString*name; @property(nonatomic,copy,readwrite,nullable)NSString*email; @property(nonatomic,copy,readwrite,nullable)NSNumber*age; @property(nonatomic,copy,readwrite,nullable)NSString*address; @end typedefRUserModelBuilder*__nonnull(^RUserModelBuilderBlock)(RUserModelBuilder*__nonnulluserModelBuilder); @interfaceRUserModel:NSObject @property(nonatomic,strong,readonly,nonnull)NSNumber*userId; @property(nonatomic,copy,readonly,nullable)NSString*name; @property(nonatomic,copy,readonly,nullable)NSString*email; @property(nonatomic,copy,readonly,nullable)NSNumber*age; @property(nonatomic,copy,readonly,nullable)NSString*address; +(nonnullinstancetype)buildWithBlock:(nonnullRUserModelBuilderBlock)builderBlock; @end
#import"RUserModel.h" @implementationRUserModelBuilder @end @interfaceRUserModel() @property(nonatomic,strong,readwrite,nonnull)NSNumber*userId; @property(nonatomic,copy,readwrite,nullable)NSString*name; @property(nonatomic,copy,readwrite,nullable)NSString*email; @property(nonatomic,copy,readwrite,nullable)NSNumber*age; @property(nonatomic,copy,readwrite,nullable)NSString*address; @end @implementationRUserModel #pragmamark-NSCopying +(nonnullinstancetype)buildWithBlock:(nonnullRUserModelBuilderBlock)builderBlock{ RUserModelBuilder*userModelBuilder=builderBlock([[RUserModelBuilderalloc]init]); RUserModel*userModel=[[RUserModelalloc]init]; userModel.userId=userModelBuilder.userId; userModel.name=userModelBuilder.name; userModel.email=userModelBuilder.email; userModel.age=userModelBuilder.age; userModel.address=userModelBuilder.address; returnuserModel; } @end
demo地址:ImmutableModel(本地下载)
参考文章:
ImprovingImmutableObjectInitializationinObjective-C
iOS创建对象的姿势
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对毛票票的支持。