iOS開發(fā)架構(gòu)的思考

一、論OOP與AOP編程思想

OOP為Object Oriented Programming的縮寫,意為面向?qū)ο缶幊獭OP針對(duì)業(yè)務(wù)處理過程的實(shí)體及其屬性和行為進(jìn)行抽象封裝和對(duì)外提供接口,以獲得更加清晰高效的邏輯單元和功能劃分。再通過繼承和多態(tài)行為實(shí)現(xiàn)模塊的縱向樹形擴(kuò)展,以達(dá)到代碼的高度重用性、靈活性和可擴(kuò)展性。因此,OOP關(guān)鍵點(diǎn)是模塊化封裝。但是隨著業(yè)務(wù)的發(fā)展,我們經(jīng)常會(huì)遇到一些通用性的功能操作需要在很多模塊中進(jìn)行使用,亦可稱之為橫向需求,例如日志統(tǒng)計(jì),性能監(jiān)控,網(wǎng)絡(luò)請(qǐng)求等。如果把它們的實(shí)現(xiàn)代碼和其他業(yè)務(wù)邏輯代碼混雜在一起,并散落在項(xiàng)目不同的地方(直接把處理這些操作的代碼加入到每個(gè)模塊中),這無疑破壞了OOP的“單一職責(zé)”原則。此外,如果繼承的層次過深,比如有個(gè)model,D的關(guān)系是A->B->C->D,僅僅想使用D這個(gè)model類的功能,則拔出蘿卜帶出泥,還要再引入A、B、C才能正常使用,模塊的可重用性會(huì)大大降低,這使得軟件系統(tǒng)的可維護(hù)性和復(fù)用性受到極大限制。
為了能夠解決這種橫向需求,AOP編程思想開始出現(xiàn)。AOP為Aspect Oriented Programming的縮寫,意為面向切面編程,即通過預(yù)編譯方式和運(yùn)行期動(dòng)態(tài)代理實(shí)現(xiàn)程序功能的統(tǒng)一維護(hù)的一種技術(shù)。AOP是針對(duì)業(yè)務(wù)處理過程中的切面進(jìn)行提取,它所面對(duì)的是處理過程中的某個(gè)步驟或階段,以獲得邏輯過程中各部分之間低耦合性的隔離效果。

那么問題來了,如何實(shí)現(xiàn)AOP功能呢?在iOS中主要有2種方法:

1. Category

Category顧名思義,就是可以將類的功能進(jìn)行分組。能夠?qū)崿F(xiàn)在不創(chuàng)建繼承類的情況下實(shí)現(xiàn)對(duì)已有類的擴(kuò)展。在swift中,已經(jīng)更新為extension,它比category功能更加強(qiáng)大,不僅可以做方法擴(kuò)展,還可以做屬性擴(kuò)展。

假設(shè)我現(xiàn)在有這么一個(gè)需求,就是想在viewController里增加一個(gè)彈出loading界面的公共方法,如下圖所示:



那么實(shí)現(xiàn)有兩種方法,一種是實(shí)現(xiàn)一個(gè)baseViewController,讓所有viewController繼承這個(gè)基類,然后在基類增加一個(gè)showLoadingView()的方法,這樣所有子類就可以使用這個(gè)方法,達(dá)到了需求的目的;另一種就是給viewController增加一個(gè)loadingView的extension。大概如下:

extension UIViewController {
    func showLoadingViewWithText(text:String) -> Void {
        let loadingView = UIView()
        loadingView.backgroundColor = UIColor.blackColor()
        ......
        self.view .addSubview(loadingView)
    }
}

在我看來,第二種方法的優(yōu)點(diǎn)顯而易見:
a. 沒有增加對(duì)類的任何依賴關(guān)系而實(shí)現(xiàn)了對(duì)功能的擴(kuò)展。
b. 對(duì)loadingView功能實(shí)現(xiàn)了分組,所有和loadingView相關(guān)的功能獨(dú)立到一個(gè)文件中管理。
c. 方便了代碼的移植,如果我將來想把viewController移植到別的項(xiàng)目,我可以很容易的摘除我不想要的功能,也可以很容易的加上我想要的功能。有效的避免了拔出蘿卜帶出泥的問題。

Category實(shí)現(xiàn)了對(duì)類功能的擴(kuò)展,但是也有其不足的地方,就是不能對(duì)類的現(xiàn)有功能進(jìn)行修改。因此,就引入了Method Swizzling。

2. Method Swizzling

2.1 什么是Method Swizzling?

Method Swizzling 是通過改變特定 selector(方法)與實(shí)際實(shí)現(xiàn)之間的映射,在 runtime 時(shí)將一個(gè)方法的實(shí)現(xiàn)替換成其它方法的實(shí)現(xiàn)?;驹砣缦聢D:

2.2 Method Swizzling有什么風(fēng)險(xiǎn)?

a. 不能在編譯時(shí)利用那些可用的安全檢查;
b. 一旦發(fā)生交叉替換,那就是永久替換,并對(duì)類永久生效;
因此,這種技術(shù)還是盡量少用,只有當(dāng)你的問題不能用 Swift 的方式解決,也不能用子類、協(xié)議或擴(kuò)展解決時(shí)才使用。

2.3 為什么要用Method Swizzling?

當(dāng)我們想實(shí)現(xiàn)AOP進(jìn)行切面編程時(shí),不可避免的會(huì)想到使用動(dòng)態(tài)代理,在開發(fā)者不知情和不對(duì)現(xiàn)有函數(shù)邏輯產(chǎn)生影響的情況下,對(duì)現(xiàn)有函數(shù)動(dòng)些手腳,以達(dá)到我們的目的。例如,在所有的viewDidLoad中插入一小段通用配置邏輯,對(duì)所有的url請(qǐng)求進(jìn)行域名替換,添加參數(shù)等等。

2.4 Swift中如何使用Method Swizzling?

Swift中使用Method Swizzling有幾個(gè)原則:
a. 繼承自NSObject的Swift類,其繼承自父類的方法具有動(dòng)態(tài)性,其他自定義方法、屬性需要加dynamic修飾才可以獲得動(dòng)態(tài)性。
b. 若方法的參數(shù)、屬性類型為Swift特有、無法映射到Objective-C的類型(如Character、Tuple),則此方法、屬性無法添加dynamic修飾(會(huì)編譯錯(cuò)誤)。
c. 純Swift類沒有動(dòng)態(tài)性,但在方法、屬性前添加dynamic修飾可以獲得動(dòng)態(tài)性。

遵循這些原則,我們就可以在Swift中使用Method Swizzling。下面看一個(gè)例子:

extension UIViewController {
    public override static func initialize() {
        struct Static {
            static var token: dispatch_once_t = 0
        }
        
        // 確保不是子類
        if self !== UIViewController.self {
            return
        }
        
        dispatch_once(&Static.token) {
            let originalSelector = Selector("viewWillAppear:")
            let swizzledSelector = Selector("newViewWillAppear:")
            
            let originalMethod = class_getInstanceMethod(self, originalSelector)
            let swizzledMethod = class_getInstanceMethod(self, swizzledSelector)
            
            let didAddMethod = class_addMethod(self, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod))
            
            if didAddMethod {
                class_replaceMethod(self, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod))
            } else {
                method_exchangeImplementations(originalMethod, swizzledMethod);
            }
        }
    }
    
    // MARK: - Method Swizzling
    func newViewWillAppear(animated: Bool) {
        self.newViewWillAppear(animated)
        if let name = self.descriptiveName {
            print("viewWillAppear: \(name)")
        } else {
            print("viewWillAppear: \(self)")
        }
    }
}

上例中,viewWillAppear方法的實(shí)現(xiàn)會(huì)被替換成 initialize中 newViewWillAppear方法的實(shí)現(xiàn)。值得注意的是,在 swizzle 后,會(huì)遞歸調(diào)用 newViewWillAppear方法,以替換之前原始的 viewWillAppear方法。所有對(duì)修改方法執(zhí)行的操作放在 dispatch_once中,這是為了確保這個(gè)過程只執(zhí)行一次。

3. 小結(jié)

在介紹了AOP的思想后,那我們就應(yīng)該倒向AOP嗎?當(dāng)然不是。不論是OOP還是AOP都有它適用的場(chǎng)景,無論什么方法都不應(yīng)該過度使用。濫用Category和Method Swizzling也會(huì)導(dǎo)致項(xiàng)目混亂。OOP更適合縱向過程,適合業(yè)務(wù)模塊化,比如設(shè)計(jì)Model,子Model,孫子Model等,形成一套縱向體系。AOP更適合橫向過程,適合功能模塊化,一個(gè)功能命中多個(gè)載體,比如在所有的頁(yè)面增加一個(gè)頁(yè)面出現(xiàn)的統(tǒng)計(jì)。

