RxSwift学习之六(Rxswift对比swift,oc用法)

2023-09-25 6 0

RxSwift学习之六(Rxswift对比swift,oc用法)

  • Rxswift 常用的数据处理
    • Target Action
    • 代理
    • 通知
    • 闭包回调
    • KVO
    • 手势
    • 网路请求
    • 定时器
    • 多个任务之间有依赖关系
    • 等待多个并发任务完成后处理结果
    • 数据绑定
  • Rxswift UI 用法
    • UILabel
    • UIButton
    • UIBarButtonItem
    • UISwitch
    • UISegmentedControl
    • UIActivityIndicatorView
    • UITextField
    • UITextView
    • UITableView
    • UICollectionView
    • UIPickerView
    • UIDatePicker
    • UISlider
    • UIStepper

Rxswift 常用的数据处理

Target Action

实例1

  • 传统代码
button.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside)func buttonTapped() {print("button Tapped")
}
  • Rxswift代码
button.rx.tap.subscribe(onNext: {print("button Tapped")}).disposed(by: disposeBag)

你不需要使用 Target Action,这样使得代码逻辑清晰可见。

代理

实例2

  • 传统代码
class ViewController: UIViewController {...override func viewDidLoad() {super.viewDidLoad()scrollView.delegate = self}
}extension ViewController: UIScrollViewDelegate {func scrollViewDidScroll(_ scrollView: UIScrollView) {print("contentOffset: \(scrollView.contentOffset)")}
}
  • Rxswift代码
class ViewController: UIViewController {...override func viewDidLoad() {super.viewDidLoad()scrollView.rx.contentOffset.subscribe(onNext: { contentOffset inprint("contentOffset: \(contentOffset)")}).disposed(by: disposeBag)}
}

Rxswift实现的代理,你不需要书写代理的配置代码,就能获得想要的结果。

通知

实例3

  • 传统代码
var ntfObserver: NSObjectProtocol!override func viewDidLoad() {super.viewDidLoad()ntfObserver = NotificationCenter.default.addObserver(forName: .UIApplicationWillEnterForeground,object: nil, queue: nil) { (notification) inprint("Application Will Enter Foreground")}
}deinit {NotificationCenter.default.removeObserver(ntfObserver)
}
  • Rxswift代码
NotificationCenter.default.rx.notification(UIResponder.keyboardWillShowNotification).subscribe(onNext: { (noti) inprint(noti)}).disposed(by: disposeBag)

闭包回调

实例4

  • 传统代码
URLSession.shared.dataTask(with: URLRequest(url: url)) {(data, response, error) inguard error == nil else {print("Data Task Error: \(error!)")return}guard let data = data else {print("Data Task Error: unknown")return}print("Data Task Success with count: \(data.count)")
}.resume()
  • Rxswift代码
URLSession.shared.rx.data(request: URLRequest(url: url)).subscribe(onNext: { data inprint("Data Task Success with count: \(data.count)")}, onError: { error inprint("Data Task Error: \(error)")}).disposed(by: disposeBag)

KVO

实例5

  • 传统代码

  • Rxswift代码
//监听person对象的name的变化self.person.rx.observeWeakly(String.self, "name").subscribe(onNext: { (value) inprint(value as Any)}).disposed(by: disposeBag)

手势

实例6

  • 传统代码

  • Rxswift代码
 let disposeBag = DisposeBag()let tap = UITapGestureRecognizer()self.label.addGestureRecognizer(tap)self.label.isUserInteractionEnabled = truetap.rx.event.subscribe(onNext: { (tap) inprint(tap.view)}).disposed(by: disposeBag)

网路请求

实例7

  • 传统代码

  • Rxswift代码
 let url = URL(string: "https://www.baidu.com")URLSession.shared.rx.response(request: URLRequest(url:  url!)).subscribe(onNext: { (response,data) inprint(response)}, onError: { (error) inprint(error)}, onCompleted: {}).disposed(by: disposeBag)

定时器

实例8

  • 传统代码

  • Rxswift代码
let disposeBag = DisposeBag()
var timer = Observable<Int>.interval(1, scheduler: MainScheduler.instance)timer.subscribe(onNext: { (num) inprint(num)}).disposed(by: disposeBag)

多个任务之间有依赖关系

实例9
例如,先通过用户名密码取得 Token 然后通过 Token 取得用户信息

  • 传统代码
/// 用回调的方式封装接口
enum API {/// 通过用户名密码取得一个 tokenstatic func token(username: String, password: String,success: (String) -> Void,failure: (Error) -> Void) { ... }/// 通过 token 取得用户信息static func userinfo(token: String,success: (UserInfo) -> Void,failure: (Error) -> Void) { ... }
}/// 通过用户名和密码获取用户信息
API.token(username: "beeth0ven", password: "987654321",success: { token inAPI.userInfo(token: token,success: { userInfo inprint("获取用户信息成功: \(userInfo)")},failure: { error inprint("获取用户信息失败: \(error)")})},failure: { error inprint("获取用户信息失败: \(error)")
})
  • Rxswift代码
/// 用 Rx 封装接口
enum API {/// 通过用户名密码取得一个 tokenstatic func token(username: String, password: String) -> Observable<String> { ... }/// 通过 token 取得用户信息static func userInfo(token: String) -> Observable<UserInfo> { ... }
}/// 通过用户名和密码获取用户信息
API.token(username: "beeth0ven", password: "987654321").flatMapLatest(API.userInfo).subscribe(onNext: { userInfo inprint("获取用户信息成功: \(userInfo)")}, onError: { error inprint("获取用户信息失败: \(error)")}).disposed(by: disposeBag)

等待多个并发任务完成后处理结果

实例10
例如,需要将两个网络请求合并成一个

通过 Rx 来实现:

/// 用 Rx 封装接口
enum API {/// 取得老师的详细信息static func teacher(teacherId: Int) -> Observable<Teacher> { ... }/// 取得老师的评论static func teacherComments(teacherId: Int) -> Observable<[Comment]> { ... }
}/// 同时取得老师信息和老师评论
Observable.zip(API.teacher(teacherId: teacherId),API.teacherComments(teacherId: teacherId)).subscribe(onNext: { (teacher, comments) inprint("获取老师信息成功: \(teacher)")print("获取老师评论成功: \(comments.count) 条")}, onError: { error inprint("获取老师信息或评论失败: \(error)")}).disposed(by: disposeBag)

这样你可用寥寥几行代码来完成相当复杂的异步操作。

数据绑定

实例11

在 RxSwift 里有一个比较重要的概念就是数据绑定(订阅)。就是指将可监听序列绑定到观察者上:

我们对比一下这两段代码:

  1. 传统代码:

将一个单独的图片设置到imageView上

let image: UIImage = UIImage(named: ...)
imageView.image = image
  1. Rx代码:
let image: Observable<UIImage> = ...
image.bind(to: imageView.rx.image)

Rx代码:上面这段代码是将一个图片序列 “同步” 到imageView上。这个序列里面的图片可以是异步产生的。这里定义的 image 就是上图中蓝色部分(可监听序列),imageView.rx.image就是上图中橙色部分(观察者)。而这种 “同步机制” 就是数据绑定(订阅)。

Rxswift UI 用法

UILabel

实例30

  • 传统代码

  • Rxswift代码
  1. Rxswift简单使用UILabel
