首页 > 解决方案 > Swift UITextField 目标动作作为闭包,不删除目标动作的问题

问题描述

我对Eric Armstrong的代码做了一些修改

将闭包作为目标添加到 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 被传递给 UIControl.removetTarget() 并且所有目标动作总是被清除!

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

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

标签: swiftuitextfieldswift-protocolstarget-action

解决方案


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

主要问题是 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"
}

推荐阅读