Swift 項目:ReactiveSwift+MVVM+RealmSwift+Alamofire+OM

這篇文章先上我要說的項目的整體框架圖吧,有興趣的可以看下去。


swift.png

那么正片現(xiàn)在就要開始了。。。。

小樣兒

我們一個一個介紹,先看看什么是MVVM模式。

當然在說這個之前,我們不得不先說說什么是MVC模式,當然這個模式是隨著編碼的積累慢慢形成的。

初入IOS的小白,可能什么代碼都會放在C(Controller)中,例如:一個視圖中UILabel、UIButton、UITableView等視圖對象,數(shù)據(jù)對象,各種邏輯判斷的flag,各種if,else滿天飛,這樣就會發(fā)現(xiàn)C(Controller)中的代碼越來越多,之后別人閱讀和維護這樣的項目,心里這種十萬個“垃圾”,“寫的像一坨屎”翻騰。

于是我們慢慢的形成一種模式,MVC。

MVC具體版

MVC簡潔版

MVC模式

 說明:從圖中可以看出,C(Controller)是連接M(Model)和V(View)的重要中轉站,
      當然C也是作為項目中地位最為重要的一部分,負責的事務:
  1.添加,顯示,更新視圖對象
  2.處理view對象中的響應事件
  3.網(wǎng)絡請求,給M(Model)傳入字典數(shù)據(jù)得到自定義的數(shù)據(jù)對象
  4.將得到的數(shù)據(jù)對象(Model)傳入view的對象中更新視圖
  5.視圖之間的交互,跳轉

為了能讓你更具體了解什么是MVC,我們就拿簡書界面來說吧。

IMG_0792.PNG

主要寫一下思路,至于代碼的部分,只是說明這個流程


class YourController: UIViewController {
   
  //View 對象(也可以是自定義視圖對象)
  private lazy var tableView : UITableView = {
        () -> UITableView in
        
        let _tableView = tableViewConfig(.zero, self, self, .plain)
        _tableView.rowHeight = RS_SCALE_LENGTH(value: 64.0)
        _tableView.backgroundColor = .clear
        registerCell(_tableView, RSScenesCell.self)
        return _tableView
    }()

//網(wǎng)絡請求返回字典數(shù)據(jù)
  func dataRequest() {
        
        Alamofire.request(URL, method: .post, parameters: mutParam, encoding: URLEncoding.default).responseJSON { response in
            
            if response.result.error == nil {
                
                 //  請求成功
                let text: NSString = NSString(data: response.data!, encoding: String.Encoding.utf8.rawValue)! as NSString
                print("Data\(text as AnyObject)")

              //向Model中傳入字典得到自定義數(shù)據(jù)對象
              //刷新tableView
            } else {
                
                print("error\((response.result.error)! as AnyObject)")
            }
        }
    }
}

extension YourController : UITableViewDelegate, UITableViewDataSource {

    //delegate or dataSource

}

//Model 部分
class SubHomeScrollDataModel: NSObject {
    
    var isViewShow: Bool?  //數(shù)據(jù)對象屬性

    class func simpleModel(dic: [String: Any]) -> SubHomeScrollDataModel {
        
        let model = SubHomeScrollDataModel()
        model.isViewShow = dic[""]
        return model
    }

}

這樣一個很簡單的MVC設計模式就出現(xiàn)了,當然這樣的好處:

1.視圖對象的封裝
2.控制器代碼相應的減少

當然也有它的缺點:

1.網(wǎng)絡層的代碼并沒有抽離出來
2.邏輯業(yè)務層的處理

當然如果你是個老司機,看的資料很多的話,認為MVC是另一種設計模式,把這里的C看作Cell,可以推薦你看看這篇文章。當然那個的理解是基于我這個理解之上的,個人對他寫的MVC的理解是,一個視圖可能會分多個模塊,而每個模塊視圖有自己的MVC模式。
簡書地址

基于上面的設計缺點,我個人的理解,就是抽離出來網(wǎng)絡層放到ViewModel中,這樣就形成了我這個項目的MVVM的模式。

Alamofire

這個算是Swift項目中網(wǎng)絡層必須用到的框架了,具體的源碼我這兒就不介紹了(畢竟我也不是很清楚啊)

哈哈

不過它的用法還是很簡單的。

    static func doPostRequest(_ param: [String: Any], URL: String, completeBlock: @escaping (_ response: DataResponse<Any>) -> Void) {
        
        let header = ["TOKEN": RSUserManager.shared().accessToken]
        
        Alamofire.request(URL, method: .post, parameters: param, encoding: URLEncoding.default, headers: header).responseJSON { response in
            
            if response.result.error == nil {
                
                /**
                 *  請求成功
                 */
                let text: NSString = NSString(data: response.data!, encoding: String.Encoding.utf8.rawValue)! as NSString
                print("Data\(text as AnyObject)")
            } else {
                
                /**
                 請求出錯了
                 
                 - print: 錯誤信息
                 */
                print("error\((response.result.error)! as AnyObject)")
            }
            
            completeBlock(response)
        }
        
    }
//注: 這里是底層的與Alamofire接口相連接的BaseRequestManager

既然與Alamofire連接好了,接著我們再封裝一層網(wǎng)絡的管理者,例如首頁獲取數(shù)據(jù)的網(wǎng)絡管理者,這個我們需要關心的是:

1.傳入的字典
2.傳出的自定義數(shù)據(jù)Model

那這個怎么玩呢? 我這里是請來了ReactiveSwift,ObjectMapper這兩位大哥鎮(zhèn)場子。

class StoreRequestManager: NSObject {
    
    class func storeDataRequest(page: Int, segType: RSStoreSegmentType) -> SignalProducer<RSStoreDataModel, NoError> {
        
        return SignalProducer<RSStoreDataModel, NoError>.init { (observer, _) in
            
            var params = [String : String]()
            params["page"] = "1"
            params["type"] = "\(segType.hashValue)"
            BaseRequestManager.doPostRequest(params,
                                             URL:RSRequestUrl.STORE_MAIN_URL) { (response) -> Void in
                                                
                                                if response.result.error == nil {
                                                    
                                                    let responseModel = Mapper<RSStoreDataModel>().map(JSONObject: response.result.value)
                                                    observer.send(value: responseModel!)
                                                    
                                                }else {
                                                    //網(wǎng)絡出錯
                                                    let responseModel = RSStoreDataModel()
                                                    responseModel.netErrorResultModel()
                                                    observer.send(value: responseModel)
                                                }
            }
        }
    }

}
//注:RSStoreDataModel傳出的數(shù)據(jù)模型,這個對象是實現(xiàn)了Mappable協(xié)議的,
//具體的可以看下面對ObjectMapper的介紹,通過ReactiveSwift中的觀察者機制傳出。

ObjectMapper

說得通俗一點,它的功能就是做數(shù)據(jù)對象的映射,把服務器的字典數(shù)據(jù)轉成自定義對象。

。。。。。什么?陳獨秀同學你說用字典的鍵值對獲取的方法也可以生成自定義對象,那么老司機我就用代碼來說說吧。

字典的格式
 /* { 
    key1: value1,
    key2: value2,
    key3: value3,
    key4: value4,
    key5:[
              {
                    key1: value1,
                    key2: value2,
                    key3: value3,
                    key4: value4,
                },
                {
                   key1: value1,
                   key2: value2,
                   key3: value3,
                   key4: value4,
                },
              ]
    msg: "成功"
    code:1
}*/

class ProductNormModel: BaseResultModel {   //商品規(guī)格數(shù)據(jù)模型
    
    var colorModels = [ProductColorModel]()  //重點
    var goodId: NSNumber = 0
    var normId: NSNumber = 0
    var normName: String = ""
    
    func mapping(map: Map) {
        
        super.mapping(map: map)
        colorModels <- map["colors"]      //數(shù)組數(shù)據(jù)的直接映射
        goodId <- map["goodId"]
        normId <- map["id"]
        normName <- map["name"]
    }
}

class ProductColorModel: Mappable {   //商品顏色數(shù)據(jù)模型
    
    var count: NSNumber = 0
    var colorId : NSNumber = 0
    var name: String = ""
    var normId: NSNumber = 0
    
    var price: NSNumber = 0
    
    init(){}
    required init?(map: Map){}
    
