简体   繁体   English

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

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

Working on a mixed framework.在混合框架上工作。 imported inside the Obj-C file but the internal classes are not visible, only the public ones.在 Obj-C 文件中导入,但内部类不可见,只有公共类不可见。

The documentation clearly states the internal clasees should be available between Swift and Obj-C:文档明确指出内部类应该在 Swift 和 Obj-C 之间可用:

Importing Swift into Objective-C将 Swift 导入 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.要在与 Objective-C 代码相同的框架目标中导入一组 Swift 文件,您不需要将任何内容导入到框架的保护伞 header 中。 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.相反,将 Xcode 为您的 Swift 代码生成的 header 文件导入您要使用 Swift 代码的任何 Objective-C.m 文件。 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.因为为框架目标生成的 header 是框架公共接口的一部分,所以只有标有 public 修饰符的声明才会出现在为框架目标生成的 header 中。 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 .您仍然可以在框架的 Objective-C 部分中使用标有内部修饰符的 Swift 方法和属性,只要它们是在继承自 Objective-C class 的 class 中声明的 For more information on access-level modifiers, see Access Control in The Swift Programming Language (Swift 2) .有关访问级别修饰符的更多信息,请参阅The Swift Programming Language (Swift 2)中的访问控制

Code Sample (Create a new project with a framework)代码示例(使用框架创建一个新项目)

// 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

As indicated in the docs, declarations marked with internal modifier don't appear in the generated header, so the compiler does not know about them and thus complaints.如文档中所示,标有internal修饰符的声明不会出现在生成的标头中,因此编译器不知道它们,因此会抱怨。 Of course, you could send messages using performSelector approach, but that's not convenient and bug-prone.当然,您可以使用performSelector方法发送消息,但这不方便且容易出错。 We just need to help the compiler know that those declarations are there.我们只需要帮助编译器知道那些声明就在那里。

First, we need to use @objc attribute variant that allows you to specify name for your symbol in Objective-C:首先,我们需要使用@objc属性变体,它允许您在 Objective-C 中为您的符号指定名称:

// SwiftObject.swift

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

And then you just need to create @interface declaration with the methods you want to use in your code - so the compiler will be happy, and also apply SWIFT_CLASS macro with the symbol name you've specified earlier - so the linker would pick the actual implementation:然后您只需要使用您想要在代码中使用的方法创建@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
  • I've used the interface declaration in .m file just for clarity, the better option would be to combine such declarations in .h file, and include it.为了清楚起见,我在 .m 文件中使用了接口声明,更好的选择是在 .h 文件中组合这些声明,并包含它。
  • By declaring methods in that interface we're making a promise to compiler, and it won't complain if you'll put there a method that does not exist (or with wrong signature, etc.) Obviously, you'll crash in runtime in that case - so be cautious.通过在该接口中声明方法,我们向编译器做出了承诺,如果您将一个不存在的方法(或签名错误等)放在那里,它不会抱怨。显然,您将在运行时崩溃在这种情况下 - 所以要小心。

For me it just worked by checking: "Allow app extension API only".对我来说,它只是通过检查:“仅允许应用扩展 API”来工作。 You find it by going to the project setting, select your target and then it is in the General tab under Deployment Info.您可以通过转到项目设置找到它,选择您的目标,然后它位于部署信息下的常规选项卡中。

Can someone explain to me, why this does solve the problem?有人可以向我解释一下,为什么这确实解决了问题?

While the above solution works ( https://stackoverflow.com/a/33159964/5945317 ), it seems overly complicated and unintuitive:虽然上述解决方案有效( https://stackoverflow.com/a/33159964/5945317 ),但它似乎过于复杂和不直观:

  • Complicated, because it seems to add more things than necessary – I will provide a smoother solution below.复杂,因为它似乎添加了不必要的东西——我将在下面提供一个更流畅的解决方案。
  • Unintuitive, because the objc macro SWIFT_CLASS resolves to SWIFT_RUNTIME_NAME, and the provided value is not actually the runtime name – nor is the objc class name in the header matching the Swift attribute param in @objc.不直观,因为 objc 宏 SWIFT_CLASS 解析为 SWIFT_RUNTIME_NAME,并且提供的值实际上不是运行时名称 - 标头中的 objc 类名称也与 @objc 中的 Swift 属性参数匹配。 Still, surprisingly, the solution works – but to me it is not clear why.尽管如此,令人惊讶的是,该解决方案有效——但对我来说,不清楚为什么。

Here is what we have tested in our own project, and believe to be the better solution (using the example above):这是我们在自己的项目中测试过的,相信是更好的解决方案(使用上面的例子):

// YetAnotherSwiftObject.swift

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

@interface OBJCPREFIXYetAnotherSwiftObject : NSObject

+ (void)doSomething;

@end

That's it.就是这样。 The interface looks like a regular objc interface.该接口看起来像一个常规的 objc 接口。 This gives the added benefit that you can include it in other header files (which you cannot do if you use the SWIFT_CLASS macro, as it comes from the autogenerated Swift header file, which in turn you cannot include in an objc header, due to circular dependency).这提供了额外的好处,您可以将其包含在其他头文件中(如果您使用 SWIFT_CLASS 宏,则不能这样做,因为它来自自动生成的 Swift 头文件,而由于循环依赖)。

On the Swift side, the only thing relevant is that you provide the class with the proper objc name.在 Swift 方面,唯一相关的是您为类提供正确的 objc 名称。 Mind that I only used the name prefix for language consistency – you can even just use YetAnotherSwiftObject everywhere (ie, in the objc header and in the @objc attribute in Swift – but you need to keep this attribute with explicit naming in any case, and need to keep it consistent with the class name in the header).请注意,我仅使用名称前缀来保持语言一致性——你甚至可以在任何地方使用YetAnotherSwiftObject (即,在 objc 头文件和 Swift 中的 @objc 属性中——但在任何情况下你都需要使用显式命名来保留该属性,并且需要保持与标题中的类名一致)。

This also makes your life easier if you're in the process of converting your objc framework step by step to Swift.如果您正在逐步将 objc 框架转换为 Swift,这也会使您的生活更轻松。 You just keep the objc header as before, and now provide the implementation in Swift.您只需像以前一样保留 objc 标头,现在在 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. Objective-C 运行时可以访问用内部修饰符标记并在继承自 Objective-C class 的 class 中声明的方法和属性。

so let's make use of that:所以让我们利用它:

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

Using the SWIFT_CLASS macro and @objc class attribute could easily lead to errors when archiving.使用SWIFT_CLASS宏和@objc class 属性很容易导致归档时出错。 This approach is safer.这种方法更安全。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM