Swift UITextField target-actions как закрытие, проблема с не удалением target-actions - PullRequest
0 голосов
/ 10 октября 2019

У меня есть такой код, немного измененный по сравнению с кодом Эрик Армстронг

Добавление замыкания в качестве цели для кнопки UIB

Но естьпроблема с обоими кодами. Те из Эрика действительно удаляют все целевые действия на

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

Моя цель - точно зарегистрировать целевые действия с замыканиями и удалить их, не удаляя все другие целевые действия, добавленные к данному UITextField через #selector. Теперь я могу удалить ВСЕ или НЕТ целевых действий, используя этот подход для целевых действий в стиле замыкания.

ОБНОВЛЕНИЕ

Основано на ответе Эрик Армстронг Я реализовал свою версию. Но что я испытал в версии, предложенной Эриком, так это то, что при добавлении целевых действий в TextField в списке TableView, когда появляются ячейки, а затем при удалении этих целевых действий из текстовых полей, когда ячейки исчезают, предыдущий код, похоже, удаляет все целевые действия при removeTarget(for:) exection,Поэтому, когда в другом месте в коде, таком как UITableViewCell, я добавил дополнительное целевое действие на совершенно другую цель (объект UITableViewCell, а не этот пользовательский объект Target ()), когда ячейки исчезали, а затем снова появлялись на экране, и затем выполнялся removeTarget (for)другие (внешние, как я их называю, целевые действия) также были удалены и больше никогда не вызывались.

Я считаю, что проблемой было использование словаря [String: Target], который является типом значения, и он использовался в случае получения свойства в objc_getAssociatedObject, где было

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

Так как японять это тогда не было объекта objc для данного ключа и Storable был nil и был вызван оператор nil-coalescing и тип статического значения Property.property return aka [String: Dictionary]

Таким образом, он был возвращен копией иЦелевой объект был сохранен в этом скопированном объекте, который не был постоянно сохранен и доступен в removeTarget (для :) всегда как nil. Таким образом, ноль был передан в UIControl.removetTarget (), и все целевые действия всегда очищались!

Я попытался просто заменить словарь [String: Target] Swift на NSMutableDictionary, который является ссылочным типом, поэтому я предполагаю, что он может быть сохранен. Но эта простая замена статической переменной и просто возвращение ее через оператор nil-coalesing привела к тому, что я предполагаю, что существует только одно такое хранилище для объектов Target, а затем при прокрутке табличного представления каждый removeForTarget () каким-то образом удаляет все целевые действия из всех UITextFields, но нетолько из текущих.

Я также считаю использование String (описывающего: type (of: Storable.self)) неправильным, так как оно всегда будет одинаковым для данного типа Storable.

1 Ответ

1 голос
/ 10 октября 2019

Хорошо, я думаю, что наконец-то решил эту проблему

Основной проблемой было использование 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"
}
...