【问题标题】:Subclassing class that only expose convenience init仅公开便利初始化的子类
【发布时间】:2015-06-04 01:43:32
【问题描述】:

我想继承声明如下的 UITableViewRowAction 类:

class UITableViewRowAction : NSObject, NSCopying {

  convenience init(style: UITableViewRowActionStyle, title: String!, handler: (UITableViewRowAction!, NSIndexPath!) -> Void)

  var style: UITableViewRowActionStyle { get }
  var title: String!
  @NSCopying var backgroundColor: UIColor! // default background color is dependent on style
  @NSCopying var backgroundEffect: UIVisualEffect?
}

我希望我的子类覆盖这里存在的便利 init 并调用 super 来获得便利 init。

这是我想做的:

public class MySubclass : UITableViewRowAction  {

  let handler : (UITableViewRowAction!, NSIndexPath!) -> Void
  public init(style: UITableViewRowActionStyle, title: String!, handler: (UITableViewRowAction!, NSIndexPath!) -> Void) {
      self.handler = handler
      super.init(style: style, title: title, handler: handler)  // error == Must call a designated initializer of the superclass 'UITableViewRowAction'
  }
}

我想这样做是为了将处理程序公开为只读属性,以便能够对处理程序执行我认为应该执行的操作进行单元测试。

所以我的问题是超类没有公开传递给 init 方法的处理程序,也没有办法在 init 方法之外设置它。

【问题讨论】:

    标签: xcode swift


    【解决方案1】:

    我不知道这个答案是否真的与您的答案完全不同,但我在尝试进行单元测试时遇到了与您类似的问题。这感觉更干净一点;但这是主观的。您可以使用 set_AssociatedObject(和 get_AssociatedObject)和一个小的私有 ActionTrigger 包装类来允许从单元测试中触发操作。

    import UIKit
    import ObjectiveC
    
    var ActionTriggerHandle: UInt8 = 0
    
    private class ActionTrigger {
        private var handler: (UITableViewRowAction!, NSIndexPath!) -> ()
        private var action: UITableViewRowAction
    
        private init(action: UITableViewRowAction, handler: (UITableViewRowAction!, NSIndexPath!) -> Void) {
            self.handler = handler
            self.action = action
        }
    
        private func trigger(indexPath: NSIndexPath) {
            handler(action, indexPath)
        }
    }
    
    public extension  UITableViewRowAction  {
        public convenience init(title: String!, style: UITableViewRowActionStyle, exposedHandler: (UITableViewRowAction!, NSIndexPath!) -> Void) {
            self.init(style: style, title: title, handler: exposedHandler)
            var actionTrigger = ActionTrigger(action: self, handler: exposedHandler)
            objc_setAssociatedObject(self, &ActionTriggerHandle, actionTrigger, objc_AssociationPolicy(OBJC_ASSOCIATION_RETAIN_NONATOMIC))
        }
    
        public func trigger(indexPath:NSIndexPath) {
            if let storedActionTrigger = objc_getAssociatedObject(self, &ActionTriggerHandle) as? ActionTrigger {
                storedActionTrigger.trigger(indexPath)
            }
        }
    }
    

    【讨论】:

    • 有趣的是,setAssociatedObject 将更及时地管理清理(如果我错了,请纠正我),这将消除我需要清理的需要。但是,我会返回处理程序,而不是使用触发方法来不将测试代码行为强加给生产代码。但有趣的选择。谢谢。
    【解决方案2】:

    你不能继承UITableViewRowAction。将其视为final 类。 (Objective-C 没有正式的声明 final 类的方式,但非正式地,由于缺少公开声明的初始化程序,这个类在 final 中。)

    如果您查看原始 Obj-C 代码,UITableViewRowAction 实例只能使用类方法创建:

    + (instancetype)rowActionWithStyle:(UITableViewRowActionStyle)style
                                 title:(NSString *)title
                               handler:(void (^)(UITableViewRowAction *action, NSIndexPath *indexPath))handler;
    

    (该类方法被转换为您在 Swift 中看到的便捷初始化器。)

    UITableViewRowAction 没有声明任何初始化器。此外,它的style 属性是只读的,不能在上面的类方法之外赋值。

    无法在此类方法之外实例化UITableViewRowAction,因此无法继承UITableViewRowAction

    【讨论】:

    • 谢谢,但我不想将 Objective-C 代码添加到我的应用程序中。我发现了一个丑陋的走动,我会在线程中发布它。
    【解决方案3】:

    我发现了一个丑陋的走动(代码需要清理)。

    四处走走涉及 UITableViewRowAction 的扩展。 不幸的是,无法将存储添加到实例中。 因此添加了一个静态数组来为创建的不同实例添加存储。 但这会引发一个保留周期问题,这里使用“WeakWrapper”会破坏该问题。

    我愿意接受更好的建议。

    import UIKit
    import FoundationAdditions
    
    public extension  UITableViewRowAction  {
    
        private static var storedHandlers = [handlerStorage]()
        var handler : ((UITableViewRowAction!, NSIndexPath!) -> Void)! {
            get {
                var needCleanUp = false
                var result = {(a: UITableViewRowAction!, b: NSIndexPath!) -> Void in }
                for storage in UITableViewRowAction.storedHandlers {
                    if let x = storage.instance.item {
                        if x === self {
                            result = storage.handler
                            break
                        }
                    } else {
                        needCleanUp = true
                    }
                }
    
                if needCleanUp {
                    //  DO the CLEAN UP
                }
    
                return result
            }
        }
    
        public convenience init(style: UITableViewRowActionStyle, title: String!, storedHandler: (UITableViewRowAction!, NSIndexPath!) -> Void) {
            self.init(style: style, title: title, handler: storedHandler)
    
            let x = handlerStorage(instance: self, handler: storedHandler)
            UITableViewRowAction.storedHandlers.append(x)
        }
    }
    
    private class handlerStorage {
        let instance : WeakWrapper<UITableViewRowAction>
        let handler : ((UITableViewRowAction!, NSIndexPath!) -> Void)
    
        init(instance: UITableViewRowAction, handler: (UITableViewRowAction!, NSIndexPath!) -> Void) {
            self.instance = WeakWrapper<UITableViewRowAction>(item: instance)
            self.handler = handler
        }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2023-02-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多