【问题标题】:How to access an internal Swift class in Objective-C within the same framework?如何在同一框架内访问 Objective-C 中的内部 Swift 类?
【发布时间】:2015-12-18 10:33:26
【问题描述】:

使用混合框架。在 Obj-C 文件中导入,但内部类不可见,只有公共类。

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

将 Swift 导入到 Objective-C
要将一组 Swift 文件导入到与 Objective-C 代码相同的框架目标中,您不需要 需要将任何内容导入框架的伞头文件中。 相反,为您的 Swift 代码导入 Xcode 生成的头文件 到任何你想从中使用 Swift 代码的 Objective-C .m 文件中。 因为为框架目标生成的标头是 框架的公共接口,只有标有公共的声明 修饰符出现在为框架目标生成的标头中。 你 仍然可以使用标有 来自框架的 Objective-C 部分的内部修饰符, 只要它们是在继承自 Objective-C 类。有关访问级别修饰符的更多信息,请参阅 Access ControlThe 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

【问题讨论】:

  • 当前文档developer.apple.com/documentation/swift/…使用 internal 修饰符标记并在继承自 Objective-C 类的类中声明的方法和属性可供 Objective-C 运行时访问。 但是,它们在编译时不可访问,并且不会出现在为框架目标生成的标头中。

标签: ios objective-c swift frameworks access-modifiers


【解决方案1】:

如文档中所示,标有internal 修饰符的声明不会出现在生成的标头中,因此编译器不知道它们,因此会抱怨。当然,您可以使用performSelector 方法发送消息,但这不方便且容易出错。我们只需要帮助编译器知道那些声明就在那里。

首先,我们需要使用@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 文件中并包含它。
  • 通过在该接口中声明方法,我们向编译器做出承诺,如果您将不存在的方法(或签名错误等)放在那里,它不会抱怨。显然,您会在这种情况下会在运行时崩溃 - 所以要小心。

【讨论】:

  • 不错!我不知道那个宏,谷歌没有显示太多。你有任何来自 Apple 文档的参考资料吗?
  • 不错吗?这是非常冗长和混乱的。这真的是你需要做的吗?我遇到了同样的问题,甚至苹果也应该设法做比这更好的事情。我要放弃这个,回到目标c,这太不合理了! +1 用于发布实际有效的内容(经过数小时的搜索)。
  • 根据 Apple 文档“您仍然可以在框架的 Objective-C 部分中使用标记为 internal 修饰符的 Swift 方法和属性,只要它们是在继承自的类中声明的一个 Objective-C 类”,如果 swift 类派生自另一个 objc 类,它应该在 objc 代码中可用。我有类似的问题,我用 @objc 标记了我的 swift 类,它继承自 NSObject 但在 objc 代码中仍然不可用。
【解决方案2】:

对我来说,它只是通过检查:“仅允许应用扩展 API”来工作。您可以通过转到项目设置找到它,选择您的目标,然后它位于部署信息下的常规选项卡中。

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

【讨论】:

  • 我爱你!哈哈 。这是地球上唯一找到绕过这个愚蠢限制的答案的地方。不应要求任何框架将其 Swift 类公开给 API 使用者,只是为了能够与内部 ObjC 代码进行互操作!
  • 小心尝试这个选项!它将在自动生成的标题中列出类和方法。仅当您不关心将其保密时才使用它。
  • 但是勾选了这个,会有一堆Cocoa API缺失。
【解决方案3】:

虽然上述解决方案有效 (https://stackoverflow.com/a/33159964/5945317),但它似乎过于复杂和不直观:

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

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

// YetAnotherSwiftObject.swift

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

@interface OBJCPREFIXYetAnotherSwiftObject : NSObject

+ (void)doSomething;

@end

就是这样。该接口看起来像一个常规的 objc 接口。这带来了额外的好处,您可以将其包含在其他头文件中(如果您使用 SWIFT_CLASS 宏,则无法执行此操作,因为它来自自动生成的 Swift 头文件,而您又无法将其包含在 objc 头文件中,因为循环依赖)。

在 Swift 方面,唯一相关的是您为类提供正确的 objc 名称。请注意,我只使用名称前缀来保持语言一致性——你甚至可以在任何地方使用YetAnotherSwiftObject(即,在 Swift 中的 objc 标头和 @objc 属性中——但无论如何你都需要使用显式命名来保留这个属性, 并且需要和头部中的类名保持一致)。

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

【讨论】:

    【解决方案4】:

    使用 internal 修饰符标记并在继承自 Objective-C 类的类中声明的方法和属性可供 Objective-C 运行时访问。

    所以让我们利用它:

    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 类属性在归档时很容易导致错误。这种方法更安全。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-09-08
      • 2013-08-24
      • 1970-01-01
      • 2023-03-15
      • 1970-01-01
      • 1970-01-01
      • 2019-04-09
      • 2015-10-19
      相关资源
      最近更新 更多