    func mapping(map: Map) {
        
        count <- map["count"]
        colorId <- map["id"]
        name <- map["name"]
        normId <- map["normId"]
        price <- map["price"]
    }
}

//當然如果用常規(guī)的鍵值對獲取的方法,也可以辦到,但那樣就感覺很繁瑣了。
//數(shù)組的映射直接是火箭式封裝

用這個框架的時候,我這邊經(jīng)過了一層數(shù)據(jù)結構的封裝,怎么說呢,就是上面的BaseResultModel,這個是做什么用的呢,容我慢慢道來。

原因:每個API接口都會有msg,code/status 這些固定字段,但如果我們?nèi)ソ馕雒總€接口時都去定義對象中相應的屬性(對應msg,code),這樣就感覺就是做重復的工作,所以我把這個工作放在底層的BaseResultModel這個對象中。

import ObjectMapper

class BaseResultModel: Mappable {
    
    var type: RequestResultType?
    var msg : String = ""
    var obj = Dictionary<String, Any>()
    
    init(){}
    required init?(map: Map){}
    
    func mapping(map: Map) {
        
        var stauts : NSNumber?
        stauts <- map["res"]
        if stauts == 1 {
            type = .RequestSuccessful
        }else if stauts == 301 {
            type = .RequestTokenIsInvalid
        }else {
            type = .RequestFail
        }
        msg <- map["msg"]
        obj <- map["obj"]
    }
    
    func netErrorResultModel() {
        
        type = RequestResultType.RequestNetError
        msg = "網(wǎng)絡請求超時"
    }
}
// 這樣我只需要繼承這個對象即可,是不是很機智??

ReactiveSwift

好了,接下來上場的是ReactiveSwift這位大哥了,那么先說說這位大哥的背景吧。

ReactiveSwift是一種函數(shù)式反應型編程,那什么是函數(shù)式反應型編程呢?有什么優(yōu)點呢?
我這兒就不給理論性的解讀了,那個資料臣妾也是看得一頭霧水,那么還是看代碼吧。

    btn.reactive.controlEvents(.touchUpInside).observeValues { (selected) in
            
     }

利用函數(shù)式編程(Functional Programming)技術去處理這些變化事件,
如這里面的函數(shù)controlEvents(.touchUpInside),然后通過給個小兵觀察這個按鈕是否發(fā)生改變,
從而給出變化后的按鈕對象。

優(yōu)點嘛
swift不是以簡潔著稱,這樣就符合咋們高大帥的氣質(zhì)嘛。

帥得一批

介紹完了大哥,就來具體的說說ReactiveSwift用法,用法嘛,其實很簡單,抓住幾個重點就可以了。
就我個人的理解,ReactiveSwift其實處理ios中的邏輯,代理,通知方面的一個整合,另外就是一個回調(diào)觀察者的機制。

例如我們ios中對象的數(shù)據(jù)或是事件的回調(diào),通常采用的方式是代理或是Block,如果是非關聯(lián)對象之間數(shù)據(jù)事件的聯(lián)系則是用通知,然后系統(tǒng)控件(UIButton,UITextField)中的響應事件或是代理方法,這些零零散散的東西,現(xiàn)在直接用這個框架就可以很快捷的解決問題了。

還是看代碼吧!

//按鈕的點擊事件
btn.reactive.controlEvents(.touchUpInside).observeValues { (selected) in
            
     }

補充:至于是繼承于UIControl的控件都可以運用此方法進行不同控件的響應。

public struct UIControlEvents : OptionSet {

    public init(rawValue: UInt)

    
    public static var touchDown: UIControlEvents { get } // on all touch downs

    public static var touchDownRepeat: UIControlEvents { get } // on multiple touchdowns (tap count > 1)

    public static var touchDragInside: UIControlEvents { get }

    public static var touchDragOutside: UIControlEvents { get }

    public static var touchDragEnter: UIControlEvents { get }

    public static var touchDragExit: UIControlEvents { get }

    public static var touchUpInside: UIControlEvents { get }

    public static var touchUpOutside: UIControlEvents { get }

    public static var touchCancel: UIControlEvents { get }

    
    public static var valueChanged: UIControlEvents { get } // sliders, etc.

