Last active
May 5, 2018 07:23
-
-
Save heronlyj/db9e82523d318b9a00a5cc66efe08183 to your computer and use it in GitHub Desktop.
一个轻量的数据监听类
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// Listener.swift | |
// BMKP | |
// | |
// Created by lyj on 19/01/2017. | |
// Copyright © 2017 bmkp. All rights reserved. | |
// | |
import Foundation | |
public struct ListenserKey { | |
fileprivate let obj: String | |
public init(obj: String) { | |
self.obj = obj | |
} | |
public func createrKey(_ v: String) -> String { | |
return obj + "." + v + ".listenerKey" | |
} | |
} | |
extension NSObject { | |
public var listenKey: ListenserKey { | |
return ListenserKey(obj: "\(hashValue)") | |
} | |
} | |
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// | |
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// | |
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// | |
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// | |
/// listener name and action | |
public struct Listener<T>: Hashable { | |
public typealias Action = (T) -> Void | |
public let key: String | |
public let action: Action | |
public var hashValue: Int { | |
return key.hashValue | |
} | |
} | |
public func ==<T>(lhs: Listener<T>, rhs: Listener<T>) -> Bool { | |
return lhs.key == rhs.key | |
} | |
/// 被观察对象 T 为 value 类型 | |
public class ListenAble<T> { | |
public typealias Change = (new: T, old: T) | |
public typealias GuardAction = (Change) -> Bool | |
public typealias SetterAction = (T) -> Void | |
/// value 为最新的值,finish 闭包是终止观察的,调用 finish() 则会 remove | |
public typealias OptionalEscapeingAction = (_ value: T, _ finish: @escaping () -> Void) -> Void | |
/// 最新的 value | |
public private(set) var value: T { | |
didSet { | |
setterAction(value) | |
listenerSet.forEach { | |
$0.action(value) | |
} | |
} | |
} | |
/// 唯一可以更新 value 的方法 需要通过 updateGuard 来判断,只有返回 true 才能更新 | |
/// | |
/// - Parameter v: 需要更新的 value | |
public func updateValue(_ v: T) { | |
guard updateGuard((new: v, old: value)) else { return } | |
value = v | |
} | |
var updateGuard: GuardAction | |
var setterAction: SetterAction | |
var listenerSet = Set<Listener<T>>() | |
/// 是否没有监听了 | |
public var emptyListen: Bool { | |
return listenerSet.isEmpty | |
} | |
/// 初始化 | |
/// | |
/// - Parameters: | |
/// - v: 给一个初始值 | |
/// - updateGuard: 设置一个 guard 用来判断是否能够更新 | |
/// - setter: 初始化绑定的更新回调 | |
public init(v: T, updateGuard: GuardAction? = nil, setter: @escaping SetterAction = { _ in }) { | |
value = v | |
setterAction = setter | |
self.updateGuard = updateGuard ?? { _ in return true } | |
} | |
/// 绑定观察者 | |
/// | |
/// - Parameters: | |
/// - key: 唯一标示 | |
/// - count: 更新触发的次数,nil 则没有限制 | |
/// - action: 观察者 | |
public func bindListener(key: String, count: Int? = nil, action: @escaping Listener<T>.Action) { | |
guard let count = count, count > 0 else { | |
listenerSet.insert(Listener<T>(key: key, action: action)) | |
return | |
} | |
let counter = createCounter(start: count) | |
listenerSet.insert(Listener<T>(key: key, action: { [weak self] t in | |
action(t) | |
// 每次触发更新,如果 count == 0 则 remove, counter 会自减 | |
if counter() == 0 { | |
self?.removeListener(key: key) | |
} | |
})) | |
} | |
/// 绑定观察者并且立即触发一次回调 | |
/// | |
/// - Parameters: | |
/// - key: 唯一标示 | |
/// - count: 更新触发的次数, nil 则没有限制,立即触发的一次回调不回改变 count | |
/// - action: 观察者 | |
public func bindAndFireListener(key: String, count: Int? = nil, action: @escaping Listener<T>.Action) { | |
bindListener(key: key, count: count, action: action) | |
action(value) | |
} | |
/// 只观察下一次的 value 更新,无论是什么值,回调一次就 remove | |
/// | |
/// - Parameters: | |
/// - action: 观察者 | |
public func fireOnce(_ action: @escaping Listener<T>.Action) { | |
bindListener(key: UUID().uuidString, count: 1, action: action) | |
} | |
/// OptionalEscapeingAction 闭包与 Listener<T>.Action 的区别是多了一个 finish 参数, 这同样是一个闭包, 它的作用是 removeListener | |
/// 调用时机自己控制,设计的使用场景是,需要满足一定条件的值,一旦得到这个值之后就不需要监听了,此时执行 finish 然后 remove | |
/// | |
/// - Parameter action: 更新回调闭包 | |
public func fireUntilCompleted(_ action: @escaping OptionalEscapeingAction) { | |
let key = UUID().uuidString | |
// 只有 finish 回调一次就 remove finish 的出发由注册的地方来实现 | |
let finish = { | |
self.removeListener(key: key) | |
} | |
self.bindListener(key: key) { | |
action($0, finish) | |
} | |
action(value, finish) | |
} | |
/// 移除观察者 | |
/// | |
/// - Parameter key: 唯一标识 | |
public func removeListener(key: String) { | |
if let index = listenerSet.index(where: { $0.key == key }) { | |
listenerSet.remove(at: index) | |
} | |
} | |
/// 移除所有 | |
public func removeAll() { | |
listenerSet.removeAll(keepingCapacity: false) | |
} | |
/// 创建一个计时器,每调用一次计数都会减一 | |
fileprivate func createCounter(start: Int) -> () -> Int { | |
var startCount = start | |
return { | |
startCount -= 1 | |
return startCount | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment