iOS/UIKIT

하드코딩 된 쿼리 파라미터 URL 문자열 보다 URLComponents와 URLQueryItem을 사용해보기

밤새는 탐험가89 2024. 9. 4. 12:09

 

리팩토링 전 코드 

class NetworkManager {
    static let shared = NetworkManager()
    
    func getCommonData(contentId: String, completion: @escaping (Result<[Item], Error>) -> Void) {
        guard let url = URL(string: "\(Constants.base_URL)/areaBasedList1?serviceKey=jlK%2B0ig7iLAbdOuTJsnkp6n0RdeEMtGKsw53jEMbKm3PcB7NFTSeUrnXixogiuvNtHQXeqxgV88buRZvTjG73w%3D%3D&numOfRows=10&pageNo=1&MobileOS=ETC&MobileApp=AppTest&_type=json&listYN=Y&arrange=O&contentTypeId=\(contentId)") else { return }
        
        let task = URLSession.shared.dataTask(with: URLRequest(url: url)) { data, _, error in
            guard let data = data, error == nil else { return }
            
            do {
                let results = try JSONDecoder().decode(AttractionResponse.self, from: data)
                completion(.success(results.response.body.items.item))
            } catch {
                completion(.failure(error))
            }
        }
        task.resume()
    }
}

 

 

리팩토링 후 코드 

class NetworkManager {
    static let shared = NetworkManager()
    
    func getCommonData(contentId: String, completion: @escaping (Result<[Item], Error>) -> Void) {
        var components = URLComponents(string: "\(Constants.base_URL)/areaBasedList1")
        
        // 쿼리 아이템 설정
        components?.queryItems = [
            URLQueryItem(name: "serviceKey", value: "jlK%2B0ig7iLAbdOuTJsnkp6n0RdeEMtGKsw53jEMbKm3PcB7NFTSeUrnXixogiuvNtHQXeqxgV88buRZvTjG73w%3D%3D"),
            URLQueryItem(name: "numOfRows", value: "10"),
            URLQueryItem(name: "pageNo", value: "1"),
            URLQueryItem(name: "MobileOS", value: "ETC"),
            URLQueryItem(name: "MobileApp", value: "AppTest"),
            URLQueryItem(name: "_type", value: "json"),
            URLQueryItem(name: "listYN", value: "Y"),
            URLQueryItem(name: "arrange", value: "O"),
            URLQueryItem(name: "contentTypeId", value: contentId)
        ]
        
        // URL 생성
        guard let url = components?.url else { return }
        
        let task = URLSession.shared.dataTask(with: URLRequest(url: url)) { data, _, error in
            guard let data = data, error == nil else { return }
            
            do {
                let results = try JSONDecoder().decode(AttractionResponse.self, from: data)
                completion(.success(results.response.body.items.item))
            } catch {
                completion(.failure(error))
            }
        }
        task.resume()
    }
}

 

 

주요 변경 사항

  1. URLComponents 사용: URLComponents를 사용하여 URL을 생성합니다. 이렇게 하면 URL을 쉽게 구성하고 쿼리 파라미터를 추가할 수 있습니다.
  2. URLQueryItem 사용: URLQueryItem을 사용하여 각 쿼리 파라미터를 설정합니다. 이는 URL 인코딩 문제를 방지하고 코드의 가독성을 높입니다.
  3. components?.url: URLComponents에서 URL을 안전하게 생성합니다. guard let을 사용하여 URL이 유효하지 않은 경우 메서드가 조기에 종료되도록 했습니다.

이점

  • 가독성: 쿼리 파라미터가 명확하게 분리되어 가독성이 향상됩니다.
  • 안전성: 잘못된 URL 구성을 방지합니다. 예를 들어, URLComponents는 URL 인코딩을 자동으로 처리하므로 하드코딩된 URL과 비교할 때 더 안전합니다.
  • 유지보수: 나중에 쿼리 파라미터를 추가하거나 수정해야 할 때 코드 변경이 쉽습니다.

 

 

⭐️ 문제 발생 ⭐️

serviceKey의 퍼센트 인코딩 문제가 발생했습니다. 

인코딩 되면서 기존의 serviceKey와 다르게 나오는데 이를 해결하고자 필요한 부분을 수동으로 수정하여 클래스를 작성했습니다. 

 

// MARK: - Network Manager
class NetworkManager {
    static let shared = NetworkManager()
    
    func getCommonData(contentId: String, completion: @escaping (Result<[Item], Error>) -> Void) {
        var components = URLComponents(string: "\(Constants.base_URL)/areaBasedList1")
        
        // 쿼리 아이템 설정
        components?.queryItems = [
            URLQueryItem(name: "serviceKey", value: Constants.api_key),
            URLQueryItem(name: "numOfRows", value: "10"),
            URLQueryItem(name: "pageNo", value: "1"),
            URLQueryItem(name: "MobileOS", value: "ETC"),
            URLQueryItem(name: "MobileApp", value: "AppTest"),
            URLQueryItem(name: "_type", value: "json"),
            URLQueryItem(name: "listYN", value: "Y"),
            URLQueryItem(name: "arrange", value: "O"),
            URLQueryItem(name: "contentTypeId", value: contentId)
        ]
        
        // 퍼센트 인코딩 후 "+"를 "%2B"로 대체
        if let encodedQuery = components?.percentEncodedQuery?.replacingOccurrences(of: "%25", with: "%") {
            components?.percentEncodedQuery = encodedQuery
        }
        
        // URL 생성
        guard let url = components?.url else { return }
        print(url)
        
        let task = URLSession.shared.dataTask(with: URLRequest(url: url)) { data, _, error in
            guard let data = data, error == nil else { return }
            
            do {
                let results = try JSONDecoder().decode(AttractionResponse.self, from: data)
                completion(.success(results.response.body.items.item))
            } catch {
                completion(.failure(error))
            }
        }
        task.resume()
    }
}