    @available(iOS 9.0, *)
    public static var primaryActionTriggered: UIControlEvents { get } // semantic action: for buttons, etc.

    
    public static var editingDidBegin: UIControlEvents { get } // UITextField

    public static var editingChanged: UIControlEvents { get }

    public static var editingDidEnd: UIControlEvents { get }

    public static var editingDidEndOnExit: UIControlEvents { get } // 'return key' ending editing

    
    public static var allTouchEvents: UIControlEvents { get } // for touch events

    public static var allEditingEvents: UIControlEvents { get } // for UITextField

    public static var applicationReserved: UIControlEvents { get } // range available for application use

    public static var systemReserved: UIControlEvents { get } // range reserved for internal framework use

    public static var allEvents: UIControlEvents { get }
}

當然這是ios系統(tǒng)中UIControlEvents的枚舉類,例如UISwitch,UITextField,UISlider,然后他們不同響應的方式,都可以用上面的代碼去做處理。

熱信號(就是代替Block和Delegate的)

A和B對象(B對象是在A對象中生成的),要把B對象中的數(shù)據(jù)x傳回A中,腫么辦呢?

//B文件中
//記得引入框架
import ReactiveCocoa
import ReactiveSwift
import Result

let (signalAction, observerTap) = Signal<Any, NoError>.pipe()

func viewBtnDidTouch(touchBtn: UIButton) {
        
    touchBtn.reactive.controlEvents(.touchUpInside).observeValues { (selected) in

        self?.observerTap.send(value:"x")
     }
       
 }

//A文件中

rightNaviView.signalAction.observeValues({ [weak self] (value) in
            
     //獲得value = “x”
 })

這里面的[weak self]是必須要加上的啊,不然就會發(fā)生內(nèi)存泄漏,類似oc的__weak typeof(self) weakSelf = self,
那樣的話,你會發(fā)現(xiàn)你退出了一個控制器后,該控制器并沒有銷毀。想當初我是一行一行代碼去檢查為什么pop控制器,它就是不執(zhí)行下面那個方法呢,原來是這兒的坑啊。

deinit {
        
    print("\(type(of: self)) deinit")
}

通知

NotificationCenter.default.reactive.notifications(forName: Notification.Name(rawValue: "UIKeyboardWillShowNotification"),
                                                                         object: nil).observeValues { (notification) in

     print("鍵盤彈起")
 }

當然這是系統(tǒng)中的通知,自定義通知我目前還不知道呢。

說完熱信號,咋們說說冷信號。二狗你可能會問,什么是熱信號什么又是冷信號呢,那我就解釋一波吧。

熱信號嘛,就是界面上與用戶交互操作的事件的響應,例如UIButton,UItextfield,鍵盤的彈出通知了
,UISwitch的切換了,是不,這些反應在界面上的就可以稱之為“熱信號”了;
而冷信號,就是程序中通過代碼去觀察事件動作的,如你定義一個獲取網(wǎng)絡的API方法,然后通過Action去返回一個SignalProducer的東東,之后再用action的start的方法監(jiān)聽返回的數(shù)據(jù),這樣的沒有與用戶交互的數(shù)據(jù)相應事件稱之為“冷信號”,是不是很有道理呢,那么就讓我們看看具體什么是冷信號吧。

小二,上代碼!!!

class StoreViewModel: NSObject {
    
    var action : Action<(), RSStoreDataModel, NoError>!   //獲取界面數(shù)據(jù)的事件
    
    func getStoreDataRequest(page:Int, type: RSStoreSegmentType, completion completed: ((_ model: RSStoreDataModel) -> Swift.Void)? = nil) {
        
        action = Action<(), RSStoreDataModel, NoError> { (_) -> SignalProducer<RSStoreDataModel, NoError> in
            
            StoreRequestManager.storeDataRequest(page: page, segType: type)
        }
        action.apply(()).start { (event) in
            
            if completed != nil {
                completed!(event.value!)
            }
        }
    }
}

class StoreRequestManager: NSObject {
    
    class func storeDataRequest(page: Int, segType: RSStoreSegmentType) -> SignalProducer<RSStoreDataModel, NoError> {
        
