개발/아이오에스

ios] Alamofire / kakao api 활용 http 통신하기

dev.jake 2021. 2. 2. 11:04

API 폴더로 따로 빼서 구조화

 

//
//  MySearchRouter.swift
//  brandi
//
//  Created by dev.geeyong on 2021/01/28.
//

import Foundation
import Alamofire

enum MySearchRouter: URLRequestConvertible {
    case searchPhotos(term: String, pageNumber: String)
    
    var baseURL: URL {
        return URL(string: API.BASE_URL + "/search/")!
    }
    
    var method: HTTPMethod {
        
        return .get
//        switch self {
//        case .searchPhotos:
//            return .get
//        }
    }
    
    var path: String {
        switch self {
        case .searchPhotos: return "image"
        }
    }
    //let queryParam = ["query" : "코카콜라"]
    var parameters: [String:String]{
        switch self {
        case let .searchPhotos(term,pageNumber): //검색어, 결과 페이지 번호, 1~50 사이의 값, 기본 값 1
            return [
                "query" : term,
                "page"  : pageNumber,
                "size"  : "30"
            ]
        }
    }
    func asURLRequest() throws -> URLRequest {
    
        let url = baseURL.appendingPathComponent(path)
        print("asURLRequest- / image 처리 url: \(url)")
        var request = URLRequest(url: url)
        request.method = method
        request = try URLEncodedFormParameterEncoder().encode(parameters, into: request)
//        switch self {
//        case let .get(parameters):
//            request = try URLEncodedFormParameterEncoder().encode(parameters, into: request)
//        case let .post(parameters):
//            request = try JSONParameterEncoder().encode(parameters, into: request)
//        }
        
        return request
        
        //https://dapi.kakao.com/v2/search/image?query=
    }
}

MySearchRouter.swift 에서 Alamofire에 URLRequestConvertible을 상속받습니다. 그리고 API URL을 설정할 수 있고 method와 path, parameters까지 설정할 수 있습니다.

 

//
//  MyLogger.swift
//  brandi
//
//  Created by dev.geeyong on 2021/01/28.
//

import Foundation
import Alamofire

final class MyLogger: EventMonitor{
    let queue = DispatchQueue(label: "ApiLog")
    
    func requestDidResume(_ request: Request) {
        print("MyLogger - requestDidResume")
       // debugPrint(request)
    }
    func request<Value>(_ request: DataRequest, didParseResponse response: DataResponse<Value, AFError>) {
        print("MyLogger - didParseResponse")
       // debugPrint(response)
    }

}

MyLogger.swift에서는 EventMonitor을 상속받아 말 그대로 http통신 이벤트 모니터를 할 수 있습니다. 

Swift에서 final키워드를 사용하면 메소드, 프로퍼티, 서브 스크립트가 오버라이드를 금지할 수 있습니다주로 재정의할 필요가 없을 때 final을 사용하는 것이 좋습니다. 클래스 자체가 상속되는 것을 막으려고 할 때에도 final을 사용할 수 있습니다

 

//
//  Interceptor.swift
//  brandi
//
//  Created by dev.geeyong on 2021/01/28.
//

import Foundation
import Alamofire

class MyInterceptor: RequestInterceptor{
    //카카오api key - accessToken
    let accessToken: String = API.API_ID
    
    func adapt(_ urlRequest: URLRequest, for session: Session, completion: @escaping (Result<URLRequest, Error>) -> Void){
        print("Interceptor adapt")
        var urlRequest = urlRequest
      
        urlRequest.headers.add(.authorization(accessToken))
        completion(.success(urlRequest))
        //공통 파라매터 추가가능부분
        //헤더에 카카오api key 추가
    }
    func retry(_ request: Request, for session: Session, dueTo error: Error, completion: @escaping (RetryResult) -> Void) { //결과 값이 없을 때 리트라이를 할거냐
        print("Interceptor retry")
        completion(.doNotRetry)
    }
}

 

MyInterceptor.swift에서는 RequestInterceptor을 상속받습니다. 여기서 헤더를 추가합니다. 카카오 api에서는 api id값을 헤더에 추가시켜줍니다.

 

//
//  AlamofireManager.swift
//  brandi
//
//  Created by dev.geeyong on 2021/01/28.
//

import Foundation
import Alamofire
import SwiftyJSON

final class AlamofireManager{
    
