Swift 项目:ReactiveSwift+MVVM+Real

作者: 怀心逝水 | 来源:发表于2018-10-02 17:35 被阅读65次

    这篇文章先上我要说的项目的整体框架图吧,有兴趣的可以看下去。


    swift.png

    那么正片现在就要开始了。。。。

    小样儿

    我们一个一个介绍,先看看什么是MVVM模式。

    当然在说这个之前,我们不得不先说说什么是MVC模式,当然这个模式是随着编码的积累慢慢形成的。

    初入IOS的小白,可能什么代码都会放在C(Controller)中,例如:一个视图中UILabel、UIButton、UITableView等视图对象,数据对象,各种逻辑判断的flag,各种if,else满天飞,这样就会发现C(Controller)中的代码越来越多,之后别人阅读和维护这样的项目,心里这种十万个“垃圾”,“写的像一坨屎”翻腾。

    于是我们慢慢的形成一种模式,MVC。

    MVC具体版
    MVC简洁版

    MVC模式

     说明:从图中可以看出,C(Controller)是连接M(Model)和V(View)的重要中转站,
          当然C也是作为项目中地位最为重要的一部分,负责的事务:
      1.添加,显示,更新视图对象
      2.处理view对象中的响应事件
      3.网络请求,给M(Model)传入字典数据得到自定义的数据对象
      4.将得到的数据对象(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
        }()
    
    //网络请求返回字典数据
      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中传入字典得到自定义数据对象
                  //刷新tableView
                } else {
                    
                    print("error\((response.result.error)! as AnyObject)")
                }
            }
        }
    }
    
    extension YourController : UITableViewDelegate, UITableViewDataSource {
    
        //delegate or dataSource
    
    }
    
    //Model 部分
    class SubHomeScrollDataModel: NSObject {
        
        var isViewShow: Bool?  //数据对象属性
    
        class func simpleModel(dic: [String: Any]) -> SubHomeScrollDataModel {
            
            let model = SubHomeScrollDataModel()
            model.isViewShow = dic[""]
            return model
        }
    
    }
    
    

    这样一个很简单的MVC设计模式就出现了,当然这样的好处:

    1.视图对象的封装
    2.控制器代码相应的减少
    

    当然也有它的缺点:

    1.网络层的代码并没有抽离出来
    2.逻辑业务层的处理
    

    当然如果你是个老司机,看的资料很多的话,认为MVC是另一种设计模式,把这里的C看作Cell,可以推荐你看看这篇文章。当然那个的理解是基于我这个理解之上的,个人对他写的MVC的理解是,一个视图可能会分多个模块,而每个模块视图有自己的MVC模式。
    简书地址

    基于上面的设计缺点,我个人的理解,就是抽离出来网络层放到ViewModel中,这样就形成了我这个项目的MVVM的模式。

    Alamofire

    这个算是Swift项目中网络层必须用到的框架了,具体的源码我这儿就不介绍了(毕竟我也不是很清楚啊)

    哈哈

    不过它的用法还是很简单的。

        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连接好了,接着我们再封装一层网络的管理者,例如首页获取数据的网络管理者,这个我们需要关心的是:

    1.传入的字典
    2.传出的自定义数据Model
    

    那这个怎么玩呢? 我这里是请来了ReactiveSwift,ObjectMapper这两位大哥镇场子。

    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 {
                                                        //网络出错
                                                        let responseModel = RSStoreDataModel()
                                                        responseModel.netErrorResultModel()
                                                        observer.send(value: responseModel)
                                                    }
                }
            }
        }
    
    }
    //注:RSStoreDataModel传出的数据模型,这个对象是实现了Mappable协议的,
    //具体的可以看下面对ObjectMapper的介绍,通过ReactiveSwift中的观察者机制传出。
    

    ObjectMapper

    说得通俗一点,它的功能就是做数据对象的映射,把服务器的字典数据转成自定义对象。

    。。。。。什么?陈独秀同学你说用字典的键值对获取的方法也可以生成自定义对象,那么老司机我就用代码来说说吧。

    字典的格式
     /* { 
        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 {   //商品规格数据模型
        
        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"]      //数组数据的直接映射
            goodId <- map["goodId"]
            normId <- map["id"]
            normName <- map["name"]
        }
    }
    
    class ProductColorModel: Mappable {   //商品颜色数据模型
        
        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"]
        }
    }
    
    //当然如果用常规的键值对获取的方法,也可以办到,但那样就感觉很繁琐了。
    //数组的映射直接是火箭式封装
    

    用这个框架的时候,我这边经过了一层数据结构的封装,怎么说呢,就是上面的BaseResultModel,这个是做什么用的呢,容我慢慢道来。

    原因:每个API接口都会有msg,code/status 这些固定字段,但如果我们去解析每个接口时都去定义对象中相应的属性(对应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 = "网络请求超时"
        }
    }
    // 这样我只需要继承这个对象即可,是不是很机智😎
    

    ReactiveSwift

    好了,接下来上场的是ReactiveSwift这位大哥了,那么先说说这位大哥的背景吧。

    ReactiveSwift是一种函数式反应型编程,那什么是函数式反应型编程呢?有什么优点呢?
    我这儿就不给理论性的解读了,那个资料臣妾也是看得一头雾水,那么还是看代码吧。

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

    利用函数式编程(Functional Programming)技术去处理这些变化事件,
    如这里面的函数controlEvents(.touchUpInside),然后通过给个小兵观察这个按钮是否发生改变,
    从而给出变化后的按钮对象。

    优点嘛
    swift不是以简洁著称,这样就符合咋们高大帅的气质嘛。

    帅得一批

    介绍完了大哥,就来具体的说说ReactiveSwift用法,用法嘛,其实很简单,抓住几个重点就可以了。
    就我个人的理解,ReactiveSwift其实处理ios中的逻辑,代理,通知方面的一个整合,另外就是一个回调观察者的机制。

    例如我们ios中对象的数据或是事件的回调,通常采用的方式是代理或是Block,如果是非关联对象之间数据事件的联系则是用通知,然后系统控件(UIButton,UITextField)中的响应事件或是代理方法,这些零零散散的东西,现在直接用这个框架就可以很快捷的解决问题了。

    还是看代码吧!

    //按钮的点击事件
    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系统中UIControlEvents的枚举类,例如UISwitch,UITextField,UISlider,然后他们不同响应的方式,都可以用上面的代码去做处理。

    热信号(就是代替Block和Delegate的)

    A和B对象(B对象是在A对象中生成的),要把B对象中的数据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]是必须要加上的啊,不然就会发生内存泄漏,类似oc的__weak typeof(self) weakSelf = self,
    那样的话,你会发现你退出了一个控制器后,该控制器并没有销毁。想当初我是一行一行代码去检查为什么pop控制器,它就是不执行下面那个方法呢,原来是这儿的坑啊。

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

    通知

    NotificationCenter.default.reactive.notifications(forName: Notification.Name(rawValue: "UIKeyboardWillShowNotification"),
                                                                             object: nil).observeValues { (notification) in
    
         print("键盘弹起")
     }
    

    当然这是系统中的通知,自定义通知我目前还不知道呢。

    说完热信号,咋们说说冷信号。二狗你可能会问,什么是热信号什么又是冷信号呢,那我就解释一波吧。

    热信号嘛,就是界面上与用户交互操作的事件的响应,例如UIButton,UItextfield,键盘的弹出通知了
    ,UISwitch的切换了,是不,这些反应在界面上的就可以称之为“热信号”了;
    而冷信号,就是程序中通过代码去观察事件动作的,如你定义一个获取网络的API方法,然后通过Action去返回一个SignalProducer的东东,之后再用action的start的方法监听返回的数据,这样的没有与用户交互的数据相应事件称之为“冷信号”,是不是很有道理呢,那么就让我们看看具体什么是冷信号吧。

    小二,上代码!!!

    class StoreViewModel: NSObject {
        
        var action : Action<(), RSStoreDataModel, NoError>!   //获取界面数据的事件
        
        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 {
                                                        //网络出错
                                                        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扩展

    再说说我们经常会用到的本地存储功能,这个一般的APP都会需要的。
    数据的本地保存是不,下次没有网络的时候可以显示之前保留下来的数据,提升用户的体验;再或是把程序关掉后,再次打开APP,你之前的用户信息因为保留下来了,那么你的状态就不是未登录的状态了。

    先说说我们常规的数据存储,最基础的是用SQLite,CoreData,可能会用SQL语句,苹果亲儿子的数据保存的coredata形式,当然二狗会说你太low了,还用这些不上台面的东东,看我操作一波,于是拿出了FMDB,对SQLite的进一步封装,直接指明需要存储的数据和文件路径即可,剩下的由底层的小弟去完成就可以了,嗯,确实很优秀。

    但我们存储数据的时候通常以对象的形式去操作,这样你存储一个对象信息的时候,你得遵循SQL的语句规则,例如

    //插入数据
    NSString *name = [NSString stringWithFormat:@"王子涵%@",@(mark_student)];
    int age = mark_student;
    NSString *sex = @"男";
    mark_student ++;
    //1.executeUpdate:不确定的参数用?来占位(后面参数必须是oc对象,;代表语句结束)
    BOOL result = [_db executeUpdate:@"INSERT INTO t_student (name, age, sex) VALUES (?,?,?)",name,@(age),sex];
    //2.executeUpdateWithForamat:不确定的参数用%@,%d等来占位 (参数为原始数据类型,执行语句不区分大小写)
    //    BOOL result = [_db executeUpdateWithFormat:@"insert into t_student (name,age, sex) values (%@,%i,%@)",name,age,sex];
    //3.参数是数组的使用方式
    //    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数据库,当然这个是必须的,不然Realm怎么知道数据放在哪里呢,当我们数据对象变化升级的时候,怎么做数据升级呢?

    class func configRealm() {
            /// 如果要存储的数据模型属性发生变化,需要配置当前版本号比之前大
            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 数据库配置失败:\(error.localizedDescription)")
                }
            }
        }
    

    至于数据库的升级,目前我也没弄明白(主要还没遇到需要升级的情况)

    2.Realm对象与NSObject对象的相互映射。这个就是数据存与取之间数据的对接了,当然不同情况下我们需要做不同的处理。

    比如:在APP中用户的基本信息(userName,userId,userToken,vip,userImgUrl等),这些在APP全局中都可能会用到的数据,最好在每次启动的时候从数据库拿,并且这个时候我们用这些数据去初始化一个单例对象,这样之后在APP中访问数据的时候,所采用的姿势也是比较优雅的;再就是之前我说的缓存数据的获取,这个肯定是哪个界面需要(通过判断网络的状态),然后根据自己的需求去数据库中取(当然得有数据才行哦),具体的可以看看我的这篇文章。
    数据的存储

    下面就用代码具体说说这个框架的用法吧,我这边采用的数据结构是 :
    需要存储的对象(A),Realm数据库的对象(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)      //更新单例对象中属性数据
            UserRealmModel.addRealmModel(userModel)   //数据写入
        }
    
    }
    
    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
        }
    
    }
    
    

    当然这是比较简单的数据对接了,相当于一对一的,但是如果是一个对象中包含多个对象呢,就是一对多咯。

    我这个中的是包含了多个蓝牙设备的信息

    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)   //通过单例对象去添加蓝牙数组数据
        }
    }
    
    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()  //保证只有最近的十条蓝牙设备信息
                }
            }
        }
    
    }
    
    

    这样基本上满足了数据结构的需要了。现在看看是不是Realm很方便快捷呢,在数据存储的方面。

    那么,我们说完了我上面所有的用到的框架,这里面还有用到Kingfisher,MBProgressHUD,当然这些的用法很简单就不在这儿赘述了。

    这儿我再附上Swift的目录结构及其功能吧


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

    这篇文章就差不多接近尾声了,如果老铁看到了这儿,我要表达的意思你都懂的话,


    心有灵犀

    那么我总结一下这篇文章的知识点吧。

    1.尽可能的采用低耦合,高内聚的编码思想.
    2.作为对象的调用,只需要关注输入和输出的数据,底层的实现只需要在相关文件中实现即可.
    3.ReactiveSwift的热信号和冷信号的运用.
    4.ObjectMapper的在不同数据结构中的用法(即数组和字典).
    5.Realm的简单用法,对象之间的相互映射,数据在Realm中的增删改查.
    6.项目模块之间的整体性.

    最后给上这个项目会用到的基础框架吧
    GitHub地址

    相关文章

      网友评论

        本文标题:Swift 项目:ReactiveSwift+MVVM+Real

        本文链接:https://www.haomeiwen.com/subject/bkrhoftx.html