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

Flutter插件iOS报错:[Security] 方法不应在主线程调用

问题:Flutter集成Mastercard Connect SDK时主线程安全警告

错误日志

2022-12-21 23:52:39.678730+0530 Runner[36839:1696429] [Security] This method should not be called on the main thread as it may lead to UI unresponsiveness.

现有代码

import Flutter
import UIKit
import Connect

public class SwiftFinicityPlugin: NSObject, FlutterPlugin {
  var flutterResult: FlutterResult!
  var connectViewController: ConnectViewController!
  var connectNavController: UINavigationController!
  public static func register(with registrar: FlutterPluginRegistrar) {
    let channel = FlutterMethodChannel(name: "finicity", binaryMessenger: registrar.messenger())
    let instance = SwiftFinicityPlugin()
    registrar.addMethodCallDelegate(instance, channel: channel)
  }

  public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
    flutterResult = result;
    guard let args = call.arguments as? [String : Any] else {return}
    let connectUrl = args["connectUrl"] as! String
    let config = ConnectViewConfig(connectUrl: connectUrl, loaded: self.connectLoaded, done: self.connectDone, cancel: self.connectCancelled, error: self.connectError, route: self.connectRoute, userEvent: self.connectUserEvent)
    flutterResult("iOS " + UIDevice.current.systemVersion)
    self.connectViewController = ConnectViewController()
    self.connectViewController.load(config: config)
  }

  func connectLoaded() {
    self.connectNavController = UINavigationController(rootViewController: self.connectViewController)
    self.connectNavController.modalPresentationStyle = .fullScreen
     self.present(self.connectNavController, animated: false)
}

func connectDone(_ data: NSDictionary?) {
  flutterResult("iOS " + UIDevice.current.systemVersion)
}

func connectCancelled() {
     // Connect flow exited prematurely
}

func connectError(_ data: NSDictionary?) {
     // Error encountered in Connect flow
}

func connectRoute(_data: NSDictionary?) {
 // Connect route changed
}

func connectUserEvent(_ data: NSDictionary?) {
 // Connect user event fired in response to user action
}
}

问题分析

  1. 主线程阻塞风险:handle方法是Flutter MethodChannel的回调,默认运行在主线程。直接在主线程调用connectViewController.load(config:),而该方法内部可能包含耗时的安全校验(如证书验证、加密操作),触发了系统的主线程安全警告。
  2. Flutter Result重复调用:handle方法中先调用了一次flutterResult返回系统版本,后续connectDone又再次调用,这会导致Flutter侧抛出"Result already called"的错误。
  3. UI操作线程问题:虽然connectLoaded中的present操作本身需要在主线程,但前置的load操作不应占用主线程资源。

解决方案

修改代码,将耗时的SDK加载操作移至后台队列,确保UI操作回到主线程,同时修复Result重复调用的问题:

import Flutter
import UIKit
import Connect

public class SwiftFinicityPlugin: NSObject, FlutterPlugin {
  private var flutterResult: FlutterResult?
  private var connectViewController: ConnectViewController?
  private var connectNavController: UINavigationController?
  // 获取当前应用的根控制器,用于present弹窗
  private var rootViewController: UIViewController? {
    UIApplication.shared.keyWindow?.rootViewController
  }

  public static func register(with registrar: FlutterPluginRegistrar) {
    let channel = FlutterMethodChannel(name: "finicity", binaryMessenger: registrar.messenger())
    let instance = SwiftFinicityPlugin()
    registrar.addMethodCallDelegate(instance, channel: channel)
  }

  public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
    flutterResult = result
    guard let args = call.arguments as? [String: Any],
          let connectUrl = args["connectUrl"] as? String else {
      result(FlutterError(code: "INVALID_ARGS", message: "缺少必要参数connectUrl", details: nil))
      return
    }

    let config = ConnectViewConfig(
      connectUrl: connectUrl,
      loaded: connectLoaded,
      done: connectDone,
      cancel: connectCancelled,
      error: connectError,
      route: connectRoute,
      userEvent: connectUserEvent
    )

    // 将SDK加载操作移至后台队列,避免阻塞主线程
    DispatchQueue.global(qos: .userInitiated).async { [weak self] in
      guard let self = self else { return }
      self.connectViewController = ConnectViewController()
      self.connectViewController?.load(config: config)
    }
  }

  private func connectLoaded() {
    // UI操作必须回到主线程
    DispatchQueue.main.async { [weak self] in
      guard let self = self, let vc = self.connectViewController else { return }
      self.connectNavController = UINavigationController(rootViewController: vc)
      self.connectNavController?.modalPresentationStyle = .fullScreen
      self.rootViewController?.present(self.connectNavController!, animated: false)
    }
  }

  private func connectDone(_ data: NSDictionary?) {
    // 仅在完成时返回结果,避免重复调用
    flutterResult?(["status": "done", "data": data])
    flutterResult = nil
  }

  private func connectCancelled() {
    flutterResult?(["status": "cancelled"])
    flutterResult = nil
  }

  private func connectError(_ data: NSDictionary?) {
    flutterResult?(FlutterError(code: "CONNECT_ERROR", message: "Connect流程出错", details: data))
    flutterResult = nil
  }

  private func connectRoute(_ data: NSDictionary?) {
    // 可通过EventChannel向Flutter发送路由变更事件,这里暂留空
  }

  private func connectUserEvent(_ data: NSDictionary?) {
    // 可通过EventChannel向Flutter发送用户事件,这里暂留空
  }
}

关键修改点

  • 使用DispatchQueue.global().async将connectViewController.load移至后台线程执行,规避主线程安全警告。
  • 所有UI操作(如present控制器)通过DispatchQueue.main.async切回主线程,符合iOS UI操作规范。
  • 修复flutterResult重复调用问题,仅在Connect流程的完成、取消、错误状态下调用一次,并及时置空避免后续误调用。
  • 添加参数校验,当缺少必要参数时返回明确的Flutter错误。
  • 使用弱引用[weak self]避免循环引用,提升内存安全性。

内容的提问来源于stack exchange,提问作者Gvs Akhil

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.07 23:35:23