Snail 0.12.0

Snail 0.12.0

Sujit PoudelCompass Mobile TeamWes BillmanRussell Stephens 维护。



Snail 0.12.0

  • 作者:
  • Compass

🐌snail Carthage 兼容 Cocoapods codecov.io SwiftPM 兼容

SNAIL

一个轻量级的观察者框架,也提供 Kotlin

安装

Carthage

您可以使用以下命令通过 Homebrew 安装 Carthage

brew update
brew install carthage

要使用 Carthage 将 Snail 集成到您的 Xcode 项目中,请在您的 Cartfile 中指定它,其中 "x.x.x" 是当前版本。

github "UrbanCompass/Snail" "x.x.x"

Swift 包管理器

使用 Swift 包管理器 安装,请确保您的 Swift 包已设置好,并将 Snail 添加为依赖项到您的 Package.swift 文件中。

dependencies: [
    .Package(url: "https://github.com/UrbanCompass/Snail.git", majorVersion: 0)
]

手动

Snail/Snail 目录下的所有文件添加到您的项目中。

本地开发

  1. 运行设置脚本以安装必要的依赖项 ./scripts/setup.sh

创建可观察对象

let observable = Observable<thing>()

Disposer

Disposer 负责移除所有订阅。Disposer 通常位于订阅发生最集中的地方(例如:在 MVVM 架构中的 UIViewController)。由于大多数订阅都绑定到不同的可观察对象,并且这些可观察对象都关联到类型,因此所有将要被清理的东西都需要符合 Disposable

如果 Disposer 有助于去除闭包并防止 retain cycle(请参阅 weak self 部分),那么为了所有示例,让我们创建一个 disposer

let disposer = Disposer()

Closure Wrapper

Disposer的主要用途是用于消除我们在Observables上创建的订阅闭包,但我们还发现它可以用来处理常规闭包。作为库的一部分,我们创建了一个小的Closure包装类,它遵循Disposable规范。这样,您可以包装简单闭包以便进行处理。

let closureCall = Closure {
    print("We ❤️ Snail")
}.add(to: Disposer)

请注意,这不会消除闭包的closureCall引用,它只会消除Closure的内容。

订阅Observables

observable.subscribe(
    onNext: { thing in ... }, // do something with thing
    onError: { error in ... }, // do something with error
    onDone: { ... } //do something when it's done
).add(to: disposer)

闭包也是可选的...

observable.subscribe(
    onNext: { thing in ... } // do something with thing
).add(to: disposer)
observable.subscribe(
    onError: { error in ... } // do something with error
).add(to: disposer)

创建Observables变量

let variable = Variable<whatever>(some initial value)
let optionalString = Variable<String?>(nil)
optionalString.asObservable().subscribe(
    onNext: { string in ... } // do something with value changes
).add(to: disposer)

optionalString.value = "something"
let int = Variable<Int>(12)
int.asObservable().subscribe(
    onNext: { int in ... } // do something with value changes
).add(to: disposer)

int.value = 42

组合Observables变量

let isLoaderAnimating = Variable<Bool>(false)
isLoaderAnimating.bind(to: viewModel.isLoading) // forward changes from one Variable to another

viewModel.isLoading = true
print(isLoaderAnimating.value) // true
Observable.merge([userCreated, userUpdated]).subscribe(
  onNext: { user in ... } // do something with the latest value that got updated
}).add(to: disposer)

userCreated.value = User(name: "Russell") // triggers 
userUpdated.value = User(name: "Lee") // triggers 
Observable.combineLatest((isMapLoading, isListLoading)).subscribe(
  onNext: { isMapLoading, isListLoading in ... } // do something when both values are set, every time one gets updated
}).add(to: disposer)

isMapLoading.value = true
isListLoading.value = true // triggers

其他Observables

let just = Just(1) // always returns the initial value (1 in this case)

enum TestError: Error {
  case test
}
let failure = Fail(TestError.test) // always fail with error

let n = 5
let replay = Replay(n) // replays the last N events when a new observer subscribes

操作符

Snail提供了某些基本操作符,用于转换和操作可观察对象。

  • map操作符允许将可观察值的值映射到另一个值。类似于对Collection类型上的map操作。

    let observable = Observable<Int>()
    let subject = observable.map { "Number: \($0)" }
    // -> subject emits `String` whenever `observable` emits.
  • filter操作符允许过滤出可观察链中的某些值。类似于对Collection类型上的filter操作。如果值应该被发射,则返回true,如果需要过滤掉该值,则返回false

    let observable = Observable<Int>()
    let subject = observable.filter { $0 % 2 == 0 }
    // -> subject will only emit even numbers.
  • flatMap操作符允许将值映射到其他可观察值,例如,您可能希望在用户点击可观察值发射时创建一个针对网络请求的可观察值。

    let fetchTrigger = Observable<Void>()
    let subject = fetchTrigger.flatMap { Variable(100).asObservable() }
    // -> subject is an `Observable<Int>` that is created when `fetchTrigger` emits.

订阅控件事件

let control = UIControl()
control.controlEvent(.touchUpInside).subscribe(
  onNext: { ... }  // do something with thing
).add(to: disposer)

let button = UIButton()
button.tap.subscribe(
  onNext: { ... }  // do something with thing
).add(to: disposer)

队列

您可以使用.subscribe(queue: <desired queue>)指定哪个队列将通知可观察值。如果没有指定,则可观察值将在.publish()时所在的队列上通知。

共有3种情况

  1. 您没有指定队列。您的观察者将在相同线程上收到与可观察值发布相同的通知。

  2. 您指定了main队列,并且可观察值也发布在main队列上。您的观察者将在主队列上同步接收通知。

  3. 您指定了一个队列。您的观察者将在指定队列上异步接收通知。

示例

订阅在DispatchQueue.main

observable.subscribe(queue: .main,
    onNext: { thing in ... }
).add(to: disposer)

弱引用是可选的

如果需要,可以使用[弱引用self],但有了Disposer的引入,当调用disposer.disposeAll()时,保留周期将被销毁。

一种想法是,当从导航栈中弹出视图控制器时调用disposer.disposeAll()

protocol HasDisposer {
    var disposer: Disposer
}

class NavigationController: UINavigationController {
    public override func popViewController(animated: Bool) -> UIViewController? {
        let viewController = super.popViewController(animated: animated)
        (viewController as? HasDisposer).disposer.disposeAll()
        return viewController
    }
}

实际上

订阅通知

NotificationCenter.default.observeEvent(Notification.Name.UIKeyboardWillShow)
  .subscribe(queue: .main, onNext: { notification in
    self.keyboardWillShow(notification)
  }).add(to: disposer)

订阅手势

let panGestureRecognizer = UIPanGestureRecognizer()
panGestureRecognizer.asObservable()
  .subscribe(queue: .main, onNext: { sender in
    // Your code here
  }).add(to: disposer)
view.addGestureRecognizer(panGestureRecognizer)

订阅 UIBarButton 点击

navigationItem.leftBarButtonItem?.tap
  .subscribe(onNext: {
    self.dismiss(animated: true, completion: nil)
  }).add(to: disposer)