Home - linxiaoxing/MyTest GitHub Wiki

Swift - RxSwift的使用详解52(MVVM架构演示2:使用Observable样例)

三、一个使用 Observable 的 MVVM 样例 1,效果图 (1)当我们在表格上方的搜索框中输入文字时,会实时地去请求 GitHub 接口查询相匹配的资源库。 (2)数据返回后,将查询结果数量显示在导航栏标题上,同时把匹配度最高的资源条目显示显示在表格中(这个是 GitHub 接口限制,由于数据太多,可能不会一次全部都返回)。 (3)点击某个单元格,会弹出显示该资源的详细信息(全名和描述) (4)删除搜索框的文字后,表格内容同步清空,导航栏标题变成显示“hangge.com” 原文:Swift - RxSwift的使用详解52(MVVM架构演示2:使用Observable样例) 原文:Swift - RxSwift的使用详解52(MVVM架构演示2:使用Observable样例) 原文:Swift - RxSwift的使用详解52(MVVM架构演示2:使用Observable样例)

2,准备工作 (1)首先我们在项目中配置好 RxSwift、Alamofire、Moya、Result 这几个库,具体步骤可以参考我之前写的这篇文章: Swift - RxSwift的使用详解49(结合Moya使用1:数据请求)

(2)为了方便将结果映射成自定义对象,我们还需要引入 ObjectMapper、Moya-ObjectMapper 这两个第三方库。具体步骤可以参考我之前写的这篇文章: Swift - RxSwift的使用详解50(结合Moya使用2:结果处理、模型转换)

3,样例代码 (1)我们先创建一个 GitHubAPI.swift 文件作为网络请求层,里面的内容如下: 首先定义一个 provider,即请求发起对象。往后我们如果要发起网络请求就使用这个 provider。 接着声明一个 enum 来对请求进行明确分类,这里我们只有一个枚举值表示查询资源。 最后让这个 enum 实现 TargetType 协议,在这里面定义我们各个请求的 url、参数、header 等信息。

import Foundation import Moya import RxMoya

//初始化GitHub请求的provider let GitHubProvider = MoyaProvider()

/** 下面定义GitHub请求的endpoints(供provider使用)**/ //请求分类 public enum GitHubAPI { case repositories(String) //查询资源库 }

//请求配置 extension GitHubAPI: TargetType { //服务器地址 public var baseURL: URL { return URL(string: "https://api.github.com")! }

//各个请求的具体路径
public var path: String {
    switch self {
    case .repositories:
        return "/search/repositories"
    }
}
 
//请求类型
public var method: Moya.Method {
    return .get
}
 
//请求任务事件(这里附带上参数)
public var task: Task {
    print("发起请求。")
    switch self {
    case .repositories(let query):
        var params: [String: Any] = [:]
        params["q"] = query
        params["sort"] = "stars"
        params["order"] = "desc"
        return .requestParameters(parameters: params,
                                  encoding: URLEncoding.default)
    default:
        return .requestPlain
    }
}
 
//是否执行Alamofire验证
public var validate: Bool {
    return false
}
 
//这个就是做单元测试模拟的数据,只会在单元测试文件中有作用
public var sampleData: Data {
    return "{}".data(using: String.Encoding.utf8)!
}
 
//请求头
public var headers: [String: String]? {
    return nil
}

}

(2)接着定义好相关模型:GitHubModel.swift(需要实现 ObjectMapper 的 Mappable 协议,并设置好成员对象与 JSON 属性的相互映射关系。) import Foundation import ObjectMapper

//包含查询返回的所有库模型 struct GitHubRepositories: Mappable { var totalCount: Int! var incompleteResults: Bool! var items: [GitHubRepository]! //本次查询返回的所有仓库集合

init() {
    print("init()")
    totalCount = 0
    incompleteResults = false
    items = []
}
 
init?(map: Map) { }
 
// Mappable
mutating func mapping(map: Map) {
    totalCount <- map["total_count"]
    incompleteResults <- map["incomplete_results"]
    items <- map["items"]
}

}

