RxSwift+Moya網(wǎng)絡(luò)請(qǐng)求之項(xiàng)目實(shí)戰(zhàn)

RxSwift+Moya之項(xiàng)目實(shí)戰(zhàn)

RxSwift相關(guān)基本介紹和用法可參考:

一. 下面將將進(jìn)行實(shí)戰(zhàn)項(xiàng)目

  • 1.登錄注冊(cè)功能
    • 輸入用戶名要大于6個(gè)字符,不然密碼不能輸入
    • 密碼必須大于6個(gè)字符猪狈,不然重復(fù)密碼不能輸入
    • 重復(fù)密碼輸入必須和密碼一樣凌箕,不然注冊(cè)按鈕不能點(diǎn)擊
    • 根據(jù)輸入的字符是否合法,按鈕動(dòng)態(tài)的改變顏色
  • 2.UITableView和搜索SertchBar的應(yīng)用
    • searchBar根據(jù)輸入的字體展示包含該字體的cell列表
    • RxSwift實(shí)現(xiàn)tableView列表展示
  • 3.Moya+RxSwift實(shí)現(xiàn)網(wǎng)絡(luò)請(qǐng)求
    • 應(yīng)用RxSwift在UICollectionView中的應(yīng)用
    • Moya進(jìn)行網(wǎng)絡(luò)請(qǐng)求
    • ObjectMapper進(jìn)行json到model的數(shù)據(jù)解析
    • 整個(gè)Demo的架構(gòu)使用MVVM

二. Demo地址

下面簡(jiǎn)單看一下demo的界面

1. 登錄注冊(cè)

登錄注冊(cè)

2. UITableView和SearchBar

UITableView和SearchBar

3. UICollectionView和Moya

UICollectionView和Moya

三. 項(xiàng)目結(jié)構(gòu)和框架

1. 結(jié)構(gòu)

demo是使用的純MVVM模式袍嬉,因?yàn)镽xSwift就是為MVVM而生憨琳。不懂MVVM的猿友可參考MVVM模式快速入門

項(xiàng)目結(jié)構(gòu)

2. 項(xiàng)目框架

// Swift三方庫(kù)
    // Rx
    pod 'RxSwift'  //RxSwift的必備庫(kù)
    pod 'RxCocoa'  //對(duì) UIKit Foundation 進(jìn)行 Rx 化
    pod 'RxDataSources'   // 幫助我們優(yōu)雅的使用tableView的數(shù)據(jù)源方法

    // 網(wǎng)絡(luò)請(qǐng)求
    pod 'Moya/RxSwift'  // 為RxSwift專用提供贡歧,對(duì)Alamofire進(jìn)行封裝的一個(gè)網(wǎng)絡(luò)請(qǐng)求庫(kù)

    // 圖片處理
    pod 'Kingfisher'  //圖片處理庫(kù)

    // 數(shù)據(jù)解析
    pod 'ObjectMapper'  //json轉(zhuǎn)模型


    
// OC庫(kù)
    // MJRefresh
    pod 'MJRefresh'   //MJ上拉下拉刷新
    pod 'SVProgressHUD'  //HUD

四. 注冊(cè)界面

  • 這里主要使用了Observable的相關(guān)知識(shí),不了解的童鞋可參考RxSwift的使用詳解01,了解Observable的操作
  • 注冊(cè)和登錄并沒有保存已注冊(cè)的賬號(hào)和密碼, 故登錄功能并不完善,后期會(huì)在完善,望知曉
  • 下面將針對(duì)注冊(cè)用戶名做簡(jiǎn)單介紹:

1. 首先在model里處理輸入字符串的語(yǔ)法法則和字符個(gè)數(shù)是否符合規(guī)范

extension InputValidator {
    //判斷字符串是否符合語(yǔ)法法則
    class func isValidEmail(_ email: String) -> Bool {
        let regular = try? NSRegularExpression(pattern: "^\\S+@\\S+\\.\\S+$", options: [])
        if let re = regular {
            let range = NSRange(location: 0, length: email.lengthOfBytes(using: .utf8))
            let result = re.matches(in: email, options: [], range: range)
            return result.count > 0
        }
        return false
    }
    
    //判斷密碼字符個(gè)數(shù)>8
    class func isValidPassword(_ password: String) -> Bool {
        return password.characters.count >= 8
    }
    
    //判斷用戶名
    class func validateUserName(_ username: String) -> Result {
        //判斷字符個(gè)數(shù)是否正確
        if username.characters.count < 6 {
            return Result.failure(message: "輸入的字符個(gè)數(shù)不能少于6個(gè)字符")
        }
        
        //賬號(hào)可用
        return Result.success(message: "賬號(hào)可用")
    }
}

其中Result是一個(gè)返回是否成功的枚舉值,可傳入字符串變量

enum Result {
    case success(message: String)
    case failure(message: String)
}

2. 根據(jù)輸入的用戶名判斷該用戶名是否可用

    var usernameObserable: Observable<Result>
    var passwordObserable: Observable<Result>
    var repeatPassObserable: Observable<Result>
    var registerBtnObserable: Observable<Bool>
    
    
    init(){
        //檢測(cè)賬號(hào)
        usernameObserable = username.asObservable().map({ (username) -> Result in
            return InputValidator.validateUserName(username)
        })
    }    

  • 該返回參數(shù)Result,控制器將根據(jù)該Result是否成功來(lái)改變輸入框是否是可編輯狀態(tài)
  • 初始化方法中闰靴,我們對(duì)傳入的序列進(jìn)行處理和轉(zhuǎn)換成相對(duì)應(yīng)的Result序列

3. controller邏輯,根據(jù)用戶名輸入改變各控件狀態(tài)

//1. 賬號(hào)判斷邏輯
        //1-1. 檢測(cè)賬號(hào)
        usernameTextField.rx.text
            .orEmpty // 將String? 類型轉(zhuǎn)為String型
            .bindTo(registerVM.username)
            .addDisposableTo(bag)
        
        //1-2. 根據(jù)賬號(hào)監(jiān)聽提示字體的狀態(tài)
        registerVM.usernameObserable
            .bindTo(usernameHintLabel.rx.validationResult)
            .addDisposableTo(bag)
        
        //1-3. 根據(jù)賬號(hào)監(jiān)聽密碼輸入框的狀態(tài)
        registerVM.usernameObserable
            .bindTo(passwordTextField.rx.enableResult)
            .addDisposableTo(bag)
            
  • 檢測(cè)輸入用戶名是否符合規(guī)范
  • 根據(jù)賬號(hào)監(jiān)聽提示字體的狀態(tài)
  • 根據(jù)賬號(hào)監(jiān)聽密碼輸入框的狀態(tài)
  • 根據(jù)賬號(hào)監(jiān)聽注冊(cè)按鈕的狀態(tài)

五. UITableView和SearchBar

  • 該UITableView展示界面并未涉及網(wǎng)絡(luò)請(qǐng)求
  • 數(shù)據(jù)來(lái)源plist文件
  • 圖片為本地圖片,可下載demo,在demo中查找圖片
  • 選用自定義UITableViewCell,故cell不做介紹
  • model小編這里也不多做介紹,詳情可下載demo看具體代碼

1. viewModel中的代碼邏輯

1-1. 讀取plist文件,獲取模型數(shù)組

fileprivate func getHeroData() -> [HeroModel]{
    // 1.獲取路徑
    let path = Bundle.main.path(forResource: "heros.plist", ofType: nil)!
        
    // 2.讀取文件內(nèi)容
    let dictArray = NSArray(contentsOfFile: path) as! [[String : Any]]
        
    // 3.遍歷所有的字典并且轉(zhuǎn)成模型對(duì)象
    return dictArray.map({ HeroModel(dict: $0) }).reversed()
}

1-2. seachBar

    lazy var heroVariable: Variable<[HeroModel]> = {
        return Variable(self.getHeroData())
    }()
    
    var searchText: Observable<String>
    init(searchText: Observable<String>) {
        self.searchText = searchText
        
        self.searchText.subscribe(onNext: { (str: String) in
            let heros = self.getHeroData().filter({ (hero: HeroModel) -> Bool in
                //過(guò)濾
                if str.isEmpty { return true }
                //model是否包含搜索字符串
                return hero.name.contains(str)
            })
            self.heroVariable.value = heros
        }).addDisposableTo(bag)
    }

  • 其中heroVariable是一個(gè)數(shù)組模型的包裝箱,在controller內(nèi)調(diào)用使用前需要asObservable或者asDriver解包裝;詳細(xì)用法可參考:RxSwift的使用詳解01
  • searchText搜索框輸入的關(guān)鍵字,根據(jù)該關(guān)鍵字從數(shù)組中過(guò)濾出所有包含該關(guān)鍵字的model
  • 對(duì)heroVariable重新賦值,發(fā)出事件

