如何使用Swift中的协议公开Objective-C对象的私有类方法?

时间:2016-06-29 19:17:50

标签: ios objective-c swift uicolor swift-protocols

考虑UIColor上的两个私有方法:

  1. 实例方法styleString,它返回颜色的RGB字符串
  2. 类方法_systemDestructiveTintColor,它返回破坏性按钮使用的红色。
  3. UIColor.h private header for reference

    对于实例方法,我可以创建@objc协议并使用unsafeBitCast来公开私有方法:

    @objc protocol  UIColorPrivate {
        func styleString() -> UIColor
    }
    
    let white = UIColor.whiteColor()
    let whitePrivate = unsafeBitCast(white, UIColorPrivate.self)
    whitePrivate.styleString() // rgb(255,255,255)
    

    但是,我不确定这对于类方法有什么用。

    首次尝试:

    @objc protocol UIColorPrivate {
        class func _systemDestructiveTintColor() -> String // Error: Class methods are only allowed within classes
    }
    

    有道理,我会将其更改为static

    @objc protocol UIColorPrivate {
        static func _systemDestructiveTintColor() -> String
    }
    
    let colorClass = UIColor.self
    let privateClass = unsafeBitCast(colorClass, UIColorPrivate.self) // EXC_BAD_ACCESS
    

    这会导致崩溃。好吧,这无处不在。我可以使用桥接头,只是将类方法公开为@interface,但有没有办法在纯Swift中公开这些私有类方法?

    我可以使用performSelector执行此操作,但我宁愿将该方法公开为接口或协议:

    if UIColor.respondsToSelector("_systemDestructiveTintColor") {
        if let red = UIColor.performSelector("_systemDestructiveTintColor").takeUnretainedValue() as? UIColor {
            // use the color
        }
    }
    

3 个答案:

答案 0 :(得分:3)

  

但有没有办法公开这些私有类方法

这就像是说你要一份由牛排组成的素食餐。你可以吃牛排,但这不是纯素餐。单词"私人"和"暴露"是对立的。

使用Objective-C的动态消息解决了这个问题。您可以通过performSelector及其系列方法使用它。你已经知道了这一点,所以很难看到还有什么可以想要的。

如果您更喜欢使用#selector语法,则可以使用包含目标函数的class方法创建虚拟static协议,以便为自己提供引用该方法的方法。然而,你的整个unsafeBitCast路线无处可去。

编辑您可以通过强制转换为AnyObject将任何已知消息发送到任何Objective-C对象,并且可以使用我的虚拟class协议使消息为已知消息:

@objc protocol Dummy {
    func hiddenMethod()
}
(someObject as AnyObject).hiddenMethod()

但我不明白为什么这比协议和#selector语法更好。

答案 1 :(得分:3)

unsafeBitCast()是一种访问私有API的糟糕方式。

  

有没有办法在纯Swift中公开这些私有类方法?

问题的答案 - 使用带有计算属性的扩展名。您仍然必须使用执行选择器,但您可以获得类型安全。

extension UIColor {
    static var systemDestructiveTintColor: UIColor {
        let privateColor = Selector("_systemDestructiveTintColor")
        if UIColor.respondsToSelector(privateColor),
            let red = UIColor.performSelector(privateColor).takeUnretainedValue() as? UIColor {
                return red
            }
        return UIColor.redColor()
    }
}

用法

let color = UIColor.systemDestructiveTintColor
print(color)

更新 - 分解不安全比特

要在评论中解决问题:

  那么他们为什么要首先公开unsafeBitCast API呢?

unsafeBitCast()的文档说明如下:

  
      
  • 警告:打破Swift类型系统的保证;小心使用。几乎总有一种更好的方法可以做任何事情。
  •   

unsafeBitCastswift/stdlib/public/core/Builtin.swift中定义为

@_transparent
public func unsafeBitCast<T, U>(_ x: T, to: U.Type) -> U {
  _precondition(sizeof(T.self) == sizeof(U.self),
    "can't unsafeBitCast between types of different sizes")
  return Builtin.reinterpretCast(x)
}

Builtin.reinterpretCastswift/include/swift/AST/Builtins.def中定义为

/// reinterpretCast has type T -> U.
BUILTIN_SIL_OPERATION(ReinterpretCast, "reinterpretCast", Special)

ReinterpretCast是C ++标识符,"reinterpretCast"是Swift中的字符串名称,Special是函数的属性,表示以下(source):

  

内置版具有自定义处理功能

那自定义处理在哪里?在swift/lib/AST/Builtins.cpp

case BuiltinValueKind::ReinterpretCast:
  if (!Types.empty()) return nullptr;
  return getReinterpretCastOperation(Context, Id);
...
...
static ValueDecl *getReinterpretCastOperation(ASTContext &ctx,
                                              Identifier name) {
  // <T, U> T -> U
  // SILGen and IRGen check additional constraints during lowering.
  GenericSignatureBuilder builder(ctx, 2);
  builder.addParameter(makeGenericParam(0));
  builder.setResult(makeGenericParam(1));
  return builder.build(name);
}

摘要

不安全位转换的目的是盲目地将1种对象的类型更改为另一种。协议实例方法恰好起作用,因为如果您将@objc protocol UIColorPrivate { func styleString() -> UIColor }的位视为UIColor.styleString() -> UIColor,则调用正确的方法。

它与类方法不起作用并不奇怪;事实上,我认为这对于例如方法来说是奇迹般的。

答案 2 :(得分:3)

通过协议实现所需的一种方法是为静态方法使用单独的协议。 Objective-C中的静态方法实际上是类的元类的实例方法,因此您可以安全地采用如下方法:

@objc protocol UIColorPrivateStatic {
    func _systemDestructiveTintColor() -> UIColor
}

let privateClass = UIColor.self as! UIColorPrivateStatic
privateClass._systemDestructiveTintColor() // UIDeviceRGBColorSpace 1 0.231373 0.188235 1

这将为您提供私有方法和协议使用的曝光,并且您摆脱了丑陋的unsafeBitCast(并不是强迫演员会更漂亮)。

请注意,如果您正在使用私有API,那么如果Apple决定更改某些类内部的代码,您的代码可能会随时中断。