我对大量文档明确表示应该使用异步代码进行联网感到非常沮丧。
我甚至读过一篇文章,其中直截了当地说:"网络本质上是一种异步操作。">
https://react-native.org/doc/network.html
因此,首先,后台处理和异步代码之间存在差异。
例如,异步运行代码并不一定意味着它在后台。为此,我们实际上可以使用后台线程。
当你编写一个iOS应用程序,并且你有几个视图控制器,每个控制器都访问模型下载的相同数据时,当你异步下载代码的数据时,你会在整个应用程序中传递令人沮丧的回调和异步消息。
当我有多个视图控制器使用相同的数据时,这会带来问题,我如何确保我没有打开的视图控制器在下载数据之前访问数据?你可能无法判断哪个控制器是首先打开的,所以这就带来了一个问题,你如何确保他们在下载完成之前不会访问数据?
我想你可以使用一个完成处理程序和一个模型来解决这个问题,然后在下载完成时触发一个键值观察通知来调用控制器(推送模型)。
但是,如果在发布通知时,所述控制器未加载,会发生什么?这是否意味着它永远不会获得数据?使用pull模型不是更有意义吗?这样,当加载控制器时,它可以检查数据是否可用,如果可用,如何使用异步范式处理?
但是,任何通知回调都无法访问控制器其余部分的外围作用域。
然而,我已经编写了一些使用锁和信号量的完全同步的代码。该模型在后台线程中同步下载数据。控制器类(如果已加载)检查Model类以查看数据是否可用。锁定意味着如果数据未下载,代码将无法访问数据。应用程序在数据完成下载时发出信号,而所有控制器和模型都使用相同的共享同步DispatchQueue,这会阻止控制器在数据阵列为空或正在下载数据时访问数据阵列。
异步代码通常会产生弱耦合代码,这些代码无法访问类的其他部分的作用域,并且在应用程序中的不同时间和不同位置有方法启动,我认为这很难跟踪。那么,为什么网络是"一种固有的异步操作"呢?
有人能提供异步代码更好的科学理由吗,或者我不应该做我对同步代码所做的事情的理由,以及如何使异步代码更安全、不那么像意大利面条一样、更易于使用和阅读的方法吗?
代码:
表视图控制器
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()
}
简而言之,当涉及到同步网络时,您喜欢这样的情况非常有限。为什么?
因为"同步请求会阻塞客户端,直到操作完成",而且通常您不希望客户端冻结。用户当时什么都不能做,因为在启用客户端之前,我们正在等待所有同步操作完成。
当使用异步请求时,您可以构建UI、显示微调器,并且根据您是否已经缓存了旧数据或具有其他功能,仍然允许用户使用您的客户端。
当我有多个视图控制器使用相同的数据时,这会带来问题,我如何确保我没有是否在下载数据之前打开访问数据?你可能不是能够判断哪个控制器首先被打开,因此这会带来问题,你如何确保他们在数据完成之前不会访问数据正在下载
我想你可以使用一个完成处理程序和一个模型来解决这个问题然后触发一个键值观测通知,调用控制器(推送模型)。
但我问这个问题,如果当通知发布了,这是否意味着它永远不会得到数据?使用拉动模型不是更有意义吗控制器已加载,它可以检查数据是否可用,如果可用,如何你用异步模式处理这个问题吗?
加载UI->显示进度对话框->执行ASYNC(加载数据,将数据设置为控制器)->取消对话框
我知道这是否是你所指的
我真的看不出有什么机会,你会更喜欢阻止UI线程并冻结应用程序,直到它同步加载数据
文章指出"网络是一种固有的异步操作",因为网络是一种内在的异步操作。
同步意味着"同时发生"。特别是在计算中,它意味着参考特定的时间段或时钟信号。例如,在电学上,CPU同步运行,这就是为什么我们谈论计算机的时钟速度。
同步并不意味着"阻塞">,尽管这是一种常见的(错误的)解释。事实上,苹果并没有帮助他们的功能名称。从技术上讲,它们应该类似于DispatchQueue.nonBlockingOperation()
和DispatchQueue.blockingOperation()
,而不是async
/sync
。
同步系统可以以更高的速度运行,但需要一个非常可控的环境,这就是为什么你发现同步操作在计算机的核心,但在它之外却不那么多
您的代码阻塞了等待下载完成的后台队列,但下载仍然异步完成。如果它同步地完成,那么就不需要信号量了。您会知道,数据将在指定的时间点(例如,从现在起0.2秒或其他时间)可用。
Downloader
类仍然通过调度组notify
异步地向视图控制器通知可用数据。
据我所见,您为解决方案增加了很多复杂性(通过添加调度组和信号量),并引入了死锁的可能性,但您仍然有异步代码。您阻塞了等待下载完成的后台队列,但仍然异步地通知数据使用者。
使用复杂度低得多的标准委托或完成处理程序模式可以实现相同的结果。如果有潜在的多方有兴趣了解新数据,那么您可以使用NotificationCentre
来"广播"该信息。
顺便说一句,松散耦合的代码通常被认为是更可取的。