简体   繁体   English

Swift UITextField 目标动作作为关闭,不删除目标动作的问题

[英]Swift UITextField target-actions as closure, problem with not removing target-actions

I have such code a little modified from code of Eric Armstrong我对Eric Armstrong的代码做了一些修改

Adding a closure as target to a UIButton 将闭包作为目标添加到 UIButton

But there is the problem with both codes.但是这两个代码都有问题。 Those from Eric does remove all target-actions on来自 Eric 的那些确实删除了所有目标操作

func removeTarget(for controlEvent: UIControl.Event = .touchUpInside)

And modified code on the other hand do not remove target-actions at all.另一方面,修改后的代码根本不会删除目标操作。 Of course it is caused by if condition, but it also means that there are no targets stored properly in Storable property.当然是 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) }
    }
}

My aim is to precisely register target-actions with closures and remove them without removing all other target-actions added to given UITextField via #selector.我的目标是使用闭包精确地注册目标操作并删除它们,而不删除通过#selector 添加到给定 UITextField 的所有其他目标操作。 Now I can have removed ALL or NONE of target-actions while using this approach for closure-style target actions.现在,我可以在使用这种方法进行闭包式目标操作时删除所有或无目标操作。

UPDATE更新

Based on Eric Armstrong answer i have implemented my version.根据Eric Armstrong的回答,我已经实现了我的版本。 But what I have experienced in version proposed by Eric was that when adding target actions to TextField on TableView list while cells appear and then removing this target actions from Text Fields while cells diseappear the previous code seems to remove all target actions on removeTarget(for:) exection.但是我在 Eric 提出的版本中所经历的是,当单元格出现时向 TableView 列表上的 TextField 添加目标操作,然后在单元格消失时从文本字段中删除此目标操作时,之前的代码似乎删除了removeTarget(for:)执行。 So when in other place in code like UITableViewCell I have added additional target action on totaly different target (UITableViewCell object, not this custom Target() objects) while cells was disappearing and then again appearing on screen and removeTarget(for) was executed then this other (external as I call them target actions) also was removed and never called again.因此,当在 UITableViewCell 之类的代码中的其他位置时,我在完全不同的目标(UITableViewCell object,而不是这个自定义 Target() 对象)上添加了额外的目标操作,而单元格消失然后再次出现在屏幕上并执行 removeTarget(for) 然后这个其他(我称之为目标操作的外部)也被删除并且再也没有调用过。

I consider that some problem was usage of [String: Target] dictionary which is value type and it was used in case of property getter in objc_getAssociatedObject where there was我认为有些问题是 [String: Target] 字典的使用,它是值类型,它用于 objc_getAssociatedObject 中的属性 getter 的情况

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

So as I understand it then there wasn't objc object for given key and Storable was nil and nil-coalescing operator was called and static value type Property.property return aka [String: Dictionary]因此,据我了解,给定键没有 objc object 并且 Storable 为 nil 并且调用了 nil-coalescing 运算符,并且 static 值类型 Property.property 返回又名 [字符串:字典]

So it was returned by copy and Target object was stored in this copied object which wasn't permanently stored and accessed in removeTarget(for:) always as nil.所以它被复制返回,目标 object 存储在这个复制的 object 中,它没有永久存储在 removeTarget(for:) 中,并且始终为 nil。 So nil was passed to UIControl.removetTarget() and all target actions was always cleared..所以 nil 被传递给 UIControl.removetTarget() 并且所有目标动作总是被清除..

I have tried simple replacing [String: Target] Swift dictionary with NSMutableDictionary which is a reference type so I assume it can be stored.我尝试用 NSMutableDictionary 简单地替换 [String: Target] Swift 字典,这是一个引用类型,所以我假设它可以被存储。 But this simple replacement for static variable and just returning it via nil-coalesing operator caused as I assume that there as only one such storage for Target objects and then while scrolling Table View each removeForTarget() has somehow remove all target actions from all UITextFields not only from current.但是这个对 static 变量的简单替换并只是通过 nil-coalesing 运算符返回它,因为我假设目标对象只有一个这样的存储,然后在滚动表视图时,每个 removeForTarget() 都以某种方式从所有 UITextFields 中删除了所有目标操作而不是仅从当前。

I also consider usage of String(describing: type(of: Storable.self)) as being wrong as it will be always the same for given Storable type.我还认为使用 String(describing: type(of: Storable.self)) 是错误的,因为对于给定的 Storable 类型,它总是相同的。

Ok, I think I finally solved this issue好的,我想我终于解决了这个问题

The main problem was usage of AssociatedKey!主要问题是 AssociatedKey 的使用! it needs to be done like below它需要像下面那样完成

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

So I ended up with such code:所以我最终得到了这样的代码:

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"
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM