如何使用 Swift 中的协议公开 Objective-C object 的私有 class 方法?
How do I expose a private class method of an Objective-C object using protocols in Swift?
考虑 UIColor
上的两个私有方法:
- 实例方法
styleString
其中returns颜色的RGB字符串
- class 方法
_systemDestructiveTintColor
其中 returns 破坏性按钮使用的红色。
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)
但是,我不确定这对 class 方法有何影响。
第一次尝试:
@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
这会导致崩溃。好吧,这不会很快。我可以使用桥接 header 并将 class 方法公开为 @interface
,但是有没有办法以纯 Swift 公开这些私有 class 方法?
我可以用 performSelector
做到这一点,但我宁愿将该方法公开为接口或协议:
if UIColor.respondsToSelector("_systemDestructiveTintColor") {
if let red = UIColor.performSelector("_systemDestructiveTintColor").takeUnretainedValue() as? UIColor {
// use the color
}
}
but is there a way to expose these private class methods
这就像说你想要一顿由牛排组成的素食餐。你可以吃牛排,但那不是纯素餐。 "private"和"expose"这两个词是相反的。
使用Objective-C的动态消息解决了这个问题。您可以通过 performSelector
及其方法系列来使用它。您已经知道这一点,所以很难看出还需要什么。
如果你更喜欢使用 #selector
语法,你可以创建一个虚拟的 class
协议,其中包含一个包含你的目标函数的 static
方法,给你自己一个方法来引用方法。但是,您的整个 unsafeBitCast
路线无处可去。
EDIT 您可以通过转换为 AnyObject 将任何已知消息发送到任何 Objective-C 对象,并且您可以使用我的虚拟 class
协议来制作已知消息:
@objc protocol Dummy {
func hiddenMethod()
}
(someObject as AnyObject).hiddenMethod()
但我不明白为什么这比协议和 #selector
语法更好。
unsafeBitCast()
是访问私有 API.
的糟糕方式
is there a way to expose these private class methods in pure 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)
更新 - 打破 unsafebit 转换
解决评论中的问题:
then why would they expose the unsafeBitCast API in the first place?
unsafeBitCast()
的文档说明如下:
- Warning: Breaks the guarantees of Swift's type system; use with extreme care. There's almost always a better way to do anything.
unsafeBitCast
在swift/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.reinterpretCast
在swift/include/swift/AST/Builtins.def中定义为
/// reinterpretCast has type T -> U.
BUILTIN_SIL_OPERATION(ReinterpretCast, "reinterpretCast", Special)
ReinterpretCast
是 C++ 标识符,"reinterpretCast"
是 Swift 中的字符串名称,Special
是函数的一个属性,表示以下 (source):
The builtin has custom proccessing
那么自定义处理在哪里?在 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 对象的类型更改为另一种对象。协议实例方法恰好可以工作,因为如果你像对待 UIColor.styleString() -> UIColor
一样对待 @objc protocol UIColorPrivate { func styleString() -> UIColor }
的位,则会调用正确的方法。
它不适用于 class 方法一点也不奇怪;事实上,我会说它适用于实例方法真是太神奇了。
通过协议实现所需功能的一种方法是为静态方法使用单独的协议。 Objective-C
中的静态方法实际上是 class 的元 class 上的实例方法,因此您可以安全地采用如下方法:
@objc protocol UIColorPrivateStatic {
func _systemDestructiveTintColor() -> UIColor
}
let privateClass = UIColor.self as! UIColorPrivateStatic
privateClass._systemDestructiveTintColor() // UIDeviceRGBColorSpace 1 0.231373 0.188235 1
这会让你同时暴露私有方法和协议的使用,并且你可以摆脱丑陋的东西unsafeBitCast
(并不是说强制转换会更漂亮)。
请注意,如果您使用的是私有 API,那么如果 Apple 决定更改 class 的某些内部结构,您的代码随时可能会中断。
考虑 UIColor
上的两个私有方法:
- 实例方法
styleString
其中returns颜色的RGB字符串 - class 方法
_systemDestructiveTintColor
其中 returns 破坏性按钮使用的红色。
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)
但是,我不确定这对 class 方法有何影响。
第一次尝试:
@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
这会导致崩溃。好吧,这不会很快。我可以使用桥接 header 并将 class 方法公开为 @interface
,但是有没有办法以纯 Swift 公开这些私有 class 方法?
我可以用 performSelector
做到这一点,但我宁愿将该方法公开为接口或协议:
if UIColor.respondsToSelector("_systemDestructiveTintColor") {
if let red = UIColor.performSelector("_systemDestructiveTintColor").takeUnretainedValue() as? UIColor {
// use the color
}
}
but is there a way to expose these private class methods
这就像说你想要一顿由牛排组成的素食餐。你可以吃牛排,但那不是纯素餐。 "private"和"expose"这两个词是相反的。
使用Objective-C的动态消息解决了这个问题。您可以通过 performSelector
及其方法系列来使用它。您已经知道这一点,所以很难看出还需要什么。
如果你更喜欢使用 #selector
语法,你可以创建一个虚拟的 class
协议,其中包含一个包含你的目标函数的 static
方法,给你自己一个方法来引用方法。但是,您的整个 unsafeBitCast
路线无处可去。
EDIT 您可以通过转换为 AnyObject 将任何已知消息发送到任何 Objective-C 对象,并且您可以使用我的虚拟 class
协议来制作已知消息:
@objc protocol Dummy {
func hiddenMethod()
}
(someObject as AnyObject).hiddenMethod()
但我不明白为什么这比协议和 #selector
语法更好。
unsafeBitCast()
是访问私有 API.
is there a way to expose these private class methods in pure 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)
更新 - 打破 unsafebit 转换
解决评论中的问题:
then why would they expose the unsafeBitCast API in the first place?
unsafeBitCast()
的文档说明如下:
- Warning: Breaks the guarantees of Swift's type system; use with extreme care. There's almost always a better way to do anything.
unsafeBitCast
在swift/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.reinterpretCast
在swift/include/swift/AST/Builtins.def中定义为
/// reinterpretCast has type T -> U.
BUILTIN_SIL_OPERATION(ReinterpretCast, "reinterpretCast", Special)
ReinterpretCast
是 C++ 标识符,"reinterpretCast"
是 Swift 中的字符串名称,Special
是函数的一个属性,表示以下 (source):
The builtin has custom proccessing
那么自定义处理在哪里?在 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 对象的类型更改为另一种对象。协议实例方法恰好可以工作,因为如果你像对待 UIColor.styleString() -> UIColor
一样对待 @objc protocol UIColorPrivate { func styleString() -> UIColor }
的位,则会调用正确的方法。
它不适用于 class 方法一点也不奇怪;事实上,我会说它适用于实例方法真是太神奇了。
通过协议实现所需功能的一种方法是为静态方法使用单独的协议。 Objective-C
中的静态方法实际上是 class 的元 class 上的实例方法,因此您可以安全地采用如下方法:
@objc protocol UIColorPrivateStatic {
func _systemDestructiveTintColor() -> UIColor
}
let privateClass = UIColor.self as! UIColorPrivateStatic
privateClass._systemDestructiveTintColor() // UIDeviceRGBColorSpace 1 0.231373 0.188235 1
这会让你同时暴露私有方法和协议的使用,并且你可以摆脱丑陋的东西unsafeBitCast
(并不是说强制转换会更漂亮)。
请注意,如果您使用的是私有 API,那么如果 Apple 决定更改 class 的某些内部结构,您的代码随时可能会中断。