1个请求上两次带有DispatchGroup调用完成处理程序的并行URLSession请求

我使用DispatchGroup尝试对我的客户端运行2个网络请求,并在两个请求都完成后返回结果。

我遇到一个问题,就是有时DispatchGroup中一个请求的完成处理程序被调用两次,而另一个请求根本没有被调用。

一个例子是-


    func fetchProfileWithRelatedArticle(onSuccess: @escaping (User,[RelatedArticle]) -> Void,onError: @escaping (Error) -> Void) {
        let dispatchGroup = DispatchGroup()

        var user: User?
        var articles: [RelatedArticle] = []

        var errors: [Error] = []

        dispatchGroup.enter()
        fetchProfileForUser(onSuccess: {
            user = $0
            print("fetchProfile:",$0)
            print("123")
            dispatchGroup.leave()
        },onError: { error in
            errors.append(error)
            dispatchGroup.leave()
        })

        dispatchGroup.enter()
        getarticlesForUser(onSuccess: {
            articles = $0
            print("getarticlesForUser:",$0)
            print("456")
            dispatchGroup.leave()
        },onError: { error in
            errors.append(error)
            dispatchGroup.leave()
        })

        dispatchGroup.notify(queue: .main) {
            guard let user = user,errors.isEmpty else { return }

            onSuccess(user,articles)
        }
    }

我在这里获取用户个人资料,还获取他们撰写的文章列表。这些通过完成处理程序返回并显示在其他位置。

在大多数情况下,此方法有效,但是有时这些请求之一会调用其自身的完成处理程序两次,而另一个请求则不会。

我怀疑这可能是我的访问令牌到期的原因,如果我短暂离开应用程序会出现这种情况。我的访问令牌的寿命为2分钟。

如果请求收到401响应,我的网络客户端中将有以下方法,该方法请求一个新令牌,然后再次调用该调用。我相信这可能无法按照我的意愿进行。


            if response.statusIs401() {
                self?.refreshHandler { success in
                    guard success else { completion(.failure(TokenError.refused)); return }
                    self?.request(resource,completion)
                }
                return
            }

我怀疑更新对我的调度组返回的请求执行了某些操作后,再次调用了该方法。

是否可以这种方式链接请求?


struct NoContent: Codable { }
typealias RefreshHandler = (@escaping (Bool) -> Void) -> ()
typealias TokenGetter = () -> [String: String]

protocol ClientType: class {
    associatedtype Route: RouterType
    func request<T: Codable>(_ resource: Route,_ completion: @escaping  (Result<T>)-> Void)
}

class Client<Route: RouterType>: ClientType {

    enum APIError: Error {
        case unknown,badResponse,jsonDecoder,other
    }

    enum TokenError: String,Error {
        case expired = "access Token Expired"
        case refused = "Refresh Token Failed"
    }

    private(set) var session: SessionType
    private(set) var tokenGetter: TokenGetter
    private(set) var refreshHandler: RefreshHandler

    private lazy var decoder: JSONDecoder = {
        let decoder = JSONDecoder()
        decoder.dateDecodingStrategy = .iso8601withFractionalSeconds
        return decoder
    }()

    init(session: SessionType,tokenGetter: @escaping TokenGetter,refreshHandler: @escaping RefreshHandler) {
        self.session = session
        self.tokenGetter = tokenGetter
        self.refreshHandler = refreshHandler
    }

    func request<T: Codable>(_ resource: Route,_ completion: @escaping  (Result<T>)-> Void) {

        let request = URLRequest(
            resource: resource,headers: tokenGetter()
        )

        URLSession.shared.dataTask(with: request) { [weak self] data,response,error in
            guard error == nil else { completion(.failure(APIError.unknown)); return }
            guard let response = response as? HTTPURLResponse else { completion(.failure(APIError.badResponse)); return }

            if response.statusIs401() {
                self?.refreshHandler { success in
                    guard success else { completion(.failure(TokenError.refused)); return }
                    self?.request(resource,completion)
                }
                return
            }

            if response.statusIsSuccess() {
                guard let self = self,let data = self.deserializeNoContentResponse(data: data) else { completion(.failure(APIError.badResponse)); return }
                do {
                    let value = try self.decoder.decode(T.self,from: data)
                    DispatchQueue.main.async {
                        completion(.success(value))
                    }
                } catch let error {
                    print(error)
                }
                return
            }

            completion(.failure(APIError.other))
        }.resume()
    }