1-3. RxTableViewController.swift主要代碼

1-3-1. searchBar搜索框,輸入字符后間隔0.5秒開始搜索

var searchText: Observable<String> {
    //輸入后間隔0.5秒搜索,在主線程運(yùn)行
    return searchBar.rx.text.orEmpty.throttle(0.5, scheduler: MainScheduler.instance)
}

1-3-2. UITableView的設(shè)置

    //2.給tableView綁定數(shù)據(jù)
    //注意: 三個(gè)參數(shù):row, model, cell三個(gè)順序不可以搞錯(cuò), 不需要的可省略 
    heroVM.heroVariable.asDriver().drive(rxTableView.rx.items(cellIdentifier: kCellID, cellType: RxTableViewCell.self)) { (_, hero, cell) in
        cell.heroModel = hero
    }.addDisposableTo(bag)
        
    // 3.監(jiān)聽UITableView的點(diǎn)擊
    rxTableView.rx.modelSelected(HeroModel.self).subscribe { (event: Event<HeroModel>) in
        print(event.element?.name ?? "")
    }.addDisposableTo(bag)

  • 將viewModel中的heroVariable進(jìn)行解包裝迎罗,如果是Driver序列,我們這里不使用bingTo句伶,而是使用的Driver劲蜻,用法和bingTo一模一樣。
  • Deriver的監(jiān)聽一定發(fā)生在主線程考余,所以很適合我們更新UI的操作
  • 如需設(shè)置delegate的代理
rxTableView.rx.setDelegate(self).addDisposableTo(bag)

然后在實(shí)現(xiàn)相應(yīng)的代理方法即可,如:

extension RxTableViewController: UITableViewDelegate{
    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        return 100
    }
}

六. UICollectionView+Moya+ObjectMapper網(wǎng)絡(luò)請(qǐng)求和數(shù)據(jù)處理

  • 與上述UITableView不同的是,這部分將以RxDataSources處理數(shù)據(jù)源
  • model數(shù)組以sections組集合處理
  • 結(jié)合Moya進(jìn)行網(wǎng)絡(luò)請(qǐng)求
  • 使用ObjectMapper進(jìn)行json數(shù)據(jù)轉(zhuǎn)模型

1. 配合ObjectMapper

這里再介紹一下ObjectMapper

class AnchorModel: Mappable {

    var name = ""    //名字
    var pic51 = ""   //頭像
    var pic74 = ""   //大圖
    var live = 0
    var push = 0
    var focus = 0    //關(guān)注量
    
    required init?(map: Map) {
        
    }
    
    func mapping(map: Map) {
        name  <- map["name"]
        pic51 <- map["pic51"]
        pic74 <- map["pic74"]
        live  <- map["live"]
        push  <- map["push"]
        focus <- map["focus"]
    }
}

  • 使用 ObjectMapper 先嬉,需要讓自己的 Model 類使用 Mappable 協(xié)議,這個(gè)協(xié)議包括兩個(gè)方法:
required init?(map: Map) {}
 
func mapping(map: Map) {}
  • 在 mapping 方法中楚堤,用 <- 操作符來(lái)處理和映射你的 JSON數(shù)據(jù)
  • 詳細(xì)的 ObjectMapper 教程可以查看它的 Github 主頁(yè)疫蔓,我在這里只做簡(jiǎn)單的介紹乡话。

2. Moya的使用

  • Moya是基于Alamofire的網(wǎng)絡(luò)請(qǐng)求庫(kù)抽莱,這里我使用了Moya/Swift,它在Moya的基礎(chǔ)上添加了對(duì)RxSwift的接口支持船老。
  • Github上的官方介紹羅列了Moya的一些特點(diǎn):
    • 編譯時(shí)檢查正確的API端點(diǎn)訪問.
    • 使你定義不同端點(diǎn)枚舉值對(duì)應(yīng)相應(yīng)的用途更加明晰.
    • 提高測(cè)試地位從而使單元測(cè)試更加容易.
  • 接下來(lái)我們來(lái)說(shuō)下Moya的使用

