Moya+RxSwift+ObjectMapper实现MVVM模式

博客地址: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

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

推荐阅读更多精彩内容