如何在同一框架内访问 Objective-C 中的内部 Swift class?

How to access an internal Swift class in Objective-C within the same framework?

在混合框架上工作。在 Obj-C 文件中导入,但内部 类 不可见,只有 public 个。

文档明确指出内部分类应该在 Swift 和 Obj-C 之间可用:

Importing Swift into Objective-C
To import a set of Swift files in the same framework target as your Objective-C code, you don’t need to import anything into the umbrella header for the framework. Instead, import the Xcode-generated header file for your Swift code into any Objective-C .m file you want to use your Swift code from. Because the generated header for a framework target is part of the framework’s public interface, only declarations marked with the public modifier appear in the generated header for a framework target. You can still use Swift methods and properties that are marked with the internal modifier from within the Objective-C part of your framework, as long they are declared within a class that inherits from an Objective-C class. For more information on access-level modifiers, see Access Control in The Swift Programming Language (Swift 2).

代码示例(使用框架创建一个新项目)

// SwiftObject.swift

public class SwiftObject: NSObject {
    public class func doSomething() {}
}

internal class YetAnotherSwiftObject: NSObject {
    internal class func doSomething() {}
}

// SomeObject.m file

@implementation SomeObject

- (void)someMethod {
    [SwiftObject doSomething];
}

- (void)someOtherMethod {
    [YetAnotherSwiftObject doSomething]; // Use of undeclared identifier
}

@end

如文档中所示,用 internal 修饰符标记的声明不会出现在生成的 header 中,因此编译器不知道它们,因此会产生投诉。当然,您可以使用 performSelector 方法发送消息,但这并不方便 bug-prone。我们只需要帮助编译器知道那些声明就在那里。

首先,我们需要使用 @objc 属性变体,它允许您在 Objective-C:

中指定交易品种的名称
// SwiftObject.swift

@objc(SWIFTYetAnotherSwiftObject)
internal class YetAnotherSwiftObject: NSObject {
    internal class func doSomething() {}
}

然后你只需要用你想在代码中使用的方法创建 @interface 声明 - 这样编译器会很高兴,并且还应用 SWIFT_CLASS 宏和你的符号名称之前指定 - 因此链接器将选择实际实现:

// SomeObject.m file

SWIFT_CLASS("SWIFTYetAnotherSwiftObject")
@interface YetAnotherSwiftObject : NSObject

+ (void)doSomething;

@end


@implementation SomeObject

- (void)someOtherMethod {
    [YetAnotherSwiftObject doSomething]; // Should work now !!!
}

@end
  • 为了清楚起见,我在 .m 文件中使用了接口声明,更好的选择是将此类声明组合在 .h 文件中,并包含它。
  • 通过在该接口中声明方法,我们向编译器做出了承诺,如果您将不存在的方法(或签名错误等)放在那里,它不会抱怨。显然,您在那种情况下会在运行时崩溃 - 所以要小心。

对我来说,它只是通过检查来工作:"Allow app extension API only"。您可以通过转到项目设置找到它,select 您的目标,然后它位于部署信息下的常规选项卡中。

谁能给我解释一下,为什么这样可以解决问题?

虽然上述解决方案可行 (https://whosebug.com/a/33159964/5945317),但它似乎过于复杂且不直观:

  • 复杂,因为它似乎增加了不必要的东西 – 我将在下面提供更流畅的解决方案。
  • 不直观,因为 objc 宏 SWIFT_CLASS 解析为 SWIFT_RUNTIME_NAME,并且提供的值实际上不是运行时名称 - [=36] 中的 objc class 名称也不是=] 匹配@objc 中的 Swift 属性参数。尽管如此,令人惊讶的是,该解决方案仍然有效——但对我来说,原因尚不清楚。

这是我们在自己的项目中测试过的,我们认为这是更好的解决方案(使用上面的示例):

// YetAnotherSwiftObject.swift

@objc(OBJCPREFIXYetAnotherSwiftObject)
internal class YetAnotherSwiftObject: NSObject {
    @objc internal class func doSomething() {}
}
// OBJCPREFIXYetAnotherSwiftObject.h

@interface OBJCPREFIXYetAnotherSwiftObject : NSObject

+ (void)doSomething;

@end

就是这样。该界面看起来像一个常规的 objc 界面。这提供了额外的好处,您可以将它包含在其他 header 文件中(如果您使用 SWIFT_CLASS 宏则不能这样做,因为它来自自动生成的 Swift header文件,由于循环依赖,你不能将其包含在 objc header 中。

在 Swift 方面,唯一相关的是您为 class 提供正确的对象名称。请注意,我只使用名称前缀来保持语言一致性——你甚至可以在任何地方使用 YetAnotherSwiftObject(即,在 objc header 和 Swift 中的 @objc 属性中——但你需要在任何情况下都要保持此属性具有显式命名,并且需要使其与 header 中的 class 名称保持一致。

如果您正在逐步将 objc 框架转换为 Swift,这也会让您的生活更轻松。您只需像以前一样保留 objc header,现在在 Swift.

中提供实现

Methods and properties that are marked with the internal modifier and declared within a class that inherits from an Objective-C class are accessible to the Objective-C runtime.

所以让我们利用它:

class MyInternalClass: NSObject {
    @objc var internalProperty = 42
}
@interface MyPublicClass()
@end

@implementation MyPublicClass

+ (void) printValue {

    Class myInternalClass = NSClassFromString(@"MyPackageNameIfAny.MyInternalClass");
    id myInternalClassInstance = [myInternalClass new];
    int value = [myInternalClassInstance performSelector:@selector(internalProperty)];

    NSLog(@"Value is %d ", value); // "value is 42"
}

@end

使用SWIFT_CLASS 宏和@objc class 属性很容易导致归档时出错。这种方式比较安全。