2-1. 創(chuàng)建一個(gè)枚舉API

//請(qǐng)求枚舉類型
enum JunNetworkTool {
    
    case getNewList
    case getHomeList(page: Int)
}

2-2. 為枚舉添加擴(kuò)展

  • 需遵循協(xié)議 TargetType
  • 這個(gè)協(xié)議的Moya這個(gè)庫(kù)規(guī)定的協(xié)議酥筝,可以單擊進(jìn)入相應(yīng)的文件進(jìn)行查看
  • 這個(gè)協(xié)議內(nèi)的每一個(gè)參數(shù)(除了validate可不重寫)都必須重寫,否則會(huì)報(bào)錯(cuò)
//請(qǐng)求參數(shù)
extension JunNetworkTool: TargetType {
    
    //統(tǒng)一基本的url
    var baseURL: URL {
        return (URL(string: "http://qf.56.com/home/v4/moreAnchor.ios"))!
    }
    
    //path字段會(huì)追加至baseURL后面
    var path: String {
        return ""
    }
    
    //請(qǐng)求的方式
    var method: Moya.Method {
        return .get
    }
    
    //參數(shù)編碼方式(這里使用URL的默認(rèn)方式)
    var parameterEncoding: ParameterEncoding {
        return URLEncoding.default
    }
    
    //用于單元測(cè)試
    var sampleData: Data {
        return "getList".data(using: .utf8)!
    }
    
    //將要被執(zhí)行的任務(wù)(請(qǐng)求:request 下載:upload 上傳:download)
    var task: Task {
        return .request
    }
    
    //請(qǐng)求參數(shù)(會(huì)在請(qǐng)求時(shí)進(jìn)行編碼)
    var parameters: [String: Any]? {
        switch self {
        case .getHomeList(let index):
            return ["index": index]
        default:
            return ["index": 1]
        }
    }
    
    //是否執(zhí)行Alamofire驗(yàn)證滚躯,默認(rèn)值為false
    var validate: Bool {
        return false
    }
}

2-3. 定義一個(gè)全局變量用于整個(gè)項(xiàng)目的網(wǎng)絡(luò)請(qǐng)求

let junNetworkTool = RxMoyaProvider<JunNetworkTool>()

至此,我們就可以使用這個(gè)全局變量來(lái)請(qǐng)求數(shù)據(jù)了

3. RxDataSources

  • RxDataSources是以section來(lái)做為數(shù)據(jù)結(jié)構(gòu)來(lái)傳輸嘿歌,這點(diǎn)很重要掸掏,比如:在傳統(tǒng)的數(shù)據(jù)源實(shí)現(xiàn)的方法中有一個(gè)numberOfSection,我們?cè)诤芏嗲闆r下只需要一個(gè)section宙帝,所以這個(gè)方法可實(shí)現(xiàn)丧凤,也可以不實(shí)現(xiàn),默認(rèn)返回的就是1步脓,這給我們帶來(lái)的一個(gè)迷惑點(diǎn):【tableView是由row來(lái)組成的】愿待,不知道在坐的各位中有沒有是這么想的呢浩螺??有的話那從今天開始就要認(rèn)清楚這一點(diǎn)呼盆,【tableView其實(shí)是由section組成的】,所以在使用RxDataSources的過(guò)程中蚁廓,即使你的setion只有一個(gè)访圃,那你也得返回一個(gè)section的數(shù)組出去!O嗲丁腿时!
  • 傳統(tǒng)方式適用于簡(jiǎn)單的數(shù)據(jù)集,但不處理需要將復(fù)雜數(shù)據(jù)集與多個(gè)部分進(jìn)行綁定的情況饭宾,或者在添加/修改/刪除項(xiàng)目時(shí)需要執(zhí)行動(dòng)畫時(shí)批糟。而使用RxDataSources時(shí),它很容易寫
  • 想了解更多關(guān)于RxDataSources的用法,請(qǐng)參考其GitHub主頁(yè)

3-1. Sections自定義

  • 在我們自定義的Model中創(chuàng)建一個(gè)AnchorSection的結(jié)構(gòu)體
  • 并遵循SectionModelType協(xié)議看铆,實(shí)現(xiàn)相應(yīng)的協(xié)議方法
//MARK: SectionModel
struct AnchorSection {
    // items就是rows
    var items: [Item]
    
    // 你也可以這里加你需要的東西徽鼎,比如 headerView 的 title
}

extension AnchorSection: SectionModelType {
    // 重定義 Item 的類型為
    typealias Item = AnchorModel
    init(original: AnchorSection, items: [AnchorSection.Item]) {
        self = original
        self.items = items
    }
}

4. ViewModel

4-1. 自定義協(xié)議BaseViewModel

我們知道MVVM思想就是將原本在ViewController的視圖顯示邏輯、驗(yàn)證邏輯弹惦、網(wǎng)絡(luò)請(qǐng)求等代碼存放于ViewModel中否淤,讓我們的ViewController瘦身。這些邏輯由ViewModel負(fù)責(zé)棠隐,外界不需要關(guān)心石抡,外界只需要結(jié)果,ViewModel也只需要將結(jié)果給到外界助泽,基于此啰扛,我們定義了一個(gè)協(xié)議

protocol JunViewModelType {
    //associatedtype: 關(guān)聯(lián)類型為協(xié)議中的某個(gè)類型提供了一個(gè)占位名(或者說(shuō)別名),其代表的實(shí)際類型在協(xié)議被采納時(shí)才會(huì)被指定
    associatedtype Input
    associatedtype Output
    
    //我們通過(guò) transform 方法將input攜帶的數(shù)據(jù)進(jìn)行處理嗡贺,生成了一個(gè)Output
    func transform(input: Input) -> Output
}

4-2. 自定義用于網(wǎng)絡(luò)請(qǐng)求的刷新狀態(tài)

  • 根據(jù)枚舉值的判斷,改變collection的刷新狀態(tài)
//刷新的狀態(tài)
enum JunRefreshStatus {
    case none
    case beingHeaderRefresh
    case endHeaderRefresh
    case beingFooterRefresh
    case endFooterRefresh
    case noMoreData
}

4-3. 自定義用于繼承的BaseViewModel

  • 定義請(qǐng)求數(shù)據(jù)的頁(yè)數(shù)index
  • 定義input和output的結(jié)構(gòu)體
class BaseViewModel: NSObject {
    // 記錄當(dāng)前的索引值
    var index: Int = 1
    
    struct JunInput {
        // 網(wǎng)絡(luò)請(qǐng)求類型
        let category: JunNetworkTool
        
        init(category: JunNetworkTool) {
            self.category = category
        }
    }
    
    struct JunOutput {
        // tableView的sections數(shù)據(jù)
        let sections: Driver<[AnchorSection]>
        // 外界通過(guò)該屬性告訴viewModel加載數(shù)據(jù)(傳入的值是為了標(biāo)志是否重新加載)
        let requestCommond = PublishSubject<Bool>()
        // 告訴外界的tableView當(dāng)前的刷新狀態(tài)
        let refreshStatus = Variable<JunRefreshStatus>(.none)
        
        //初始化時(shí),section的數(shù)據(jù)
        init(sections: Driver<[AnchorSection]>) {
            self.sections = sections
        }
    }
}

4-4. 自定義AnchorViewModel

    1. 繼承BaseViewModel
class AnchorViewModel : BaseViewModel{
    // 存放著解析完成的模型數(shù)組
    let anchorArr = Variable<[AnchorModel]>([])

}
    1. 遵循JunViewModelType協(xié)議
extension AnchorViewModel: JunViewModelType {
    typealias Input = JunInput
    typealias Output = JunOutput