//单个仓库模型 struct GitHubRepository: Mappable { var id: Int! var name: String! var fullName:String! var htmlUrl:String! var description:String!

init?(map: Map) { }
 
// Mappable
mutating func mapping(map: Map) {
    id <- map["id"]
    name <- map["name"]
    fullName <- map["full_name"]
    htmlUrl <- map["html_url"]
    description <- map["description"]
}

}

(3)下面就是本文的重头戏了。我们创建一个 ViewModel,它的作用就是将用户各种输入行为,转换成输出状态。本样例中,不管输入还是输出都是 Observable 类型。 import Foundation import RxSwift import Result

class ViewModel { /**** 输入部分 ***/ //查询行为 fileprivate let searchAction:Observable

/**** 输出部分 ***/
//所有的查询结果
let searchResult: Observable<GitHubRepositories>
 
//查询结果里的资源列表
let repositories: Observable<[GitHubRepository]>
 
//清空结果动作
let cleanResult: Observable<Void>
 
//导航栏标题
let navigationTitle: Observable<String>
 
//ViewModel初始化(根据输入实现对应的输出)
init(searchAction:Observable<String>) {
    self.searchAction = searchAction
     
    //生成查询结果序列
    self.searchResult = searchAction
        .filter { !$0.isEmpty } //如果输入为空则不发送请求了
        .flatMapLatest{  //也可考虑改用flatMapFirst
            GitHubProvider.rx.request(.repositories($0))
                .filterSuccessfulStatusCodes()
                .mapObject(GitHubRepositories.self)
                .asObservable()
                .catchError({ error in
                    print("发生错误:",error.localizedDescription)
                    return Observable<GitHubRepositories>.empty()
                })
        }.share(replay: 1) //让HTTP请求是被共享的
     
    //生成清空结果动作序列
    self.cleanResult = searchAction.filter{ $0.isEmpty }.map{ _ in Void() }
     
    //生成查询结果里的资源列表序列(如果查询到结果则返回结果,如果是清空数据则返回空数组)
    self.repositories = Observable.of(searchResult.map{ $0.items },
                                      cleanResult.map{[]}).merge()
     
    //生成导航栏标题序列(如果查询到结果则返回数量,如果是清空数据则返回默认标题)
    self.navigationTitle = Observable.of(
        searchResult.map{ "共有 \($0.totalCount!) 个结果" },
        cleanResult.map{ "hangge.com" })
        .merge()
}

} (4)最后我们视图控制器(ViewController)只需要调用 ViewModel 进行数据绑定就可以了。可以看到由于网络请求、数据处理等逻辑已经被剥离到 ViewModel 中,VC 这边的负担大大减轻了。

import UIKit import RxSwift import RxCocoa

