iOS React Native中Objective-C闭包传递至JavaScript的实现问询
当然可以在 Objective-C 里构造能被 JavaScript 调用的闭包,但在 React Native 桥接场景下——尤其是你给 tipsi-stripe 扩展 Apple Pay 配送/联系人修改支持的场景——很容易因为闭包生命周期、桥接封装的问题导致 JS 端拿到 null。我帮你梳理下核心解决思路和具体实现步骤:
问题根源拆解
React Native 的 Objective-C 桥接对回调的格式、生命周期有严格要求:
- 如果原生端的闭包没有被正确保留,或者没有按 RN 规范封装成可序列化的回调类型,JS 端就会收到
null - Apple Pay 的
PKPaymentAuthorizationViewControllerDelegate回调(比如配送方式、联系人变更)是异步触发的,需要把原生的 completion block 暂存,等 JS 处理完结果后再触发,这一步很容易出错
具体实现方案
1. 暂存原生 Completion Block
首先在你的扩展模块(比如 StripeApplePayModule.m)里添加属性,用来保存 Apple Pay 回调的 completion block,避免它被提前释放:
// 头文件或实现文件里定义属性 @property (nonatomic, copy) void (^shippingMethodCompletion)(PKPaymentAuthorizationStatus, NSArray<PKShippingMethod *> *); @property (nonatomic, copy) void (^contactCompletion)(PKPaymentAuthorizationStatus);
2. 转发原生事件到 JS
在 Apple Pay 的 delegate 方法中,暂存 completion block,然后通过 RN 的事件发射器把事件传递给 JS:
// 处理配送方式变更 - (void)paymentAuthorizationViewController:(PKPaymentAuthorizationViewController *)controller didSelectShippingMethod:(PKShippingMethod *)shippingMethod completion:(void (^)(PKPaymentAuthorizationStatus status, NSArray<PKShippingMethod *> *shippingMethods))completion { // 暂存 completion block self.shippingMethodCompletion = completion; // 把配送方式转成 JS 可识别的字典(自己实现转换逻辑) NSDictionary *shippingMethodDict = [self convertPKShippingMethodToDict:shippingMethod]; // 发送事件到 JS [self sendEventWithName:@"applePayShippingMethodSelected" body:shippingMethodDict]; } // 处理联系人变更 - (void)paymentAuthorizationViewController:(PKPaymentAuthorizationViewController *)controller didSelectContact:(PKContact *)contact completion:(void (^)(PKPaymentAuthorizationStatus status))completion { self.contactCompletion = completion; NSDictionary *contactDict = [self convertPKContactToDict:contact]; [self sendEventWithName:@"applePayContactSelected" body:contactDict]; }
3. JS 端处理并回调原生
在 React Native 代码里监听事件,处理完业务逻辑后调用原生暴露的方法触发 completion:
import { NativeEventEmitter, NativeModules } from 'react-native'; const { Stripe } = NativeModules; const stripeEmitter = new NativeEventEmitter(Stripe); // 监听配送方式变更事件 stripeEmitter.addListener('applePayShippingMethodSelected', async (shippingMethod) => { // 这里处理你的业务逻辑,比如计算新运费、更新配送选项 const updatedShippingMethods = await yourShippingCalculationLogic(shippingMethod); // 调用原生方法完成回调 await Stripe.completeShippingMethodSelection({ status: 'success', // 或 'failure' shippingMethods: updatedShippingMethods }); }); // 监听联系人变更事件 stripeEmitter.addListener('applePayContactSelected', async (contact) => { // 验证联系人信息等逻辑 const isValid = await yourContactValidationLogic(contact); await Stripe.completeContactSelection({ status: isValid ? 'success' : 'failure' }); });
4. 原生端接收 JS 回调并触发 Completion
在 Objective-C 里暴露方法,接收 JS 的处理结果,然后调用之前暂存的 completion block:
// 完成配送方式变更回调 RCT_EXPORT_METHOD(completeShippingMethodSelection:(NSDictionary *)params) { if (!self.shippingMethodCompletion) return; // 转换 JS 传递的状态 PKPaymentAuthorizationStatus status = [params[@"status"] isEqualToString:@"success"] ? PKPaymentAuthorizationStatusSuccess : PKPaymentAuthorizationStatusFailure; // 转换 JS 传递的配送方式数组 NSArray<PKShippingMethod *> *shippingMethods = [self convertDictArrayToPKShippingMethods:params[@"shippingMethods"]]; // 触发原生 completion self.shippingMethodCompletion(status, shippingMethods); // 调用后清空 block,避免循环引用 self.shippingMethodCompletion = nil; } // 完成联系人变更回调 RCT_EXPORT_METHOD(completeContactSelection:(NSDictionary *)params) { if (!self.contactCompletion) return; PKPaymentAuthorizationStatus status = [params[@"status"] isEqualToString:@"success"] ? PKPaymentAuthorizationStatusSuccess : PKPaymentAuthorizationStatusFailure; self.contactCompletion(status); self.contactCompletion = nil; }
关键注意事项
- 循环引用规避:调用完 completion block 后必须把属性置为
nil,否则 block 会持有 self,导致内存泄漏 - 数据序列化:确保
PKShippingMethod、PKContact和 JS 之间的转换逻辑正确,所有传递的数据必须是 RN 桥接支持的类型(字典、数组、字符串等) - 生命周期管理:确保
PKPaymentAuthorizationViewController的 delegate 不会被提前释放,否则回调会失效 - 调试技巧:在原生端加
NSLog检查 completion block 是否被正确保存,JS 端打印事件参数确认数据是否正常接收
内容的提问来源于stack exchange,提问作者Paul Spencer
相关产品推荐
相关产品推荐