import UIKit
import RxSwift
import RxCocoaclass ViewController: UIViewController {let disposeBag = DisposeBag()override func viewDidLoad() {//创建文本标签let label = UILabel(frame:CGRect(x:20, y:40, width:300, height:100))self.view.addSubview(label)//创建一个计时器(每0.1秒发送一个索引数)let timer = Observable<Int>.interval(0.1, scheduler: MainScheduler.instance)//将已过去的时间格式化成想要的字符串,并绑定到label上timer.map{ String(format: "%0.2d:%0.2d.%0.1d",arguments: [($0 / 600) % 600, ($0 % 600 ) / 10, $0 % 10]) }.bind(to: label.rx.text).disposed(by: disposeBag)}
}
  1. UILabel富文本 :将数据绑定到 attributedText 属性上
import UIKit
import RxSwift
import RxCocoaclass ViewController: UIViewController {let disposeBag = DisposeBag()override func viewDidLoad() {//创建文本标签let label = UILabel(frame:CGRect(x:20, y:40, width:300, height:100))self.view.addSubview(label)//创建一个计时器(每0.1秒发送一个索引数)let timer = Observable<Int>.interval(0.1, scheduler: MainScheduler.instance)//将已过去的时间格式化成想要的字符串,并绑定到label上timer.map(formatTimeInterval).bind(to: label.rx.attributedText).disposed(by: disposeBag)}//将数字转成对应的富文本func formatTimeInterval(ms: NSInteger) -> NSMutableAttributedString {let string = String(format: "%0.2d:%0.2d.%0.1d",arguments: [(ms / 600) % 600, (ms % 600 ) / 10, ms % 10])//富文本设置let attributeString = NSMutableAttributedString(string: string)//从文本0开始6个字符字体HelveticaNeue-Bold,16号attributeString.addAttribute(NSAttributedStringKey.font,value: UIFont(name: "HelveticaNeue-Bold", size: 16)!,range: NSMakeRange(0, 5))//设置字体颜色attributeString.addAttribute(NSAttributedStringKey.foregroundColor,value: UIColor.white, range: NSMakeRange(0, 5))//设置文字背景颜色attributeString.addAttribute(NSAttributedStringKey.backgroundColor,value: UIColor.orange, range: NSMakeRange(0, 5))return attributeString}
}

UIButton

实例40

  • 传统代码
 self.button.addTarget(self, action:#selector(buttonTapped(sender:)), for: UIControlEvents.touchUpInside)@objc func buttonTapped(sender:UIButton?){}
  • Rxswift代码
 let disposeBag = DisposeBag()//由于tap事件里点击事件用的最多,所以RX默认的tap就是点击事件self.button.rx.tap.subscribe(onNext: { () inprint("点击来了")}).disposed(by: disposeBag)//RXSwift监听按钮除了点击外的事件:self.button.rx.controlEvent(.touchUpOutside).subscribe(onNext: { () in}).disposed(by: disposeBag)
import UIKit
import RxSwift
import RxCocoaclass ViewController: UIViewController {let disposeBag = DisposeBag()@IBOutlet weak var button: UIButton!override func viewDidLoad() {//按钮点击响应1button.rx.tap.subscribe(onNext: { [weak self] inself?.showMessage("按钮被点击")}).disposed(by: disposeBag)//按钮点击响应2button.rx.tap.bind { [weak self] inself?.showMessage("按钮被点击")}.disposed(by: disposeBag)//}//按钮标题(title)的绑定
func test1() {
//创建一个计时器(每1秒发送一个索引数)
let timer = Observable<Int>.interval(1, scheduler: MainScheduler.instance)//根据索引数拼接最新的标题,并绑定到button上
timer.map{"计数\($0)"}.bind(to: button.rx.title(for: .normal)).disposed(by: disposeBag)
}//按钮富文本标题(attributedTitle)的绑定
func test2() {//创建一个计时器(每1秒发送一个索引数)let timer = Observable<Int>.interval(1, scheduler: MainScheduler.instance)//将已过去的时间格式化成想要的字符串,并绑定到button上timer.map(formatTimeInterval).bind(to: button.rx.attributedTitle()).disposed(by: disposeBag)
}//按钮图标(image)的绑定
func test3() {
//创建一个计时器(每1秒发送一个索引数)
let timer = Observable<Int>.interval(1, scheduler: MainScheduler.instance)//根据索引数选择对应的按钮图标,并绑定到button上
timer.map({let name = $0%2 == 0 ? "back" : "forward"return UIImage(named: name)!
})
.bind(to: button.rx.image())
.disposed(by: disposeBag)
}//按钮背景图片(backgroundImage)的绑定
func test4() {
//创建一个计时器(每1秒发送一个索引数)
let timer = Observable<Int>.interval(1, scheduler: MainScheduler.instance)//根据索引数选择对应的按钮背景图,并绑定到button上
timer.map{ UIImage(named: "\($0%2)")! }.bind(to: button.rx.backgroundImage()).disposed(by: disposeBag)
}//将数字转成对应的富文本func formatTimeInterval(ms: NSInteger) -> NSMutableAttributedString {let string = String(format: "%0.2d:%0.2d.%0.1d",arguments: [(ms / 600) % 600, (ms % 600 ) / 10, ms % 10])//富文本设置let attributeString = NSMutableAttributedString(string: string)//从文本0开始6个字符字体HelveticaNeue-Bold,16号attributeString.addAttribute(NSAttributedStringKey.font,value: UIFont(name: "HelveticaNeue-Bold", size: 16)!,range: NSMakeRange(0, 5))//设置字体颜色attributeString.addAttribute(NSAttributedStringKey.foregroundColor,value: UIColor.white, range: NSMakeRange(0, 5))//设置文字背景颜色attributeString.addAttribute(NSAttributedStringKey.backgroundColor,value: UIColor.orange, range: NSMakeRange(0, 5))return attributeString}//显示消息提示框func showMessage(_ text: String) {let alertController = UIAlertController(title: text, message: nil, preferredStyle: .alert)let cancelAction = UIAlertAction(title: "确定", style: .cancel, handler: nil)alertController.addAction(cancelAction)self.present(alertController, animated: true, completion: nil)}
}

UIBarButtonItem

实例50

  • 传统代码

  • Rxswift代码

UISwitch

实例60

  • 传统代码

  • Rxswift代码
import UIKit
import RxSwift
import RxCocoaclass ViewController: UIViewController {//分段选择控件@IBOutlet weak var segmented: UISegmentedControl!//图片显示控件@IBOutlet weak var imageView: UIImageView!let disposeBag = DisposeBag()override func viewDidLoad() {//创建一个当前需要显示的图片的可观察序列let showImageObservable: Observable<UIImage> =segmented.rx.selectedSegmentIndex.asObservable().map {let images = ["js.png", "php.png", "react.png"]return UIImage(named: images[$0])!}//把需要显示的图片绑定到 imageView 上showImageObservable.bind(to: imageView.rx.image).disposed(by: disposeBag)}func test1() {
switch1.rx.isOn.asObservable().subscribe(onNext: {print("当前开关状态:\($0)")}).disposed(by: disposeBag)
}func test2() {
switch1.rx.isOn.bind(to: button1.rx.isEnabled).disposed(by: disposeBag)
}
}

UISegmentedControl

实例70

  • 传统代码

  • Rxswift代码
import UIKit
import RxSwift
import RxCocoaclass ViewController: UIViewController {//分段选择控件@IBOutlet weak var segmented: UISegmentedControl!//图片显示控件@IBOutlet weak var imageView: UIImageView!let disposeBag = DisposeBag()override func viewDidLoad() {//创建一个当前需要显示的图片的可观察序列let showImageObservable: Observable<UIImage> =segmented.rx.selectedSegmentIndex.asObservable().map {let images = ["js.png", "php.png", "react.png"]return UIImage(named: images[$0])!}//把需要显示的图片绑定到 imageView 上showImageObservable.bind(to: imageView.rx.image).disposed(by: disposeBag)}func test1() {
segmented.rx.selectedSegmentIndex.asObservable().subscribe(onNext: {print("当前项:\($0)")}).disposed(by: disposeBag)
}
}

UIActivityIndicatorView

实例80

  • 传统代码

  • Rxswift代码

UITextField

实例90:UITextField使用Rxswift的基本用法

  • 传统代码

  • Rxswift代码
self.textFiled.rx.text.orEmpty.subscribe(onNext: { (text) inprint(text)}).disposed(by: disposeBag)
// textfiled绑定Button的文字
self.textFiled.rx.text.bind(to: self.button.rx.title()).disposed(by: disposeBag)

实例91:Rxswift监听单个 textField 内容的变化

  • Rxswift代码
import UIKit
import RxSwift
import RxCocoaclass ViewController: UIViewController {let disposeBag = DisposeBag()override func viewDidLoad() {//创建文本输入框let textField = UITextField(frame: CGRect(x:10, y:80, width:200, height:30))textField.borderStyle = UITextBorderStyle.roundedRectself.view.addSubview(textField)//当文本框内容改变时,将内容输出到控制台上textField.rx.text.orEmpty.asObservable().subscribe(onNext: {print("您输入的是:\($0)")}).disposed(by: disposeBag)//当文本框内容改变时,将内容输出到控制台上
textField.rx.text.orEmpty.changed.subscribe(onNext: {print("您输入的是:\($0)")}).disposed(by: disposeBag)}
}

实例92:Rxswift将textField的内容绑定到其他控件上

  • Rxswift代码
import UIKit
import RxSwift
import RxCocoaclass ViewController: UIViewController {let disposeBag = DisposeBag()override func viewDidLoad() {//创建文本输入框let inputField = UITextField(frame: CGRect(x:10, y:80, width:200, height:30))inputField.borderStyle = UITextBorderStyle.roundedRectself.view.addSubview(inputField)//创建文本输出框let outputField = UITextField(frame: CGRect(x:10, y:150, width:200, height:30))outputField.borderStyle = UITextBorderStyle.roundedRectself.view.addSubview(outputField)//创建文本标签let label = UILabel(frame:CGRect(x:20, y:190, width:300, height:30))self.view.addSubview(label)//创建按钮let button:UIButton = UIButton(type:.system)button.frame = CGRect(x:20, y:230, width:40, height:30)button.setTitle("提交", for:.normal)self.view.addSubview(button)//当文本框内容改变let input = inputField.rx.text.orEmpty.asDriver() // 将普通序列转换为 Driver.throttle(0.3) //在主线程中操作,0.3秒内值若多次改变,取最后一次//内容绑定到另一个输入框中input.drive(outputField.rx.text).disposed(by: disposeBag)//内容绑定到文本标签中input.map{ "当前字数:\($0.count)" }.drive(label.rx.text).disposed(by: disposeBag)//根据内容字数决定按钮是否可用input.map{ $0.count > 5 }.drive(button.rx.isEnabled).disposed(by: disposeBag)}
}

实例93:Rxswift同时监听多个 textField 内容的变化

  • Rxswift代码
import UIKit
import RxSwift
import RxCocoaclass ViewController: UIViewController {let disposeBag = DisposeBag()@IBOutlet weak var textField1: UITextField!@IBOutlet weak var textField2: UITextField!@IBOutlet weak var label: UILabel!override func viewDidLoad() {Observable.combineLatest(textField1.rx.text.orEmpty, textField2.rx.text.orEmpty) {textValue1, textValue2 -> String inreturn "你输入的号码是:\(textValue1)-\(textValue2)"}.map { $0 }.bind(to: label.rx.text).disposed(by: disposeBag)}
}

实例94:Rxswift实现textField事件监听
通过 rx.controlEvent 可以监听输入框的各种事件,且多个事件状态可以自由组合。除了各种 UI 控件都有的 touch 事件外,输入框还有如下几个独有的事件:

  • editingDidBegin:开始编辑(开始输入内容)

  • editingChanged:输入内容发生改变

  • editingDidEnd:结束编辑

  • editingDidEndOnExit:按下 return 键结束编辑

  • allEditingEvents:包含前面的所有编辑相关事件

  • Rxswift代码

textField.rx.controlEvent([.editingDidBegin]) //状态可以组合.asObservable().subscribe(onNext: { _ inprint("开始编辑内容!")}).disposed(by: disposeBag)
import UIKit
import RxSwift
import RxCocoaclass ViewController: UIViewController {//用户名输入框@IBOutlet weak var username: UITextField!//密码输入框@IBOutlet weak var password: UITextField!let disposeBag = DisposeBag()override func viewDidLoad() {super.viewDidLoad()//在用户名输入框中按下 return 键username.rx.controlEvent(.editingDidEndOnExit).subscribe(onNext: {[weak self] (_) inself?.password.becomeFirstResponder()}).disposed(by: disposeBag)//在密码输入框中按下 return 键password.rx.controlEvent(.editingDidEndOnExit).subscribe(onNext: {[weak self] (_) inself?.password.resignFirstResponder()}).disposed(by: disposeBag)}
}

实例95:Rxswift实现textField事件监听

  • Rxswift代码

UITextView

实例100

  • 传统代码

  • Rxswift代码
import UIKit
import RxSwift
import RxCocoaclass ViewController: UIViewController {let disposeBag = DisposeBag()@IBOutlet weak var textView: UITextView!override func viewDidLoad() {//开始编辑响应textView.rx.didBeginEditing.subscribe(onNext: {print("开始编辑")}).disposed(by: disposeBag)//结束编辑响应textView.rx.didEndEditing.subscribe(onNext: {print("结束编辑")}).disposed(by: disposeBag)//内容发生变化响应textView.rx.didChange.subscribe(onNext: {print("内容发生改变")}).disposed(by: disposeBag)//选中部分变化响应textView.rx.didChangeSelection.subscribe(onNext: {print("选中部分发生变化")}).disposed(by: disposeBag)}
}

UITableView

  • 传统Swift使用UITableView
    实例110
//歌曲结构体
struct Music {let name: String //歌名let singer: String //演唱者init(name: String, singer: String) {self.name = nameself.singer = singer}
}//歌曲列表数据源
struct MusicListViewModel {let data = [Music(name: "无条件", singer: "陈奕迅"),Music(name: "你曾是少年", singer: "S.H.E"),Music(name: "从前的我", singer: "陈洁仪"),Music(name: "在木星", singer: "朴树"),]
}class ViewController: UIViewController {//tableView对象@IBOutlet weak var tableView: UITableView!//歌曲列表数据源let musicListViewModel = MusicListViewModel()override func viewDidLoad() {super.viewDidLoad()//设置代理tableView.dataSource = selftableView.delegate = self}
}extension ViewController: UITableViewDataSource {//返回单元格数量func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {return musicListViewModel.data.count}//返回对应的单元格func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath)-> UITableViewCell {let cell = tableView.dequeueReusableCell(withIdentifier: "musicCell")!let music = musicListViewModel.data[indexPath.row]cell.textLabel?.text = music.namecell.detailTextLabel?.text = music.singerreturn cell}
}extension ViewController: UITableViewDelegate {//单元格点击func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {print("你选中的歌曲信息【\(musicListViewModel.data[indexPath.row])】")}
}
  • Rxswift 的UITableView

实例111

/*这里我们将 data 属性变成一个可观察序列对象(Observable Squence),
而对象当中的内容和我们之前在数组当中所包含的内容是完全一样的。
*/
//歌曲列表数据源
struct MusicListViewModel {let data = Observable.just([Music(name: "无条件", singer: "陈奕迅"),Music(name: "你曾是少年", singer: "S.H.E"),Music(name: "从前的我", singer: "陈洁仪"),Music(name: "在木星", singer: "朴树"),])
}import UIKit
import RxSwift
import RxCocoaclass ViewController: UIViewController {//tableView对象@IBOutlet weak var tableView: UITableView!//歌曲列表数据源let musicListViewModel = MusicListViewModel()//负责对象销毁let disposeBag = DisposeBag()override func viewDidLoad() {super.viewDidLoad()//将数据源数据绑定到tableView上musicListViewModel.data.bind(to: /*rx.items(cellIdentifier:):这是 Rx 基于 cellForRowAt 数据源方法的一个封装。传统方式中我们还要有个 numberOfRowsInSection 方法,使用 Rx 后就不再需要了(Rx 已经帮我们完成了相关工作)。*/ tableView.rx.items(cellIdentifier:"musicCell")) { _, music, cell incell.textLabel?.text = music.namecell.detailTextLabel?.text = music.singer}.disposed(by: disposeBag)//tableView点击响应/*rx.modelSelected: 这是 Rx 基于 UITableView委托回调方法 didSelectRowAt 的一个封装。*/ tableView.rx.modelSelected(Music.self).subscribe(onNext: { music inprint("你选中的歌曲信息【\(music)】")}).disposed(by: disposeBag)/*DisposeBag:作用是 Rx 在视图控制器或者其持有者将要销毁的时候,自动释法掉绑定在它上面的资源。它是通过类似“订阅处置机制”方式实现(类似于 NotificationCenter 的 removeObserver)。*/}
}

UICollectionView

实例120

  • 传统代码

  • Rxswift代码

UIPickerView

实例130

  • 传统代码

  • Rxswift代码

UIDatePicker

实例140

  • 传统代码

  • Rxswift代码

UISlider

实例150

  • 传统代码

  • Rxswift代码

UIStepper

实例160

  • 传统代码

  • Rxswift代码

代码编程
赞赏

相关文章

LeetCode之Isomorphic Strings
LeetCode之Combination Sum III
LeetCode之Find Minimum in Rotated Sorted Array II
LeetCode之Majority Element II
LeetCode之Product of Array Except Self
LeetCodeConvert Sorted List to Binary Search Tree