    func transform(input: AnchorViewModel.JunInput) -> AnchorViewModel.JunOutput {
        let sectionArr = anchorArr.asDriver().map { (models) -> [AnchorSection] in
            // 當(dāng)models的值被改變時(shí)會(huì)調(diào)用
            return [AnchorSection(items: models)]
        }.asDriver(onErrorJustReturn: [])
        
        let output = JunOutput(sections: sectionArr)
        
        output.requestCommond.subscribe(onNext: { (isReloadData) in
            self.index = isReloadData ? 1 : self.index + 1
            //開始請(qǐng)求數(shù)據(jù)
            junNetworkTool.request(JunNetworkTool.getHomeList(page: self.index))
                .mapObjectArray(AnchorModel.self)
                .subscribe({ (event) in
                    switch event {
                    case let .next(modelArr):
                        self.anchorArr.value = isReloadData ? modelArr : (self.anchorArr.value) + modelArr
                        SVProgressHUD.showSuccess(withStatus: "加載成功")
                    case let .error(error):
                        SVProgressHUD.showError(withStatus: error.localizedDescription)
                    case .completed:
                        output.refreshStatus.value = isReloadData ? .endHeaderRefresh : .endFooterRefresh
                    }
            }).addDisposableTo(bag)
        }).addDisposableTo(bag)
        
        return output
    }
}
  • sectionArr是將model數(shù)組按照section分別存儲(chǔ)
  • 當(dāng)請(qǐng)求回來(lái)的anchorArr數(shù)據(jù)改變的時(shí)候, sectionArr隨之會(huì)發(fā)生改變
  • isReloadData用于區(qū)分是下拉刷新(true時(shí)), 還是上拉加載更多(false時(shí))

5. RxCollectionViewController控制器中

  • 創(chuàng)建數(shù)據(jù)源RxDataSources
  • 綁定cell
  • 初始化input和output請(qǐng)求
  • 綁定section數(shù)據(jù)
  • 設(shè)置刷新

5-1. 創(chuàng)建數(shù)據(jù)源RxDataSources

// 創(chuàng)建一個(gè)數(shù)據(jù)源屬性隐解,類型為自定義的Section類型
let dataSource = RxCollectionViewSectionedReloadDataSource<AnchorSection>()

5-2. 綁定cell(自定義的cell要提前注冊(cè))

dataSource.configureCell = { dataSource, collectionView, indexPath, item in
    let cell = collectionView.dequeueReusableCell(withReuseIdentifier: kCollecCellID, for: indexPath) as! RxCollectionViewCell
    cell.anchorModel = item
    return cell
}
  • 以上四個(gè)參數(shù)的順序分別為:dataSource, collectionView(或者tableView), indexPath, model, 其對(duì)應(yīng)類型不言而喻,不多做介紹

5-3. 初始化input和output請(qǐng)求

let vmInput = AnchorViewModel.JunInput(category: .getNewList)
let vmOutput = anchorVM.transform(input: vmInput)

5-4. 綁定section數(shù)據(jù)

//4-1. 通過(guò)dataSource和section的model數(shù)組綁定數(shù)據(jù)(demo的用法, 推薦)
vmOutput.sections
    .asDriver()
    .drive(collectionVIew.rx.items(dataSource: dataSource))
    .addDisposableTo(bag)

5-5. 設(shè)置刷新

5-5-0. 在controller中初始化刷新狀態(tài)

collectionVIew.mj_header = MJRefreshNormalHeader(refreshingBlock: {
    vmOutput.requestCommond.onNext(true)
})
collectionVIew.mj_header.beginRefreshing()
        
collectionVIew.mj_footer = MJRefreshAutoNormalFooter(refreshingBlock: {
    vmOutput.requestCommond.onNext(false)
})

5-5-1. 添加刷新的序列

  • 在JunOutput的結(jié)構(gòu)體中添加刷新序列
  • 我們?cè)谶M(jìn)行網(wǎng)絡(luò)請(qǐng)求并得到結(jié)果之后,修改refreshStatus的value為相應(yīng)的JunRefreshStatus項(xiàng)
  • MJRefre遍會(huì)根據(jù)該狀態(tài)做出相應(yīng)的刷新事件
  • 默認(rèn)狀態(tài)為none
// 告訴外界的tableView當(dāng)前的刷新狀態(tài)
let refreshStatus = Variable<JunRefreshStatus>(.none)

5-5-2. 外界訂閱output的refreshStatus

  • 外界訂閱output的refreshStatus诫睬,并且根據(jù)接收到的值進(jìn)行相應(yīng)的操作
  • refreshStatus每次改變都會(huì)觸發(fā)刷新事件
//5. 設(shè)置刷新狀態(tài)
vmOutput.refreshStatus.asObservable().subscribe(onNext: { (status) in
    switch status {
    case .beingHeaderRefresh:
        self.collectionVIew.mj_header.beginRefreshing()
    case .endHeaderRefresh:
        self.collectionVIew.mj_header.endRefreshing()
    case .beingFooterRefresh:
        self.collectionVIew.mj_footer.beginRefreshing()
    case .endFooterRefresh:
        self.collectionVIew.mj_footer.endRefreshing()
    case .noMoreData:                   
        self.collectionVIew.mj_footer.endRefreshingWithNoMoreData()
    default:
        break
    }
}).addDisposableTo(bag)

