iOS - 在原生模块中使用 Swift
Swift 是在 iOS 上开发原生应用的官方和默认语言。
本指南将探讨如何使用 Swift 编写原生模块。
React Native 的核心主要用 C++ 编写,尽管 Apple 开发了互操作层,但 Swift 和 C++ 之间的互操作性并不理想。
因此,由于语言不兼容性,本指南中编写的模块不会是纯 Swift 实现。您需要编写一些 Objective-C++ 胶水代码,但本指南的目标是最大限度地减少所需的 Objective-C++ 代码量。如果您正在将现有原生模块从旧架构迁移到新架构,这种方法应该允许您重用大部分代码。
本指南从《原生模块》指南的 iOS 实现开始。在深入了解本指南之前,请务必熟悉该指南,并可能实现指南中的示例。
适配器模式
目标是使用 Swift 模块实现所有业务逻辑,并在 Objective-C++ 中设置一个薄薄的胶水层,以便将应用与 Swift 实现连接起来。
您可以通过利用适配器设计模式来实现这一点,将 Swift 模块与 Objective-C++ 层连接起来。
Objective-C++ 对象由 React Native 创建,它保留了对 Swift 模块的引用,并处理其生命周期。Objective-C++ 对象将所有方法调用转发给 Swift。
创建 Swift 模块
第一步是将实现从 Objective-C++ 层移动到 Swift 层。
为此,请遵循以下步骤
- 在 Xcode 项目中创建一个新的空文件,并将其命名为
NativeLocalStorage.swift
- 按照以下方式在您的 Swift 模块中添加实现
import Foundation
@objc public class NativeLocalStorage: NSObject {
let userDefaults = UserDefaults(suiteName: "local-storage");
@objc public func getItem(for key: String) -> String? {
return userDefaults?.string(forKey: key)
}
@objc public func setItem(for key: String, value: String) {
userDefaults?.set(value, forKey: key)
}
@objc public func removeItem(for key: String) {
userDefaults?.removeObject(forKey: key)
}
@objc public func clear() {
userDefaults?.dictionaryRepresentation().keys.forEach { removeItem(for: $0) }
}
}
请注意,您必须将所有需要从 Objective-C 调用的方法声明为 public
并带有 @objc
注解。另请记住,您的类必须继承自 NSObject
,否则无法从 Objective-C 中使用它。
更新 RCTNativeLocalStorage
文件
然后,您需要更新 RCTNativeLocalStorage
的实现,以便能够创建 Swift 模块并调用其方法。
- 打开
RCTNativeLocalStorage.mm
文件 - 按如下方式更新它
// RCTNativeLocalStorage.m
// TurboModuleExample
#import "RCTNativeLocalStorage.h"
+#import "SampleApp-Swift.h"
- static NSString *const RCTNativeLocalStorageKey = @"local-storage";
-@interface RCTNativeLocalStorage()
-@property (strong, nonatomic) NSUserDefaults *localStorage;
-@end
-@implementation RCTNativeLocalStorage
+@implementation RCTNativeLocalStorage {
+ NativeLocalStorage *storage;
+}
-RCT_EXPORT_MODULE(NativeLocalStorage)
- (id) init {
if (self = [super init]) {
- _localStorage = [[NSUserDefaults alloc] initWithSuiteName:RCTNativeLocalStorageKey];
+ storage = [NativeLocalStorage new];
}
return self;
}
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:(const facebook::react::ObjCTurboModule::InitParams &)params {
return std::make_shared<facebook::react::NativeLocalStorageSpecJSI>(params);
}
- (NSString * _Nullable)getItem:(NSString *)key {
- return [self.localStorage stringForKey:key];
+ return [storage getItemFor:key];
}
- (void)setItem:(NSString *)value key:(NSString *)key {
- [self.localStorage setObject:value forKey:key];
+ [storage setItemFor:key value:value];
}
- (void)removeItem:(NSString *)key {
- [self.localStorage removeObjectForKey:key];
+ [storage removeItemFor:key];
}
- (void)clear {
- NSDictionary *keys = [self.localStorage dictionaryRepresentation];
- for (NSString *key in keys) {
- [self removeItem:key];
- }
+ [storage clear];
}
++ (NSString *)moduleName
+{
+ return @"NativeLocalStorage";
+}
@end
代码实际上没有改变。您不是直接创建对 NSUserDefaults
的引用,而是使用 Swift 实现创建一个新的 NativeLocalStorage
,并且每当调用原生模块函数时,该调用都会被转发到 Swift 中实现的 NativeLocalStorage
。
请记住导入 "SampleApp-Swift.h"
头文件。这是一个由 Xcode 自动生成的头文件,其中包含您的 Swift 文件的公共 API,其格式可供 Objective-C 使用。头文件中的 SampleApp
部分实际上是您的应用程序名称,因此如果您创建应用程序时使用的名称与 SampleApp
不同,则必须更改它。
另请注意,RCT_EXPORT_MODULE
宏不再需要,因为原生模块是使用 package.json
注册的,如此处所述。
这种方法在接口中引入了一些代码重复,但它允许您以很少的额外工作量重用代码库中可能已有的 Swift 代码。
实现桥接头文件
如果您是库作者,开发将作为单独库分发的原生模块,则不需要此步骤。
将 Swift 代码与 Objective-C++ 对应部分连接的最后一步是桥接头文件。
桥接头文件是一个您可以导入所有需要被 Swift 代码可见的 Objective-C 头文件的文件。
您的代码库中可能已经有桥接头文件,但如果没有,您可以按照以下步骤创建一个新的
- 在 Xcode 中,创建一个新文件并将其命名为
"SampleApp-Bridging-Header.h"
- 按如下方式更新
"SampleApp-Bridging-Header.h"
的内容
//
// Use this file to import your target's public headers that you would like to expose to Swift.
//
+ #import <React-RCTAppDelegate/RCTDefaultReactNativeFactoryDelegate.h>
- 在您的项目中链接桥接头文件
- 在项目导航器中,选择您的应用程序名称(左侧的
SampleApp
) - 点击
Build Settings
- 筛选
"Bridging Header"
- 添加“桥接头文件”的相对路径,示例中为
SampleApp-Bridging-Header.h
- 在项目导航器中,选择您的应用程序名称(左侧的
构建并运行您的应用程序
现在您可以按照《原生模块指南》的最后一步操作,您应该会看到您的应用程序运行着一个用 Swift 编写的原生模块。