Skip to content

Instantly share code, notes, and snippets.

@muukii
Created April 29, 2021 17:26
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save muukii/9d22d9e95a492bb0c4d1e940b1744ddf to your computer and use it in GitHub Desktop.
Save muukii/9d22d9e95a492bb0c4d1e940b1744ddf to your computer and use it in GitHub Desktop.
//
import Foundation
public final class InputFilter {
private final class Storage<T> {
let lock = NSLock()
var rawStorage: [CodeLocation : T] = [:]
func value(for key: CodeLocation) -> T? {
guard let value = rawStorage[key] else {
return nil
}
return value
}
func setValue(_ value: T, for key: CodeLocation) {
rawStorage[key] = value
}
}
private struct CodeLocation: Hashable {
let file: String
let line: UInt
let column: UInt
}
private let _anyStorage: Storage<Any> = .init()
private let _intStorage: Storage<Int> = .init()
private let _stringStorage: Storage<String> = .init()
private func _doIfChanged<T>(
targetStorage: Storage<Any>,
key: CodeLocation,
value: T,
compare: (T, T) -> Bool,
perform: (T) -> Void
) {
targetStorage.lock.lock()
guard let cachedRawValue = targetStorage.value(for: key) else {
targetStorage.setValue(value, for: key)
targetStorage.lock.unlock()
perform(value)
return
}
let cachedValue = cachedRawValue as! T
guard compare(cachedValue, value) == false else {
targetStorage.lock.unlock()
return
}
targetStorage.setValue(value, for: key)
targetStorage.lock.unlock()
perform(value)
}
private func _specialized_doIfChanged<T>(
targetStorage: Storage<T>,
key: CodeLocation,
value: T,
compare: (T, T) -> Bool,
perform: (T) -> Void
) {
targetStorage.lock.lock()
guard let cachedValue: T = targetStorage.value(for: key) else {
targetStorage.setValue(value, for: key)
targetStorage.lock.unlock()
perform(value)
return
}
guard compare(cachedValue, value) == false else {
targetStorage.lock.unlock()
return
}
targetStorage.setValue(value, for: key)
targetStorage.lock.unlock()
perform(value)
}
/// [Experimental]
/// should be renamed
public func doIfChanged<T>(
file: StaticString = #file,
line: UInt = #line,
column: UInt = #column,
_ value: T,
_ compare: @escaping (T, T) -> Bool,
_ perform: (T) -> Void
) {
_doIfChanged(
targetStorage: _anyStorage,
key: .init(file: file.description, line: line, column: column),
value: value,
compare: compare,
perform: perform
)
}
/// [Experimental]
/// should be renamed
public func doIfChanged<T: Equatable>(
file: StaticString = #file,
line: UInt = #line,
column: UInt = #column,
_ value: T,
_ perform: (T) -> Void
) {
_doIfChanged(
targetStorage: _anyStorage,
key: .init(file: file.description, line: line, column: column),
value: value,
compare: ==,
perform: perform
)
}
/// [Experimental] [Specialized]
/// should be renamed
public func doIfChanged(
file: StaticString = #file,
line: UInt = #line,
column: UInt = #column,
_ value: Int,
_ perform: (Int) -> Void
) {
_specialized_doIfChanged(
targetStorage: _intStorage,
key: .init(file: file.description, line: line, column: column),
value: value,
compare: ==,
perform: perform
)
}
/// [Experimental] [Specialized]
/// should be renamed
public func doIfChanged(
file: StaticString = #file,
line: UInt = #line,
column: UInt = #column,
_ value: String,
_ perform: (String) -> Void
) {
_specialized_doIfChanged(
targetStorage: _stringStorage,
key: .init(file: file.description, line: line, column: column),
value: value,
compare: ==,
perform: perform
)
}
}
private var _storageKey: Void?
extension NSObject {
public var associatedFilter: InputFilter {
objc_sync_enter(self)
defer {
objc_sync_exit(self)
}
if let associated = objc_getAssociatedObject(self, &_storageKey)
as? InputFilter
{
return associated
} else {
let associated = InputFilter()
objc_setAssociatedObject(self, &_storageKey, associated, .OBJC_ASSOCIATION_RETAIN)
return associated
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment