如何在同一框架内访问 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 属性很容易导致归档时出错。这种方式比较安全。
在混合框架上工作。在 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 属性很容易导致归档时出错。这种方式比较安全。