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

Swift Widget异步API调用未返回,无法加载至Timeline求助

天气Widget异步数据加载问题排查与解决

问题描述

开发天气App的Widget组件时,App内可正常获取天气数据,但Widget中调用异步API后,数据无法加载到Timeline中。控制台先输出No location和no weather,之后才会获取到位置数据和天气API请求结果,但数据始终无法进入Timeline。

相关代码

获取天气的异步方法

func getCurrentWeather(latitude : CLLocationDegrees, longitude : CLLocationDegrees)  async throws -> ResponseBody
{
    let forReal = "https://api.openweathermap.org/data/2.5/weather?lat=\(latitude)&lon=\(longitude)&appid=&units=metric"
    guard let url = URL(string: forReal) else {fatalError("MISSING URL")}

    let urlrequest = URLRequest(url: url)
    let (data, response) = try await URLSession.shared.data(for: urlrequest)
    
    guard (response as? HTTPURLResponse)?.statusCode == 200 else { fatalError("ERROR FETCHING CURRENT WEATHER")}
    let decodedData = try JSONDecoder().decode(ResponseBody.self, from: data)
    print(decodedData)
    return decodedData
}

Widget的getTimeline方法

@State var weather: ResponseBody?
var weatherManager = WeatherManager()

func getTimeline(in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
    var entries: [SimpleEntry] = []
    widgetLocationManager.fetchLocation(handler: { location in
            print(location) })
    
    if let location = widgetLocationManager.locationManager?.location
    {
        Task
        {
            do {
            weather =  try await weatherManager.getCurrentWeather(latitude: location.coordinate.latitude, longitude: location.coordinate.longitude)
            } catch {  print("ERROR GETTING WEATHER:", error)  }
        }
        if let weather = weather
        {
            entries.append(SimpleEntry(date: Date(), feelslike: weather.main.feelsLike, description: weather.weather[0].description))
            entries.append(SimpleEntry(date: Calendar.current.date(byAdding: .minute, value: 60, to: Date())!, feelslike: weather.main.feelsLike, description: weather.weather[0].description))
        } else
        {
            print("no weather")
        }
    }
    else {
        print("No location")
    }
    let timeline = Timeline(entries: entries, policy: .atEnd)
    completion(timeline)
}

问题根源

  1. 位置获取的异步处理错误:widgetLocationManager.fetchLocation是异步回调方法,代码中直接同步判断widgetLocationManager.locationManager?.location是否存在,此时位置还未获取到,所以先输出No location。
  2. Task异步执行不阻塞主线程:Task{}中的异步代码是并发执行的,不会等待它完成就继续往下执行后续逻辑,此时weather还是nil,所以输出no weather,并直接调用completion返回空的Timeline。后续异步任务完成时,Timeline已经生成并返回,无法再更新。
  3. @State在Widget中无效:Widget的TimelineProvider是结构体,@State属性不适合用在这里,且getTimeline是一次性执行的方法,状态不会被保留。
  4. fatalError会导致Widget崩溃:获取天气的方法中使用fatalError,在Widget环境中会直接导致组件崩溃,无法优雅处理错误。

解决方案

1. 修正位置获取的异步逻辑

将后续天气请求逻辑放到位置获取的回调中,确保拿到有效位置后再处理天气数据。

2. 正确处理异步天气请求

在位置回调内启动Task,等待天气请求完成后再创建Timeline并调用completion,确保数据正确传入Entry。

3. 替换fatalError为自定义错误

避免Widget崩溃,改用抛出自定义错误并在上层捕获处理。

修改后的代码

自定义天气错误枚举

enum WeatherError: Error {
    case invalidURL
    case invalidResponse
    case decodingFailed(Error)
}

修正后的getCurrentWeather方法

func getCurrentWeather(latitude : CLLocationDegrees, longitude : CLLocationDegrees) async throws -> ResponseBody {
    let urlString = "https://api.openweathermap.org/data/2.5/weather?lat=\(latitude)&lon=\(longitude)&appid=&units=metric"
    guard let url = URL(string: urlString) else {
        throw WeatherError.invalidURL
    }

    let urlrequest = URLRequest(url: url)
    let (data, response) = try await URLSession.shared.data(for: urlrequest)
    
    guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else {
        throw WeatherError.invalidResponse
    }
    do {
        let decodedData = try JSONDecoder().decode(ResponseBody.self, from: data)
        print(decodedData)
        return decodedData
    } catch {
        throw WeatherError.decodingFailed(error)
    }
}

修正后的getTimeline方法

var weatherManager = WeatherManager()

func getTimeline(in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
    // 依赖异步位置回调,确保拿到位置后再处理
    widgetLocationManager.fetchLocation(handler: { location in
        guard let location = location else {
            print("No location received")
            // 返回默认状态的Entry
            let defaultEntry = SimpleEntry(date: Date(), feelslike: 0, description: "无法获取位置")
            let timeline = Timeline(entries: [defaultEntry], policy: .atEnd)
            completion(timeline)
            return
        }
        
        // 在位置回调内启动异步任务获取天气
        Task {
            do {
                let weather = try await self.weatherManager.getCurrentWeather(
                    latitude: location.coordinate.latitude,
                    longitude: location.coordinate.longitude
                )
                // 天气获取成功,创建Entry
                let currentDate = Date()
                guard let nextUpdateDate = Calendar.current.date(byAdding: .minute, value: 60, to: currentDate) else {
                    throw WeatherError.invalidResponse
                }
                let entries = [
                    SimpleEntry(date: currentDate, feelslike: weather.main.feelsLike, description: weather.weather[0].description),
                    SimpleEntry(date: nextUpdateDate, feelslike: weather.main.feelsLike, description: weather.weather[0].description)
                ]
                let timeline = Timeline(entries: entries, policy: .atEnd)
                completion(timeline)
            } catch {
                print("ERROR GETTING WEATHER:", error)
                // 返回错误状态的Entry
                let errorEntry = SimpleEntry(date: Date(), feelslike: 0, description: "获取天气失败")
                let timeline = Timeline(entries: [errorEntry], policy: .atEnd)
                completion(timeline)
            }
        }
    })
}

关键修改说明

  • 移除了无效的@State变量,改用局部变量存储天气数据
  • 将所有逻辑嵌套到位置获取的回调中,确保获取到位置后再执行天气请求
  • 在Task中等待天气请求完成后才创建Timeline并调用completion,保证数据正确加载
  • 增加了错误处理的 fallback,无论位置或天气请求失败,都返回有效的Timeline
  • 替换fatalError为自定义错误,避免Widget崩溃

内容的提问来源于stack exchange,提问作者Matt Pidden

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.19 04:10:32