        return SignalProducer<RSStoreDataModel, NoError>.init { (observer, _) in
            
            var params = [String : Any]()
            params["pageNo"] = page
            params["pageSize"] = 16
            params["isPresale"] = "\(segType.hashValue)"
            BaseRequestManager.doGetRequest(params,
                                             URL:RSRequestUrl.STORE_MAIN_URL) { (response) -> Void in
                                                
                                                if response.result.error == nil {
                                                    
                                                    let responseModel = Mapper<RSStoreDataModel>().map(JSONObject: response.result.value)
                                                    observer.send(value: responseModel!)
                                                    
                                                }else {
                                                    //網(wǎng)絡出錯
                                                    let responseModel = RSStoreDataModel()
                                                    responseModel.netErrorResultModel()
                                                    observer.send(value: responseModel)
                                                }
            }
        }
    }
}

冷信號一般都是返回SignalProducer的對象的,具體的就看你想怎么操作了。

當然這個冷信號的核心就是簡潔了,比如說在你需要的地方要請求API,那么只需要:

func dataRequest() {
        
        MBProgressHUD.showAdded(to: self.view, animated: true)
        
        RSStoreViewModel().getProductDetailRequest(productId: productId) { (resultModel) in
            
            MBProgressHUD.hide(for: self.view, animated: true)
            
            if resultModel.type == .RequestSuccessful {

            }else {
                
                RSHelper.showViewDidResAbnormal(resultModel: resultModel)
            }
        }
    }

是不是很帥呢!

最后如果你還是欲求不滿的話,給個車牌號你吧。

開一波車

ReactiveSwift擴展

再說說我們經(jīng)常會用到的本地存儲功能,這個一般的APP都會需要的。
數(shù)據(jù)的本地保存是不,下次沒有網(wǎng)絡的時候可以顯示之前保留下來的數(shù)據(jù),提升用戶的體驗;再或是把程序關掉后,再次打開APP,你之前的用戶信息因為保留下來了,那么你的狀態(tài)就不是未登錄的狀態(tài)了。

先說說我們常規(guī)的數(shù)據(jù)存儲,最基礎的是用SQLite,CoreData,可能會用SQL語句,蘋果親兒子的數(shù)據(jù)保存的coredata形式,當然二狗會說你太low了,還用這些不上臺面的東東,看我操作一波,于是拿出了FMDB,對SQLite的進一步封裝,直接指明需要存儲的數(shù)據(jù)和文件路徑即可,剩下的由底層的小弟去完成就可以了,嗯,確實很優(yōu)秀。

但我們存儲數(shù)據(jù)的時候通常以對象的形式去操作,這樣你存儲一個對象信息的時候,你得遵循SQL的語句規(guī)則,例如

//插入數(shù)據(jù)
NSString *name = [NSString stringWithFormat:@"王子涵%@",@(mark_student)];
int age = mark_student;
NSString *sex = @"男";
mark_student ++;
//1.executeUpdate:不確定的參數(shù)用?來占位(后面參數(shù)必須是oc對象,;代表語句結束)
BOOL result = [_db executeUpdate:@"INSERT INTO t_student (name, age, sex) VALUES (?,?,?)",name,@(age),sex];
//2.executeUpdateWithForamat:不確定的參數(shù)用%@,%d等來占位 (參數(shù)為原始數(shù)據(jù)類型,執(zhí)行語句不區(qū)分大小寫)
//    BOOL result = [_db executeUpdateWithFormat:@"insert into t_student (name,age, sex) values (%@,%i,%@)",name,age,sex];
//3.參數(shù)是數(shù)組的使用方式
//    BOOL result = [_db executeUpdate:@"INSERT INTO t_student(name,age,sex) VALUES  (?,?,?);" withArgumentsInArray:@[name,@(age),sex]];
if (result) {
    NSLog(@"插入成功");
} else {
    NSLog(@"插入失敗");
}

給人一種拘束的感覺。

那么讓我給大家隆重的介紹即將登場的。

Realm

先給出他的官方地址吧

Realm swift官方地址

別的不多說,二狗你只需要好好看代碼就可以了,能用到的不多,注意幾點就可以。

1.配置Realm數(shù)據(jù)庫,當然這個是必須的,不然Realm怎么知道數(shù)據(jù)放在哪里呢,當我們數(shù)據(jù)對象變化升級的時候,怎么做數(shù)據(jù)升級呢?

