同步与异步网络的优缺点是什么?

时间:2018-10-19 17:42:33

标签: android ios

大量文档明确表示您应该使用异步代码进行联网,对此我感到非常沮丧。

我什至读过一篇文章,直言“网络是一种固有的异步操作。”

https://react-native.org/doc/network.html

因此,首先在后台处理和异步代码之间存在一个差异

例如,异步运行代码并不一定意味着它在后台。为此,我们实际上可以使用后台线程。

当您编写一个iOS应用程序时,您有多个视图控制器,每个视图控制器都访问由模型下载的相同数据,而当异步下载数据用于代码时,您会感到沮丧的是,回调和异步消息被传递整个应用程序中。

当我有多个使用相同数据的视图控制器时,这会带来问题,如何确保我没有打开下载数据之前打开视图的视图控制器?您可能无法知道首先打开哪个控制器,所以这带来了一个问题,如何确保它们在完成下载之前不访问数据?

我想您使用完成处理程序和模型解决此问题,然后在完成下载时触发​​一个调用控制器的键值观察通知(推送模型)。

但是如果在发布通知时未加载所述控制器 会发生什么情况,这是否意味着它永远不会获取数据?使用pull模型不是更有意义,因此在加载控制器时,它可以检查数据是否可用,如果可以,您如何使用异步范式处理它?<​​/ p>

但是任何通知回调都无法访问控制器其余部分的外部范围。

但是,我已经编写了一些完全使用锁和信号量的同步代码。该模型在 background 线程中同步下载数据。控制器类(如果已加载,则为 )检查Model类,以查看数据是否可用。锁表示如果未下载数据,代码将无法访问数据。当数据完成下载并且所有控制器和模型使用相同的共享同步DispatchQueue时,该App会发出信号,这可以防止控制器在数据阵列为空或正在下载数据时访问它们。

异步代码通常生成弱耦合的代码,这些代码无法访问类的其余部分,并且您在应用程序的不同时间和不同位置触发了一些方法,我认为这很难跟踪。那么为什么网络“天生就是异步操作”?

任何人都可以提供合理的科学理由来说明为什么异步代码更好,或者为什么我不应该对同步代码做些什么,以及如何使异步代码更安全,像意大利面条一样少,更易于使用的方法。更容易阅读?

代码:

表视图控制器

override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)


    if !fromSelectionCtrllr {

        let downloader = Downloader.sharedInstance
        let group = downloader.group

        group.notify(queue: .main, execute: {

            let defaults : UserDefaults = UserDefaults.standard
            let firstLaunch = defaults.bool(forKey: "firstLaunch")

            if firstLaunch {
                self.arrayOfData = Model.sharedInstance.provideData()
            } else {
                self.arrayOfData = Model.sharedInstance.provideNewData()
            }

            for object in self.arrayOfData {

                if let deviceName = object.chargeDeviceName {

                    let theSubscript = deviceName.prefix(1)

                    let theString = String(theSubscript)

                    if !self.sectionTitles.contains(theString) {

                        self.sectionTitles.append(theString)
                    }
                } else {

                    self.sectionTitles.append("")
                }

                if let deviceName = object.chargeDeviceName  {

                    let string = String(describing: deviceName.prefix(1))

                    var arry = self.chargingPointDict[string]

                    if arry == nil {

                        arry = []
                    }

                    arry?.append(object)

                    self.chargingPointDict.updateValue(arry!, forKey: string)

                } else {

                    self.chargingPointDict[" "]?.append(object)
                }
            }

            self.sectionTitles = self.removeDuplicates(array: self.sectionTitles)

            self.sectionTitles = self.sectionTitles.sorted( by: { $0 < $1 })

            DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
                self.myTableView.reloadData()
            }
        })

    }

    fromSelectionCtrllr = false

}

CellForRowAtIndexPath

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)

    if (searchController.searchBar.text?.isEmpty)! {

        if self.sectionTitles.isEmpty {

            cell.textLabel?.text = "Nothing to display"
            return cell

        } else {

            let mySectionIndex = self.sectionTitles[indexPath.section]

            if mySectionIndex != "" {

                let arrayOfPoints : [ChargingPoint] = self.chargingPointDict[mySectionIndex]!

                let object : ChargingPoint = arrayOfPoints[indexPath.row]

                cell.textLabel?.text = object.chargeDeviceName

                return cell

            } else {

                return cell
            }

        }

    } else {

        let object : ChargingPoint = self.filteredPoints[indexPath.row]

        cell.textLabel?.text = object.chargeDeviceName

        return cell
    }


}

