본문 바로가기

iOS

[Swift] Alamofire를 Moya처럼 사용해보자! By Router Pattern (4편 - EventLogger로 통신 결과 확인하기)

 

 

[Swift] Alamofire를 Moya처럼 사용해보자! By Router Pattern (1편 - Foundation Setting)

[Swift] Alamofire를 Moya처럼 사용해보자! By Router Pattern (2편 - Services, Routers 구현)

[Swift] Alamofire를 Moya처럼 사용해보자! By Router Pattern (3편 - body, queryBody, requestPlain, Multipart 구현)


3편까지는 다양한 request 타입이나 task의 종류에 따른 대응을 하는 과정이었습니다.

이번 편에서는 개발 과정에서 Network 통신의 상태를 편하게 확인할 수 있도록 하는 EventLogger를 제작해 보겠습니다.

EventMonitor 프로토콜

Alamofire에는 request나 response 시에 호출되는 메서드들을 정의해 놓은 EventMonitor라는 프로토콜이 있습니다.
/// Protocol outlining the lifetime events inside Alamofire. It includes both events received from the various
/// `URLSession` delegate protocols as well as various events from the lifetime of `Request` and its subclasses.
public protocol EventMonitor {

해석해보면 알라모파이어 내에 존재하는 생활주기를 추적할 수 있게 만든 프로토콜이라고 하는데요, URLSession delegate 프로토콜에서 온 이벤트나 Request의 생활주기에서 온 이벤트들을 포함하여 받는다고 합니다.

 

request가 생성되거나, response가 왔거나, request가 취소되었거나 하는 상황에 실행되는 메서드들이 존재하기 때문에, 그 메서드 안에 원하는 처리를 해주면 log를 띄우거나 error 처리를 할 수 있습니다.

 

아래는 EventMonitor를 채택하여 구현한 APIEventLogger 클래스입니다.

import Alamofire
import Foundation

class APIEventLogger: EventMonitor {
    
    let queue = DispatchQueue(label: "NetworkLogger")
    
    func requestDidFinish(_ request: Request) {
        print("----------------------------------------------------\n\n" + "              🛰 NETWORK Reqeust LOG\n" + "\n----------------------------------------------------")
        print("1️⃣ URL / Method / Header" + "\n" + "URL: " + (request.request?.url?.absoluteString ?? "")  + "\n"
              + "Method: " + (request.request?.httpMethod ?? "") + "\n"
              + "Headers: " + "\(request.request?.allHTTPHeaderFields ?? [:])")
        print("----------------------------------------------------\n2️⃣ Body")
        if let body = request.request?.httpBody?.toPrettyPrintedString {
            print("Body: \(body)")
        } else { print("보낸 Body가 없습니다.")}
        print("----------------------------------------------------\n")
    }
    
    func request<Value>(_ request: DataRequest, didParseResponse response: DataResponse<Value, AFError>) {
        print("              🛰 NETWORK Response LOG")
        print("\n----------------------------------------------------")
        
        switch response.result {
            
        case .success(_):
            print("3️⃣ 서버 연결 성공")
        case .failure(_):
            print("3️⃣ 서버 연결 실패")
            print("올바른 URL인지 확인해보세요.")
        }
        
        print("Result: " + "\(response.result)" + "\n"
            + "StatusCode: " + "\(response.response?.statusCode ?? 0)"
        )
        
        if let statusCode = response.response?.statusCode {
            switch statusCode {
            case 400..<500:
                print("❗오류 발생 : RequestError\n" + "잘못된 요청입니다. request를 재작성 해주세요.")
            case 500:
                print("❗오류 발생 : ServerError\n" + "Server에 문제가 발생했습니다.")
            default:
                break
            }
        }
        
        print("----------------------------------------------------")
        print("4️⃣ Data 확인하기")
        if let response = response.data?.toPrettyPrintedString {
            print(response)
        } else { print("❗데이터가 없거나, Encoding에 실패했습니다.")}
        print("----------------------------------------------------")
    }
    
    func request(_ request: Request, didFailTask task: URLSessionTask, earlyWithError error: AFError) {
        print("URLSessionTask가 Fail 했습니다.")
    }
    
    func request(_ request: Request, didFailToCreateURLRequestWithError error: AFError) {
        print("URLRequest를 만들지 못했습니다.")
    }
    
    func requestDidCancel(_ request: Request) {
        print("request가 cancel 되었습니다")
    }
}

extension Data {
    var toPrettyPrintedString: String? {
        guard let object = try? JSONSerialization.jsonObject(with: self, options: []),
              let data = try? JSONSerialization.data(withJSONObject: object, options: [.prettyPrinted]),
              let prettyPrintedString = NSString(data: data, encoding: String.Encoding.utf8.rawValue) else { return nil }
        return prettyPrintedString as String
    }
}

requestDidFinish : 말 그대로 request가 끝났을 때 호출됩니다. 파라미터 request에 접근하여 header, body, method 등을 확인할 수 있습니다.

request<Value>(_:didParseResponse:) : response가 오면 호출됩니다. response의 결과에 따라 통신 결과를 요약해서 확인할 수 있습니다.

Custom Session에 Logger 할당해주기

이렇게 EventLogger를 만들었으니, 기존에 BaseService에 만든 AFManager에 EventLogger를 할당해주기만 하면, AFManager를 통해 실행되는 모든 request에서 동일한 EventLogger를 적용할 수 있습니다.

class BaseService {
    let AFManager: Session = {
        var session = AF
        let configuration = URLSessionConfiguration.af.default
        configuration.timeoutIntervalForRequest = NetworkEnvironment.requestTimeOut
        configuration.timeoutIntervalForResource = NetworkEnvironment.resourceTimeOut
        let eventLogger = APIEventLogger()
        session = Session(configuration: configuration, eventMonitors: [eventLogger])
        return session
    }()
}

아래는 실행 결과입니다.

----------------------------------------------------

              🛰 NETWORK Reqeust LOG

----------------------------------------------------
1️⃣ URL / Method / Header
URL: "-"
Method: GET
Headers: ["accesstoken": "", "Content-Type": "Application/json"]
----------------------------------------------------
2️⃣ Body
보낸 Body가 없습니다.
----------------------------------------------------

              🛰 NETWORK Response LOG

----------------------------------------------------
3️⃣ 서버 연결 성공
Result: success(1228 bytes)
StatusCode: 200
----------------------------------------------------
4️⃣ Data 확인하기
{
  "status" : 200,
  "success" : true,
  "message" : "스케줄 조회 성공",
  "data" : {
    "onSale" : 0,
    "category" : "도서\/티켓\/음반",
    "content" : "새 책입니다.",
    "isPriceSuggestion" : "가격제안가능",
    "price" : "16,000원",
    "title" : "최태성 한국사",
    "image" : [
      "-",
      "-",
      "-"
    ],
    "view" : 3,
    "isLiked" : false,
    "createdAt" : "5분전",
    "user" : {
      "profile" : "-",
      "name" : "Ussser",
      "area" : "개봉동"
    }
  }
}
----------------------------------------------------

 

참고

https://ios-development.tistory.com/733