    // some calls return a 200/201 with no data
    private func deserializeNoContentResponse(data: Data?) -> Data? {

        if data?.count == 0 {
            return "{ }".data(using: .utf8)
        }

        return data
    }
}

llaijj123456 回答:1个请求上两次带有DispatchGroup调用完成处理程序的并行URLSession请求

听起来,您需要在网络客户端中执行以下操作:

ID: 1
Address: ****
Encoding: UTF-8
Http-Method: POST
Content-Type: multipart/related; type="text/xml"; boundary="uuid:5210b5e9-ee88-4b88-9330-f95ee62bb28e"; start="<root.message@cxf.apache.org>"; start-info="text/xml"
Headers: {Accept=[*/*],SOAPAction=["****"]}
Payload: --uuid:5210b5e9-ee88-4b88-9330-f95ee62bb28e
Content-Type: text/xml; charset=UTF-8
Content-Transfer-Encoding: binary
Content-ID: <root.message@cxf.apache.org>

<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Header>
</soap:Header>
<soap:Body>
    <metodo>
        <documento>
            <contenido href="8849d331-555b-4ab4-b90d-6ff0996cadcc@apache.org"/>
            <hash>0p9oDCPlhlxNemvtT5tj6e46SHdPWLOxTi1sLJBeCz4=</hash>
        </documento>
    </metodo>
</soap:Body>
</soap:Envelope>

--uuid:5210b5e9-ee88-4b88-9330-f95ee62bb28e
Content-Type: application/pdf
Content-Transfer-Encoding: binary
Content-ID: <8849d331-555b-4ab4-b90d-6ff0996cadcc@apache.org>

%PDF-1.4
%????
2 0 obj
<</Length 76/Filter/FlateDecode>>stream
x?+?r
?26S?00I?2P?5?1??
?B?4<Rsr???rR
?JS?
@?]C????
endstream
endobj
4 0 obj
<</Parent 3 0 R/Contents 2 0 R/Type/Page/Resources<</ProcSet [/PDF /Text /ImageB /ImageC /ImageI]/Font<</F1 1 0 R>>>>/MediaBox[0 0 595 842]>>
endobj
1 0 obj
<</BaseFont/Helvetica/Type/Font/Encoding/WinAnsiEncoding/Subtype/Type1>>
endobj
3 0 obj
<</ITXT(2.1.7)/Type/Pages/Count 1/Kids[4 0 R]>>
endobj
5 0 obj
<</Type/Catalog/Pages 3 0 R>>
endobj
6 0 obj
<</Producer(iText 2.1.7 by 1T3XT)/ModDate(D:20150327140035+01'00')/CreationDate(D:20150327140035+01'00')>>
endobj
xref
0 7
0000000000 65535 f
0000000314 00000 n
0000000015 00000 n
0000000402 00000 n
0000000157 00000 n
0000000465 00000 n
0000000510 00000 n
trailer
<</Root 5 0 R/ID [<5d256b5d87f178862dd33943e65718e8><ecd829bf4e1d355aef68138d0de19d76>]/Info 6 0 R/Size 7>>
startxref
632
%%EOF

--uuid:5210b5e9-ee88-4b88-9330-f95ee62bb28e--

这将进行调用,检查响应代码,刷新需要的令牌,如果调用成功,则调用方法,该方法应使用首先传递给的完成处理程序再次做出响应,而无需先调用它。因此,只有在第二次调用API之后,才会调用完成处理程序。

当然,为您自己的代码采用这种方式,应该不会太难

本文链接:https://www.f2er.com/3021737.html

大家都在问