나의 발자취

KakaoAPI를 활용한 BookSearch 앱 만들기 (1) 본문

앱 개발/iOS

KakaoAPI를 활용한 BookSearch 앱 만들기 (1)

달모드 2024. 9. 6. 14:57

Kakao API를 통해, 책 검색 API를 사용해 검색한 키워드대로 책 목록을 가져오고, 목록을 클릭하면 상세 화면으로 넘어가 책에 대한 상세 내용을 웹뷰로 띄워줄것이다.

 

func search()

먼저 Kakao API로 책을 검색할 수 있는 함수를 만들어줄 것이다. (API 활용부분은 넘어가기로 함.)

func search() 안에 url, request를 구현해준다.

func search(){
        var endPoint = "https://dapi.kakao.com/v3/search/book?sort=recency&query=love"
        guard let url = URL(string: endPoint) else { return }
        var request = URLRequest(url: url)

 

Header 교체

카카오 API key를 넣어주기 위해 헤더에 값을 넣어줄 것이다.

        request.httpMethod = "GET" // optional이라 기본값이 있어서, 안넣어도 됨.
        request.addValue(apiKey, forHTTPHeaderField: "Authorization")

 

세션 연결

let session = URLSession.shared

세션 연결 코드를 보면, URLSession.shared이라고 된 걸 볼 수 있다.  property를 호출하고 있는건데, 이것은 Singleton 객체 라는 것을 알면 된다. 충돌을 방지하기 위해, 세션은 하나만 열려야 하므로 싱글톤 패턴을 사용했다.

 

Task 생성 & 실행

세션을 통해 request를 보내는 task 변수를 생성해줄 것이다.

let task = session.dataTask(with: request)

 

그리고 task를 생성만 하고선 실행을 해주어야지만 동작한다!! 그러므로 아래쪽에 아래와 같이 코드를 작성해준다.

 

 

Singleton Pattern

여기서 잠깐, dataTask()를 보자.⭐️

'

dataTask()를 보면, 받는 인자는 두개인것을 알 수 있고, completionHandler 파라미터의 경우 함수를 받는 것을 알 수 있다. 이 함수는 세 개의 인자값을 가지는데, Data?, URLResponse?, (any Error)? 을 받아서 return은 void값을 하는 함수라는 것을 알 수 있다. 그래서 completionHandler를 클로저로 해주어야하는 것이다.

클로저이기때문에 엔터를 쳐서 클로저로 만들어준다!

 

 

지금 task 변수가 하는 일을 짚어보자.

 let task = session.dataTask(with: request)

  • task를 받아서 request를 받는다. 그다음 뒤의 클로저가 호출된다.
    { data, response, error in ~~~~~
let task = session.dataTask(with: request) { data, response, error in
            if let error {
                print("error 발생")
                return
            }

 

 

JSON Serialization (데이터 직렬화), 예외 처리

root 변수를 보면, 우리가 지금 받는 데이터는 지금 아래와 같이 크게 documents, meta 데이터로 구분할 수 있다.

documents - Array, meta - Object (Dictionary) 타입인 걸 알 수 있으니 [String:Any]로 받는다.

  • 여기서 'root'로 변수명을 설정한 이유는, 시멘틱하게 지어서 그렇다.
  • 또한 우리가 받는 타입이 Object(Diction) type 이므로 .jsonObject 를 사용했지만 Array인 경우 .jsonArray를 사용한다.
guard let data else { return }
            // Data 직렬화
            do {
                guard let root = try JSONSerialization.jsonObject(with: data) as? [String:Any],
                let documents = root["documents"] as? [[String:Any]]
                else { return }
            } catch {
                print("JSON Parsing Error Occured")
            }

 

* 한줄 위로: Cmd+Opt+{

 

코드 간결성을 위해서 guard let 두개였는데 각각으로 분리해서 빼줬다.

guard let data else { return }
            // Data 직렬화
            do {
                // root 객체 - Object(Diction) type 이므로 .jsonObject (Array인 경우 .jsonArray)
                guard let root = try JSONSerialization.jsonObject(with: data) as? [String:Any]
                else { return }
                let documents = root["documents"] as? [[String:Any]]
            } catch {
                print("JSON Parsing Error Occured")
            }

 

그리고 'document' 변수를 클래스 전역변수로 사용할 수 있도록 클래스 제일 위 선언부에 선언해주었다.

var documents: [[String:Any]]?

 

클로저 안에서 메서드에 접근하려면, 앞에 'self' 키워드를 붙여주어야한다.!! 클로저가 호출되는 시점에서 컨텍스트를 다 가지고 간다.

self.documents = root["documents"] as? [[String:Any]]

do {
                // root 객체 - Object(Diction) type 이므로 .jsonObject (Array인 경우 .jsonArray)
                guard let root = try JSONSerialization.jsonObject(with: data) as? [String:Any]
                else { return }
                self.documents = root["documents"] as? [[String:Any]]
            } catch {
                print("JSON Parsing Error Occured")
            }

 

func 인자값 넣기

func search () 에서 

    func searchWithQuery(_ query: String?, page: Int){ 로 바꾸었다.

 

 

그럼 함수 호출할때도 이런식 >> searchWithQuery("사랑", page: 2) 으로 호출을 해주어야할 것이다.

 

URL 내 한글문자 인코딩하여 처리하기

let strURL = endPoint.addingPercentEncoding(withAllowedCharacters: urlQueryAllowed)

 

옵셔널이므로, 이 또한 언래핑을 해주어야해서 guard-let이 두 번 들어가야 하는 상황이다. 따라서 마침 url의 guard-let 처리도 아래에 있으므로 합쳐서 하나로 만들어준다.

 

<Before>

let strURL = endPoint.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)
guard let url = URL(string: endPoint) else { return }

 

<After>

guard let strURL = endPoint.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed),
      let url = URL(string: endPoint) else { return }

 

 

 

SearchBarDelegate 메서드 구현하기

스토리보드에 이렇게 구현을 해주었는데, search bar를 보면 Delegate는 이미 생성된것을 볼 수 있어서 메서드 정의만 해줄것이다.

 

 

extension BookTableViewController: UISearchBarDelegate {
    func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
        page = 1
        searchWithQuery(searchBar.text, page: page)
    }
}

 

 

searchBar.text 가 옵셔널이라, 위쪽 searchWithQuery 함수에서 옵셔널을 처리해주기로 한다.

guard let query else { return }

 

 

UITableViewController: numberOfSections, numberOfRowsInSection 구현

override func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }

 

그리고

documents.count를 쓰면 옵셔널이라 에러가 나서, nil-coalescing operator를 써서 optional 바인딩을 해준다.

override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return documents?.count ?? 0

 

cellForRowAt

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "book", for: indexPath)

        guard let book = documents?[indexPath.row] else { return cell }
        let imageView = cell.viewWithTag(1) as? UIImageView
        
        let lblTitle = cell.viewWithTag(2) as? UILabel
        lblTitle?.text = book["title"] as? String
        
        let lblAuthors = cell.viewWithTag(3) as? UILabel
        let authors = book["authors"] as? [String]
        lblAuthors?.text = authors?.joined(separator: ", ")
        
        let lblPublisher = cell.viewWithTag(4) as? UILabel
        lblPublisher?.text = book["publisher"] as? String
        
        let lblPrice = cell.viewWithTag(5) as? UILabel
        lblPrice?.text = "\((book["price"] as? Int) ?? 0)원"

        return cell
    }

 

 

그리고 실행해보면, 시뮬레이터에 나오지 않는다.

그 이유는? GCD를 알아야한다.

다음 편에 계속 --

728x90
반응형
Comments