博客地址:http://riversunny.top
具体详见demo
简单来说MVVM (Model–view–viewmodel) 是为了解决 MVC模式中C(controller)部分随着业务的复杂过于臃肿,代码的可维护性差、复用性低这些问题。
- M:Model层用于数据模型化存储
- V:view层视图层GUI用来展示并和用户交互
- VM:viewModel层,负责业务处理和数据转化
架构模式图:
RxSwift简单介绍
RxSwift 函数响应式编程框架和Rxjava一样都是Github的ReactiveX组织开发、维护的.RxSwift实际上是把我们程序的每个操作都看做一个事件,比如网络请求事件、按钮点击事件,发出这些事件的地方我们叫做事件源而我们的程序运行就是不断处理这些源发出的事件的过程。
Observable和Observer
RxSwift 进一步抽象事件发出-处理这个过程即抽象出观察者模式, Observable为事件的发出方事件源、Observer事件的观察者事件的处理方而这两者之间通过订阅(subscribe)被观察者Observable,Observer才能收到Observable发出的消息。
Observable里有三种事件——next, completed, error
- next事件主要是当Observable里出现新的数据时会发出的事件,同时该事件会携带新的数据对象。
- completed事件是当Observable不再有新的数据出现,Observable被标记完成,并且将数据流终结(terminate)。
- error事件是指当数据流遇到了错误会发出的事件,该事件也会导致Observable被终结。
Observable常用的创建方法just, of, from
参见http://www.jianshu.com/p/992f8687fefd创建后就可以通过实例发送相应的事件了
subscribe订阅的实现
subscribe(on:(Event) -> void):订阅所有消息(Next, Error, and Completed)
subscribeNext((Element) -> void):只订阅Next
subscribeError((ErrorType) -> void):只订阅Error
subscribeCompleted(() -> Void):只订阅Completed
subscribe(onNext:(Element) -> void, onError:(ErrorType) -> void, onCompleted:() -> Void, onDisposed:() -> Void)订阅多个消息
更加详细的内容参见RxSwift使用教程
Moya简单介绍
Moya是一个基于Alamofire开发的,轻量级的Swift网络封装层,同时Moya的可扩展性特别强,可以方便的RXSwift,ObjectMapper结合使用。
- Moya结合Alamofire 使用
1.首先创建枚举类,将你的每个请求定义成枚举
enum NetAPI{
case Login(username:String,password:String)
}
2.枚举类型要遵守TargetType的协议
extension NetAPI:TargetType{
/// The method used for parameter encoding.
var parameterEncoding: ParameterEncoding {
return URLEncoding.default
}
var baseURL: URL {
return URL(string: BaseUrl)!
}
var path: String {
switch self {
case .Login(_,_):
return "/topics/hot.json"
}
}
var method: Moya.Method {
switch self {
case .Login(_,_):
return .get
}
}
var parameters: [String: Any]? {
switch self {
/*
* 正常请求需要参数时,可写成
* case .Login(let username,let password):
* return ["username": username, "password": password]
*/
case .Login(_,_):
return nil
}
}
var sampleData: Data {
switch self {
case .Login(_,_):
return "Login successfully".data(using: String.Encoding.utf8)!
}
}
var task: Task {
return .request
}
var validate: Bool {
return false
}
}
3.通过TargetType协议定义好每个target之后,使用RxMoyaProvider<NetAPI>()对象来发送网络请求
let netApi = RxMoyaProvider<NetAPI>()
netApi.request(.Login(username: username, password: password){ result in
// do something with result
}
ObjectMapper介绍和使用
ObjectMapper 是一个基于 Swift 语言开发的能够让 JSON 与 Object 之间轻易转换的类库。通过 ObjectMapper 我们可以将 JSON 数据转换成 Model 对象或将 Model 对象转换成 JSON 数据。我们demo中请求的网络数据格式如下:
[
{
"id" : 391539,
"title" : "一朋友公司,询问同事工资被举报了 被罚了 2000 她工资 5500 这个月工资下来她就收到了 2200 多...",
"url" : "http://www.v2ex.com/t/391539",
"content" : "她房租 1300 整下来一个月就剩 900 了 现在跟我混吃混喝 问下 v 友 问工资被罚这么多钱正常吗?",
"content_rendered" : "她房租 1300 整下来一个月就剩 900 了 现在跟我混吃混喝 问下 v 友 问工资被罚这么多钱正常吗?",
"replies" : 138,
"member" : {
"id" : 235778,
"username" : "yasumoto",
"tagline" : "",
"avatar_mini" : "//v2ex.assets.uxengine.net/avatar/c3de/86d4/235778_mini.png?m=1497585507",
"avatar_normal" : "//v2ex.assets.uxengine.net/avatar/c3de/86d4/235778_normal.png?m=1497585507",
"avatar_large" : "//v2ex.assets.uxengine.net/avatar/c3de/86d4/235778_large.png?m=1497585507"
},
"node" : {
"id" : 300,
"name" : "programmer",
"title" : "程序员",
"title_alternative" : "Programmer",
"url" : "http://www.v2ex.com/go/programmer",
"topics" : 16902,
"avatar_mini" : "//v2ex.assets.uxengine.net/navatar/94f6/d7e0/300_mini.png?m=1505536818",
"avatar_normal" : "//v2ex.assets.uxengine.net/navatar/94f6/d7e0/300_normal.png?m=1505536818",
"avatar_large" : "//v2ex.assets.uxengine.net/navatar/94f6/d7e0/300_large.png?m=1505536818"
},
"created" : 1505701687,
"last_modified" : 1505701687,
"last_touched" : 1505730173
},
{
"id" : 391497,
"title" : "除了肚子痛,还有什么理由可以很合理的请一天假",
"url" : "http://www.v2ex.com/t/391497",
"content" : "每次都肚子痛,很不合适啊",
"content_rendered" : "\u003Cp\u003E每次都肚子痛,很不合适啊\u003C/p\u003E\u000A",
"replies" : 73,
"member" : {
"id" : 209700,
"username" : "wwsww",
"tagline" : "",
"avatar_mini" : "//v2ex.assets.uxengine.net/gravatar/7cf52b16f74d575111470944910c030e?s=24&d=retro",
"avatar_normal" : "//v2ex.assets.uxengine.net/gravatar/7cf52b16f74d575111470944910c030e?s=48&d=retro",
"avatar_large" : "//v2ex.assets.uxengine.net/gravatar/7cf52b16f74d575111470944910c030e?s=73&d=retro"
},
"node" : {
"id" : 12,
"name" : "qna",
"title" : "问与答",
"title_alternative" : "Questions and Answers",
"url" : "http://www.v2ex.com/go/qna",
"topics" : 91929,
"avatar_mini" : "//v2ex.assets.uxengine.net/navatar/c20a/d4d7/12_mini.png?m=1505712246",
"avatar_normal" : "//v2ex.assets.uxengine.net/navatar/c20a/d4d7/12_normal.png?m=1505712246",
"avatar_large" : "//v2ex.assets.uxengine.net/navatar/c20a/d4d7/12_large.png?m=1505712246"
},
"created" : 1505694388,
"last_modified" : 1505694388,
"last_touched" : 1505731238
}
]
对应的数据模型定义:
class UserModel: Mappable {
var id: Int?
var username: String?
var tagline: String?
var avatar: String?
var website: String?
var github: String?
var twitter: String?
var location: String?
required init?(map: Map) {
}
func mapping(map: Map) {
id <- map["id"]
username <- map["username"]
tagline <- map["tagline"]
avatar <- map["avatar"]
website <- map["website"]
github <- map["github"]
twitter <- map["twitter"]
location <- map["location"]
}
}
class NoteModel: Mappable {
var id:Int?
var name:String?
var title:String?
var titleAlternative:String?
var url:String?
var topics:Int?
var header:String?
var footer:String?
var isCollected:Bool?
required init?(map: Map) {
}
func mapping(map: Map) {
id <- map["id"]
name <- map["name"]
title <- map["title"]
titleAlternative <- map["titleAlternative"]
url <- map["url"]
topics <- map["topics"]
header <- map["header"]
footer <- map["footer"]
isCollected <- map["isCollected"]
}
}
class TopicModel: Mappable {
var id:Int?
var title:String?
var url:String?
var content:String?
var contentRendered:String?
var replies:Int?
var member:UserModel?
var node:NoteModel?
var created:Int32?
var lastModified:Int32?
var lastTouched:Int32?
required init?(map: Map) {
}
func mapping(map: Map) {
id <- map["id"]
title <- map["title"]
url <- map["url"]
content <- map["content"]
contentRendered <- map["contentRendered"]
replies <- map["replies"]
member <- map["member"]
node <- map["node"]
created <- map["created"]
lastModified <- map["lastModified"]
lastTouched <- map["lastTouched"]
}
}
通过下面这段代码:
func login(username:String,password:String) -> Observable<[TopicModel]> {
return netApi.request(.Login(username: username, password: password))
.filterSuccessfulStatusCodes()
.mapJSON()
.mapArray(type: TopicModel.self)
}
1.netApi.request调用网络请求的Api Login发送请求
2.再调用Moya对RxSwift扩展方法filterSuccessfulStatusCodes()得到成功的网络请求
3.mapJSON() 也是Moya RxSwift的扩展方法,可以把返回的数据解析成 JSON 格式
4.mapArray(type: TopicModel.self)为对Observable的扩展方法解析成[TopicModel]数组
func mapArray<T: Mappable>(type: T.Type) -> Observable<[T]> {
return self.map { response in
/*as?类型转换,第一步判断response是否可以转换成[Any]对象数组RxSwiftMoyaError.ParseJSONError转换失败返回nil抛出RxSwiftMoyaError.ParseJSONError*/
guard let array = response as? [Any] else {
throw RxSwiftMoyaError.ParseJSONError
}
/*as?类型转换,第二步判断response是否可以转换成[[String: Any]]字典对象数组RxSwiftMoyaError.ParseJSONError转换失败返回nil抛出RxSwiftMoyaError.ParseJSONError*/
guard let dicts = array as? [[String: Any]] else {
throw RxSwiftMoyaError.ParseJSONError
}
return Mapper<T>().mapArray(JSONArray: dicts)
}
}
具体详见demo