class ViewController: UIViewController {

//显示资源列表的tableView
var tableView:UITableView!
 
//搜索栏
var searchBar:UISearchBar!
 
let disposeBag = DisposeBag()
 
override func viewDidLoad() {
    super.viewDidLoad()
     
    //创建表视图
    self.tableView = UITableView(frame:self.view.frame, style:.plain)
    //创建一个重用的单元格
    self.tableView!.register(UITableViewCell.self, forCellReuseIdentifier: "Cell")
    self.view.addSubview(self.tableView!)
     
    //创建表头的搜索栏
    self.searchBar = UISearchBar(frame: CGRect(x: 0, y: 0,
                                               width: self.view.bounds.size.width, height: 56))
    self.tableView.tableHeaderView =  self.searchBar
     
    //查询条件输入
    let searchAction = searchBar.rx.text.orEmpty
        .throttle(0.5, scheduler: MainScheduler.instance) //只有间隔超过0.5k秒才发送
        .distinctUntilChanged()
        .asObservable()
     
    //初始化ViewModel
    let viewModel = ViewModel(searchAction: searchAction)
     
    //绑定导航栏标题数据
    viewModel.navigationTitle.bind(to: self.navigationItem.rx.title).disposed(by: disposeBag)
     
    //将数据绑定到表格
    viewModel.repositories.bind(to: tableView.rx.items) { (tableView, row, element) in
        let cell = UITableViewCell(style: .subtitle, reuseIdentifier: "Cell")
        cell.textLabel?.text = element.name
        cell.detailTextLabel?.text = element.htmlUrl
        return cell
        }.disposed(by: disposeBag)
     
    //单元格点击
    tableView.rx.modelSelected(GitHubRepository.self)
        .subscribe(onNext: {[weak self] item in
            //显示资源信息(完整名称和描述信息)
            self?.showAlert(title: item.fullName, message: item.description)
        }).disposed(by: disposeBag)
}
 
//显示消息
func showAlert(title:String, message:String){
    let alertController = UIAlertController(title: title,
                                            message: message, preferredStyle: .alert)
    let cancelAction = UIAlertAction(title: "确定", style: .cancel, handler: nil)
    alertController.addAction(cancelAction)
    self.present(alertController, animated: true, completion: nil)
}
 
override func didReceiveMemoryWarning() {
    super.didReceiveMemoryWarning()
}

}

功能改进:将网络请求服务提取出来 (1)从上面的样例可以发现,我们在 ViewModel 中是直接调用 Moya 的 Provider 进行数据请求,并进行模型转换。 (2)我们也可以把网络请求和数据转换相关代码提取出来,作为一个专门的 Service。比如 GitHubNetworkService,内容如下:

import RxSwift import RxCocoa import ObjectMapper

class GitHubNetworkService {

//搜索资源数据
func searchRepositories(query:String) -> Observable<GitHubRepositories> {
    return GitHubProvider.rx.request(.repositories(query))
        .filterSuccessfulStatusCodes()
        .mapObject(GitHubRepositories.self)
        .asObservable()
        .catchError({ error in
            print("发生错误:",error.localizedDescription)
            return Observable<GitHubRepositories>.empty()
        })
}

}

(3)ViewModel 这边不再直接调用 provider,而是通过这个 Service 就获取需要的数据。可以看到代码简洁许多: import Foundation import RxSwift import Result

class ViewModel { /**** 数据请求服务 ***/ let networkService = GitHubNetworkService()

/**** 输入部分 ***/
//查询行为
fileprivate let searchAction:Observable<String>
 
/**** 输出部分 ***/
//所有的查询结果
let searchResult: Observable<GitHubRepositories>
 
//查询结果里的资源列表
let repositories: Observable<[GitHubRepository]>
 
//清空结果动作
let cleanResult: Observable<Void>
 
//导航栏标题
let navigationTitle: Observable<String>
 
//ViewModel初始化(根据输入实现对应的输出)
init(searchAction:Observable<String>) {
    self.searchAction = searchAction
     
    //生成查询结果序列
    self.searchResult = searchAction
        .filter { !$0.isEmpty } //如果输入为空则不发送请求了
        .flatMapLatest(networkService.searchRepositories) //也可考虑改用flatMapFirst
        .share(replay: 1) //让HTTP请求是被共享的
     
    //生成清空结果动作序列
    self.cleanResult = searchAction.filter{ $0.isEmpty }.map{ _ in Void() }
     
    //生成查询结果里的资源列表序列(如果查询到结果则返回结果,如果是清空数据则返回空数组)
    self.repositories = Observable.of(searchResult.map{ $0.items },
                                      cleanResult.map{[]}).merge()
     
    //生成导航栏标题序列(如果查询到结果则返回数量,如果是清空数据则返回默认标题)
    self.navigationTitle = Observable.of(
        searchResult.map{ "共有 \($0.totalCount!) 个结果" },
        cleanResult.map{"hangge.com"})
        .merge()
}

}

原文出自:www.hangge.com 转载请保留原文链接:http://www.hangge.com/blog/cache/detail_2019.html

⚠️ **GitHub.com Fallback** ⚠️