這篇文章先上我要說的項目的整體框架圖吧,有興趣的可以看下去。
那么正片現(xiàn)在就要開始了。。。。
我們一個一個介紹,先看看什么是MVVM模式。
當然在說這個之前,我們不得不先說說什么是MVC模式,當然這個模式是隨著編碼的積累慢慢形成的。
初入IOS的小白,可能什么代碼都會放在C(Controller)中,例如:一個視圖中UILabel、UIButton、UITableView等視圖對象,數(shù)據(jù)對象,各種邏輯判斷的flag,各種if,else滿天飛,這樣就會發(fā)現(xiàn)C(Controller)中的代碼越來越多,之后別人閱讀和維護這樣的項目,心里這種十萬個“垃圾”,“寫的像一坨屎”翻騰。
于是我們慢慢的形成一種模式,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,我們就拿簡書界面來說吧。
主要寫一下思路,至于代碼的部分,只是說明這個流程
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)
}
}
}
是不是很帥呢!
最后如果你還是欲求不滿的話,給個車牌號你吧。
再說說我們經(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
先給出他的官方地址吧
別的不多說,二狗你只需要好好看代碼就可以了,能用到的不多,注意幾點就可以。
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的目錄結構及其功能吧
這篇文章就差不多接近尾聲了,如果老鐵看到了這兒,我要表達的意思你都懂的話,
那么我總結一下這篇文章的知識點吧。
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地址