前言
RxSwift 做了什么?
RxSwift
把我们程序中每一个 操作
都看成一个 事件
比如:一个 TextField
文本的改变,一个 Button
按钮被点击,一个 网络请求结束
等,每一个 事件源
都可以看成一个 管道
,也就是 sequence
比如:TextField
,当我们改变文本内容时,这个 TextField
就会不断的发出事件,从这个 sequence
中不断流出,我们只需要监听这个 sequence
,每流出一个 事件
就做相应的处理。
同理,Button
也是一个 sequence
,每点击一次就流出一个事件
RxSwift 的核心思想是 Observable
RxSwift Observable 有三种信号
新的信号到来:Next
信号发生错误,序列不会再产生信号:Error
序列发送信号完成,不会再产生新的信号:Completed
RxSwift Observable 取消监听
在有限的时间自动结束(Completed/Error),比如:一个网络请求作为一个序列,当网络请求完成时,Observable
自动结束,资源被释放
信号不会自己结束,比如:Timer,每隔一段时间发送一个新的信号,这时需要手动取消监听,来释放相应的资源
在比如:一个 label.rx.text
是一个 Observable
,通常需要调用 addDisposableTo(disposeBag)
来让其 deinit
,也就是所有者要释放的时候,自动取消监听
当然,除了手动释放,RxSwift
也提供了一些操作符,比如:takeUntil
来根据条件取消
RxSwift 引入 Podfile文件中添加如下,并 pod update
1 2 3 # 响应式编程库 pod 'RxSwift' pod 'RxCocoa'
RxSwift 简单使用
1 2 import RxSwift import RxCocoa
其次,创建 deinit
属性,也就是 所有者要释放的时候,自动取消监听
注: fileprivate 关键字:只能在当前文件下使用 private 关键字:只能在当前类中使用,类扩展中也可以使用 lazy 延迟存储:在第一次使用的时候才会初始化
1 2 // 创建deinit属性,在观察者被释放的时候,自动取消监听 fileprivate lazy var bag = DisposeBag()
RxSwift
监听按钮的点击
1 2 3 4 5 // 传统方式 let btn = UIButton(type: UIButton.ButtonType.custom) btn.backgroundColor = UIColor.blue btn.frame = CGRect(x: 100, y: 100, width: 100, height: 50) self.view.addSubview(btn)
1 2 3 4 5 6 7 8 btn.addTarget(self, action: #selector(btnClick(btn:)), for: UIControl.Event.touchUpInside) 🔽 @objc func btnClick(btn: UIButton) { print("clickBtn") // 打印结果:clickBtn }
1 2 3 4 5 // RxSwift btn.rx.tap.subscribe { event in print("RxSwift btnClick") // 打印结果:RxSwift btnClick }.disposed(by: bag)
RxSwift
监听 UITextField
输入框文字变化
1 2 3 4 5 let tf = UITextField(frame: CGRect(x: 100, y: 200, width: 100, height: 50)) // 设置代理 tf.delegate = self tf.backgroundColor = UIColor.blue self.view.addSubview(tf)
传统方式:设置代理 tf.delegate = self
,遵循协议 UITextFieldDelegate
1 2 3 4 5 6 func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { // 打印文字变化结果 print(textField.text as Any) // 结果:Optional(Optional("好👌?")) return true }
1 2 3 4 5 6 7 8 9 // RxSwift tf.rx.text.subscribe { (event: Event<String?>) in // 将UITextField文字改变的内容打印 print(event.element as Any) // 打印结果:Optional(Optional("好👌?")) }.disposed(by: bag) // 可以将当前文本绑定到label上 // tf.rx.text.bind(to: lb.rx.text).disposed(by: bag)
RxSwift
监听 UILabel
文字/frame等属性改变
1 2 3 4 5 let label = UILabel(frame: CGRect(x: 100, y: 300, width: 100, height: 50)) label.text = "my name is zj" label.textColor = UIColor.red label.backgroundColor = UIColor.blue self.view.addSubview(label)
1 2 3 4 5 6 7 8 9 10 11 // RxSwift // 监听text label.rx.observe(String.self, "text").subscribe { (str: String?) in print(str as Any) // 打印结果:Optional("my name is zj") }.disposed(by: bag) // 监听frame label.rx.observe(CGRect.self, "frame").subscribe { (rect: CGRect?) in print(rect!) // 打印结果:(100.0, 300.0, 100.0, 50.0) }.disposed(by: bag)
通知
1 2 3 // 发送额外的数据 let info = ["name":"zj","age":32] as [String:Any] NotificationCenter.default.post(name: NSNotification.Name(rawValue: "ComplexNotification"), object: nil, userInfo: info)
1 2 3 4 5 6 7 8 // 接收通知 NotificationCenter.default.addObserver(self, selector: #selector(handleNotification(noti:)), name: Notification.Name(rawValue: "ComplexNotification"), object: nil) // 接收到通知后的回调 @objc func handleNotification(noti: Notification) { print(noti.userInfo as Any) // 打印结果:Optional([AnyHashable("age"): 32, AnyHashable("name"): "zj"]) }
1 2 3 4 // RxSwift NotificationCenter.default.rx.notification(Notification.Name(rawValue: "ComplexNotification")).subscribe { (noti: Notification) in print(noti.userInfo as Any) // 打印结果:Optional([AnyHashable("age"): 32, AnyHashable("name"): "zj"]) }.disposed(by: bag)
监听滚动偏移
1 2 3 scrollView.rx.contentOffset.subscribe { point in print("滚动偏移:\(point.element!)") }.disposed(by: bag)
闭包回调
1 2 let url = URL(string: "https://www.qctt.cn/home") let urlRequest = URLRequest.init(url: url!)
1 2 3 4 5 6 7 8 9 10 11 12 // 传统写法 URLSession.shared.dataTask(with: urlRequest) { data, response, error in guard error != nil else { print("Data Task Error: \(String(describing: error))") return } guard let data = data else { print("Data Task Error: unkonwn") return } print("Data Task Success with count: \(data.count)") }.resume()
1 2 3 4 5 6 7 // RxSwift URLSession.shared.rx.response(request: urlRequest) .subscribe { response,data in print("Data Task Success with count: \(data) \n \(response)") } onError: { error in print("Data Task Error: \(String(describing: error))") }.disposed(by: bag)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 curl -X GET "https://www.qctt.cn/home" -i -v Success (1177ms): Status 200 Data Task Success with count: 115533 bytes <NSHTTPURLResponse: 0x283380800> { URL: https://www.qctt.cn/home } { Status Code: 200, Headers { "Access-Control-Allow-Origin" = ( "*" ); "Cache-Control" = ( "no-store, no-cache, must-revalidate", "no-cache" ); "Content-Encoding" = ( gzip ); "Content-Type" = ( "text/html; charset=UTF-8" ); Date = ( "Mon, 27 Mar 2023 04:32:01 GMT" ); Expires = ( "Thu, 19 Nov 1981 08:52:00 GMT" ); Pragma = ( "no-cache" ); Server = ( nginx ); "Set-Cookie" = ( "PHPSESSID=533eb488d609128045ec4606bd6ed24e; path=/", "XSRF-TOKEN=eyJpdiI6IjlFYmNraHFcL29lUk1aRTFMNVlsQkJBPT0iLCJ2YWx1ZSI6Ikh1TWlEalwvMHpTeU5tVWM5d0U1SHUyb1wvdExmNWFZdEJNbUVZT1NHUFhOYWdza2VFSUJreDlhTXdsTHM0anFpZGpteXlIMXVtZ1BCT3RBdktxaGE4QkE9PSIsIm1hYyI6ImZhYTQxODk3ZGZmZjBlYWE1MmJkYzUzZjc1NWM2ZWQ2N2IyZTUwODZiYmFmNzY5YjhkNzY5YWE3MmYwYjliNjkifQ%3D%3D; expires=Mon, 27-Mar-2023 06:32:01 GMT; Max-Age=7200; path=/", "laravel_session=eyJpdiI6ImJnc3ZTNTNlM05RTVRRa0x5b1k0TUE9PSIsInZhbHVlIjoiQ2JOeHdlVDNSb3JvWnBcL0xNVG1PODFVNmgzQUNzUXg0KzhsdTB2bnpCOEpqb0hNVktnaTFyc1JhVHJNMFh1QzEyQmRVcXdSdG9mbnZrNHhyVEVcL3ZyUT09IiwibWFjIjoiNjM0MzMxMTk5NWI1N2U2YWJhMDJjNmY2MzMyNjRlMzc4YmUzODY1NzVhMTUzZWQzNDZlNDI2NWRhOTQ0YjkzMCJ9; expires=Mon, 27-Mar-2023 06:32:01 GMT; Max-Age=7200; path=/; HttpOnly" ); Vary = ( "Accept-Encoding" ); "access-control-allow-credentials" = ( true ); "access-control-allow-headers" = ( "x-requested-with,Authorization" ); "access-control-allow-methods" = ( "*" ); } }
多个任务之间有依赖关系
例如:先通过用户名和密码获取 Token
,然后通过 Token
获取用户信息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 class UserInfo { var name = "" var age = 0 var sex = 0 } enum API { // 通过用户名密码获取一个接口 static func token(username: String, password: String, success: (String) -> Void, failure: (Error) -> Void) { success("获取token成功") } // 通过 token 获取 用户信息 static func userInfo(token: String, success: (String) -> Void, failure: (Error) -> Void) { success("获取userInfo成功") } } 🔽 API.token(username: "zj", password: "pass") { token in print(token) API.userInfo(token: token) { userInfo in print(userInfo) } failure: { error in print("获取userInfo失败: \(error)") } } failure: { error in print("获取token失败: \(error)") }
RxSwift 监听 系统方法
1 2 3 4 // 视图将要出现self.vc?.rx.sentMessage(#selector(HomeVC.viewWillAppear(_:))).subscribe(onNext: { value in // 手动触发下拉刷新 self.tableV.startHeaderRefreshing(animated: true) }).disposed(by: bag)