iOS端React Native调用原生gRPC模块循环调用致服务端崩溃如何解决?
问题根因定位
- RN侧防抖逻辑存在缺陷:使用异步更新的
useState存储调用时间,且无论是否触发原生请求都会更新时间,同时时间校验逻辑存在竞态 - 原生侧频率限制逻辑位置错误:请求完成后才更新最后调用时间,导致请求过程中触发的新调用无法被拦截
- 原生代码存在未处理的异常分支:gRPC请求失败后仅打印日志未调用
reject(),会导致RN侧Promise永久pending,同时存在变量名笔误(resolve(getInfoResponse)应改为resolve(testResponse)) - RN原生模块默认线程调度问题:同步阻塞的
wait()调用如果在主线程执行,可能导致RN事件循环阻塞,重复触发未响应的点击事件
修复方案
1. 修复RN侧调用逻辑
将存储调用时间的变量改为同步更新的useRef,新增调用状态锁避免pending状态下重复触发,仅在符合调用条件时更新时间:
const { SwiftNativeGrpcClient } = NativeModules; export default function App() { const nativeGrpcClient = useRef(SwiftNativeGrpcClient) // 改用useRef存储最后调用时间,同步更新无延迟 const lastCallTime = useRef(new Date()) // 加调用锁,避免pending状态下重复触发 const isCalling = useRef(false) const rnGetGrpcTest = async () => { try { const currentTime = new Date() const timeDiff = currentTime - lastCallTime.current // 加双重校验:时间间隔 + 调用状态锁 if (timeDiff > 1000 && !isCalling.current) { isCalling.current = true // 调用前先更新时间 lastCallTime.current = currentTime await nativeGrpcClient.current.swiftGetGrpcTest() } } catch (error) { console.error(error.message) } finally { // 请求结束后释放锁 isCalling.current = false } } // ... }
2. 修复Swift原生模块逻辑
调整最后调用时间的更新时机,补全异常分支的reject调用,指定原生方法运行在后台队列避免阻塞主线程,新增调用锁避免并发校验失效:
@objc(SwiftNativeGrpcClient) class SwiftNativeGrpcClient: NSObject { // 指定原生模块不需要运行在主线程 @objc static func requiresMainQueueSetup() -> Bool { return false } private let methodQueue = DispatchQueue(label: "com.yourapp.grpcQueue", attributes: .concurrent) // 加调用锁避免并发校验失效 private let callLock = NSLock() // 复用gRPC服务客户端实例,不要每次调用新建 private var grpcServiceClient: Service_ServiceName? // ... @objc func swiftGetGrpcTest( _ resolve: RCTPromiseResolveBlock, rejecter reject: RCTPromiseRejectBlock ) { callLock.lock() defer { callLock.unlock() } print("SwiftNativeGrpcClient.swiftGetGrpcTest()") // 调用前先校验时间,通过立即更新最后调用时间 if (getMilliSecondsSinceLastCall() < 1000) { print("Method called too soon.") reject("0", "Method called too soon", nil) return } self.lastCallTime = DispatchTime.now() // connect to gRPC channel if necessary if (self.secureGrpcChannel == nil) { self.createSecureChannel() } // 复用全局的grpcServiceClient实例 if (self.grpcServiceClient == nil) { self.grpcServiceClient = Service_ServiceName(channel: self.secureGrpcChannel!) } let testRequest: Service_TestRequest = Service_TestRequest() let testCall = self.grpcServiceClient!.getTest(testRequest) do { let testResponse = try testCall.response.wait() print(testResponse) resolve(testResponse) } catch { print("RPC method 'getInfo' failed \(error)") // 异常分支必须调用reject,否则RN侧Promise会永久pending reject("1", "RPC call failed", error) return } } // ... }
3. gRPC通道优化
保持gRPC长连接复用,不要在每次请求结束后主动关闭通道,避免服务端频繁处理通道关闭请求触发未捕获的异常。如果遇到通道断开的情况,统一在原生侧做异步重连,不要每次调用都重建通道。
内容的提问来源于stack exchange,提问作者Adonis Gaitatzis
相关产品推荐
相关产品推荐

