在 Swift 扩展中存储变量的替代方法
Alternative to storing a variable in a Swift extension
我目前正在开发一个大型应用程序,我们需要能够跟踪源自 UIView
的基本上任何自定义 class 的特定事件(点击、滑动..)。例如,我们有 UITableView
的多个子 class,它们都需要以不同的组合响应点击 and/or 滑动事件 - 这些事件的发生然后将属性发送到外部服务。 Subclassing UIView
并将其用作我们所有其他自定义 subclass 的父 class 不是一种选择。
更重要的是,当这些事件发生时发送的数据因显示 UI 元素的应用页面而异。我的第一个想法是创建一个 Trackable
协议。理想情况下,我想将所有用于设置手势识别器的样板代码放在该协议的扩展中,但不能,因为 #selector
语法需要协议扩展中不可用的 @objc
注释。
此外,当我尝试扩展 UIView
时,我不再能够访问 Trackable
协议所需的 属性 并且无法添加它的默认实现,因为,上面提到,扩展不支持变量声明。以下是我想要实现的(非常粗略的)想法。这可能吗?是否存在更好的模式?我还查看了委托模式,它并没有解决上述任何问题。
protocol Trackable: class {
var propertiesToSend: [String : [String : String]] { get set }
}
extension UIView: Trackable {
//need alternative way to achieve this, as it is not allowed here
var propertiesToSend = [:]
func subscribe(to event: String, with properties: [String : String]) {
propertiesToSend[event] = properties
startListening(for: event)
}
func unsubscribe(from event: String) {
propertiesToSend.removeValue(forKey: event)
}
private func startListening(for event: String) {
switch (event) {
case "click":
let clickRecogniser = UITapGestureRecognizer(target: self, action: #selector(track(event:)))
addGestureRecognizer(clickRecogniser)
case "drag":
for direction: UISwipeGestureRecognizerDirection in [.left, .right] {
let swipeRecogniser = UISwipeGestureRecognizer(target: self, action: #selector(track(event:)))
swipeRecogniser.direction = direction
addGestureRecognizer(swipeRecogniser)
}
default: return
}
}
@objc
func track(event: UIEvent) {
let eventString: String
switch (event.type) {
case .touches:
eventString = "click"
case .motion:
eventString = "drag"
default: return
}
if let properties = propertiesToSend[eventString] {
sendPropertiesToExternalService("Interaction", properties: properties)
}
}
}
不要让它变得比需要的更复杂。 UIView
及其子类必须派生自 NSObject
。阅读有关 objc_getAssociatedObject
和 objc_getAssociatedObject
的文档。不需要协议或其他抽象。
import ObjectiveC
private var key: Void? = nil // the address of key is a unique id.
extension UIView {
var propertiesToSend: [String: [String: String]] {
get { return objc_getAssociatedObject(self, &key) as? [String: [String: String]] ?? [:] }
set { objc_setAssociatedObject(self, &key, newValue, .OBJC_ASSOCIATION_RETAIN) }
}
}
可以按如下方式使用。
let button = UIButton()
button.propertiesToSend = ["a": ["b": "c"]]
print(button.propertiesToSend["a"]?["b"] ?? "unknown")
我目前正在开发一个大型应用程序,我们需要能够跟踪源自 UIView
的基本上任何自定义 class 的特定事件(点击、滑动..)。例如,我们有 UITableView
的多个子 class,它们都需要以不同的组合响应点击 and/or 滑动事件 - 这些事件的发生然后将属性发送到外部服务。 Subclassing UIView
并将其用作我们所有其他自定义 subclass 的父 class 不是一种选择。
更重要的是,当这些事件发生时发送的数据因显示 UI 元素的应用页面而异。我的第一个想法是创建一个 Trackable
协议。理想情况下,我想将所有用于设置手势识别器的样板代码放在该协议的扩展中,但不能,因为 #selector
语法需要协议扩展中不可用的 @objc
注释。
此外,当我尝试扩展 UIView
时,我不再能够访问 Trackable
协议所需的 属性 并且无法添加它的默认实现,因为,上面提到,扩展不支持变量声明。以下是我想要实现的(非常粗略的)想法。这可能吗?是否存在更好的模式?我还查看了委托模式,它并没有解决上述任何问题。
protocol Trackable: class {
var propertiesToSend: [String : [String : String]] { get set }
}
extension UIView: Trackable {
//need alternative way to achieve this, as it is not allowed here
var propertiesToSend = [:]
func subscribe(to event: String, with properties: [String : String]) {
propertiesToSend[event] = properties
startListening(for: event)
}
func unsubscribe(from event: String) {
propertiesToSend.removeValue(forKey: event)
}
private func startListening(for event: String) {
switch (event) {
case "click":
let clickRecogniser = UITapGestureRecognizer(target: self, action: #selector(track(event:)))
addGestureRecognizer(clickRecogniser)
case "drag":
for direction: UISwipeGestureRecognizerDirection in [.left, .right] {
let swipeRecogniser = UISwipeGestureRecognizer(target: self, action: #selector(track(event:)))
swipeRecogniser.direction = direction
addGestureRecognizer(swipeRecogniser)
}
default: return
}
}
@objc
func track(event: UIEvent) {
let eventString: String
switch (event.type) {
case .touches:
eventString = "click"
case .motion:
eventString = "drag"
default: return
}
if let properties = propertiesToSend[eventString] {
sendPropertiesToExternalService("Interaction", properties: properties)
}
}
}
不要让它变得比需要的更复杂。 UIView
及其子类必须派生自 NSObject
。阅读有关 objc_getAssociatedObject
和 objc_getAssociatedObject
的文档。不需要协议或其他抽象。
import ObjectiveC
private var key: Void? = nil // the address of key is a unique id.
extension UIView {
var propertiesToSend: [String: [String: String]] {
get { return objc_getAssociatedObject(self, &key) as? [String: [String: String]] ?? [:] }
set { objc_setAssociatedObject(self, &key, newValue, .OBJC_ASSOCIATION_RETAIN) }
}
}
可以按如下方式使用。
let button = UIButton()
button.propertiesToSend = ["a": ["b": "c"]]
print(button.propertiesToSend["a"]?["b"] ?? "unknown")