Expo原生代码在模拟器、macOS正常,iOS TestFlight构建无响应
问题排查:Expo原生模块TestFlight环境无响应
- 开发环境(模拟器、macOS构建)中,Swift编写并通过Objective-C绑定的
sendPhoto原生模块可正常异步发送带图片的POST请求,服务器能接收数据。 - 经EAS构建发布到TestFlight后,在iPhone 12 Pro、iPhone 14 Pro、iPhone 7(均为最新iOS版本)上无任何响应:既不崩溃,也不发送请求。
- 添加了大量
print和os_log调试语句,用于确认函数调用、异步任务执行、请求发送状态,但在iPhone日志存档和控制台中未看到任何调试日志,推测函数可能未被调用,但JavaScript调用时应用未崩溃。 - 原生模块文件结构包含
ReplateModule.swift、ReplateModule.m及桥接文件,未修改npx expo prebuild生成的AppDelegate等文件,怀疑EAS构建时缺少原生模块相关配置,但未找到对应文档。
核心代码
@objc public func sendPhoto( _ token: String, photoName: String, metadata: String, sender: String, resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock ) -> Void { let log = OSLog(subsystem: "it.myorg.manager", category: "ManagerDebugging") print("进入sendPhoto函数") os_log("进入sendPhoto函数", log: log, type: .default) DispatchQueue.global(qos: .userInitiated).async { print("进入异步队列") os_log("进入异步队列", log: log, type: .default) semaphore.wait() sentImagesCounter += 1 semaphore.signal() print("图片计数器已递增") os_log("图片计数器已递增", log: log, type: .default) // 服务器请求地址 guard let url = URL(string: "https://api.myendpoint.it/app/uploader/png") else { print("无效的端点URL") reject("0", "无效的端点URL", nil) os_log("创建URL对象失败", log: log, type: .default) return } // PNG图片数据 guard let image = UIImage(named: photoName), let imageData = image.pngData() else { print("无法获取图片数据。") reject("0", "无法获取图片数据。", nil) os_log("获取图片数据失败", log: log, type: .default) return } let isLast = self.isLast(sender: sender) // 要发送的字符串参数 let parameters: [String : Any] = [ "metadata": metadata, "sender": sender, "last": isLast ] // 创建请求 var request = URLRequest(url: url) request.httpMethod = "POST" print("已创建URL请求") os_log("已创建URL请求", log: log, type: .default) // 创建请求体 let boundary = "Boundary-\(UUID().uuidString)" request.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type") request.setValue(token, forHTTPHeaderField: "Authorization") var data = Data() // 向请求体添加图片数据 data.append("--\(boundary)\r\n".data(using: .utf8)!) data.append("Content-Disposition: form-data; name=\"image\"; filename=\"\(photoName)\"\r\n".data(using: .utf8)!) data.append("Content-Type: image/png\r\n\r\n".data(using: .utf8)!) data.append(imageData) data.append("\r\n".data(using: .utf8)!) // 向请求体添加字符串参数 for (key, value) in parameters { data.append("--\(boundary)\r\n".data(using: .utf8)!) data.append("Content-Disposition: form-data; name=\"\(key)\"\r\n\r\n".data(using: .utf8)!) data.append("\(value)\r\n".data(using: .utf8)!) } data.append("--\(boundary)--\r\n".data(using: .utf8)!) // 设置请求体 request.httpBody = data // 执行请求 let task = URLSession.shared.dataTask(with: request) { data, response, error in if let error = error { os_log("发送请求失败", log: log, type: .default) print("请求错误: \(error)") return } if let data = data { // 处理服务器响应 os_log("请求已发送", log: log, type: .default) print("服务器响应: \(String(data: data, encoding: .utf8) ?? "")") } } task.resume() print("已调用task.resume()") os_log("已调用task.resume()", log: log, type: .default) // 使用信号量处理竞态条件 semaphore.wait() resolve(sentImagesCounter) if(isLast){ sentImagesCounter = 0 print("重置图片计数器") os_log("重置图片计数器", log: log, type: .default) } semaphore.signal() print("异步调用结束") os_log("异步调用结束", log: log, type: .default) } } @objc private func isLast(sender: String) -> Bool{ semaphore.wait() let isLast = sentImagesCounter == requiredPhotos semaphore.signal() return isLast }
排查方向与解决方案
1. 确认原生模块在EAS构建中的注册状态
- 检查
ReplateModule.m是否正确声明模块和方法:确保RCT_EXPORT_MODULE()和RCT_EXPORT_METHOD(sendPhoto:photoName:metadata:sender:resolver:rejecter:)(参数名需与Swift函数完全匹配)存在且格式正确。 - 验证桥接文件(如
YourProject-Bridging-Header.h)是否正确导入Swift模块头文件,且Xcode项目设置中已配置桥接路径(EAS构建会读取Xcode配置,需确认prebuild后路径未被覆盖)。
2. 处理Release环境的日志输出限制
- TestFlight属于Release环境,
print语句会被系统优化移除,os_log需通过特定方式查看:- 用Xcode的
Window > Devices and Simulators选中目标设备,点击View Device Logs,搜索subsystem(it.myorg.manager)和category(ManagerDebugging)。 - 或在设备
设置 > 隐私与安全性 > 分析与改进 > 分析数据中查找应用日志文件。
- 用Xcode的
- 临时开启Release日志:修改Xcode Build Settings,将
OS_ACTIVITY_MODE设为default,EAS构建可在eas.json的build配置中添加xcodebuild参数实现。
3. 检查Release环境下的资源访问问题
UIImage(named: photoName)在Release环境可能加载失败:确保图片已添加到Assets.xcassets且EAS构建时被正确打包;或改用UIImage(contentsOfFile:)传入图片绝对路径,避免依赖资源目录加载机制。
4. 排查异步任务与信号量的潜在死锁
- 确认
semaphore初始化值为1(let semaphore = DispatchSemaphore(value: 1)),若初始值为0,异步队列中semaphore.wait()会导致线程阻塞,后续代码无法执行。 - 检查
isLast方法中信号量的使用,避免嵌套等待引发死锁。
5. 验证网络请求的权限与环境差异
- 确认iOS设备已授予应用网络权限(在设置中查看应用的蜂窝网络权限)。
- Release环境下URLSession请求可能被ATS拦截:检查服务器HTTPS证书有效性,若需特殊配置,在
app.json的ios.infoPlist中添加NSAppTransportSecurity相关设置。
6. 检查EAS构建的配置完整性
- 确保
app.json/app.config.js中正确声明原生模块:Expo自定义模块需在expo.modules中添加模块信息,或确认npx expo prebuild已生成正确的模块注册代码。 - 查看EAS构建日志,搜索原生模块编译、注册相关的警告或错误,比如模块未链接、编译失败等。
内容的提问来源于stack exchange,提问作者Filippo Cavallari
相关产品推荐
相关产品推荐

