iOS 에서 다 중 네트워크 가 요청 한 스 레 드 보안 에 대한 자세 한 설명

머리말
iOS 네트워크 프로 그래 밍 에서 흔히 볼 수 있 는 장면 은 두 가지 요 구 를 병행 처리 하고 모두 성공 한 후에 야 다음 처 리 를 할 수 있다 는 것 이다.다음은 일부 흔히 볼 수 있 는 처리 방식 이지 만 사용 과정 에서 도 오류 가 발생 하기 쉽다.
  • Dispatch Group:GCD 메커니즘 을 통 해 여러 요청 을 한 그룹 에 넣 은 다음DispatchGroup.wait() DispatchGroup.notify() 을 통 해 성공 적 으로 처리 합 니 다.
  • Operation Queue:모든 요청 에 하나의 Operation 대상 을 예화 한 다음 에 이 대상 을 Operation Queue 에 추가 하고 그들의 의존 관계 에 따라 집행 순 서 를 결정 합 니 다.
  • 동기 화 DispatchQueue:동기 화 대기 열과 NSLock 메커니즘 을 통 해 데이터 경쟁 을 피하 고 비동기 다 중 스 레 드 에서 동기 화 보안 접근 을 실현 합 니 다.
  • 제3자 라 이브 러 리:Futures/Promises 와 응답 식 프로 그래 밍 은 더욱 높 은 등급 의 병행 추상 을 제공 했다.
  • 다년간 의 실천 과정 에서 나 는 위의 이런 방법 들 이 모두 어느 정도 결함 이 있다 는 것 을 깨 달 았 다.또 이런 종류의 라 이브 러 리 를 완전히 정확하게 사용 하려 면 어렵다.
    동시 프로 그래 밍 의 도전
    동시 다발 적 인 사고 문 제 를 사용 하 는 것 은 매우 어렵다.대부분 우 리 는 이 야 기 를 읽 는 방식 으로 코드 를 읽는다.첫 줄 에서 마지막 줄 까지.코드 의 논리 가 선형 이 아니라면 우리 에 게 어느 정도 이해 의 어려움 을 줄 수 있다.단일 스 레 드 환경 에서 여러 가지 유형 과 프레임 워 크 를 디 버 깅 하고 추적 하 는 프로그램 이 실 행 된 것 은 이미 매우 골 치 아 픈 일이 다.다 중 스 레 드 환경 에서 이런 상황 은 상상 할 수 없다.
    데이터 경쟁 문제:다 중 스 레 드 병행 환경 에서 데이터 읽 기 작업 은 스 레 드 가 안전 하고 쓰기 작업 은 비 스 레 드 가 안전 합 니 다.여러 스 레 드 가 동시에 메모리 에 쓰기 작업 을 하면 데이터 경쟁 으로 인해 잠재 적 인 데이터 오류 가 발생 할 수 있 습 니 다.
    다 중 스 레 드 환경 에서 의 동태 행 위 를 이해 하 는 것 자체 가 쉬 운 일이 아니 므 로 데이터 경쟁 을 야기 하 는 스 레 드 를 찾 는 것 이 더욱 번거롭다.비록 우 리 는 상호 배척 잠 금 체 제 를 통 해 데이터 경쟁 문 제 를 해결 할 수 있 지만 수정 가능 한 코드 에 있어 서 상호 배척 잠 금 체제 의 유 지 는 매우 어 려 운 일이 다.
    테스트 하기 어렵다:병발 환경 에서 많은 문제 가 개발 과정 에서 나타 나 지 않 는 다.Xcode 와 LLVM 은 이러한 문 제 를 검사 하 는 데 사용 되 는Thread Sanitizer도 구 를 제공 하지만 이러한 문제 들 의 디 버 깅 과 추적 은 여전히 매우 어렵다.병발 환경 에서 코드 자체 의 영향 을 제외 하고 응용 도 시스템 의 영향 을 받 기 때문이다.
    병발 상황 을 처리 하 는 간단 한 방법
    병행 프로 그래 밍 의 복잡성 을 고려 하여 우 리 는 병행 하 는 여러 가지 요 구 를 어떻게 해결 해 야 합 니까?
    가장 쉬 운 방법 은 병렬 코드 를 만 드 는 것 을 피 하 는 것 이 아니 라 여러 개의 요청 이 선형 으로 연결 되 어 있 는 것 입 니 다.
    
    let session = URLSession.shared
    
    session.dataTask(with: request1) { data, response, error in
     // check for errors
     // parse the response data
    
     session.dataTask(with: request2) { data, response error in
      // check for errors
      // parse the response data
    
      // if everything succeeded...
      callbackQueue.async {
       completionHandler(result1, result2)
      }
     }.resume()
    }.resume()
    코드 의 간결 함 을 유지 하기 위해 서 여 기 는 많은 세부 처 리 를 무시 합 니 다.예 를 들 어 오류 처리 와 취소 요청 등 입 니 다.그러나 이렇게 하면 관련 이 없 는 요청 선형 정렬 에 문제 가 숨 어 있다.예 를 들 어 서버 가 HTTP/2 프로 토 콜 을 지원 한다 면 우 리 는 HTTP/2 프로 토 콜 에서 같은 링크 를 통 해 여러 요청 을 처리 하 는 특성 을 보 내지 않 았 고 선형 처리 도 프로세서 의 성능 을 잘 이용 하지 못 했다 는 것 을 의미한다.
    URLSession 에 대한 잘못된 인식
    가능 한 데이터 경쟁 과 스 레 드 안전 문 제 를 피하 기 위해 서 나 는 위의 코드 를 내장 요청 으로 바 꾸 었 다.즉,동시 요청 으로 바 꾸 면 요청 은 끼 워 넣 을 수 없 으 며,두 요청 은 같은 메모리 에 대해 쓰기 작업 을 할 수 있 으 며,데이터 경쟁 은 재현 과 디 버 깅 이 매우 어렵다.
    문 제 를 해결 하 는 실행 가능 한 방법 은 잠 금 체 제 를 통 해 한 동안 하나의 스 레 드 만 공유 메모리 에 대해 쓰기 작업 을 할 수 있 도록 하 는 것 이다.잠 금 메커니즘 의 실행 과정 도 매우 간단 하 다.잠 금 요청,실행 코드,잠 금 해제.물론 자물쇠 메커니즘 을 완전히 정확하게 사용 하려 면 약간의 기교가 있다.
    그러나 URLSession 의문서.설명 에 따 르 면 여기 에는 동시 다발 요청 의 더욱 간단 한 해결 방안 이 있다.
    
    init(configuration: URLSessionConfiguration,
       delegate: URLSessionDelegate?,
       delegateQueue queue: OperationQueue?)
    […]
    queue : An operation queue for scheduling the delegate calls and completion handlers. The queue should be a serial queue, in order to ensure the correct ordering of callbacks. If nil, the session creates a serial operation queue for performing all delegate method calls and completion handler calls.
    이것 은 모든 URLSession 의 인 스 턴 스 대상 이 URLSession.shared 단일 사례 의 리 셋 을 포함 하여 동시에 실행 되 지 않 는 다 는 것 을 의미 합 니 다.
    URLSession 확장 및 지원
    위 에서 URLSession 에 대한 새로운 인식 을 바탕 으로 스 레 드 의 안전 한 병행 요청(코드 주소 완성)을 지원 하도록 확대 합 니 다.
    
    enum URLResult {
     case response(Data, URLResponse)
     case error(Error, Data?, URLResponse?)
    }
    
    extension URLSession {
     @discardableResult
     func get(_ url: URL, completionHandler: @escaping (URLResult) -> Void) -> URLSessionDataTask
    }
    
    // Example
    
    let zen = URL(string: "https://api.github.com/zen")!
    session.get(zen) { result in
     // process the result
    }
    우선,우 리 는 URLSession DataTask 리 셋 에서 얻 을 수 있 는 다양한 결 과 를 모 의 하기 위해 간단 한 URL Result 매 거 진 을 사용 했다.이 매 거 진 유형 은 여러 개의 동시 요청 결 과 를 간소화 하 는 데 유리 하 다.여기 서 글 의 간결 함 을 위해URLSession.get(_:completionHandler:) 방법 이 완전 하 게 구현 되 지 않 았 습 니 다.이 방법 은 GET 방법 으로 해당 하 는 URL 을 요청 하고 자동 으로 실행resume() 한 다음 에 실행 결 과 를 URL Result 대상 으로 밀봉 하 는 것 입 니 다.
    
    @discardableResult
    func get(_ left: URL, _ right: URL, completionHandler: @escaping (URLResult, URLResult) -> Void) -> (URLSessionDataTask, URLSessionDataTask) {
     
    }
    이 세그먼트 API 코드 는 두 개의 URL 인 자 를 받 아들 이 고 두 개의 URLSession DataTask 인 스 턴 스 를 되 돌려 줍 니 다.다음 코드 는 함수 실현 의 첫 번 째 부분 입 니 다.
    
     precondition(delegateQueue.maxConcurrentOperationCount == 1,
      "URLSession's delegateQueue must be configured with a maxConcurrentOperationCount of 1.")
    URLSession 대상 을 예화 할 때 도 동시 다발 적 인 Operation Queue 대상 에 들 어 갈 수 있 기 때문에 이 코드 를 사용 하여 이러한 상황 을 제거 해 야 합 니 다.
    
    var results: (left: URLResult?, right: URLResult?) = (nil, nil)
    
    func continuation() {
     guard case let (left?, right?) = results else { return }
     completionHandler(left, right)
    }
    이 코드 를 실현 에 계속 추가 합 니 다.그 중에서 결 과 를 되 돌려 주 는 메타 변수 results 를 정의 합 니 다.또한,우 리 는 함수 내부 에서 두 요청 이 모두 결과 처 리 를 마 쳤 는 지 확인 하기 위해 다른 도구 함 수 를 정의 했다.
    
    let left = get(left) { result in
     results.left = result
     continuation()
    }
    
    let right = get(right) { result in
     results.right = result
     continuation()
    }
    
    return (left, right)
    마지막 으로 이 코드 를 실현 에 추 가 했 습 니 다.그 중에서 우 리 는 각각 두 개의 URL 을 요 청 했 고 요청 이 모두 끝 난 후에 결 과 를 되 돌려 주 었 습 니 다.주의해 야 할 것 은 여기 서 우 리 는 두 번 의 집행continuation() 을 통 해 요청 이 모두 완성 되 었 는 지 판단 하 는 것 이다.
  • 처음 실행continuation() 할 때 한 요청 이 완료 되 지 않 았 기 때문에 리 셋 함 수 는 실행 되 지 않 습 니 다.
  • 두 번 째 실행 시 두 가지 요청 이 모두 완료 되 고 리 셋 처 리 를 수행 합 니 다.
  • 다음 에 우 리 는 간단 한 요청 을 통 해 이 코드 를 테스트 할 수 있다.
    
    extension URLResult {
     var string: String? {
      guard case let .response(data, _) = self,
      let string = String(data: data, encoding: .utf8)
      else { return nil }
      return string
     }
    }
    
    URLSession.shared.get(zen, zen) { left, right in
     guard case let (quote1?, quote2?) = (left.string, right.string)
     else { return }
    
     print(quote1, quote2, separator: "
    ") // Approachable is better than simple. // Practicality beats purity. }
    병렬 역설
    나 는 병렬 문 제 를 해결 하 는 가장 간단 하고 우아 한 방법 은 가능 한 한 병렬 프로 그래 밍 을 적 게 사용 하 는 것 이 며,우리 의 프로 세 서 는 선형 코드 를 실행 하기에 매우 적합 하 다 는 것 을 발견 했다.그러나 큰 코드 블록 이나 작업 을 여러 개의 병렬 실행 코드 블록 과 작업 으로 나 누 면 코드 를 더욱 쉽게 읽 고 유지 할 수 있 습 니 다.
    총결산
    이상 은 이 글 의 전체 내용 입 니 다.본 논문 의 내용 이 여러분 의 학습 이나 업무 에 어느 정도 참고 학습 가치 가 있 기 를 바 랍 니 다.궁금 한 점 이 있 으 시 면 댓 글 을 남 겨 주 셔 서 저희 에 대한 지지 에 감 사 드 립 니 다.
    저자:아담 샤프,시간:2017/9/21
    오류 가 있 으 면 지적 을 환영 합 니 다.링크

    좋은 웹페이지 즐겨찾기