Alamofire 소스 분석해보기.
1. http://happyhourguide.blogspot.kr/2016/10/uiimaveview-extension.html
2. http://happyhourguide.blogspot.kr/2016/10/alamofire-2.html
3. http://happyhourguide.blogspot.kr/2016/12/alamofire-queue.html
https://github.com/Alamofire/Alamofire
1. http://happyhourguide.blogspot.kr/2016/10/uiimaveview-extension.html
2. http://happyhourguide.blogspot.kr/2016/10/alamofire-2.html
3. http://happyhourguide.blogspot.kr/2016/12/alamofire-queue.html
https://github.com/Alamofire/Alamofire
- Alamofire --- SessionManager --- SessionDelegate --- Request --- TaskDelegate -- Response
Alamofire
- 사용자가 웹주소를 String, URL, URLComponents으로 사용하면 이를 URL로 변경해주는 URLConvertible을 제공한다.
- 사용자가 URLRequest를 뽑아낼 수 있는 URLRequestConvertible을 사용할 수도 있다.
SessionManager
- default SessionManager를 제공한다.
- URLSessionConfiguration을 사용해서 항상 사용할 HTTP 헤더를 지정한다: Accept-Encoding, Accept-Language, User-Agent
- request, download, upload, stream
- SessionDelegate를 URLSession에 연결하여 연결 세션에 대한 콜백을 받도록 해준다.
- 보통 콜백이 불리면 상황에 따라 Request의 delegate를 호출한다.
- "delegate[task] = request"를 통해 delegate가 request를 저장하고 있도록 한다.
- 기본은 request.resume()를 호출하여 request에 관련된 task를 실행한다.
- 서버 요청은 별도의 쓰레드에서 이루어지도록 한다: DispatchQueue
- upload
- async로 시작한다: DispatchQueue.global(qos: .utility).async
- multipartFormData를 실행하여 사용자로부터 data를 입력받는다. <- MultipartFormData
- "Content-Type: multipart/form-data; boundary=alamofire.boundary.%08x%08x"
- form data의 크기가 지정한 크기 보다 큰지 확인한다.(기본: 10MB)
- 크지 않은 경우 메모리에서 바로 처리한다.
- 큰 경우 데이타를 파일에 먼저 쓰고 이것을 서버에 보낸다.
- URLSessionConfiguration을 사용해서 항상 사용할 HTTP 헤더를 지정한다: Accept-Encoding, Accept-Language, User-Agent
- 보통 콜백이 불리면 상황에 따라 Request의 delegate를 호출한다.
- async로 시작한다: DispatchQueue.global(qos: .utility).async
- multipartFormData를 실행하여 사용자로부터 data를 입력받는다. <- MultipartFormData
- "Content-Type: multipart/form-data; boundary=alamofire.boundary.%08x%08x"
- form data의 크기가 지정한 크기 보다 큰지 확인한다.(기본: 10MB)
- 크지 않은 경우 메모리에서 바로 처리한다.
- 큰 경우 데이타를 파일에 먼저 쓰고 이것을 서버에 보낸다.
SessionDelegate
- URLSessionTask를 기준으로 하여 request를 저장하고 있는다.
- NSObject를 상속한다.
- extension으로 URLSessionDelegate, URLSessionTaskDelegate, URLSessionDataDelegate, URLSessionDownloadDelegate, URLSessionStreamDelegate를 구현한다.
- Requests credentials
- NSURLAuthenticationMethodServerTrust이면 serverTrustPolicyManager로부터 등록된 serverTrustPolicy를 사용한다.
- serverTrustPolicy가 유효하면 credential을 생성하여 넘겨준다.
- NSURLAuthenticationMethodServerTrust이면 serverTrustPolicyManager로부터 등록된 serverTrustPolicy를 사용한다.
- serverTrustPolicy가 유효하면 credential을 생성하여 넘겨준다.
ParameterEncoding
- encode: URLRequestConvertible에 parameters를 포함한 URLRequest를 생성한다.
- parameter는 percent-escaped, URL encoded query string으로 변환한다.
- parameter는 percent-escaped, URL encoded query string으로 변환한다.
Request
- URLSession과 RequestTask로 초기화 한다.
- RequestTask의 종류(data, download, upload, stream)에 따라 관련 TaskDelegate를 갖는다.
- authenticate
- authorizationHeader
- header에 추가하기 위한 것
- Authorization: Basic 'base64 encoded of user:password'
- header에 추가하기 위한 것
- Authorization: Basic 'base64 encoded of user:password'
DataRequest
- session의 dataTask를 사용하여 URLRequest를 요청한다.
- DataTaskDelegate와 연결된다.
- extension으로 response 함수를 추가한다.
TaskDelegate
- NSObject를 상속
- objc에 의해 실행되는 함수들을 가지고 있다.
- 사용자로부터의 response handler를 실행하기 위한 queue를 가지고 있는다.
- OperationQueue
- DataTaskDelegate, DownloadTaskDelegate, UploadTaskDelegate
- objc에 의해 실행되는 함수들을 가지고 있다.
- OperationQueue
- DataTaskDelegate, DownloadTaskDelegate, UploadTaskDelegate
Response
- DefaultDataResponse: data나 upload의 non-serialized response에 사용
- DataResponse<Value>: data나 upload의 serialized response에 사용
- DefaultDownloadResponse: download의 non-serialized response에 사용
- DownloadResponse<Value>: download의 serialized response에 사용
- 애플이 제공하는 'URL Session Task Metrics'의 사용
- 위의 모든 response에 추가로 Response 프로토콜을 정의하여 metric을 내부에 저장한다.
- 위의 모든 response에 추가로 Response 프로토콜을 정의하여 metric을 내부에 저장한다.
ResponseSerialization
- extension의 형태로 서버로부터 온 데이타를 serialize해주는 함수들을 정의한다.
- Raw data -> no serialize
- Data -> serializeResponseData
- String -> serializeResponseString
- JSON -> serializeResponseJSON
- Property List -> serializeResponsePropertyList
- 내부에 저장할 타입과 이를 만들 serialize 함수를 가지고 있는 DataResponseSerializerProtocol 프로토콜을 정의한다.
- DataResponseSerializerProtocol를 구현한 구조체로 DataResponseSerializer<Value>를 정의한다.
- 특정 serialize 함수를 사용할 response 함수를 정의한다: responseJSON, responseString 등...
- JSON을 응답으로 주는 경우에 대한 코드를 살펴보자.
- JSON의 serializer로 DataRequest.jsonResponseSerializer를 사용한다.
- DataRequest.jsonResponseSerializer는 'struct DataResponseSerializer<Any>'를 리턴하는데 이 struct는 DataResponseSerializerProtocol을 구현하고 있다.
- DataResponseSerializerProtocol은 serialize의 결과를 리턴하는 serializeResponse 함수(closure)를 가지고 있다.
- DataResponseSerializer는 serializeResponse 함수로 Request.serializeResponseJSON를 사용한다.
- serializeResponseJSON는 JSONSerialization.jsonObject를 사용해서 응답 data로부터 object를 생성한다.
- 내가 새로운 serializer를 추가한다면 DataResponseSerializerProtocol를 상속한 struct를 만들고 이를 response 함수에 넘겨주면 된다.
- Raw data -> no serialize
- Data -> serializeResponseData
- String -> serializeResponseString
- JSON -> serializeResponseJSON
- Property List -> serializeResponsePropertyList
- DataResponseSerializerProtocol를 구현한 구조체로 DataResponseSerializer<Value>를 정의한다.
- 특정 serialize 함수를 사용할 response 함수를 정의한다: responseJSON, responseString 등...
- JSON의 serializer로 DataRequest.jsonResponseSerializer를 사용한다.
- DataRequest.jsonResponseSerializer는 'struct DataResponseSerializer<Any>'를 리턴하는데 이 struct는 DataResponseSerializerProtocol을 구현하고 있다.
- DataResponseSerializerProtocol은 serialize의 결과를 리턴하는 serializeResponse 함수(closure)를 가지고 있다.
- DataResponseSerializer는 serializeResponse 함수로 Request.serializeResponseJSON를 사용한다.
- serializeResponseJSON는 JSONSerialization.jsonObject를 사용해서 응답 data로부터 object를 생성한다.
Timeline
- Timeline
- Alamofire 자체에서 추적하는 값
- startTime: request 시작시 CFAbsoluteTimeGetCurrent()로 저장
- requestCompletedTime: request 시작시 delegate.queue에 endTime을 계산하는 함수를 추가해주어 응답 시간이 바로 계산되어 저장되도록 한다.
- initialResponseTime: delegate의 urlSession이 호출되는 시점에 값을 계산해준다.
- serializationCompletedTime: completionHandler에서 serialization을 마친 후의 값.
- latency = initialResponseTime - requestStartTime
- requestDuration = requestCompletedTime - requestStartTime
- serializationDuration = serializationCompletedTime - requestCompletedTime
- totalDuration = serializationCompletedTime - requestStartTime
- CustomStringConvertible과 CustomDebugStringConvertible을 extension으로 구현.
- URL Session Task Metrics
- iOS와 tvOS 10과 macOS 10.12에서 사용가능한 URLSessionTaskMetrics API.
- SessionDelegate에 didFinishCollectingMetrics의 경우 불리는 urlSession을 정의한다: metric을 TaskDelegate의 metrics에 넘긴다.
- completionHandler가 불릴 때 delegate로부터의 metrics를 response에 넘긴다: response의 _metrics에 metric이 저장이 된다.
- 사용자가 response.metrics를 부르면 _metrics를 리턴한다.
- Alamofire 자체에서 추적하는 값
- startTime: request 시작시 CFAbsoluteTimeGetCurrent()로 저장
- requestCompletedTime: request 시작시 delegate.queue에 endTime을 계산하는 함수를 추가해주어 응답 시간이 바로 계산되어 저장되도록 한다.
- initialResponseTime: delegate의 urlSession이 호출되는 시점에 값을 계산해준다.
- serializationCompletedTime: completionHandler에서 serialization을 마친 후의 값.
- latency = initialResponseTime - requestStartTime
- requestDuration = requestCompletedTime - requestStartTime
- serializationDuration = serializationCompletedTime - requestCompletedTime
- totalDuration = serializationCompletedTime - requestStartTime
- CustomStringConvertible과 CustomDebugStringConvertible을 extension으로 구현.
- iOS와 tvOS 10과 macOS 10.12에서 사용가능한 URLSessionTaskMetrics API.
- SessionDelegate에 didFinishCollectingMetrics의 경우 불리는 urlSession을 정의한다: metric을 TaskDelegate의 metrics에 넘긴다.
- completionHandler가 불릴 때 delegate로부터의 metrics를 response에 넘긴다: response의 _metrics에 metric이 저장이 된다.
- 사용자가 response.metrics를 부르면 _metrics를 리턴한다.
ServerTrustPolicy
- ServerTrustPolicyManager에 의해 호스트 별로 관리될 수 있다.
- URLSession에 ServerTrustPolicyManager를 저장해 놓기 위해 AssociatedKeys를 사용한다.
- 5가지의 policy가 있다: performDefaultEvaluation, pinCertificates, pinPublicKeys, disableEvaluation, customEvaluation
- SessionDelegate에서 서버로부터 authentication관련 응답이 왔을 때 사용된다.
MultipartFormData
- 서버에 보낼 내용을 BodyPart에 저장한다.
- append: Data, InputStream, URL의 형태로 추가한다.
- 파일의 URL을 주는 경우
- mimetype을 사용자가 지정해 주지 않으면 mimetype을 확인한다.
- content type을 가져올 수 있으면 가져오고 안되면 "application/octet-stream"으로 한다.
- content type가져올 때 사용하는 함수: UTTypeCreatePreferredIdentifierForTag -> UTTypeCopyPreferredTagWithClass
- 파일이 올바른지를 위해 아래의 5가지를 확인한다.
- is file URL?
- is file URL reachable?
- is file URL a directory?
- can the file size be extracted?
- can a stream be created from file URL?
- BodyPart
- HTTPHeaders: [String: String]
- InputStream
- encode
- initialData
- "--(boundary)(EncodingCharacters.crlf)"
- headerData
- Content-Disposition: form-data; name="file1"; filename="a.txt"
- Content-Type: text/plain
- 마지막에 EncodingCharacters.crlf 를 추가한다.
- bodyStreamData
- finalBoundaryData
- "(EncodingCharacters.crlf)--(boundary)--(EncodingCharacters.crlf)"
- writeEncodedData
- 서버에 보낼 내용이 큰 경우에는 파일로 저장한 후 보낼 수 있도록 한다.
- mimetype을 사용자가 지정해 주지 않으면 mimetype을 확인한다.
- content type을 가져올 수 있으면 가져오고 안되면 "application/octet-stream"으로 한다.
- content type가져올 때 사용하는 함수: UTTypeCreatePreferredIdentifierForTag -> UTTypeCopyPreferredTagWithClass
- 파일이 올바른지를 위해 아래의 5가지를 확인한다.
- is file URL?
- is file URL reachable?
- is file URL a directory?
- can the file size be extracted?
- can a stream be created from file URL?
- HTTPHeaders: [String: String]
- InputStream
- initialData
- "--(boundary)(EncodingCharacters.crlf)"
- headerData
- Content-Disposition: form-data; name="file1"; filename="a.txt"
- Content-Type: text/plain
- 마지막에 EncodingCharacters.crlf 를 추가한다.
- bodyStreamData
- finalBoundaryData
- "(EncodingCharacters.crlf)--(boundary)--(EncodingCharacters.crlf)"
- 서버에 보낼 내용이 큰 경우에는 파일로 저장한 후 보낼 수 있도록 한다.
RequestAdapter
- public protocol RequestAdapter
- adapt 함수를 구현하도록 한다.
- URLRequest를 변경하여 변경된 Request를 서버에 요청하도록 한다.
- SessionManager에서 adapter를 로컬 변수로 들고 있다. 따라서, 여기에 내가 원하는 adapter를 구현한 후 이것을 SessionManager의 adapter에 설정해 준다.
- URLRequest에 extension으로 adapt 함수를 구현한다. 이 함수는 RequestAdapter를 input으로 받아서 RequestAdapter의 adapt 함수를 호출해준다.
- 동작 방법
- SessionManager의 request 함수를 호출한다.
- DataRequest의 Requestable의 task 함수를 통해 URLSessionTask를 생성한다.
- task 함수의 input으로 RequestAdapter를 넣는다.
- task 함수는 URLSessionTask를 생성하기 전에 URLRequest의 adapt를 먼저 호출한다.
- adapt 함수로부터 리턴받은 URLRequest를 URLSessionTask를 생성할 때 input으로 넣어준다.
- adapt 함수를 구현하도록 한다.
- SessionManager의 request 함수를 호출한다.
- DataRequest의 Requestable의 task 함수를 통해 URLSessionTask를 생성한다.
- task 함수의 input으로 RequestAdapter를 넣는다.
- task 함수는 URLSessionTask를 생성하기 전에 URLRequest의 adapt를 먼저 호출한다.
- adapt 함수로부터 리턴받은 URLRequest를 URLSessionTask를 생성할 때 input으로 넣어준다.
RequestRetrier
- 실행중 에러가 발생했을 때 다시 시도할 수 있도록 한다.
- public protocol RequestRetrier
- should 함수를 구현하도록 한다. -> 사용자가 필요한 작업을 한 후 completion을 호출하도록 한다.
- Request에 retryCount가 로컬 변수로 있어서 retry시 값을 증가시켜준다. 따라서, should 함수 구현시 retryCount 값을 통해 몇번이나 다시 시도 중인지 파악할 수 있다.(예를 들어 무한 시도를 막는 용도로 사용할 수 있다.)
- 동작 방법
- SessionManager의 request 함수를 호출한다.
- 에러 발생시 allowRetrier 함수를 호출한다.
- DispatchQueue.global의 qos를 utility로 한 asynchronous로 RequestRetrier의 should 함수를 호출한다.
- should 함수에서의 completion은 input으로 다시 시도를 할지 안할지를 받아서 다시 시도 할 거면 URLSessionTask를 만들어서 실행하는 작업을 다시 시도하고 아니면 끝낸다.
- should 함수를 구현하도록 한다. -> 사용자가 필요한 작업을 한 후 completion을 호출하도록 한다.
- SessionManager의 request 함수를 호출한다.
- 에러 발생시 allowRetrier 함수를 호출한다.
- DispatchQueue.global의 qos를 utility로 한 asynchronous로 RequestRetrier의 should 함수를 호출한다.
- should 함수에서의 completion은 input으로 다시 시도를 할지 안할지를 받아서 다시 시도 할 거면 URLSessionTask를 만들어서 실행하는 작업을 다시 시도하고 아니면 끝낸다.
Response시 실행되는 handler의 동작
- TaskDelegate에 OperationQueue를 설정한다.
- maxConcurrentOperationCount = 1로 한번에 하나씩 순서대로 실행되도록 한다.
- isSuspended = true로 중단된 상태로 둔다.
- qualityOfService = utility로 한다.
- request에 response handler를 등록하면 OperationQueue에 관련된 operation을 하는 함수를 등록하는데 보통 이 함수의 끝에서 response handler를 호출해 준다.
- 사용자가 별도로 queue를 설정해 주지 않았으면 보통 DispatchQueue.main에서 handler를 실행한다.
- 서버로부터 응답이 오거나 request 중 에러가 발생하면 isSuspended를 false로 설정하여 등록된 operation들이 순차적으로 실행되도록 한다.
- maxConcurrentOperationCount = 1로 한번에 하나씩 순서대로 실행되도록 한다.
- isSuspended = true로 중단된 상태로 둔다.
- qualityOfService = utility로 한다.
- 사용자가 별도로 queue를 설정해 주지 않았으면 보통 DispatchQueue.main에서 handler를 실행한다.