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

时间:2023-01-15 15:50:49

Consider two private methods on UIColor:

考虑UIColor的两种私有方法:

  1. The instance method styleString which returns the RGB string of the color
  2. 实例方法styleString,它返回颜色的RGB字符串
  3. The class method _systemDestructiveTintColor which returns the red color used by destructive buttons.
  4. 类方法_systemstructivetintcolor,返回破坏性按钮使用的红色。

UIColor.h private header for reference

用户界面颜色。h专用标头供参考

For instance methods, I can create an @objc protocol and use unsafeBitCast to expose the private method:

例如,我可以创建一个@objc协议并使用unsafeBitCast来公开私有方法:

@objc protocol  UIColorPrivate {
    func styleString() -> UIColor
}

let white = UIColor.whiteColor()
let whitePrivate = unsafeBitCast(white, UIColorPrivate.self)
whitePrivate.styleString() // rgb(255,255,255)

However, I'm not sure how this would work for class methods.

然而,我不确定这对类方法如何起作用。

First attempt:

第一次尝试:

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

Makes sense, I'll change it to static:

有道理,我把它改成static:

@objc protocol UIColorPrivate {
    static func _systemDestructiveTintColor() -> String
}

let colorClass = UIColor.self
let privateClass = unsafeBitCast(colorClass, UIColorPrivate.self) // EXC_BAD_ACCESS

This causes a crash. Well this is going nowhere fast. I could use a bridging header and just expose the class methods as an @interface, but is there a way to expose these private class methods in pure Swift?

这导致崩溃。这不会很快。我可以使用一个桥接头,将类方法公开为@interface,但是有没有一种方法可以用pure Swift公开这些私有类方法?

I could do this with performSelector, but I'd rather expose the method as an interface or protocol:

我可以使用performSelector来实现这一点,但我宁愿将方法作为接口或协议公开:

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

3 个解决方案

#1


3  

One way to achieve what you want via protocols is to use a separate protocol for the static method. Static methods in Objective-C are actually instance methods on the metaclass of the class, so you can safely take an approach like below:

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

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

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

This will give you both exposure of the private method and usage of protocols, and you get rid of the ugly unsafeBitCast (not that a forced cast would be more beautiful).

这将使您对私有方法和协议的使用都有一定的了解,并且您可以摆脱丑陋的不安全角色(不是强制转换更漂亮)。

Just note that as always if you are working with private API's your code can break at any time if Apple decides to change some of the internals of the class.

请注意,如果您使用的是私有API,那么如果Apple决定更改类的一些内部特性,您的代码随时都可能崩溃。

#2


3  

unsafeBitCast() is a terrible way to access private API.

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

is there a way to expose these private class methods in pure Swift?

是否有一种方法可以用pure Swift公开这些私有类方法?

There is an answer to that question - use an extension with a computed property. You still have to use perform selector but you get the type safety.

这个问题有一个答案——使用带有计算属性的扩展。仍然需要使用执行选择器,但是类型安全。

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

Usage

使用

let color = UIColor.systemDestructiveTintColor
print(color)

Update - breaking down unsafebit cast

To address the question in the comments:

在评论中回答这个问题:

then why would they expose the unsafeBitCast API in the first place?

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

The documentation for unsafeBitCast() says the following:

unsafeBitCast()的文档说:

  • Warning: Breaks the guarantees of Swift's type system; use with extreme care. There's almost always a better way to do anything.
  • 警告:打破Swift类型系统的保障;使用极端的保健。几乎总是有更好的方法来做任何事情。

unsafeBitCast is defined in swift/stdlib/public/core/Builtin.swift as

unsafeBitCast定义为swift/stdlib/public/core/Builtin。迅速,

@_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 is defined in swift/include/swift/AST/Builtins.def as

内装式。重新解释cast定义为swift/include/swift/AST/ build .def as

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

ReinterpretCast is the C++ identifier, "reinterpretCast" is the string name in Swift and Special is an attribute of the function which means the following (source):

retcast是c++标识符,“retcast”是Swift的字符串名称,Special是函数的属性,意思是以下(来源):

The builtin has custom proccessing

这幢楼有海关处理。

So where is that custom processing? In swift/lib/AST/Builtins.cpp

那么自定义处理在哪里呢?在斯威夫特/ 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);
}

Summary

The purpose of unsafe bit cast is to blindly change a type of 1 object to another. The protocol instance method just happens to work because if you treat the bits of @objc protocol UIColorPrivate { func styleString() -> UIColor } like it was UIColor.styleString() -> UIColor the correct method is called.

不安全位转换的目的是盲目地将一种类型的对象转换为另一种对象。协议实例方法刚好可以工作,因为如果您将@objc协议UIColorPrivate {func styleString() -> UIColor}的位作为UIColor.styleString() -> UIColor的位,那么正确的方法将被调用。

It's not at all strange that it doesn't work with class methods; in fact i'd say it's miraculous that it works for instance methods.

它不与类方法一起工作一点也不奇怪;事实上,我认为它在实例方法中起作用是不可思议的。

#3


3  

but is there a way to expose these private class methods

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

That's like saying you want a vegan meal consisting of steak. You can have the steak, but that would not be a vegan meal. The words "private" and "expose" are opposites.

这就好比说你想要一顿牛排组成的素食餐。你可以吃牛排,但那不是素食。“私隐”和“暴露”是反义词。

Using Objective-C's dynamic messaging solves the problem. You can use it by way of performSelector and its family of methods. You already know this, so it's hard to see what more can be wanted.

