【问题标题】:Swift UITextField target-actions as closure, problem with not removing target-actionsSwift UITextField 目标动作作为闭包,不删除目标动作的问题
【发布时间】:2019-10-10 18:40:50
【问题描述】:

我对 Eric Armstrong

的代码做了一些修改

Adding a closure as target to a UIButton

但是这两个代码都有问题。来自 Eric 的那些确实删除了

上的所有目标操作
func removeTarget(for controlEvent: UIControl.Event = .touchUpInside)

另一方面,修改后的代码根本不会删除目标操作。当然是 if 条件造成的,但这也意味着 Storable 属性中没有正确存储目标。

extension UIControl: ExtensionPropertyStorable {

    class Property: PropertyProvider {
        static var property = NSMutableDictionary()

        static func makeProperty() -> NSMutableDictionary? {
            return NSMutableDictionary()
        }
    }

    func addTarget(for controlEvent: UIControl.Event = .touchUpInside, target: @escaping (_ sender: Any) ->()) {
        let key = String(describing: controlEvent)
        let target = Target(target: target)

        addTarget(target, action: target.action, for: controlEvent)
        property[key] = target
    }

    func removeTarget(for controlEvent: UIControl.Event = .touchUpInside) {
        let key = String(describing: controlEvent)
        if let target = property[key] as? Target {
            removeTarget(target, action: target.action, for: controlEvent)
            property[key] = nil
        }

    }
}


// Wrapper class for the selector
class Target {

    private let t: (_ sender: Any) -> ()
    init(target t: @escaping (_ sender: Any) -> ()) { self.t = t }
    @objc private func s(_ sender: Any) { t(sender) }

    public var action: Selector {
        return #selector(s(_:))
    }
}

// Protocols with associatedtypes so we can hide the objc_ code
protocol PropertyProvider {
    associatedtype PropertyType: Any

    static var property: PropertyType { get set }
    static func makeProperty() -> PropertyType?
}

extension PropertyProvider {
    static func makeProperty() -> PropertyType? {
        return nil
    }
}

protocol ExtensionPropertyStorable: class {
    associatedtype Property: PropertyProvider
}

// Extension to make the property default and available
extension ExtensionPropertyStorable {

    typealias Storable = Property.PropertyType

    var property: Storable {
        get {
            let key = String(describing: type(of: Storable.self))

            guard let obj = objc_getAssociatedObject(self, key) as? Storable else {

                if let property = Property.makeProperty() {
                    objc_setAssociatedObject(self, key, property, .OBJC_ASSOCIATION_RETAIN)
                }

                return objc_getAssociatedObject(self, key) as? Storable ?? Property.property
            }

            return obj
        }
        set {
            let key = String(describing: type(of: Storable.self))
            return objc_setAssociatedObject(self, key, newValue, .OBJC_ASSOCIATION_RETAIN) }
    }
}

我的目标是使用闭包精确注册目标操作并删除它们,而不删除通过#selector 添加到给定 UITextField 的所有其他目标操作。现在我可以在使用这种方法进行闭包式目标操作时删除所有或无目标操作。

更新

根据 Eric Armstrong 的回答,我已经实现了我的版本。 但是我在 Eric 提出的版本中所经历的是,当单元格出现时向 TableView 列表上的 TextField 添加目标操作,然后在单元格消失时从文本字段中删除此目标操作时,之前的代码似乎删除了 removeTarget(for:) 执行上的所有目标操作.因此,当在 UITableViewCell 之类的代码中的其他位置时,我在完全不同的目标(UITableViewCell 对象,而不是此自定义 Target() 对象)上添加了额外的目标操作,而单元格正在消失,然后再次出现在屏幕上并执行 removeTarget(for) 然后这个其他(我称之为目标操作的外部)也被删除并且再也没有调用过。

我认为有些问题是 [String: Target] 字典的使用,它是值类型,它用于 objc_getAssociatedObject 中的属性 getter

objc_getAssociatedObject(self, key) as? Storable ?? Property.property

所以据我了解,给定键没有 objc 对象,而 Storable 为 nil,调用了 nil-coalescing 运算符,静态值类型 Property.property 返回又名 [String : Dictionary]

所以它是由副本返回的,并且目标对象存储在这个复制的对象中,该对象不是永久存储和在 removeTarget(for:) 中访问的,始终为 nil。所以 nil 被传递给 UIControl.removetTarget() 并且所有目标动作总是被清除!