二、我眼中的viewController

1. viewController在做什么?

每一個(gè)viewController無非就是在做這幾件事:
a. 初始化界面;
b. 請(qǐng)求網(wǎng)絡(luò)數(shù)據(jù)或響應(yīng)用戶操作;
c. 根據(jù)數(shù)據(jù)或操作更新界面。



看起來只有三件事,但是其中的邏輯錯(cuò)綜復(fù)雜,一不留神viewController就洋洋灑灑幾千行代碼出去了。那么設(shè)計(jì)一個(gè)可讀性強(qiáng)和擴(kuò)展性強(qiáng)的viewController就顯得十分重要。

2. viewController的代碼布局

我認(rèn)為,viewController的結(jié)構(gòu)大概是這樣的:

class ALYViewController: UIViewController {
    
    //MARK: IB variables
    @IBOutlet var button : UIButton?
    @IBOutlet var label : UILabel?
    @IBOutlet var imageView : UIImageView?
    ......
    
    //instance variables
    private var variable1 : String = ""
    private var variable2 : CGFloat = 0
    private var variable3 : Array<String> = []
    ......

    //init
    override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?) {
        super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
        // Custom initialization
    }
    
    required init?(coder aDecoder: NSCoder)
    {
        super.init(coder: aDecoder)
    }

    
    //MARK: life cycle
    override func viewDidLoad() {
        super.viewDidLoad()
    }
    
    override func viewWillAppear(animated: Bool) {
        super.viewWillAppear(animated)
    }
    ......
    
    //MARK: tableView dataSource & delegate
    
    //MARK: network service
    
    //MARK: custom delegate
    
    //MARK: event response(button & gestureRecognizer action)
    
    //MARK: private & help methods
    func setViewWidth(view: UIView?, width: CGFloat) -> Void {
        if view == nil {
            return
        }        
        var frame = view?.frame
        frame?.size.width = width
        view?.frame = frame!
    }
    
    //MARK: lazy initialize variables
    lazy var messageBarButton : UIButton = {
        let button = UIButton(frame: CGRectMake(0, 0, 38, 38))
        
        return button
    } ()
}

上述代碼布局基本是按照代碼的閱讀先后順序來寫的:
a. 在代碼前幾行,簡(jiǎn)明的列出一些基本變量,并不是這些變量對(duì)閱讀會(huì)帶來什么幫助,一般人一上來也不會(huì)看變量命名,而是C語(yǔ)言編程風(fēng)格的沿襲,這也是大多數(shù)代碼的編程風(fēng)格;
b. viewController的初始化工作,最開始要做的事情;
c. view生命周期函數(shù),了解一個(gè)界面流程最重要的地方;
d. tableView初始化的邏輯,因?yàn)閠ableView太重要了,基本上每個(gè)頁(yè)面都會(huì)用到,所以單獨(dú)寫出來;
e. 網(wǎng)絡(luò)服務(wù)函數(shù),并不是每個(gè)頁(yè)面都需要用到;
f. 自定義的delegate,用于實(shí)現(xiàn)一些界面間回調(diào)功能;
g. 按鍵和手勢(shì)等的響應(yīng)行為;
h. 自己封裝的一些幫助執(zhí)行的方法;
i. 變量的lazy初始化。原因摘自別人的文章,getter & setter類似于lazy load。

因?yàn)橐粋€(gè)ViewController很有可能會(huì)有非常多的view,就像上面給出的代碼樣例一樣,如果getter和setter寫在前面,就會(huì)把主要邏輯扯到后面去,其他人看的時(shí)候就要先劃過一長(zhǎng)串getter和setter,這樣不太好。然后要求業(yè)務(wù)工程師寫代碼的時(shí)候按照順序來分配代碼塊的位置,先是life cycle,然后是Delegate方法實(shí)現(xiàn),然后是event response,然后才是getters and setters。這樣后來者閱讀代碼時(shí)就能省力很多。

3. 如何簡(jiǎn)化viewController?

