HTTP中的HTTP请求同时在Swift中

时间:2017-09-11 18:19:43

标签: swift asynchronous grand-central-dispatch

在启动我的应用程序时,我会执行一些http请求,一些沉重的http请求(下载一些图像)和一些UIGraphics的繁重任务(例如,使用GraphicsContext从两个UIImages和其他操作中为GMSMarker执行图标)。这需要一些时间,所以我想同时完成所有这些任务。你能告诉我最好的方法吗? 一开始我必须:

  1. 下载并写入本地数据库所有设备
  2. 下载并写入本地数据库所有地理围栏
  3. 下载并向本地数据库写入所有用户
  4. 下载并写入本地数据库所有职位

  5. 下载设备,用户和地理围栏的图片

  6. 为设备,用户和地理围栏设置GMSMarkers(在该对象的图像可用后 - 用于设置标记图标)

  7. 我的登录功能代码(虽然有效,但速度太慢):

    func loginPressed(_ sender: UIButton) {
    
        guard
            let username = self.usernameTextField.text,
            let password = self.passwordTextField.text,
            !username.isEmpty,
            !password.isEmpty
            else {
                return
        }
    
        self.loginButton.isEnabled = false
        self.activityIndicator.startAnimating()
    
        WebService.shared.connect(email: username, password: password) { error, loggedUser in
            guard
                error == nil,
                let loggedUser = loggedUser
                else {
                    self.showAlert(title: "Ошибка подключения", message: error?.localizedDescription ?? "", style: .alert)
                    self.activityIndicator.stopAnimating()
                    self.loginButton.isEnabled = true
                    return
            }
            DB.users.client.insert(loggedUser)
    
    
            print("Start loading user photo...")
            loggedUser.loadPhoto() { image in
                if let image = image {
                    loggedUser.photo = UIImageJPEGRepresentation(image, 0.0)
                }
                print("User photo loaded...")
                loggedUser.marker = UserMarker(loggedUser, at: CLLocation(latitude: 48.7193900, longitude: 44.50183))
                DB.users.client.modify(loggedUser)
            }
    
            DB.geofences.server.getAll() { geofences in
                DB.devices.server.getAll() { devices in
                    DB.positions.server.getAll() { positions in
    
                        for device in devices {
                            device.loadPhoto() { image in
                                if let image = image {
                                    device.photo = UIImageJPEGRepresentation(image, 0.0)
                                }
                                if let position = positions.findById(device.positionId) {
                                    device.marker = DeviceMarker(device, at: position)
                                }
                                device.attributes.battery = device.lastKnownBattery(in: positions)
                            }
                        }
    
                        geofences.forEach({$0.marker = GeofenceMarker($0)})
                        DB.geofences.client.updateAddress(geofences) { geofences in
                            if DEBUG_LOGS {
                                print("Geofences with updated addresses: ")
                                geofences.forEach({print("\($0.name), \($0.address ?? "")")})
                            }
    
                            DB.devices.client.insert(devices)
                            DB.geofences.client.insert(geofences)
                            DB.positions.client.insert(positions)
    
                            self.activityIndicator.stopAnimating()
    
                            WebService.shared.addObserver(DefaultObserver.shared)
                            self.performSegue(withIdentifier: "toMapController", sender: self)
                        }
                    }
                }
            }
        }
    }
    

    不确定在这里发布所有类和对象的代码片段是个好主意,希望你能理解。 任何帮助,将不胜感激。

    P.S。如果你想知道什么是DB,它的数据库,它由两部分组成 - 每组对象的服务器端和客户端,所以第一个任务是从服务器获取所有对象并将它们写入内存(在客户端数据库中)

    P.S。我已经将登录时的“下载所有内容”改为“立即下载所有我需要的内容并稍后下载休息”。所以现在我拥有所有设备,地理围栏和位置后我正在执行MapController,我将在其上显示所有这些对象。刚登录后我正在使用默认的iconView显示deviceMarkers(GMSMarker)。问题是 - 我可以在显示所有对象的地图之后开始下载背景中的设备照片,然后用那些照片刷新标记(当然是在主线程中)吗?

2 个答案:

答案 0 :(得分:0)

默认情况下,网络请求是异步的,因此您要求的已经是预期的行为。

然而,您可以使用诸如then之类的Promises库让您的生活更加简单。

示例用法可能如下所示:

login(email: "foo@bar.com", password: "pa$$w0rd")
    .whenAll(syncDevices(), syncGeofences(), syncUsers(), syncPositions(), syncImages())
    .onError { err in
       // process error
    }
    .finally {
       // update UI
    }

答案 1 :(得分:0)

您的登录功能很慢,因为您正在下载并写入磁盘(正如您在问题中所提到的)“所有”闭包中的数据(地理围栏,设备和/或位置)。此外,所有操作都在主线程中执行。您绝不应该在主线程中执行I / O(联网,写入磁盘),因为此线程主要用于UI更新。您应该使用GCD将昂贵的任务卸载到另一个线程。

此外,值得一提的是,写入磁盘的操作相对较慢,特别是如果您正在为正在下载的每个项目执行此操作。

我建议您只需下载要显示的数据,然后在DispatchQueue(GCD)中使用异步任务,以便在UI中显示数据后将数据下载到磁盘。

我不确定DB.geofences.server.getAll()行对地理围栏,设备和位置(关于如何处理网络或数据库提取)的作用,所以我不能就此提出建议。我可以给你的建议是,按照以下方式构建你的代码:

  1. 当用户登录时,对DB(远程)进行验证并防止有效登录。接下来转换到下一个视图控制器(不要执行所有逻辑),因为我可以看到您对登录操作(对于您的登录按钮)负有太多责任。
  2. 从第二个视图控制器,使用.UserInitiated优先级在另一个线程上异步通过网络调用获取数据(以快速获得结果)。
  3. 完成所有网络操作后,请调用DispatchQueue.main.async { ... }使用您刚获得的数据在主线程中异步更新UI。
  4. 显示下载的数据后,您可以将其保留到本地数据库,最好使用其他DispatchQueue异步任务。
  5. 如果我上面说的任何内容对你没有任何意义,请阅读AppCoda关于GCD here和RayWenderlich的GCD文章here的文章。他们将为您提供有关iOS中GCD的基本知识。完成后,请回过头来尝试按照我上面推荐的方式构建代码。

    我希望这有帮助!