macOS Swift命令行应用执行Task语句时崩溃问题求助
Swift命令行项目中async/await的Task执行崩溃问题
我在练习Swift的async/await用法时,遇到应用执行Task语句就崩溃的问题,错误信息如下:
Thread 1: EXC_BAD_ACCESS (code=1, address=0x0)
捕获异常后调用栈没有提供有效排查信息,我怀疑是COMMAND LINE模板不支持这种简单形式的Task,相关代码如下:
// // main.swift // import Foundation struct User { var username: String var movies = [Movie]() } struct Movie { var title: String var rating: Double? } class ManagerUsingAsync { func getUsers() async throws -> [User] { let allIsGood = true // Bool.random() if allIsGood { let users = ["mary", "casey", "theo", "dick"].map { User(username: $0) } return users } else { throw URLError(.cannotConnectToHost) } } func getUsersMovies(username: String) async throws -> [Movie] { let allIsGood = true // Bool.random() if allIsGood { let movies = ["breaking away", "diner", "the great escape"].map { Movie(title: $0) } return movies } else { throw URLError(.cannotCreateFile) } } func getMovieRating(title: String) async -> Double? { let allIsGood = true // Bool.random() if allIsGood { return Double.random(in: 1...10) } return nil } } func testAsync() async -> [User] { var _users = [User]() let manager = ManagerUsingAsync() do { _users = try await manager.getUsers() for var user in _users { user.movies = try await manager.getUsersMovies(username: user.username) for var movie in user.movies { movie.rating = await manager.getMovieRating(title: movie.title) } } } catch (let error) { print(error.localizedDescription) } return _users } print("TEST ASYNC/AWAIT...") Task { let users = await testAsync() for user in users { print( "Username: \(user.username) Movies: \(user.movies)" ) } }
问题原因
根本原因不是命令行模板不支持Task,而是命令行程序的主函数执行完毕后会立刻退出进程,而Task是异步执行的,主进程结束时异步任务还在运行,导致内存被释放,触发EXC_BAD_ACCESS错误。
解决方法
1. 可靠等待异步任务完成
修改最后一段代码,通过Task的value属性等待任务执行完成:
print("TEST ASYNC/AWAIT...") let task = Task { let users = await testAsync() for user in users { print( "Username: \(user.username) Movies: \(user.movies)" ) } } // 等待异步任务完成,避免主进程提前退出 try? await task.value
2. 修复值类型修改无效的问题
代码里还有一个逻辑问题:User和Movie是值类型,遍历的时候修改的是副本,原数组里的对象不会被更新。可以改成两种方式:
- 将
User和Movie改为引用类型(class) - 用索引遍历修改原数组元素:
func testAsync() async -> [User] { var _users = [User]() let manager = ManagerUsingAsync() do { _users = try await manager.getUsers() // 用索引遍历修改原数组的User for i in 0..<_users.count { var user = _users[i] user.movies = try await manager.getUsersMovies(username: user.username) // 用索引遍历修改当前User的movies数组 for j in 0..<user.movies.count { var movie = user.movies[j] movie.rating = await manager.getMovieRating(title: movie.title) user.movies[j] = movie } _users[i] = user } } catch (let error) { print(error.localizedDescription) } return _users }
内容的提问来源于stack exchange,提问作者mobibob
相关产品推荐
相关产品推荐

