4、iOS端MethodChannel实现:在AppDelegate中注册FlutterMethodChannel、处理Flutter端的方法调用、返回结果给Flutter端
好,咱们接着聊。上一章我们把Flutter端的MethodChannel搭好了,现在轮到iOS端上场了。说白了,iOS端要做的事情就三件:注册通道、处理调用、返回结果。听起来简单,但里面有不少细节,我踩过的坑可不少。
4.1 在AppDelegate中注册FlutterMethodChannel
iOS端的入口是AppDelegate。你打开项目里的AppDelegate.swift(或者AppDelegate.m,看你用的是Swift还是OC),这里就是咱们的主战场。
我个人习惯用Swift,代码更简洁。先看核心代码:
import UIKit
import Flutter
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
// 获取Flutter引擎的rootViewController
let controller = window?.rootViewController as! FlutterViewController
// 创建MethodChannel,名字必须和Flutter端完全一致
let batteryChannel = FlutterMethodChannel(
name: "com.example.flutter_battery/battery",
binaryMessenger: controller.binaryMessenger)
// 设置方法调用处理器
batteryChannel.setMethodCallHandler { [weak self] (call: FlutterMethodCall, result: @escaping FlutterResult) in
// 处理来自Flutter的调用
self?.handleBatteryMethodCall(call: call, result: result)
}
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
private func handleBatteryMethodCall(call: FlutterMethodCall, result: @escaping FlutterResult) {
// 具体处理逻辑
}
}
嗯,这里要注意几个关键点:
- 通道名称必须一致:Flutter端和iOS端的
name参数要一模一样,大小写、标点都不能错。我曾经因为一个下划线写成了横线,排查了整整一下午。 - 获取binaryMessenger:通过
FlutterViewController的binaryMessenger属性来创建通道。这是Flutter引擎和原生通信的桥梁。 - 弱引用self:在闭包中使用
[weak self]避免循环引用,这是iOS开发的基本素养。
💡 我的小技巧:建议把通道名称定义成常量,放在一个单独的文件里。Flutter端和iOS端都引用同一个常量,这样就不会出现名字不一致的问题。
4.2 处理Flutter端的方法调用
现在通道注册好了,Flutter端发来的调用会进入setMethodCallHandler的闭包。咱们需要解析FlutterMethodCall对象,它包含两个重要属性:
- method:方法名,字符串类型,对应Flutter端调用的方法名
- arguments:参数,可以是任意类型(String、Number、Map、List等)
来看一个完整的处理示例,这次我们获取设备电量:
private func handleBatteryMethodCall(call: FlutterMethodCall, result: @escaping FlutterResult) {
switch call.method {
case "getBatteryLevel":
// 获取电池电量
let device = UIDevice.current
device.isBatteryMonitoringEnabled = true
let batteryLevel = device.batteryLevel
if batteryLevel > 0 {
// 返回成功结果
result(Int(batteryLevel * 100))
} else {
// 返回错误信息
result(FlutterError(
code: "UNAVAILABLE",
message: "电池信息不可用",
details: nil))
}
case "getDeviceName":
// 获取设备名称
let deviceName = UIDevice.current.name
result(deviceName)
default:
// 未识别的方法
result(FlutterMethodNotImplemented)
}
}
这里有几个关键点:
- 使用switch处理多个方法:一个通道可以处理多个方法调用,用switch清晰明了
- 参数类型转换:如果Flutter端传了参数,比如
call.arguments as? String,记得做类型检查和转换 - 异步操作:如果方法需要异步处理(比如网络请求),记得在闭包中捕获
result,等异步完成后再调用
⚠️ 避坑指南:我曾经在异步回调中忘记调用
result,结果Flutter端一直卡在那里,界面也不响应。记住:每个方法调用必须且只能调用一次result,否则会造成内存泄漏或界面卡死。
4.3 返回结果给Flutter端
返回结果的方式有三种,咱们用表格总结一下:
| 返回类型 | 调用方式 | 适用场景 |
|---|---|---|
| 成功返回 | result(值) |
正常获取到数据,返回任意类型 |
| 错误返回 | result(FlutterError(...)) |
操作失败,需要传递错误码和描述 |
| 未实现 | result(FlutterMethodNotImplemented) |
Flutter调用了iOS端未注册的方法 |
来看一个更复杂的例子,这次我们传递一个字典:
case "getDeviceInfo":
let device = UIDevice.current
let info: [String: Any] = [
"name": device.name,
"systemVersion": device.systemVersion,
"model": device.model,
"batteryLevel": device.batteryLevel >= 0 ? Int(device.batteryLevel * 100) : -1
]
result(info)
Flutter端收到后,可以直接当作Map<String, dynamic>来用。这种传递结构化数据的方式,在实际项目中非常常见。
📌 核心要点:
- 通道名称必须完全一致,这是通信的基础
- 每个方法调用必须调用一次result,不能多也不能少
- 参数和返回值支持基本类型、Map、List,复杂对象需要序列化
- 异步操作时注意捕获result,避免回调时找不到
4.4 完整代码示例
最后,我把完整的AppDelegate.swift贴出来,方便你直接参考:
import UIKit
import Flutter
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
let controller = window?.rootViewController as! FlutterViewController
// 注册电池通道
let batteryChannel = FlutterMethodChannel(
name: "com.example.flutter_battery/battery",
binaryMessenger: controller.binaryMessenger)
batteryChannel.setMethodCallHandler { [weak self] (call, result) in
guard let self = self else { return }
switch call.method {
case "getBatteryLevel":
self.handleGetBatteryLevel(result: result)
case "getDeviceName":
result(UIDevice.current.name)
case "getDeviceInfo":
self.handleGetDeviceInfo(result: result)
default:
result(FlutterMethodNotImplemented)
}
}
// 注册其他通道...
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
private func handleGetBatteryLevel(result: @escaping FlutterResult) {
let device = UIDevice.current
device.isBatteryMonitoringEnabled = true
let batteryLevel = device.batteryLevel
if batteryLevel >= 0 {
result(Int(batteryLevel * 100))
} else {
result(FlutterError(
code: "UNAVAILABLE",
message: "电池信息不可用",
details: nil))
}
}
private func handleGetDeviceInfo(result: @escaping FlutterResult) {
let device = UIDevice.current
device.isBatteryMonitoringEnabled = true
let info: [String: Any] = [
"name": device.name,
"systemVersion": device.systemVersion,
"model": device.model,
"batteryLevel": device.batteryLevel >= 0 ? Int(device.batteryLevel * 100) : -1
]
result(info)
}
}
你看,整个流程其实不复杂。注册通道、处理调用、返回结果,三步走。但每一步都有它的讲究。我刚开始做的时候,经常忘记调用result,或者通道名字写错,导致Flutter端一直收不到响应。嗯,这些坑踩过一次就记住了。
💡 进阶建议:如果你的项目有多个通道,建议把每个通道的处理逻辑封装到单独的类中,不要在AppDelegate里写太多代码。比如创建一个
BatteryChannelHandler类,专门处理电池相关的调用。这样代码更清晰,也方便测试。
好了,iOS端的MethodChannel实现就讲到这里。记住:通道名称一致、result必须调用、参数类型要匹配,这三点做到位,基本不会出大问题。
公众号:蓝海资料掘金营,微信deep3321