歡迎光臨
每天分享高質量文章

iOS創建Model的最佳實踐

作者:大神Q

鏈接:https://www.jianshu.com/p/7b9ac06f7ede

Immutable Model

我們以UserModle為例,我們可以像這樣創建:

public class UserModelNSObject {

    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 UserModelNSObject {

    public let userId: NSNumber
    public private(setvar name: String?
    public private(setvar email: String?
    public private(setvar age: Int?
    public private(setvar address: String?

}


那麼怎麼寫初始化方法呢?

Initializer mapping arguments to properties

當我們把property設置成readonly後,我們只能在init的時候賦值,這個時候就變成這樣:

public class UserNSObject {

    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 UserModelNSObject {

    public let userId: NSNumber
    public private(setvar name: String?
    public private(setvar email: String?
    public private(setvar age: Int?
    public private(setvar 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"asInt
        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 <NSCopyingNSMutableCopying>

@property (nonatomiccopyreadwriteNSString *title;
@property (nonatomicstrongreadwriteNSDate *date;
@property (nonatomicassignreadwriteBOOL 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 UserModelNSObject{

    public let userId: NSNumber
    public private(setvar name: String?
    public private(setvar email: String?
    public private(setvar age: Int?
    public private(setvar 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 UserModelBuilderNSObject {

    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 = "[email protected]"
    builder.name = "roy"
    builder.age = 27
    builder.address = "上海市楊浦區"
    return builder
}


這種方式雖然我們需要為Model再創建一個Builder,略顯啰嗦和複雜,但是當property較多,對Model的需求又比較複雜的時候這又確實是一種值得推薦的方式。


以上全是Swift的代碼實現,下麵我再貼上對應的OC代碼


#import 

@interface RUserModelBuilder : NSObject

@property (nonatomicstrongreadwritenonnullNSNumber *userId;
@property (nonatomiccopyreadwritenullableNSString *name;
@property (nonatomiccopyreadwritenullableNSString *email;
@property (nonatomiccopyreadwritenullableNSNumber *age;
@property (nonatomiccopyreadwritenullableNSString *address;

@end

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

@interface RUserModel : NSObject

@property (nonatomicstrongreadonlynonnullNSNumber *userId;
@property (nonatomiccopyreadonlynullableNSString *name;
@property (nonatomiccopyreadonlynullableNSString *email;
@property (nonatomiccopyreadonlynullableNSNumber *age;
@property (nonatomiccopyreadonlynullableNSString *address;

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

@end


#import "RUserModel.h"

@implementation RUserModelBuilder

@end

@interface RUserModel ()

@property (nonatomicstrongreadwritenonnullNSNumber *userId;
@property (nonatomiccopyreadwritenullableNSString *name;
@property (nonatomiccopyreadwritenullableNSString *email;
@property (nonatomiccopyreadwritenullableNSNumber *age;
@property (nonatomiccopyreadwritenullableNSString *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 創建物件的姿勢 http://mrpeak.cn/blog/ios-init/



編號316,輸入編號直達本文

●輸入m獲取文章目錄

推薦↓↓↓

Web開發

更多推薦18個技術類微信公眾號

涵蓋:程式人生、演算法與資料結構、黑客技術與網絡安全、大資料技術、前端開發、Java、Python、Web開發、安卓開發、iOS開發、C/C++、.NET、Linux、資料庫、運維等。

赞(0)

分享創造快樂