    //싱글톤 적용
    static let shared = AlamofireManager()
    
    let interceptors = Interceptor(interceptors:
        [
            MyInterceptor() //headers 값 관리
        ])
    let monitors = [MyLogger()]
    
    var sesstion: Session
    
    private init(){
        sesstion = Session(interceptor: interceptors, eventMonitors: monitors)
    }
    
    func getPhotos(searcTerm userInput: String, pageNumber: String, completion: @escaping(Result<[Photo],MyError>)->Void){
        
        print("Manager - getphotos")
        
        self.sesstion
            .request(MySearchRouter.searchPhotos(term: userInput, pageNumber: pageNumber))
            .validate(statusCode: 200..<401)
            .responseJSON(completionHandler: {response in
                
                guard let reponseValue = response.value else { return }
                
                let responseJson = JSON(reponseValue)
                
                let jsonArray = responseJson["documents"]
          
                var photos = [Photo]()
                
                for (index, subJson): (String, JSON) in jsonArray{
                    
                    //print("index: \(index) , subJson: \(subJson)")
                    
                    let thumbnail = subJson["thumbnail_url"].string ?? ""
                    let datetime = subJson["datetime"].string ?? ""
                    let displaySite = subJson["display_sitename"].string ?? ""
                    let imageURL = subJson["image_url"].string ?? ""
                    
                    let photoItem = Photo(thumbnailURL: thumbnail, datetime: datetime, displaySiteName: displaySite, imageURL: imageURL)
                    
                    photos.append(photoItem)
                    
                }
      
                //값 전송
                if photos.count > 0{
                    completion(.success(photos))
                }else{
                    completion(.failure(.noContent))
                }
                
                let jsonMetaData = responseJson["meta"]["is_end"]
                if jsonMetaData == true{ //"is_end" 현재 페이지가 마지막 페이지인지 여부
                    return
                }
                
            })
    }
    
}

 

AlamofireManager.swift에서는 위에 모든 swift 파일들을 모아 컨트롤을 해줍니다. 싱글톤을 적용을 해서 생성된 객체를 어디에서든지 참조할 수 있도록 했습니다. 인터셉터와 모니터를 만든 파일로 설정을 해주고, 세션을 통해 init을 해줍니다. 그리고 getPhotos라는 함수를 만들어 http통신을 해줍니다.

 

//MARK: - AlamofireManager - getDataResult

extension ViewController{
    func  getDataResult (searcTerm: String, pageNumber: Int) {
        logoImageView.isHidden = true
        
        AlamofireManager.shared.getPhotos(searcTerm: searcTerm, pageNumber: "\(pageNumber)" ,completion:  {
            [weak self]
            result in
            
            guard let self = self else {return}
            
            switch result{
            case .success(let fetchdPhotos):
                print("success AlamofireManager", fetchdPhotos.count)
                self.photosData = fetchdPhotos //데이터 저장
                
                if fetchdPhotos.count > 30 { //최초 30개 데이터 fetch
                    print("30 data append")
                    for i in self.limit-30..<self.limit{
                        self.photos.append(self.photosData[i])
                    }
                }else{ // 검색 결과의 이미지 갯수가 30개 이하인 경우 처리
                    for i in 0..<fetchdPhotos.count{
                        self.photos.append(self.photosData[i])
                    }
                }
                
                DispatchQueue.main.async {
                    self.collectionView.reloadData()
                    if self.limit == 30{ //새로운 이미지 검색시 스크롤을 맨 위로
                        let indexPath = IndexPath(item: 0, section: 0)
                        self.collectionView.scrollToItem(at: indexPath, at: .bottom, animated: true)
                    }
                    
                }
                
            case .failure(let failError):
                print("home get error \(failError.rawValue)")
                self.view.makeToast(failError.rawValue, duration: 3.0, position: .center) //Toast_Swift
                self.collectionView.reloadData ()
                self.logoImageView.isHidden = false
            }
            
        })
        
    }
}

이제 ViewController에 돌아와 만든 AlamofireManager을 통해 싱글톤으로 접근해서 getphotos라는 함수를 사용하는 모습입니다.

 

 

github.com/dev-geeyong/brandi_submit-main

 

dev-geeyong/brandi_submit-main

Contribute to dev-geeyong/brandi_submit-main development by creating an account on GitHub.

github.com