模型类

class Model: NSObject {

var currentChargingPointArray : [ChargingPoint] = []
var newChargingPointArray : [ChargingPoint] = []

var latitude : Double?
var longitude : Double?

var annotationArray : [ChargingPointAnnotation] = []
var newAnnotationArray : [ChargingPointAnnotation] = []

static let downloader = Downloader.sharedInstance

var savedRegion : MKCoordinateRegion? = nil

/* The model class is a singleton */
static let sharedInstance : Model = {
    let instance = Model()

    return instance
}()

fileprivate override init( ) {} //This prevents others from using the default '()' initializer for this class.

func setLocation(lat: Double, long: Double) {

    self.latitude = lat
    self.longitude = long
}

func returnData(array: Array<ChargingPoint>) {

    currentChargingPointArray = []

    var seen = Set<String>()
    var unique = [ChargingPoint]()
    for point in array {
        if !seen.contains(point.chargeDeviceId!) {
            unique.append(point)
            seen.insert(point.chargeDeviceId!)
        }
    }

    currentChargingPointArray = unique

}

func returnNewData(array: Array<ChargingPoint>) {

    newChargingPointArray = []

    var seen = Set<String>()
    var unique = [ChargingPoint]()
    for point in array {
        if !seen.contains(point.chargeDeviceId!) {
            unique.append(point)
            seen.insert(point.chargeDeviceId!)
        }
    }

    newChargingPointArray = unique

}

func provideData() -> [ChargingPoint] {

    return currentChargingPointArray

}

func provideNewData() -> [ChargingPoint] {

    return newChargingPointArray

}

func makeAnnotations() -> [ChargingPointAnnotation] {

    let queue = DispatchQueue(label: "com.jackspacie.ChargeFinder", qos: .background, attributes: [])

    queue.sync {

        self.annotationArray = []

        for chargingPoint in currentChargingPointArray {

            let location = CLLocationCoordinate2D( latitude: chargingPoint.latitude!, longitude: chargingPoint.longitude!)

            let annotation = ChargingPointAnnotation(location: location)
            annotation?.title = chargingPoint.chargeDeviceName
            annotation?.pointTitle = chargingPoint.chargeDeviceName

            annotation?.chargingPoint = chargingPoint

            self.annotationArray.append(annotation!)
        }

    }

    return self.annotationArray
}

func makeNewAnnotations() -> [ChargingPointAnnotation] {

    let queue = DispatchQueue(label: "com.jackspacie.ChargeFinder", qos: .background, attributes: [])

    queue.sync {

        self.newAnnotationArray = []

        for chargingPoint in newChargingPointArray {

            let location = CLLocationCoordinate2D( latitude: chargingPoint.latitude!, longitude: chargingPoint.longitude!)

            let annotation = ChargingPointAnnotation(location: location)
            annotation?.title = chargingPoint.chargeDeviceName
            annotation?.pointTitle = chargingPoint.chargeDeviceName

            annotation?.chargingPoint = chargingPoint

            self.newAnnotationArray.append(annotation!)
        }



    }

    return self.newAnnotationArray
}

下载器类

var group = DispatchGroup()
var model = Model.sharedInstance

/* The downloader class is a singleton */
static let sharedInstance : Downloader = {
    let instance = Downloader()

    return instance
}()

fileprivate override init() {} //This prevents others from using the default '()' initializer for this class.


func download(lat: Double, long: Double, dist: Int)  {

    func recursive(lat: Double, long: Double, dist: Int) {

        var chargeDeviceArray : [ChargingPoint] = []

        let url = URL(string: “https://www.blah.com/lat/\(lat)/long/\(long)/dist/\(dist)/")!

        let semaphore = DispatchSemaphore(value: 0)
        let task = URLSession.shared.dataTask(with: url) { data, response, error in

            if error != nil {
                print("urlSession Error")

                recursive(lat: lat, long: long, dist: dist)

                return
            } else {

                guard let unwrappedData = data else { return }
                do {
                    let jsonDict : [String: Any] = try JSONSerialization.jsonObject(with: unwrappedData, options: [] ) as! [String : Any]

                    let arrayOfDicts = jsonDict["ChargeDevice"] as? [[String: Any]]

                    for value in arrayOfDicts! {

                        let chargePoint = ChargingPoint()

                        // process data into objects.


                        chargeDeviceArray.append(chargePoint)


                    }

                    var seen = Set<String>()
                    var unique = [ChargingPoint]()
                    for point in chargeDeviceArray {
                        if !seen.contains(point.chargeDeviceId!) {
                            unique.append(point)
                            seen.insert(point.chargeDeviceId!)
                        }
                    }



                    if self.model.currentChargingPointArray.isEmpty {

                        self.model.returnData(array: unique)

                    } else {

                        self.model.returnNewData(array: unique)
                    }



                } catch {

                    print("json error: \(error)")
                }

                semaphore.signal()
            }

            //print(response) 

        }
        task.resume()
        semaphore.wait(timeout: .distantFuture)
    }

    self.group.enter()

    let queue = DispatchQueue(label: "com.myapp.charge”, qos: .background, attributes: [])

    queue.sync {


        recursive(lat: lat, long: long, dist: dist)

    }

    self.group.leave()

}

3 个答案:

答案 0 :(得分:0)

简而言之,当涉及到同步网络时,在非常有限的情况下,您更愿意这样做。为什么?

因为“同步请求会阻塞客户端,直到操作完成”,并且通常您不希望客户端冻结。当时用户无法执行任何操作,因为我们正在等待所有同步操作完成后再启用客户端。

使用异步请求时,您可以构建UI,显示微调框,并取决于用户是否已缓存较旧的数据或具有其他功能,仍然允许用户使用客户端。

答案 1 :(得分:0)

  

当我有多个使用相同数据的视图控制器时,这会带来一个问题,如何确定我没有哪个视图控制器   打开访问数据下载之前打开?你可能不是   能够知道首先打开哪个控制器,所以这带来了问题,   您如何确定他们直到完成才访问数据   正在下载?

     

我想您使用完成处理程序和模型来解决此问题   然后触发键值观察通知,调用   下载完成后的控制器(推模型)。

     

但是我问这个问题,如果在未加载该控制器时会发生什么情况   通知已发布,这是否意味着它永远不会获取数据?   使用拉动模型是否更有意义,所以当   控制器已加载,它可以检查dat是否可用,如果可以,如何   你用异步范式来处理吗?

加载UI->显示进度对话框->执行ASYNC(LoadData,SetData intoController)-> DismissDialog

Idk(如果这是您要引用的内容)

我真的没有看到机会,您会更喜欢阻塞UI线程并冻结App,直到它同步加载数据为止

答案 2 :(得分:0)

文章指出“联网是固有的异步操作”,因为联网 是固有的异步操作。

“同步”表示“同时发生”。具体而言,在计算中,它指的是参考特定的时间段或时钟信号。例如,在电气方面,CPU同步运行,这就是我们谈论计算机时钟速度的原因。

同步并不意味着“阻塞” ,尽管这是常见的(错误)解释。实际上,Apple在这里没有提供函数名。从技术上讲,它们应该类似于DispatchQueue.nonBlockingOperation()DispatchQueue.blockingOperation(),而不是async / sync

同步系统可以以更高的速度运行,但是需要一个非常受控的环境,这就是为什么您发现同步操作是在计算机的核心而不是在计算机的外部。

您的代码阻止了后台队列等待下载完成,但是下载仍然异步完成。如果它同步完成 ,则不需要信号灯。您会知道该数据将在指定的时间点(例如,从现在起0.2秒或任何时间)可用。

您的Downloader类还仍通过调度组notify异步通知视图控制器有关可用数据的信息。

据我所知,您为解决方案增加了很多复杂性(通过添加调度组和信号灯),并引入了潜在的死锁,但是您仍然拥有异步代码。您可以阻止后台队列等待下载完成,但仍会异步通知数据使用者。

使用标准委派或完成处理程序模式可以实现相同的结果,而复杂度要低得多。如果潜在的多个方有兴趣了解新数据,则可以使用NotificationCentre“广播”该信息。

顺便说一句,通常认为松耦合代码更可取。