現(xiàn)在流行的iOS架構(gòu)大概有MVC,MVCS,MVVM。

3.1 MVC

最經(jīng)典的軟件結(jié)構(gòu)設(shè)計(jì)模式,也是曾經(jīng)Apple推薦的設(shè)計(jì)模式,我們?cè)谔O果的官方文檔中還能夠看到MVC的痕跡Apple MVC。MVC基本模型是

就像C語(yǔ)言在編程語(yǔ)言的地位, 現(xiàn)在的設(shè)計(jì)模式都是基于MVC的演進(jìn)。但是MVC在現(xiàn)今的APP設(shè)計(jì)中,已經(jīng)變得臃腫不堪,很容易使得viewController變的非常龐大和笨重。因?yàn)関iewController既要負(fù)責(zé)頁(yè)面的所有生命周期,又要負(fù)責(zé)網(wǎng)絡(luò)處理,還要處理用戶的交互操作,導(dǎo)致大量的代碼邏輯堆積在viewController,極大的影響了代碼的可讀性和擴(kuò)展性。因此,為了解決上述問題,又衍生出了MVCS和MVVM等模式。

3.2 MVCS

MVCS是在MVC的基礎(chǔ)上衍生出來的架構(gòu)模式,S是Store的意思??梢院?jiǎn)單的理解為,MVCS是把網(wǎng)絡(luò)層從Controller中剝離出來,使用獨(dú)立的Store類進(jìn)行網(wǎng)絡(luò)數(shù)據(jù)的請(qǐng)求和數(shù)據(jù)處理,從而達(dá)到減輕viewController壓力的目的。在蘋果很多的官方示例中都有這種結(jié)構(gòu)的寫法。下面來看一個(gè)簡(jiǎn)單的例子:

class ALYViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        //處理頁(yè)面邏輯
        self.view.addSubview(self.displayView)
        //處理網(wǎng)絡(luò)邏輯
        self.fetchDataFromServer()
    }
    
    func fetchDataFromServer() -> Void {
        self.dataStore.fetchDataFromServiceWithCompletion { [weak self]() in
            if self?.dataStore.dataSource.count > 0 {
                self?.displayView.updateViewWithData((self?.dataStore.dataSource)! )
            } else {
                //默認(rèn)處理
            }
        }
    }
    
    lazy var dataStore : ALYDataStore = {
        var _dataStore = ALYDataStore()
        return _dataStore
    }()
    
    lazy var displayView : ALYDisplayView = {
        var _displayView = ALYDisplayView()
        return _displayView
    }()
}

class ALYDisplayView: UIView {
    let label = UILabel()
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        self.addSubview(label)
    }
    
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    func updateViewWithData(dataSource:Array<ALYModel>) -> Void {
        self.label.text = dataSource[0].text
    }
}

class ALYDataStore: NSObject {
    //用來存儲(chǔ)取回來的數(shù)據(jù),字典或數(shù)組
    var dataSource : [ALYModel] = []
    
    func fetchDataFromServiceWithCompletion(completion:(Void)->Void) -> Void {
        let url = "http://alibaba-inc.com"
        let parameters = ["username":"zhaizun", "password":"123456"]
        
        self.fetchServiceListWithUrl(url, parameters: parameters, completion: completion)
        
    }
    
    func fetchServiceListWithUrl(url:String, parameters:NSDictionary, completion:(Void)->Void) -> Void {
        let request = NSURLRequest(URL:  NSURL.init(string: url)!)
        let connection = NSURLConnection.init(request: request, delegate: self)
        connection?.start()
        
        let finishCallBack : (Void)->Void = { [weak self](Void)->Void in
            //偽造的json數(shù)據(jù)
            let json = [["a":"1231"], ["b":"32131"], ["c":"312312"]]
            //從服務(wù)器回來的model處理
            for object in json {
                let model = ALYModel()
                model.jsonToModel(object)
                self?.dataSource.append(model)
            }
            completion()
        }
        
        finishCallBack()
    }
}

class ALYModel: NSObject {
    var text : String = ""
    
    func jsonToModel(data:[String:AnyObject]) -> ALYModel {
        let model = ALYModel()
        //Model轉(zhuǎn)化邏輯
        //......
        return model
    }
}

上面這個(gè)例子對(duì)于MVCS的展現(xiàn)是M->ALYModel,V->ALYDisplayView,C->ALYViewController和S->ALYDataStore。可以看出,大部分的網(wǎng)絡(luò)服務(wù)和數(shù)據(jù)處理都被移交到了ALYDataStore中去做,而ALYViewController只需要把握請(qǐng)求網(wǎng)絡(luò)和更新界面的時(shí)機(jī)。這樣就在一定程度上達(dá)到了為Controller減負(fù)的目的。

3.3 MVVM

MVVM是Model,View和ViewModel的縮寫,那么Controller去哪了?ViewModel就是Controller嗎?在別的語(yǔ)言可能是,但是對(duì)于iOS這是不可能的,所有的頁(yè)面都是依賴于Controller。那么ViewModel等同于viewController嗎?也不是,理解起來并非如此簡(jiǎn)單,我們需要構(gòu)造出一個(gè)ViewModel的概念,而MVVM比較難以理解和實(shí)現(xiàn)也正是因?yàn)榇烁拍睿琕iewModel不是Controller,它所做的事情依然是幫助Controller減負(fù)。一個(gè)頁(yè)面有不止一個(gè)View,所以也會(huì)存在不止一個(gè)ViewModel,那么這么多的ViewModel肯定需要有人去管理它,這個(gè)工作就需要Controller去完成。

對(duì)于一個(gè)APP,用戶主要需求是看到界面,至于后面有多少控制邏輯和數(shù)據(jù)處理,用戶完全不在乎,所以我們所有的工作都是為界面服務(wù)的。因此,我們每個(gè)View都有一個(gè)Controller和至少一個(gè)Model為其服務(wù)。而ViewModel的思想正是由此而生,把服務(wù)于View的邏輯從View,Controller和Model中剝離出來,放到ViewModel中,讓ViewModel去處理View和Model之間的交互,加工和展示工作,而Controller只是在其中起到一個(gè)橋梁連接的工具。不過正是因?yàn)檫@個(gè)橋梁,使得Controller和ViewModel之間需要大量的粘合代碼,這也正是ViewModel開發(fā)成本高的地方。其示意圖大概如下:



為了便于理解,還是來看一個(gè)例子:

class ALYViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        
        //處理頁(yè)面邏輯
        self.view.addSubview(self.displayView)
        //網(wǎng)絡(luò)處理服務(wù)
        self.displayViewModel.fetchDataFromServiceWithCompletion { [weak self](success, error) in
            if success == true {
                self?.displayView.updateViewWithViewModel(self!.displayViewModel)
            } else {
                //默認(rèn)處理
                print(error)
            }
        }
    }
    
    lazy var displayViewModel : ALYDisplayViewModel = {
        var _displayViewModel = ALYDisplayViewModel()
        return _displayViewModel
    }()
    
    lazy var displayView : ALYDisplayView = {
        var _displayView = ALYDisplayView()
        
        return _displayView
    }()
}

class ALYDisplayView : UIView {
    
    let nameLabel = UILabel()
    let ageLabel = UILabel()
    let dateLabel = UILabel()

    override init(frame: CGRect) {
        super.init(frame: frame)
        self.addSubview(nameLabel)
        self.addSubview(ageLabel)
        self.addSubview(dateLabel)
    }
    
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    func updateViewWithViewModel(viewModel:ALYDisplayViewModel) -> Void {
        
        let model = viewModel.dataSource
        
        self.nameLabel.text = model.name
        self.ageLabel.text = model.age
        self.dateLabel.text = model.date
    }
}

class ALYDisplayViewModel: NSObject {
    
    var dataSource : ALYModel = ALYModel()
    
    func fetchDataFromServiceWithCompletion(completion:(success:Bool, error:NSError?)->Void) -> Void {
        let url = "http://alibaba-inc.com"
        let parameters = ["username":"zhaizun", "password":"123456"]
        
        self.fetchServiceListWithUrl(url, parameters: parameters, completion: completion)
        
    }
    
    func fetchServiceListWithUrl(url:String, parameters:NSDictionary, completion:(success:Bool, error:NSError?)->Void) -> Void {
        let request = NSURLRequest(URL:  NSURL.init(string: url)!)
        let connection = NSURLConnection.init(request: request, delegate: self)
        connection?.start()
        
        let finishCallBack : (Void)->Void = { [weak self](Void)->Void in
            //偽造的json數(shù)據(jù)
            let json = ["name":"1231", "age":"32131", "date":"2016-04-12"]
            //從服務(wù)器回來的model處理
            self!.dataSource.jsonToModel(json)
            
            completion(success: true, error: nil)
        }
        
        finishCallBack()
    }
}

