返回

iOS Google Places API 自动完成失败排查与解决

IOS

iOS 应用中 Google Places 自动完成 API 请求失败的排查与解决

最近在 iOS 应用里集成 Google Places 的自动完成 API,遇到了一些问题,请求总是失败,而且错误信息还很笼统。错误日志大概长这样:

Domain=com.google.places.ErrorDomain Code=-1 "Something went wrong with the connection to the Places API server." UserInfo={NSLocalizedFailureReason=Something went wrong with the connection to the Places API server., NSUnderlyingError=0x6000035a8000 {Error Domain=com.google.places.api.server.ErrorDomain Code=-1 "(null)" UserInfo={NSUnderlyingError=0x6000035c5890 {Error Domain=NSURLErrorDomain Code=-999 "cancelled" UserInfo={NSErrorFailingURLStringKey=https://maps.googleapis.com/maps/api/place/autocomplete/json?key={apiKey}&sessiontoken=BC3ED61C-AA05-4F3B-8F5E-B94AB5380CA5&input=texa, NSErrorFailingURLKey=https://maps.googleapis.com/maps/api/place/autocomplete/json?key={apiKey}&sessiontoken=BC3ED61C-AA05-4F3B-8F5E-B94AB5380CA5&input=texa, _NSURLErrorRelatedURLSessionTaskErrorKey=( "LocalDataTask <61FD3877-12B9-4294-94F8-5FFE135978D2>.<4>" ), _NSURLErrorFailingURLSessionTaskErrorKey=LocalDataTask <61FD3877-12B9-4294-94F8-5FFE135978D2>.<4>, NSLocalizedDescription=cancelled}}}}}

看到这个错误,"Something went wrong",很头疼,完全不知道问题出在哪儿。 别急,下面咱们就来一步步排查,找出原因并解决它。

一、问题根源分析

出现这种"笼统"的错误,可能有好几个原因,咱们按可能性从大到小梳理一下:

  1. API Key 问题 : 最常见的情况,API Key 没配置对,或者被禁用了。
  2. 网络问题 : 用户的设备没网,或者网络环境不稳定。
  3. 请求参数错误 : 比如,必填参数没填,或者格式不对。
  4. API 配额超限 : 免费配额用完了,或者请求太频繁。
  5. iOS Bundle ID 限制 : API Key 开启了 Bundle ID 限制, 但是输入了错误的ID。
  6. 代码逻辑问题 : 请求被取消,或代码实现存在Bug。
  7. Google 服务端问题 : 虽然不常见,但 Google 服务端偶尔也会抽风。

从给出的错误日志看,NSURLErrorDomain Code=-999 "cancelled",请求被取消了,这个信息有点价值。这说明问题可能出在网络层面,客户端取消,或代码层面。

二、问题排查与解决方案

知道了可能的原因,我们就逐一排查。

1. 检查 API Key 和启用状态

这是最容易忽视,但最容易解决的问题。

  • 原理: Google Maps Platform 的 API 需要有效的 API Key 才能正常使用。
  • 步骤:
    1. 登录 Google Cloud Console (console.cloud.google.com)。
    2. 找到你的项目。
    3. 在 "API 和服务" 中找到 "凭据"。
    4. 检查你使用的 API Key 是否存在,并且状态是 "有效"。
    5. 检查 API Key 是否启用了 "Places API"。如果没有, 开启。

2. 验证 API Key 限制 (Application Restrictions)

确保你的 iOS 应用的 Bundle Identifier 已正确配置到 API Key 的限制列表中。

  • 原理: 为了安全,你可以限制哪些应用可以使用你的 API Key。
  • 步骤:
    1. 在 Google Cloud Console 的 "凭据" 页面,找到你的 API Key,点击编辑。
    2. 在 "应用限制" (Application restrictions) 部分,选择 "iOS apps"。
    3. 在列表中检查是否有你的应用的 Bundle Identifier,并且完全匹配。
    4. 如果没有或者不匹配,添加或修改它。 记住要保存。

3. 网络连接检查

确保设备能够正常访问互联网。

  • 原理: Places API 请求需要通过网络发送到 Google 服务器。

  • 步骤:

    • 打开浏览器,试试能不能访问 google.com。
    • 可以在代码里使用 NetworkReachability (Swift) 或 SCNetworkReachability (Objective-C) 来检测网络状态。
    • Swift 示例:
        import SystemConfiguration
    
        func isNetworkAvailable() -> Bool {
            var zeroAddress = sockaddr_in()
            zeroAddress.sin_len = UInt8(MemoryLayout.size(ofValue: zeroAddress))
            zeroAddress.sin_family = sa_family_t(AF_INET)
    
            let defaultRouteReachability = withUnsafePointer(to: &zeroAddress) {
                $0.withMemoryRebound(to: sockaddr.self, capacity: 1) {zeroSockAddress in
                    SCNetworkReachabilityCreateWithAddress(nil, zeroSockAddress)
                }
            }
    
            var flags = SCNetworkReachabilityFlags()
            if !SCNetworkReachabilityGetFlags(defaultRouteReachability!, &flags) {
                return false
            }
            let isReachable = (flags.rawValue & UInt32(kSCNetworkFlagsReachable)) != 0
            let needsConnection = (flags.rawValue & UInt32(kSCNetworkFlagsConnectionRequired)) != 0
            return (isReachable && !needsConnection)
        }
    

4. 检查请求参数

仔细检查你的请求参数,确保它们符合 API 文档的要求。

  • 原理: API 有自己的格式和数据要求。
  • 检查内容:
    • input: 必填,用户输入的文本。
    • key: 必填,你的 API Key。
    • sessiontoken: 建议使用,用于标识一个自动完成会话,节省费用。
    • 其他可选参数,比如 location, radius, language 等,根据需求检查是否正确。
  • 示例URL(GET 请求):
https://maps.googleapis.com/maps/api/place/autocomplete/json?input=Paris&key=YOUR_API_KEY&sessiontoken=UNIQUE_SESSION_TOKEN

5. 检查 API 配额和计费

虽然不太可能直接导致 "cancelled" 错误,但还是值得检查一下。

  • 原理: Google Maps Platform 有免费配额,超过配额需要付费。
  • 步骤:
    1. 在 Google Cloud Console,进入 "API 和服务" -> "信息中心"。
    2. 找到 "Places API",查看 "配额" 部分。
    3. 确保已经设置好了 账单信息(billing)。

6. 仔细检查代码逻辑: 请求取消的排查

结合 NSURLErrorDomain Code=-999 "cancelled" 这个错误信息,着重排查代码逻辑中可能导致请求被取消的地方。

  • 原理: 有时候,代码中的某些操作可能会意外地取消网络请求。

  • 排查要点:

    • 重复请求: 是否在短时间内发起了多个相同的请求? 可能是 UI 上的问题,导致用户多次触发了请求。
    • URLSessionTask 管理: 如果你手动管理 URLSessionTask,检查是否在不恰当的时候调用了 cancel() 方法。比如: 在View 销毁时取消,在 TableView/CollectionView Cell 重用时取消等。
    • 异步操作 : 检查在进行网络请求的异步操作中,是否有提前返回或者退出导致请求被取消的逻辑。
    • 第三方库 : 如果你使用了第三方网络库(比如 Alamofire, Moya),查阅其文档,了解如何处理请求取消,以及是否有相关的配置项。
  • 示例(Swift 使用 URLSession):

    //推荐把 task 作为变量存储, 以备手动取消等操作
    var placesTask: URLSessionDataTask?

    func fetchPlaces(input: String, sessionToken: String) {
        //取消之前的请求 (如果有)
        placesTask?.cancel()

        guard let url = URL(string: "https://maps.googleapis.com/maps/api/place/autocomplete/json?input=\(input)&key=YOUR_API_KEY&sessiontoken=\(sessionToken)") else {
             return
         }

        placesTask = URLSession.shared.dataTask(with: url) { (data, response, error) in
            // ... 处理结果 ...
            if let error = error as NSError?, error.code == NSURLErrorCancelled {
                // 请求被取消, 通常不需要特别处理, 但要排除代码问题.
                print("Request cancelled")
                return;
            }

            // 处理成功和失败情况 ...

        }
        placesTask?.resume()
    }

进阶:Debouncer/Throttler 防抖

在自动完成场景下, 用户输入很快,会产生大量请求,使用 Debouncer 或 Throttler 可以有效减少请求次数。

  • 原理:

    • Debouncer (防抖): 一段时间内,只响应最后一次操作。 比如设定500ms, 用户连续输入,只在停止输入500ms后才发起请求。
    • Throttler (节流): 一段时间内,只响应第一次操作。
  • 示例(Swift, 简单的Debouncer):

import Foundation

class Debouncer {
    private var workItem: DispatchWorkItem?
    private let queue: DispatchQueue
    private let delay: TimeInterval

    init(delay: TimeInterval, queue: DispatchQueue = .main) {
        self.delay = delay
        self.queue = queue
    }

    func debounce(action: @escaping () -> Void) {
        workItem?.cancel()
        let newWorkItem = DispatchWorkItem { action() }
        workItem = newWorkItem
        queue.asyncAfter(deadline: .now() + delay, execute: newWorkItem)
    }
}
//用法:
let debouncer = Debouncer(delay: 0.5) // 0.5 秒

func textFieldDidChange(_ textField: UITextField) {
  debouncer.debounce {
        // 获取 textField.text, 发起 Place Autocomplete 请求
        self.fetchPlaces(input: textField.text ?? "", sessionToken: "...")
    }
}

7. Google 服务端问题

如果以上都排除了,那可能是 Google 服务端的问题。

  • 原理: 服务端问题我们没办法直接解决。
  • 怎么办:
    • 查看 Google Maps Platform 的状态页面 (status.cloud.google.com)。
    • 稍等片刻,或者几个小时后再试。
    • 在 Google Maps Platform 的 Issue Tracker 里搜索或报告问题。

通过以上步骤的排查和处理,基本可以解决 iOS 应用中 Google Places 自动完成 API 请求失败的问题了. 请根据你的情况逐一排查, 早日搞定这个问题!