我尝试用 NSMutableDictionary 简单地替换 [String: Target] Swift 字典,它是一个引用类型,所以我假设它可以被存储。但是这个简单的静态变量替换并只是通过 nil-coalesing 运算符返回它,因为我假设目标对象只有一个这样的存储,然后在滚动表视图时,每个 removeForTarget() 都以某种方式从所有 UITextFields 中删除了所有目标操作,而不是仅从当前。

我还认为使用 String(describing: type(of: Storable.self)) 是错误的,因为对于给定的 Storable 类型,它总是相同的。

【问题讨论】:

    标签: swift uitextfield swift-protocols target-action


    【解决方案1】:

    好的,我想我终于解决了这个问题

    主要问题是 AssociatedKey 的使用!它需要像下面那样完成

    https://stackoverflow.com/a/48731142/4415642

    所以我最终得到了这样的代码:

    import UIKit
    
    /**
     * Swift 4.2 for UIControl and UIGestureRecognizer,
     * and and remove targets through swift extension
     * stored property paradigm.
     * https://stackoverflow.com/a/52796515/4415642
     **/
    
    extension UIControl: ExtensionPropertyStorable {
    
        class Property: PropertyProvider {
            static var property = NSMutableDictionary()
    
            static func makeProperty() -> NSMutableDictionary? {
                return NSMutableDictionary()
            }
        }
    
        func addTarget(for controlEvent: UIControl.Event = .touchUpInside, target: @escaping (_ sender: Any) ->()) {
            let key = String(describing: controlEvent)
            let target = Target(target: target)
    
            addTarget(target, action: target.action, for: controlEvent)
            property[key] = target
    
            print("ADDED \(ObjectIdentifier(target)), \(target.action)")
        }
    
        func removeTarget(for controlEvent: UIControl.Event = .touchUpInside) {
            let key = String(describing: controlEvent)
    
            if let target = property[key] as? Target {
                print("REMOVE \(ObjectIdentifier(target)), \(target.action)")
                removeTarget(target, action: target.action, for: controlEvent)
                property[key] = nil
    
            }
    
        }
    }
    
    extension UIGestureRecognizer: ExtensionPropertyStorable {
    
        class Property: PropertyProvider {
            static var property: Target?
        }
    
        func addTarget(target: @escaping (Any) -> ()) {
            let target = Target(target: target)
            addTarget(target, action: target.action)
            property = target
        }
    
        func removeTarget() {
            let target = property
            removeTarget(target, action: target?.action)
            property = nil
        }
    }
    
    // Wrapper class for the selector
    class Target {
    
        private let t: (_ sender: Any) -> ()
        init(target t: @escaping (_ sender: Any) -> ()) { self.t = t }
        @objc private func s(_ sender: Any) { t(sender) }
    
        public var action: Selector {
            return #selector(s(_:))
        }
    
        deinit {
            print("Deinit target: \(ObjectIdentifier(self))")
        }
    }
    
    // Protocols with associatedtypes so we can hide the objc_ code
    protocol PropertyProvider {
        associatedtype PropertyType: Any
    
        static var property: PropertyType { get set }
        static func makeProperty() -> PropertyType?
    }
    
    extension PropertyProvider {
        static func makeProperty() -> PropertyType? {
            return nil
        }
    }
    
    protocol ExtensionPropertyStorable: class {
        associatedtype Property: PropertyProvider
    }
    
    // Extension to make the property default and available
    extension ExtensionPropertyStorable {
    
        typealias Storable = Property.PropertyType
    
        var property: Storable {
            get {
                guard let obj = objc_getAssociatedObject(self, &AssociatedKeys.property) as? Storable else {
    
                    if let property = Property.makeProperty() {
                        objc_setAssociatedObject(self, &AssociatedKeys.property, property, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
                    }
    
                    return objc_getAssociatedObject(self, &AssociatedKeys.property) as? Storable ?? Property.property
                }
    
                return obj
            }
            set {
                return objc_setAssociatedObject(self, &AssociatedKeys.property, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) }
        }
    }
    
    private struct AssociatedKeys {
        static var property = "AssociatedKeys.property"
    }
    

    【讨论】:

    • 重要的部分是这个 &AssociatedKeys.property。它甚至可以在 Eric 的代码中使用 [String: Target] Storable 并且只是在 objc_getAssociated 之后的简单的 nil-coalescing 运算符...
    猜你喜欢
    • 2019-01-21
    • 2011-03-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-10-19
    • 1970-01-01
    相关资源
    最近更新 更多