class ALYModel: NSObject {
    var name : String = ""
    var age : String = ""
    var date : String = ""

    func jsonToModel(data:[String:AnyObject]) -> ALYModel {
        let model = ALYModel()
        //Model轉(zhuǎn)化邏輯
        //......
        return model
    }
}

上面的例子中,View和Model的部分邏輯都移動(dòng)到了ViewModel,減輕了Controller的負(fù)擔(dān)。但大家一定會(huì)感覺怪怪的,因?yàn)閙odel更新了之后還要手動(dòng)的去讓Controller用ViewModel去更新View,感覺多次一舉。如果能夠?qū)崿F(xiàn)Model更新了自動(dòng)去更新View,或者View變化了可以直接去更新Model,那么MVVM就顯得更加優(yōu)雅了?;谶@種雙向綁定機(jī)制的想法,ReactiveCocoa就出現(xiàn)了。但是ReactiveCocoa的使用門檻很高,而且調(diào)試?yán)щy,建議大家慎重使用,不要為了MVVM而MVVM。

3.4 小結(jié)

講了這么多,那么之前說的筆者認(rèn)為的viewController呢,我想到的viewController就是MVCS和MVVM的結(jié)合,既有Store,又有ViewModel。Store負(fù)責(zé)網(wǎng)絡(luò)數(shù)據(jù)處理,ViewModel負(fù)責(zé)將數(shù)據(jù)轉(zhuǎn)化為View需要的內(nèi)容展示出來。


三、胖model與瘦model

上面介紹了viewController,下面來說說Model。當(dāng)我們使用網(wǎng)絡(luò)請(qǐng)求的時(shí)候,自然而然的就會(huì)涉及到數(shù)據(jù)處理。那么提供什么樣Model才是合適的呢?現(xiàn)在主流的Model有2種,胖Model和瘦Model。

1. 胖model

正常情況下,我們很難從服務(wù)器拿到可以直接展示到View上的數(shù)據(jù),我們從網(wǎng)絡(luò)接口解析出來的數(shù)據(jù),往往需要再加工才能夠讓View進(jìn)行使用。胖Model就是不僅做了承載數(shù)據(jù)的工作,也做了加工數(shù)據(jù)的工作。Controller從胖Model這里拿到數(shù)據(jù)之后,不用額外做操作或者只要做非常少的操作,就能夠?qū)?shù)據(jù)直接應(yīng)用在View上。舉個(gè)例子:

/* 服務(wù)器數(shù)據(jù):
 json = {
    "name" = "小明",
    "birthDate" = "19990909"
 }
 */

class ALYModel: NSObject {
    var name : String = ""
    var birthDate : String = ""

    func jsonToModel(data:[String:String]) -> ALYModel {
        let model = ALYModel()
        //Model轉(zhuǎn)化邏輯
        self.name = data["name"]!
        self.birthDate = data["birthDate"]!
        
        return model
    }
    
    func formatDate(date:String) -> String {
        let formatter = NSDateFormatter()
        formatter.dateFormat = "yyyyMMdd"
        let date = formatter.dateFromString(date)
        formatter.dateFormat = "yyyy-MM-dd"
        let str = formatter.stringFromDate(date!)
        
        return str
    }
}

2. 瘦model

瘦Model就是只專注于數(shù)據(jù)的解析,其他的功能都在Adaptor中完成,從而達(dá)到和業(yè)務(wù)脫離的目的。舉個(gè)例子:

/*
 服務(wù)器數(shù)據(jù):
 json = {
    "name" = "小明",
    "birthDate" = "19990909"
 }
 */

class ALYModel: NSObject {
    var name : String = ""
    var birthDate : String = ""

    func jsonToModel(data:[String:String]) -> ALYModel {
        let model = ALYModel()
        //Model轉(zhuǎn)化邏輯
        self.name = data["name"]!
        self.birthDate = data["birthDate"]!
        
        return model
    }
}

class ALYModelAdaptor: NSObject {
    
