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

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


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地址

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,440评论 5 467
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,814评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,427评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,710评论 1 270
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,625评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,014评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,511评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,162评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,311评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,262评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,278评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,989评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,583评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,664评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,904评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,274评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,856评论 2 339

推荐阅读更多精彩内容