class func configRealm() {
        /// 如果要存儲的數(shù)據(jù)模型屬性發(fā)生變化,需要配置當前版本號比之前大
        let dbVersion : UInt64 = 2
        let docPath = NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.documentDirectory, FileManager.SearchPathDomainMask.userDomainMask, true)[0] as String
        let dbPath = docPath.appending("/defaultDB.realm")
        let config = Realm.Configuration(fileURL: URL.init(string: dbPath), inMemoryIdentifier: nil, syncConfiguration: nil, encryptionKey: nil, readOnly: false, schemaVersion: dbVersion, migrationBlock: { (migration, oldSchemaVersion) in
            
        }, deleteRealmIfMigrationNeeded: false, shouldCompactOnLaunch: nil, objectTypes: nil)
        Realm.Configuration.defaultConfiguration = config
        Realm.asyncOpen { (realm, error) in
            if let _ = realm {
                print("Realm 服務器配置成功!")
            }else if let error = error {
                print("Realm 數(shù)據(jù)庫配置失敗:\(error.localizedDescription)")
            }
        }
    }

至于數(shù)據(jù)庫的升級,目前我也沒弄明白(主要還沒遇到需要升級的情況)

2.Realm對象與NSObject對象的相互映射。這個就是數(shù)據(jù)存與取之間數(shù)據(jù)的對接了,當然不同情況下我們需要做不同的處理。

比如:在APP中用戶的基本信息(userName,userId,userToken,vip,userImgUrl等),這些在APP全局中都可能會用到的數(shù)據(jù),最好在每次啟動的時候從數(shù)據(jù)庫拿,并且這個時候我們用這些數(shù)據(jù)去初始化一個單例對象,這樣之后在APP中訪問數(shù)據(jù)的時候,所采用的姿勢也是比較優(yōu)雅的;再就是之前我說的緩存數(shù)據(jù)的獲取,這個肯定是哪個界面需要(通過判斷網(wǎng)絡的狀態(tài)),然后根據(jù)自己的需求去數(shù)據(jù)庫中取(當然得有數(shù)據(jù)才行哦),具體的可以看看我的這篇文章。
數(shù)據(jù)的存儲

下面就用代碼具體說說這個框架的用法吧,我這邊采用的數(shù)據(jù)結構是 :
需要存儲的對象(A),Realm數(shù)據(jù)庫的對象(B),單例對象(C)
C(UserManager) -> A(userModel) -> B(realmModel)

class UserManager {
    
    var userModel  =  UserModel()
       
    var bluetoothDicArr = Array<RSBluetoothModel>()
    
    class func shared() -> RSUserManager {
        
        return sharedManager
    }
   
    func sign(dic: []<String, Any>)  {
        
        userModel = UserModel().userInfo(dic)      //更新單例對象中屬性數(shù)據(jù)
        UserRealmModel.addRealmModel(userModel)   //數(shù)據(jù)寫入
    }

}

class UserModel {
    
    var userId  = 0
    var nickName = ""
    var userImgUrl = ""
    var phoneNum = ""
    var accessToken = ""
    
   func userInfo(_ userDic: Dictionary<String, Any>)  {
        
        userId = userDic["userId"] as! Int
        nickName = userDic["nickname"] as! String
        userImgUrl = (userDic["headimgurl"] as! String)
        phoneNum = userDic["phone"] as! String
        accessToken = userDic["accessToken"] as! String
        
    }


import RealmSwift
import Realm

class UserRealmModel: Object {
    
    @objc dynamic var userId = 0
    @objc dynamic var nickName = ""
    @objc dynamic var userImgUrl = ""
    @objc dynamic var sex = ""
    @objc dynamic var accessToken = ""

    class func addRealmModel(_ userModel: UserModel) {
        
        let realm = try! Realm()
        let realmModel: UserRealmModel = realmModelWithUserModel(userModel)
        try! realm.write {
            
            realm.deleteAll()
            realm.add(realmModel)
        }
    }

    class func realmModelWithUserModel(_ userModel: UserModel) -> UserRealmModel {
        
        let model = UserRealmModel()
        model.userId = Int(userModel.userId)
        model.nickName = userModel.nickName
        model.userImgUrl = userModel.userImgUrl
        model.sex = userModel.sex
        model.accessToken = userModel.accessToken
        
        return model
    }

}

當然這是比較簡單的數(shù)據(jù)對接了,相當于一對一的,但是如果是一個對象中包含多個對象呢,就是一對多咯。

我這個中的是包含了多個藍牙設備的信息

class UserManager {
    
    var bluetoothDicArr = Array<BluetoothModel>()

    func addBluetoothModel(uuid: String, nameStr name: String, deviceCode: String) {
        
        bluetoothModel = BluetoothModel.bluetoothModel(uuid: uuid, deviceName: name, deviceCode: deviceCode)
        UserRealmModel.addBluetoothInModel(bluetoothModel: bluetoothModel)   //通過單例對象去添加藍牙數(shù)組數(shù)據(jù)
    }
}

class BluetoothRealmModel: Object {

    @objc dynamic var deviceUUID = ""
    @objc dynamic var deviceName = ""
    @objc dynamic var deviceCode = ""  //設備編號

    class func bluetoothRealmModel(model: RSBluetoothModel) -> RSBluetoothRealmModel {  // swiftModel -> realm
        
        let realmModel = BluetoothRealmModel()
        realmModel.deviceUUID = model.deviceUUID
        realmModel.deviceName = model.deviceName
        realmModel.deviceCode = model.deviceCode
        
        return realmModel
    }
}

class BluetoothModel: NSObject {
    
    var deviceUUID: String = ""
    var deviceName : String = ""
    var deviceCode : String = ""
    
    class func bluetoothModel(uuid: String, deviceName name: String, deviceCode code: String) -> BluetoothModel {
        
        let model = BluetoothModel()
        model.deviceUUID = uuid
        model.deviceName = name
        model.deviceCode = code
        
        return model
    }
    
    class func bluetoothModel(realm: BluetoothRealmModel) -> BluetoothModel {   // Realm -> swiftModel
        
        let model = BluetoothModel()
        model.deviceUUID = realm.deviceUUID
        model.deviceName = realm.deviceName
        model.deviceCode = realm.deviceCode
        
        return model
    }
}

class UserRealmModel: Object {

var bluetoothArr = List<BluetoothRealmModel>()

    class func addBluetoothInModel(bluetoothModel: BluetoothModel) {
        
        let realm = try! Realm()
        let model = realm.objects(UserRealmModel.self).first
        
        for obj in (model?.bluetoothArr)! {  //防止重復添加
            
            if obj.deviceUUID == bluetoothModel.deviceUUID {
                return
            }
        }
        
        try! realm.write {
            
            let bluetoothModel =SBluetoothRealmModel.bluetoothRealmModel(model: bluetoothModel)
            model?.bluetoothArr.append(bluetoothModel)
            if (model?.bluetoothArr.count)! > 10 {
                
                model?.bluetoothArr.removeFirst()  //保證只有最近的十條藍牙設備信息
            }
        }
    }

}

這樣基本上滿足了數(shù)據(jù)結構的需要了。現(xiàn)在看看是不是Realm很方便快捷呢,在數(shù)據(jù)存儲的方面。

那么,我們說完了我上面所有的用到的框架,這里面還有用到Kingfisher,MBProgressHUD,當然這些的用法很簡單就不在這兒贅述了。

這兒我再附上Swift的目錄結構及其功能吧


98BD3760-421E-4BA3-AAAD-8E473F21C583.png

這篇文章就差不多接近尾聲了,如果老鐵看到了這兒,我要表達的意思你都懂的話,


心有靈犀

那么我總結一下這篇文章的知識點吧。

1.盡可能的采用低耦合,高內(nèi)聚的編碼思想.
2.作為對象的調(diào)用,只需要關注輸入和輸出的數(shù)據(jù),底層的實現(xiàn)只需要在相關文件中實現(xiàn)即可.
3.ReactiveSwift的熱信號和冷信號的運用.
4.ObjectMapper的在不同數(shù)據(jù)結構中的用法(即數(shù)組和字典).
5.Realm的簡單用法,對象之間的相互映射,數(shù)據(jù)在Realm中的增刪改查.
6.項目模塊之間的整體性.

最后給上這個項目會用到的基礎框架吧
GitHub地址

?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

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