    func formatDate(date:String) -> String {
        let formatter = NSDateFormatter()
        formatter.dateFormat = "yyyyMMdd"
        let date = formatter.dateFromString(date)
        formatter.dateFormat = "yyyy-MM-dd"
        let str = formatter.stringFromDate(date!)
        
        return str
    }
}

3. 小結(jié)

在這兩種Model中,我更傾向于瘦Model,然后提供Adaptor,進(jìn)行數(shù)據(jù)轉(zhuǎn)化。因?yàn)槿绻艳D(zhuǎn)化邏輯寫進(jìn)了Model中,那么就等于Model中耦合進(jìn)了業(yè)務(wù)邏輯,那么將來的可復(fù)用性就會(huì)有所降低。而瘦Model中,業(yè)務(wù)邏輯都存在于Adaptor中,將來如果業(yè)務(wù)變動(dòng),只要改動(dòng),增加和刪除Adaptor就好了。

四、我對(duì)代碼規(guī)范的理解

  1. 代碼整齊,有必要的換行縮進(jìn),看起來舒服;
  2. 函數(shù)功能分類,每個(gè)功能用#pragram mark - 或//MARK:區(qū)分開,并附有說明;
  3. 函數(shù)命名可讀性強(qiáng),讓別人根據(jù)函數(shù)名即可知道函數(shù)的功能。如果函數(shù)的功能比較復(fù)雜,應(yīng)有關(guān)鍵步驟的注釋,有函數(shù)參數(shù),返回值和函數(shù)功能的注釋說明;建議使用VVDocumenter寫函數(shù)注釋。
  4. 函數(shù)中的變量要有意義。不要出現(xiàn)int a,var b這樣的字樣。盡量采用text,number,date,width等有意義的文字。如果零時(shí)使用,可以采用tmp,local等前綴字樣,讓別人快速的明白是零時(shí)使用。
  5. 一個(gè)類單獨(dú)形成一個(gè)文件。如果一個(gè)文件中包含多個(gè)類,那么這些類之間必須要有相互關(guān)系或一個(gè)體系,比如純虛基類和實(shí)現(xiàn)子類,一個(gè)類和僅僅為該類服務(wù)的help類。
  6. 類的功能由自己內(nèi)部實(shí)現(xiàn),而不是通過外部設(shè)置,萬(wàn)不得已不對(duì)外暴露變量,對(duì)外只提供更新的接口。外部只需要提供簡(jiǎn)單的設(shè)置和數(shù)據(jù)就能夠讓類完成自己的大部分功能。
  7. 資源文件按業(yè)務(wù)功能模塊分開。比如Images.xcassets拆分出多個(gè),每個(gè)模塊有自己獨(dú)立的Images.xcassets文件,類似ModuelA.xcassets,ModuelB.xcassets等等,通用的icon文件放在Images.xcassets中。
  8. 看代碼時(shí),不用文檔,或很少文檔,就能讓其他人上手。模塊目錄按照功能進(jìn)行分類,比如聯(lián)系人頁(yè)面所有涉及到的Controller,View、Model和資源文件一起放在Contacts文件夾里。我見過有的代碼是將所有Controllers放在一個(gè)文件夾中,Views放在一個(gè)文件夾中,Models放在一個(gè)文件夾中,這樣的文件目錄讀起來感覺非常不方便。
  9. 粒度夠細(xì),集成度高,復(fù)用度高。功能函數(shù)化,參數(shù)變量化。能夠達(dá)到改動(dòng)一個(gè)地方,所有地方生效的結(jié)果。
  10. 盡量使用自定義View,View封裝的粒度要細(xì),自己能夠完成大部分功能,對(duì)外僅暴露初始化接口和回調(diào),便于移植和修改。
  11. image使用時(shí),icon使用imageNamed函數(shù)初始化,圖片文件采用imageWithContentsOfFile函數(shù)初始化,用以防止不必要的緩存。
  12. 網(wǎng)絡(luò)請(qǐng)求采用統(tǒng)一的接口設(shè)置,便于將來接口的修改。

五、引用的文章

https://segmentfault.com/a/1190000004715337
http://casatwy.com/iosying-yong-jia-gou-tan-kai-pian.html
http://blog.devtang.com/2015/11/02/mvc-and-mvvm/
http://gracelancy.com/blog/2016/01/06/ape-ios-arch-design/

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容