Created
April 29, 2021 17:26
-
-
Save muukii/9d22d9e95a492bb0c4d1e940b1744ddf 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
// | |
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