12

最初的问题是在苹果论坛上,但没有人能提供帮助。 https://developer.apple.com/forums/thread/654967?answerId=622833022#622833022

我决定问一下。

问题

我正在开发一个显示来自 REST api 的内容的小部件扩展。它显示更新的以下股票信息。

我的小部件用户界面

小部件代码,第 3 部分:推进时间线对我没有帮助。

调查给我带来了一个“这是 iOS 测试版的错误”:

public func timeline(
    for configuration: ConfigurationIntent,
    with context: Context,
    completion: @escaping (Timeline<Entry>) -> ()
  ) {
    print("Provider.timeline: ")

    var entries: [SimpleEntry] = []

    let currentDate = Date()
    entries.append(SimpleEntry(
      date: Calendar.current.date(byAdding: .second, value: 15, to: currentDate)!,
      configuration: configuration
    ))
    
    let timeline = Timeline(entries: entries, policy: reloadPolicy)

    completion(timeline)
  }

以上代码仅在 1 秒内打印Provider.timeline: 14->19 次。


这是我处理网络请求但没有成功的代码:

public func timeline(
    for configuration: ConfigurationIntent,
    with context: Context,
    completion: @escaping (Timeline<Entry>) -> ()
  ) {
    print("Provider.timeline: ")
    
    fetchStocks { (stocks: [Stock], error: Bool) in
      print("fetchStocks: stocks: ", stocks) 
      completion(getTimeLineFromStocks(stocks: stocks, for: configuration, with: context, reloadPolicy: .atEnd))
    }
}

func getTimeLineFromStocks(
    stocks: [Stock],
    for configuration: ConfigurationIntent,
    with context: Context,
    reloadPolicy: TimelineReloadPolicy
  ) -> Timeline<Entry> {
    var entries: [SimpleEntry] = []
    let currentDate = Date()
    entries.append(SimpleEntry(
      date: Calendar.current.date(byAdding: .second, value: 15, to: currentDate)!,
      configuration: configuration,
      stocks: stocks
    ))
    
    let timeline = Timeline(entries: entries, policy: reloadPolicy)
    
    return timeline
  }


  func fetchStocks(completion: @escaping ([Stock], Bool) -> Void) {
    // Fetch stocks info from API    
    myStockService.getSearchResults(searchTerm: "FIT", perPage: 5) { results, errorMessage in
      if !errorMessage.isEmpty {
        print("--> Search error: " + errorMessage)
        completion([], true)
      } else if results == nil {
        print("--> Search result with ERROR: nil results")
        completion([], true)
      } else {
        print("--> searchResults: ", results)
        completion(results!, false)
        // reloadTimeline()
      }
    }
  }



// ------- MyStockService.swift -------
// If I set breakpoint I can see the list of stocks
func getSearchResults(searchTerm: String,  perPage: Int, completion: @escaping QueryResult) {
    // 1
    dataTask?.cancel()
    
    // 2
    if var urlComponents = URLComponents(string: "https://****************/my-stocks") {
      urlComponents.query = "foo=bar"

      // 3
      guard let url = urlComponents.url else {
        return
      }
    
      // 4
      dataTask = defaultSession.dataTask(with: url) { [weak self] data, response, error in
        defer {
          self?.dataTask = nil
        }
        
        // 5
        if let error = error {
          self?.errorMessage += "DataTask error: " + error.localizedDescription + "\n"
        } else if
          let data = data,
          let response = response as? HTTPURLResponse,
          response.statusCode == 200 {
          
            // update the: self?.resultItems, self?.errorMessage
            self?.updateSearchResults(data, perPage: perPage)
          
          // 6
          DispatchQueue.main.async {
            completion(self?.resultItems, self?.errorMessage ?? "")
          }
        }
      }
      
      // 7
      dataTask?.resume()
    }
  }

  func updateSearchResults(....) {
     ... This fn convert data to [Stock] and assign it to resultItems 
  }

我得到了日志:

Provider.timeline: 
Provider.timeline: 
Provider.timeline: 
Provider.timeline: 
Provider.timeline: 
Provider.timeline: 
Provider.timeline: 
--> Search error: DataTask error: cancelled
DataTask error: cancelled
DataTask error: cancelled
DataTask error: cancelled

fetchStocks: stocks:  []
--> Search error: DataTask error: cancelled
DataTask error: cancelled
DataTask error: cancelled
DataTask error: cancelled

fetchStocks: stocks:  []
2020-07-23 18:06:38.131476+0700 my-widgetExtension[5315:1272323] libMobileGestalt MobileGestaltCache.c:166: Cache loaded with 4563 pre-cached in CacheData and 53 items in CacheExtra.
--> Search error: DataTask error: cancelled
DataTask error: cancelled
DataTask error: cancelled
DataTask error: cancelled

fetchStocks: stocks:  []
Provider.timeline: 
Provider.timeline: 
Provider.timeline: 
--> Search error: DataTask error: cancelled
DataTask error: cancelled
DataTask error: cancelled
DataTask error: cancelled
DataTask error: cancelled

fetchStocks: stocks:  []
--> Search error: DataTask error: cancelled
DataTask error: cancelled
DataTask error: cancelled
DataTask error: cancelled
DataTask error: cancelled

fetchStocks: stocks:  []
2020-07-23 18:06:39.751035+0700 my-widgetExtension[5315:1272388] [connection] nw_resolver_start_query_timer_block_invoke [C1] Query fired: did not receive all answers in time for api-t19.24hmoney.vn:443
2020-07-23 18:06:51.891582+0700 my-widgetExtension[5315:1272323] [widget] No intent in timeline(for:with:completion:)

上面的代码不会更新或向小部件 UI 显示 Stock,它有时会出现一些奇怪的崩溃。

任何人都可以帮助我使它工作吗?

4

2 回答 2

3

在等待它稳定约 3 个月后,我回到了小部件开发。

我可以确认,自从我升级到Xcode Version 12.2 beta 2 (12B5025f)昨天以来,我之前的代码基本上可以正常工作。但是我需要删除小部件并创建一个新的小部件以避免过时的代码。

- - 更新 - -

iOS 在某些方面限制了小部件刷新,因此每 5 分钟从 API 刷新一次内容可能并不总是像我预期的那样工作。

请参阅有关有效刷新小部件的官方文档

如文档中所述,使用以下方法来优化小部件刷新:

  • 让包含的应用程序在小部件需要数据之前为小部件准备数据。使用共享组容器来存储数据。
  • 在您的应用程序中使用后台处理时间来使共享数据保持最新。有关更多信息,请参阅使用后台应用刷新更新您的应用。
  • 为所显示的信息选择最合适的刷新策略,如上一节所述。
  • 仅当小部件当前显示的信息发生更改时才调用 reloadTimelines(ofKind:)。
  • 当您的应用处于前台、有活动的媒体会话或使用标准位置服务时,刷新不计入小部件的每日限制
于 2020-10-21T06:52:20.757 回答
0

我不是快速网络方面的专家,但在我看来,你有一个实例dataTask并在发出新请求之前取消它。那么,当您的时间线重新加载时,是否会取消现有请求,从而使您没有任何条目导致它再次重新加载?

Provider如果没有看到您的实现以及重新加载策略,很难说出真正发生的事情。

于 2020-07-24T17:31:09.947 回答