5-5-3. output提供一個(gè)requestCommond用于控制是否請(qǐng)求數(shù)據(jù)

  • PublishSubject 的特點(diǎn):即可以作為Observable厢漩,也可以作為Observer,說(shuō)白了就是可以發(fā)送信號(hào)岩臣,也可以訂閱信號(hào)
  • 當(dāng)你訂閱PublishSubject的時(shí)候溜嗜,你只能接收到訂閱他之后發(fā)生的事件。subject.onNext()發(fā)出onNext事件架谎,對(duì)應(yīng)的還有onError()和onCompleted()事件
// 外界通過(guò)該屬性告訴viewModel加載數(shù)據(jù)(傳入的值是為了標(biāo)志是否重新加載)
let requestCommond = PublishSubject<Bool>()

七. 總結(jié)

  • 為了研究RxSwift相關(guān)知識(shí), 工作之余的時(shí)間,差不多一個(gè)月了
  • 學(xué)習(xí)的瓶頸大部分在于網(wǎng)絡(luò)請(qǐng)求和配合刷新這一模塊
  • 文中如出現(xiàn)self循環(huán)引用的問題,還望大神多多指正
  • 小編目前也還在初學(xué)階段,文中如出現(xiàn)小錯(cuò)誤還望多多指正,如有更好的方法,也希望不吝分享
  • 如果喜歡,可以收藏,也可以在Github上star一下

最后再一次附上Demo地址

參考文獻(xiàn):

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末炸宵,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子谷扣,更是在濱河造成了極大的恐慌土全,老刑警劉巖捎琐,帶你破解...
    沈念sama閱讀 216,496評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異裹匙,居然都是意外死亡瑞凑,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門概页,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)籽御,“玉大人,你說(shuō)我怎么就攤上這事惰匙〖继停” “怎么了?”我有些...
    開封第一講書人閱讀 162,632評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵项鬼,是天一觀的道長(zhǎng)哑梳。 經(jīng)常有香客問我,道長(zhǎng)绘盟,這世上最難降的妖魔是什么鸠真? 我笑而不...
    開封第一講書人閱讀 58,180評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮龄毡,結(jié)果婚禮上弧哎,老公的妹妹穿的比我還像新娘。我一直安慰自己稚虎,他們只是感情好撤嫩,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,198評(píng)論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著蠢终,像睡著了一般序攘。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上寻拂,一...
    開封第一講書人閱讀 51,165評(píng)論 1 299
  • 那天程奠,我揣著相機(jī)與錄音,去河邊找鬼祭钉。 笑死瞄沙,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的慌核。 我是一名探鬼主播距境,決...
    沈念sama閱讀 40,052評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼垮卓!你這毒婦竟也來(lái)了垫桂?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,910評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤粟按,失蹤者是張志新(化名)和其女友劉穎诬滩,沒想到半個(gè)月后霹粥,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,324評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡疼鸟,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,542評(píng)論 2 332
  • 正文 我和宋清朗相戀三年后控,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片空镜。...
    茶點(diǎn)故事閱讀 39,711評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡浩淘,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出姑裂,到底是詐尸還是另有隱情馋袜,我是刑警寧澤男旗,帶...
    沈念sama閱讀 35,424評(píng)論 5 343
  • 正文 年R本政府宣布舶斧,位于F島的核電站,受9級(jí)特大地震影響察皇,放射性物質(zhì)發(fā)生泄漏茴厉。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,017評(píng)論 3 326
  • 文/蒙蒙 一什荣、第九天 我趴在偏房一處隱蔽的房頂上張望矾缓。 院中可真熱鬧,春花似錦稻爬、人聲如沸嗜闻。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,668評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)琉雳。三九已至,卻和暖如春友瘤,著一層夾襖步出監(jiān)牢的瞬間翠肘,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,823評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工辫秧, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留束倍,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,722評(píng)論 2 368
  • 正文 我出身青樓盟戏,卻偏偏與公主長(zhǎng)得像绪妹,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子柿究,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,611評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容