使用Objective-C的动态消息传递解决了这个问题。你可以通过performSelector和它的方法家族来使用它。你已经知道这一点,所以很难看出还需要什么。

If you prefer to use #selector syntax, you can create a dummy class protocol with a static method containing your target function, to give yourself a way to refer to the method. Your whole unsafeBitCast route, however, is going nowhere.

如果您更喜欢使用#selector语法,您可以使用包含目标函数的静态方法创建一个哑类协议,以便为自己提供引用该方法的方法。然而,你的整个不安全的路线却毫无进展。

EDIT You can send any known message to any Objective-C object by casting to AnyObject, and you can use my dummy class protocol to make the message known:

编辑您可以通过对任何对象的强制转换向任何Objective-C对象发送任何已知消息,并且您可以使用我的虚拟类协议来让消息为人所知:

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

But I don't see why this is better than the protocol and #selector syntax.

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

#1


3  

One way to achieve what you want via protocols is to use a separate protocol for the static method. Static methods in Objective-C are actually instance methods on the metaclass of the class, so you can safely take an approach like below:

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

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

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

This will give you both exposure of the private method and usage of protocols, and you get rid of the ugly unsafeBitCast (not that a forced cast would be more beautiful).

这将使您对私有方法和协议的使用都有一定的了解,并且您可以摆脱丑陋的不安全角色(不是强制转换更漂亮)。

Just note that as always if you are working with private API's your code can break at any time if Apple decides to change some of the internals of the class.

请注意,如果您使用的是私有API,那么如果Apple决定更改类的一些内部特性,您的代码随时都可能崩溃。

#2


3  

unsafeBitCast() is a terrible way to access private API.

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

is there a way to expose these private class methods in pure Swift?

是否有一种方法可以用pure Swift公开这些私有类方法?

There is an answer to that question - use an extension with a computed property. You still have to use perform selector but you get the type safety.

这个问题有一个答案——使用带有计算属性的扩展。仍然需要使用执行选择器,但是类型安全。

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

Usage

使用

let color = UIColor.systemDestructiveTintColor
print(color)

Update - breaking down unsafebit cast

To address the question in the comments:

在评论中回答这个问题:

then why would they expose the unsafeBitCast API in the first place?

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

The documentation for unsafeBitCast() says the following:

unsafeBitCast()的文档说:

  • Warning: Breaks the guarantees of Swift's type system; use with extreme care. There's almost always a better way to do anything.
  • 警告:打破Swift类型系统的保障;使用极端的保健。几乎总是有更好的方法来做任何事情。

unsafeBitCast is defined in swift/stdlib/public/core/Builtin.swift as

unsafeBitCast定义为swift/stdlib/public/core/Builtin。迅速,

@_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 is defined in swift/include/swift/AST/Builtins.def as

内装式。重新解释cast定义为swift/include/swift/AST/ build .def as

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

ReinterpretCast is the C++ identifier, "reinterpretCast" is the string name in Swift and Special is an attribute of the function which means the following (source):

retcast是c++标识符,“retcast”是Swift的字符串名称,Special是函数的属性,意思是以下(来源):

The builtin has custom proccessing

这幢楼有海关处理。

So where is that custom processing? In swift/lib/AST/Builtins.cpp

那么自定义处理在哪里呢?在斯威夫特/ 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);
}

Summary

The purpose of unsafe bit cast is to blindly change a type of 1 object to another. The protocol instance method just happens to work because if you treat the bits of @objc protocol UIColorPrivate { func styleString() -> UIColor } like it was UIColor.styleString() -> UIColor the correct method is called.

不安全位转换的目的是盲目地将一种类型的对象转换为另一种对象。协议实例方法刚好可以工作,因为如果您将@objc协议UIColorPrivate {func styleString() -> UIColor}的位作为UIColor.styleString() -> UIColor的位,那么正确的方法将被调用。

It's not at all strange that it doesn't work with class methods; in fact i'd say it's miraculous that it works for instance methods.

它不与类方法一起工作一点也不奇怪;事实上,我认为它在实例方法中起作用是不可思议的。

#3


3  

but is there a way to expose these private class methods

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

That's like saying you want a vegan meal consisting of steak. You can have the steak, but that would not be a vegan meal. The words "private" and "expose" are opposites.

这就好比说你想要一顿牛排组成的素食餐。你可以吃牛排,但那不是素食。“私隐”和“暴露”是反义词。

Using Objective-C's dynamic messaging solves the problem. You can use it by way of performSelector and its family of methods. You already know this, so it's hard to see what more can be wanted.

使用Objective-C的动态消息传递解决了这个问题。你可以通过performSelector和它的方法家族来使用它。你已经知道这一点,所以很难看出还需要什么。

If you prefer to use #selector syntax, you can create a dummy class protocol with a static method containing your target function, to give yourself a way to refer to the method. Your whole unsafeBitCast route, however, is going nowhere.

如果您更喜欢使用#selector语法,您可以使用包含目标函数的静态方法创建一个哑类协议,以便为自己提供引用该方法的方法。然而,你的整个不安全的路线却毫无进展。

EDIT You can send any known message to any Objective-C object by casting to AnyObject, and you can use my dummy class protocol to make the message known:

编辑您可以通过对任何对象的强制转换向任何Objective-C对象发送任何已知消息,并且您可以使用我的虚拟类协议来让消息为人所知:

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

But I don't see why this is better than the protocol and #selector syntax.

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