2015년 9월 22일 화요일

OkHttp 이해하기 - 5

HTTP 요청을 할 때마다 이에 대한 정보를 OkHttpClient가 저장하고 있습니다. 큐에 저장해 놨다가 나중에 실행한다던가 실행중인 요청을 취소한다던가 하는 것들을 하기 위해서 입니다.
크게 보면 바로 실행하는(Call에서의 execute 함수를 통한 실행)하는 것과 큐에 저장해 놓았다가 나중에 실행하는(Call에서의 enqueue 함수를 통한 실행)이 있습니다.
바로 실행하는 요청을 저장하기 위해 아래의 Deque를 사용합니다.
private final Deque<Call> executedCalls = new ArrayDeque<>();
바로 실행하기 때문에 이의 관리는 간단합니다.executed가 호출되면 executedCalls에 넣고 finished가 호출되면 executedCalls에서 빼줍니다.
나중에 실행하는 요청을 저장하기 위해서는 아래 두개의 Deque를 사용합니다. 현재 실행중인 요청은 runningCalls에 넣고 최대 요청수가 다 찾으면 readyCalls에 넣어줍니다. runningCall에서의 요청중 끝난 것이 있으면 최대 요청수를 넘었는지 아닌지 확인한 후 넘지 않았으면 readyCalls에서 앞에서 부터 순서대로 꺼내어 최대 요청수까지 실행시켜줍니다.
private final Deque<AsyncCall> readyCalls = new ArrayDeque<>();
private final Deque<AsyncCall> runningCalls = new ArrayDeque<>();
쓰레드 관리를 위해 기본으로 ThreadPoolExecutor를 제공하고 최대 요청수는 64를, 호스트당 최대 요청 수는 5를 기본 값으로 합니다. 비동기 요청을 위해 enqueue를 사용하면 아래와 같이 현재 실행중인 요청이 최대 요청수를 넘지 않고, 호스트당 최대 요청 수도 최대 값을 넘지 않으면 runningCalls에 요청을 넣고 바로 실행을 시켜줍니다. 이 조건을 만족하지 못하면 나중에 다른 요청이 끝나면 실행될 수 있도록 readyCall에 넣어줍니다.
synchronized void enqueue(AsyncCall call) {
  if (runningCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
    runningCalls.add(call);
    getExecutorService().execute(call);
  } else {
    readyCalls.add(call);
  }
}
앞의 Call안의 AsyncCall의 execute 함수를 보면 맨 마지막에 모든 작업이 끝나면 finished 를 호출해 줍니다. 이 함수에서 작업이 끝난 AsyncCall을 runningCalls에서 제거하고 promoteCalls 함수를 호출하여 또다른 요청의 실행이 가능하면 readyCalls에서 하나를 꺼내어 runningCalls에 넣고 실행을 시켜줍니다.
synchronized void finished(AsyncCall call) {
  if (!runningCalls.remove(call)) throw new AssertionError("AsyncCall wasn't running!");
  promoteCalls();
}
private void promoteCalls() {
  if (runningCalls.size() >= maxRequests) return; // Already running max capacity.
  if (readyCalls.isEmpty()) return; // No ready calls to promote.

  for (Iterator<AsyncCall> i = readyCalls.iterator(); i.hasNext(); ) {
    AsyncCall call = i.next();

    if (runningCallsForHost(call) < maxRequestsPerHost) {
      i.remove();
      runningCalls.add(call);
      getExecutorService().execute(call);
    }

    if (runningCalls.size() >= maxRequests) return; // Reached max capacity.
  }
}
요청의 취소는 cancel 함수를 통해 할 수 있습니다. 이때 파라메터로 tag를 넣어줍니다. 실행 요청은 readyCall와 runningCalls와 executedCalls 이 셋중의 하나에 들어 있을 것입니다. 그러므로 이 셋을 순서대로 루프를 돌면서 원하는 Call을 찾아서 cancel 함수를 호출해 줍니다. 음 근데 readyCalls와 runningCalls는 같은 것인데 취소해주는 방식이 다르네요. 결국 같은거 같은데... 왜 이렇게 되어 있을까요?

댓글 없음:

댓글 쓰기

Building asynchronous views in SwiftUI 정리

Handling loading states within SwiftUI views self loading views View model 사용하기 Combine을 사용한 AnyPublisher Making SwiftUI views refreshable r...