iOS創(chuàng)建Model的最佳實踐

Immutable Model

我們以UserModle為例欣范,我們可以像這樣創(chuàng)建:


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 = "上海市楊浦區(qū)"

這樣創(chuàng)建一個User對象好處是彈性很大变泄,我可以隨意選擇設(shè)定某個property的值,但是背后同樣帶有很大的缺點恼琼,就是這個Model變得異常開放妨蛹,不安分,這種Model我們一般叫Mutable Model晴竞。有的時候我們需要Mutable Model蛙卤,但大部分的時候出于數(shù)據(jù)安全和解耦考慮我們不希望創(chuàng)建的property在外部可以隨意改變,在初始化后不可變的Model叫做Immutable Model,在開發(fā)中我的建議盡量使用Immutable Model颤难。我們通過把property設(shè)置成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

當(dāng)我們把property設(shè)置成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: "上海市楊浦區(qū)")

這樣創(chuàng)建Model安全可靠飘千,大多數(shù)時候是有效的栈雳,但是也有一些缺點:

  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. 如果字典沒有某個屬性對應(yīng)的key的時候會崩潰苛让,編譯器并不能幫助我們排查這種運行時的崩潰。
  2. 不能很好的滿足某些時候只需要Model的某些property的需求瘦材。

Mutable subclass

我們看看Improving Immutable Object Initialization in Objective-C關(guān)于這個是怎么描述的

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.

大致意思是創(chuàng)建一個可變子類食棕,它將所有屬性重新定義為readwrite宣蠕。這種方式的主要缺點是我們最終得到兩倍的類。而且镀层,可變子類通常僅作為初始化和修改其不可變版本的方式存在唱逢。偶然使用可變子類可能會導(dǎo)致許多錯誤屋休。例如,在設(shè)置屬性時會出現(xiàn)心理負(fù)擔(dān)痪枫。我們必須始終檢查是否存在可變子類奶陈。

還有一點這種方式只能在Objective-C中使用附较。

Builder pattern

Builder pattern 模式需要我們使用一個Builder來創(chuàng)建目標(biāo)對象,目標(biāo)對象的property依舊是readonly徐勃,但是Builder的對應(yīng)property卻可以選擇為readwrite僻肖。依舊用UserModel為例扎酷,我們需要為其進(jìn)行適當(dāng)?shù)母脑於舸遥脑熘螅?/p>


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
    }
}

之后是對應(yīng)的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 = "上海市楊浦區(qū)"
    return builder
}

這種方式雖然我們需要為Model再創(chuàng)建一個Builder凡纳,略顯啰嗦和復(fù)雜荐糜,但是當(dāng)property較多,對Model的需求又比較復(fù)雜的時候這又確實是一種值得推薦的方式延塑。

以上全是Swift的代碼實現(xiàn)答渔,下面我再貼上對應(yīng)的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 創(chuàng)建對象的姿勢

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市务豺,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌蚪燕,老刑警劉巖奔浅,帶你破解...
    沈念sama閱讀 218,451評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件乘凸,死亡現(xiàn)場離奇詭異,居然都是意外死亡营勤,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,172評論 3 394
  • 文/潘曉璐 我一進(jìn)店門寿羞,熙熙樓的掌柜王于貴愁眉苦臉地迎上來绪穆,“玉大人虱岂,你說我怎么就攤上這事∧丫” “怎么了蔑滓?”我有些...
    開封第一講書人閱讀 164,782評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長燎窘。 經(jīng)常有香客問我,道長钩骇,這世上最難降的妖魔是什么铝量? 我笑而不...
    開封第一講書人閱讀 58,709評論 1 294
  • 正文 為了忘掉前任慢叨,我火速辦了婚禮,結(jié)果婚禮上烛缔,老公的妹妹穿的比我還像新娘轩拨。我一直安慰自己,他們只是感情好晕翠,可當(dāng)我...
    茶點故事閱讀 67,733評論 6 392
  • 文/花漫 我一把揭開白布砍濒。 她就那樣靜靜地躺著,像睡著了一般樊卓。 火紅的嫁衣襯著肌膚如雪碌尔。 梳的紋絲不亂的頭發(fā)上券敌,一...
    開封第一講書人閱讀 51,578評論 1 305
  • 那天,我揣著相機與錄音颈走,去河邊找鬼咱士。 笑死,一個胖子當(dāng)著我的面吹牛锐膜,可吹牛的內(nèi)容都是我干的道盏。 我是一名探鬼主播,決...
    沈念sama閱讀 40,320評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼荷逞,長吁一口氣:“原來是場噩夢啊……” “哼种远!你這毒婦竟也來了顽耳?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,241評論 0 276
  • 序言:老撾萬榮一對情侶失蹤膝迎,失蹤者是張志新(化名)和其女友劉穎限次,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體掂恕,經(jīng)...
    沈念sama閱讀 45,686評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡弛槐,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,878評論 3 336
  • 正文 我和宋清朗相戀三年乎串,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片鸯两。...
    茶點故事閱讀 39,992評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡钧唐,死狀恐怖匠襟,靈堂內(nèi)的尸體忽然破棺而出该园,到底是詐尸還是另有隱情里初,我是刑警寧澤忽舟,帶...
    沈念sama閱讀 35,715評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站刁品,受9級特大地震影響挑随,放射性物質(zhì)發(fā)生泄漏及刻。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,336評論 3 330
  • 文/蒙蒙 一暑劝、第九天 我趴在偏房一處隱蔽的房頂上張望担猛。 院中可真熱鬧丢氢,春花似錦、人聲如沸蒸走。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,912評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至夫椭,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間扰付,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,040評論 1 270
  • 我被黑心中介騙來泰國打工粒督, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留禽翼,地道東北人族跛。 一個月前我還...
    沈念sama閱讀 48,173評論 3 370
  • 正文 我出身青樓礁哄,卻偏偏與公主長得像,于是被迫代替她去往敵國和親夺脾。 傳聞我的和親對象是個殘疾皇子茉继,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,947評論 2 355

推薦閱讀更多精彩內(nèi)容