Skip to content
Permalink
Branch: master
Find file Copy path
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
161 lines (138 sloc) 7.06 KB
layout title subtitle date key tags
article
iOS 如何判断多个同时进行的网络请求全部结束
"对于iOS中的多个同时进行的网络请求,大家都很熟悉了,但如果让你在所有的请求结束之后再去刷新UI呢?"
2018-01-20 13:00:00 -0800
1009
iOS
Swift
网络请求

前言

这应该是一种比较常见的情况吧,多个网络请求之后刷新 UI,。由于所有的网络请求都是异步的,怎么判断才好呢? 这是笔者在实际开发过程中遇到的情况,简单总结下:

一个一个地执行网络请求

requestOne {
	///第一个请求结束,执行第二个请求
    requestTwo {
        /// 第二个请求结束,执行第三个请求
    	requestThree {
    		/// to do something.
    	}
    }
}

如以上代码所示,在每一个请求结束之后再去执行新的请求。可想而知,本来网络请求就是费时操作,这样一来,更加费时了。而且有系统的多线程可以利用,为啥不用。所以这种方式不建议使用。

使用变量标记

var isCompletion: (Bool, Bool, Bool) = (false, false, false)
func makeRequest() {
    requestOne { [weak self] in
        self?.isCompletion.0 = true
        if self?.isCompletion.0 == true && self?.isCompletion.1 == true && self?.isCompletion.2 == true {
            self?.isCompletion = (false, false, false)
            /// to do something.
        }
    }
        
    requestTwo { [weak self] in
        self?.isCompletion.1 = true
        if self?.isCompletion.0 == true && self?.isCompletion.1 == true && self?.isCompletion.2 == true {
            self?.isCompletion = (false, false, false)
            /// to do something.
        }
    }
        
    requestThree { [weak self] in
        self?.isCompletion.2 = true
        if self?.isCompletion.0 == true && self?.isCompletion.1 == true && self?.isCompletion.2 == true {
            self?.isCompletion = (false, false, false)
            /// to do something.
        }
    }
}

以上代码利用了全局元组变量isCompletion来标记,当且仅当isCompletion里面的三个值全部为true的时候, 才表示三个请求全部加载完毕, 这种方式对于请求数目比较少的话还好处理, 一旦数目很多, isCompletion 元组中包含的Bool值也会增多, 在代码阅读和判断书写上就不会那么便利. 所以对于这种方式,请求数目比较少(小于等于3)推荐使用,请求数目比较多(大于3)不推荐使用。

使用完成次数标记

var remainRequestCount = 0
func makeRequest() {
    remainRequestCount += 1
    requestOne { [weak self] in
        self?.remainRequestCount -= 1
        if self?.remainRequestCount == 0 {
            /// to do something
        }
    }
    
    remainRequestCount += 1
    requestTwo { [weak self] in
        self?.remainRequestCount -= 1
        if self?.remainRequestCount == 0 {
            /// to do something
        }
    }
    
    remainRequestCount += 1
    requestThree { [weak self] in
        self?.remainRequestCount -= 1
        if self?.remainRequestCount == 0 {
            /// to do something
        }
    }
}

以上代码使用了变量remainRequestCount来统计当前剩余请求的个数. 在每次请求前将至 +1, 请求完成后 -1, 等于0就表示所有的请求已经进行完. 这种方式可以有效解决上面的元组带来的繁琐问题. 同时相对于元组的方式, 请求数目较多时也可以达到简化代码的效果. 所以这种方式在实际开发过程中,也是极为推荐使用的。

使用信号量

什么是信号量,对于很多iOS开发者可能这都是一个陌生的词,因为真的不常用。简单点理解,信号量就是一个资源计时器。与 ARC 内存的引用计数相似,可以认为有一个整形变量,当信号量进入时,该整形变量+1,离开时该整形变量-1。当该整形变量为0时就执行接下来的操作。

iOS 中使用信号量与这三个函数有关:

  • DispatchSemaphore(value:_): 创建一个信号量;
  • signal(): 发送一个信号量;
  • wait(wallTimeout:_): 等待一个信号量。
let dispatchGroup = DispatchGroup()
let semaphore = DispatchSemaphore(value: 0)
dispatchGroup.notify(queue: DispatchQueue(label: "com.vsccw.test1")) {
    let value = semaphore.signal()
    sleep(2)
    /// request 1
}

dispatchGroup.notify(queue: DispatchQueue(label: "com.vsccw.test2")) {
    let value = semaphore.signal()
    sleep(2)
    /// request 2
}
_ = semaphore.wait(wallTimeout: .distantFuture)
/// 上面的操作执行完毕就会执行
/// to do something

RxSwift

可以通过使用 RxSwiftzip 操作符来实现所有的网络请求结束后再进行相关的操作。 关于 zip:通过一个函数将多个 Observables 的元素组合起来,然后将每一个组合的结果发出来。

Observable.zip(request1, request2) { ($0, $1) }
    .subscribe(onNext: { 
        /// to do something.
    })
    .disposed(by: disposeBag)

Promises

Promises 本是前端的东西,可是它真的好用到iOS上也有。

Promise 对象是 JavaScript 的异步操作解决方案,为异步操作提供统一接口。它起到代理作用(proxy),充当异步操作与回调函数之间的中介,使得异步操作具备同步操作的接口。Promise 可以让异步操作写起来,就像在写同步操作的流程,而不必一层层地嵌套回调函数。以上来源自 [Promise 对象]

在这里可以使用all操作符: all 会在所有的Promise实例执行完毕后,再去执行then操作。经验证,all里面的所有操作都是异步的。简直完美解决这个问题。

func requestOne() { }
func requestTwo() { }
func requestThree() { }
all([requestOne(), requestTwo(), requestThree()]).then { result in
  /// to do something.
}

以上代码参考:google/promises

总结

笔者在上面提供的方法里面,可以说使用普遍度是依次降低的,当然笔者在实际的开发项目中使用的第二种,使用了变量的形式使用变量标记的形式,相对以上几种来说,这种方式,更快捷也更易懂;第四种对RxSwift比较熟悉的同学还是没什么难度的, 不过不熟悉的同学也可以去了解一下; 第三种使用信号量的方式建议掌握, 即使自己在实际开发中不会用到, 但是这算是GCD信号量的基本使用方式之一了;最后一种Promise方式是最近*(2018-06-07)*看到Google开源的这个库之后才了解的,也很推荐使用。

参考

You can’t perform that action at this time.