You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.24 05:24:07