If your iOS project uses Objective-C SDKs, you’ll find that the compiler does a good job of translating those APIs to Swift. Whenever you try to use one of them in Swift, you’ll be greeted with a Swiftified version of it that has common Objective-C standards translated to Swift. Foundation types like NSData
and NSString
will be translated to their Swift variants (Data
, String
), nullables
will become optionals, and the name of the method will change to follow Swift's naming conventions. If the method can fail (by having an error pointer), it will even become a throwing function:
如果您的iOS项目使用Objective-C SDK,您会发现编译器将这些API转换为Swift的工作非常出色。 每当您尝试在Swift中使用其中之一时,都会看到它的Swiftized版本,该版本具有转换为Swift的通用Objective-C标准。 诸如NSData
和NSString
类的基础类型将转换为它们的Swift变体( Data
, String
), nullables
将变为可选值,并且方法的名称将更改为遵循Swift的命名约定。 如果该方法可能失败(通过具有错误指针),则该方法甚至会变成抛出函数:
- (NSData *)dataForRow:(NSInteger)row error:(NSError **)error;
// becomes:
let data: Data = try data(for: row)
In most cases, this does the trick. While I’m not a fan of how the naming can end up sometimes, there are attributes that you can use to finetune the final result (e.g. NS_SWIFT_NAME(customName)
to use a customized name or NS_SWIFT_NOTHROW
to disable the error pointer -> throws conversion).
在大多数情况下,这可以解决问题。 虽然我不喜欢命名有时会如何结束,但是您可以使用一些属性来NS_SWIFT_NAME(customName)
最终结果(例如NS_SWIFT_NAME(customName)
以使用自定义名称,或NS_SWIFT_NOTHROW
以禁用错误指针->引发转换)。
However, what if you want that Objective-C API to become something completely different? One example of a common architectural difference between the languages is the usage of methods versus properties. Most things in Objective-C are methods, while Swift will advise you to use computed properties for things that are computed but yet don’t involve actually processing data. The compiler isn’t that smart, so by default, you’ll end up with methods even if they are better defined as something else in Swift:
但是,如果您希望Objective-C API变成完全不同的东西怎么办? 语言之间常见的体系结构差异的一个示例是方法与属性的使用。 Objective-C中的大多数东西都是方法,而Swift会建议您将计算属性用于已计算但尚未实际处理数据的事物。 编译器不是那么聪明,因此默认情况下,即使在Swift中将它们更好地定义为其他方法,您也会得到方法:
MyClass.sharedInstance() // would work better in Swift as `MyClass.shared`!
Additionally, the automatic API translation doesn’t consider Swift-only features like default arguments and generics in methods — and those are really good tools for developing APIs in Swift. Fortunately, the compiler provides you with a way to completely customize how Objective-C APIs end up in Swift.
此外,自动API转换不考虑仅Swift的功能,例如方法中的默认参数和泛型-这些对于在Swift中开发API确实是很好的工具。 幸运的是,编译器为您提供了一种完全自定义Objective-C API在Swift中最终状态的方式。
NS_REFINED_FOR_SWIFT (NS_REFINED_FOR_SWIFT)
The NS_REFINED_FOR_SWIFT
attribute can be added to Objective-C methods to indicate that you want to have full control of how this API is translated to Swift. When added to an Objective-C API, the compiler will still port it, but it will do so in the shape of a special hidden method that you can use to redefine it as something else. Here's an example of a singleton in Objective-C:
可以将NS_REFINED_FOR_SWIFT
属性添加到Objective-C方法中,以指示您希望完全控制如何将此API转换为Swift。 当添加到Objective-C API时,编译器仍会移植它,但是它将以特殊的隐藏方法的形式进行移植,您可以使用该方法将其重新定义为其他名称。 这是Objective-C中一个单例的示例:
@interface SRMyClass : NSObject
+ (instancetype)sharedInstance;
@end
By default, this will be converted to a sharedInstance()
method in Swift, but in Swift standards, this would look better as a computed property instead.
默认情况下,它将在Swift中转换为sharedInstance()
方法,但是在Swift标准中,作为计算属性,它看起来会更好。
To customize how it’ll be translated, let’s add the attribute to the Objective-C definition:
为了自定义翻译方式,我们将属性添加到Objective-C定义中:
@interface SRMyClass : NSObject
+ (instancetype)sharedInstance NS_REFINED_FOR_SWIFT;
@end
As mentioned, using this attribute won’t stop the method from being migrated to Swift, but it’ll be done in a special way. In the case of methods, their name will be prefixed by two underscores (__
):
如前所述,使用此属性不会阻止方法迁移到Swift,但是会以特殊方式完成。 对于方法,其名称将以两个下划线( __
)作为前缀:
let instance = SRMyClass.__sharedInstance()
The reason for this is precisely to indicate that this method shouldn’t be used as-is. In fact, if you try to implement this example, you’ll notice that while you can use it, it will not show in code completion at all. The intention, instead, is for you to abstract this special method into what you actually want this to look like in Swift. In the case of our singleton example, if we want it to become a computed property, we should define that property in our Swift code and implement it by calling the exposed unrefined method:
这样做的原因恰恰表明该方法不应原样使用。 实际上,如果您尝试实现此示例,则会注意到虽然可以使用它,但它完全不会显示在代码完成中。 相反,其目的是让您将此特殊方法抽象为您实际希望它在Swift中的外观。 在我们的单例示例中,如果我们希望它成为计算属性,则应该在Swift代码中定义该属性,并通过调用暴露的未精炼方法来实现该属性:
extension SRMyClass {
var shared: SRMySingleton {
return __sharedInstance()
}
}
Because the original unrefined method doesn’t even show up in code completion, you can be sure that the developers will always use the correct Swiftified version of it.
由于原始的未优化方法甚至不会在代码完成中显示,因此可以确保开发人员将始终使用正确的Swiftized版本。
My personal favorite use of this attribute is to add default parameters to methods, which is something normally ignored in Objective-C for not being easy to implement but extremely simple and useful in Swift. To do so, we just need to create a version of the method that contains default parameters and internally call the original unrefined one:
我个人最喜欢使用此属性的方法是将默认参数添加到方法中,这在Objective-C中通常被忽略,因为它不易于实现,但在Swift中极其简单和有用。 为此,我们只需要创建一个包含默认参数的方法版本,并在内部调用原始的未优化的方法:
NS_REFINED_FOR_SWIFT
is also a great way to enforce type safety in places where it wouldn't be applicable in Objective-C. In Swift, you can easily abstract unsafe id
(Any
/AnyObject
) Objective-C methods (e.g. under generics).
NS_REFINED_FOR_SWIFT
还是在Objective-C中不适用的地方强制执行类型安全的好方法。 在Swift中,您可以轻松地提取不安全的id
( Any
/ AnyObject
)Objective-C方法(例如,泛型)。
public extension SRPersistance {
func object<T>(forKey key: String) -> T? {
return __object(forKey: key) as? T
}
}
Unfortunately, you can’t redefine entire types with NS_REFINED_FOR_SWIFT
, as only methods, properties, and initializers are supported. But in my experience, that's enough to give legacy code a good Swift experience.
不幸的是,您不能使用NS_REFINED_FOR_SWIFT
重新定义整个类型,因为仅支持方法,属性和初始化程序。 但是以我的经验,这足以为遗留代码提供良好的Swift体验。