iOS创建Model的最佳实践

Immutable Model

我们以UserModle为例,我们可以像这样创建:


public class UserModel: NSObject {
    
    public var userId: NSNumber
    public var name: String?
    public var email: String?
    public var age: Int?
    public var address: String?
    
    init(userId: NSNumber) {
        
        self.userId = userId
        
        super.init()
    }
}

用的时候可以像这样:

let userModel = UserModel(userId: 1)
user.email = "335050309@qq.com"
user.name = "roy"
user.age = 27
user.address = "上海市杨浦区"

这样创建一个User对象好处是弹性很大,我可以随意选择设定某个property的值,但是背后同样带有很大的缺点,就是这个Model变得异常开放,不安分,这种Model我们一般叫Mutable Model。有的时候我们需要Mutable Model,但大部分的时候出于数据安全和解耦考虑我们不希望创建的property在外部可以随意改变,在初始化后不可变的Model叫做Immutable Model,在开发中我的建议尽量使用Immutable Model。我们通过把property设置成readonly,在Swift可以用let或者private(set)。也就是这样:

public class UserModel: NSObject {
    
    public let userId: NSNumber
    public private(set) var name: String?
    public private(set) var email: String?
    public private(set) var age: Int?
    public private(set) var address: String?
    
}

那么怎么写初始化方法呢?

Initializer mapping arguments to properties

当我们把property设置成readonly后,我们只能在init的时候赋值,这个时候就变成这样:

public class User: NSObject {
    
    public var userId: NSNumber
    public var name: String?
    public var email: String?
    public var age: Int?
    public var address: 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
    }
}

使用的时候就变成这样:

let user = User.init(userId: 1, name: "335050309@qq.com", email: "roy", age: 27, address: "上海市杨浦区")

这样创建Model安全可靠,大多数时候是有效的,但是也有一些缺点:

  1. 如果property很多,init方法就有很多形参,然后变得又臭又长。
  2. 有的时候我们只需要Model的某些property,这样我们可能为各个不同的需求写不同的init方法,最终让UserModel变得很庞大。

Initializer taking dictionary

初始化的时候注入一个字典,就是下面的样子:

public class UserModel: NSObject {
    
    public let userId: NSNumber
    public private(set) var name: String?
    public private(set) var email: String?
    public private(set) var age: Int?
    public private(set) var address: 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
    }
}

很显然这解决上一种第一个缺点,但是还是有一个不足之处:

  1. 如果字典没有某个属性对应的key的时候会崩溃,编译器并不能帮助我们排查这种运行时的崩溃。
  2. 不能很好的满足某些时候只需要Model的某些property的需求。

Mutable subclass

我们看看Improving Immutable Object Initialization in Objective-C关于这个是怎么描述的

We end up unsatisfied and continue our quest for the best way to initialize immutable objects. Cocoa is a vast land, so we can – and should – steal some of the ideas used by Apple in its frameworks. We can create a mutable subclass of Reminder class which redefines all properties as readwrite:

@interface MutableReminder : Reminder <NSCopying, NSMutableCopying>

@property (nonatomic, copy, readwrite) NSString *title;
@property (nonatomic, strong, readwrite) NSDate *date;
@property (nonatomic, assign, readwrite) BOOL showsAlert;

@end

Apple uses this approach for example in NSParagraphStyle and NSMutableParagraphStyle. We move between mutable and immutable counterparts with -copy and -mutableCopy. The most common case matches our example: a base class is immutable and its subclass is mutable.

The main disadvantage of this way is that we end up with twice as many classes. What's more, mutable subclasses often exist only as a way to initialize and modify their immutable versions. Many bugs can be caused by using a mutable subclass by accident. For example, a mental burden shows in setting up properties. We have to always check if a mutable subclass exists, and if so use copy modifier instead of strong for the base class.

大致意思是创建一个可变子类,它将所有属性重新定义为readwrite。这种方式的主要缺点是我们最终得到两倍的类。而且,可变子类通常仅作为初始化和修改其不可变版本的方式存在。偶然使用可变子类可能会导致许多错误。例如,在设置属性时会出现心理负担。我们必须始终检查是否存在可变子类。

还有一点这种方式只能在Objective-C中使用。

Builder pattern

Builder pattern 模式需要我们使用一个Builder来创建目标对象,目标对象的property依旧是readonly,但是Builder的对应property却可以选择为readwrite。依旧用UserModel为例,我们需要为其进行适当的改造,改造之后:


typealias UserModelBuilderBlock = (UserModelBuilder) -> UserModelBuilder

public class UserModel: NSObject{
    
    public let userId: NSNumber
    public private(set) var name: String?
    public private(set) var email: String?
    public private(set) var age: Int?
    public private(set) var address: String?
    
    init(userId: NSNumber) {

        self.userId = userId
        
        super.init()
    }
    
    convenience init(userId: NSNumber ,with block: UserModelBuilderBlock){
    
        let userModelBuilder = 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

class UserModelBuilder: NSObject {
    
    public let userId: NSNumber
    public var name: String?
    public var email: String?
    public var age: Int?
    public var address: String?
    
    init(userId: NSNumber) {
        
        self.userId = userId
        super.init()
    }
}

然后可以像下面这样使用:

let userModle = UserModel(userId: 1) { (builder) -> UserModelBuilder in
    
    builder.email = "335050309@qq.com"
    builder.name = "roy"
    builder.age = 27
    builder.address = "上海市杨浦区"
    return builder
}

这种方式虽然我们需要为Model再创建一个Builder,略显啰嗦和复杂,但是当property较多,对Model的需求又比较复杂的时候这又确实是一种值得推荐的方式。

以上全是Swift的代码实现,下面我再贴上对应的OC代码

#import <Foundation/Foundation.h>

@interface RUserModelBuilder : 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

typedef RUserModelBuilder *__nonnull(^RUserModelBuilderBlock)(RUserModelBuilder *__nonnull userModelBuilder);

@interface RUserModel : 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;

+ (nonnull instancetype)buildWithBlock:(nonnull RUserModelBuilderBlock)builderBlock;

@end

#import "RUserModel.h"

@implementation RUserModelBuilder

@end

@interface RUserModel ()

@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

@implementation RUserModel

#pragma mark - NSCopying

+ (nonnull instancetype)buildWithBlock:(nonnull RUserModelBuilderBlock)builderBlock {

    RUserModelBuilder *userModelBuilder = builderBlock([[RUserModelBuilder alloc] init]);

    RUserModel *userModel = [[RUserModel alloc] init];

    userModel.userId = userModelBuilder.userId;
    userModel.name = userModelBuilder.name;
    userModel.email = userModelBuilder.email;
    userModel.age = userModelBuilder.age;
    userModel.address = userModelBuilder.address;

    return userModel;
}

@end

demo地址ImmutableModel

参考文章:

Improving Immutable Object Initialization in Objective-C
iOS 创建对象的姿势

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,142评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,298评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,068评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,081评论 1 291
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,099评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,071评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,990评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,832评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,274评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,488评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,649评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,378评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,979评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,625评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,796评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,643评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,545评论 2 352

推荐阅读更多精彩内容