[새싹 iOS] 4주차_챌린지

임승섭·2023년 8월 13일
0

새싹 iOS

목록 보기
12/45

API 연속 호출

  • 입력된 언어를 감지하고, 해당 언어를 영어로 번역하는 코드를 작성했다
  • 먼저 언어 감지 api를 호출해서 결과를 저장한다
  • 저장한 결과를 가지고 번역 api를 호출한다. target은 영어로 고정한다

맨 처음

  • 단순하게 함수 두개 만들어서 실행시켰다
  • 문제는 첫 번째 함수의 결과가 나오기 전에 두 번째 함수가 실행되어버리기 때문에 번역 작업을 할 수가 없었다

그 다음

  • 첫 번째 함수의 api 호출에서 case .success 안에다가
    두 번째 api 호출을 진행했다
  • 정상적으로 기능 구현하는 건 성공했지만, 뭔가 깔끔해 보이지 않았다
 func translate() {
		
        // 언어 감지할 텍스트 (번역할 때도 사용)
        guard let txt = firstTextView.text else { return }

        let header: HTTPHeaders = [
            "X-Naver-Client-Id" : APIKey.naver,
            "X-Naver-Client-Secret" : APIKey.naverSecret
        ]

		// 감지한 결과 저장할 변수 (ko, en, ...)
        var langType = ""


        /* ========== 언어 감지 ========== */
        // 최종 url
        let url2 = "https://openapi.naver.com/v1/papago/detectLangs"

        let parameter2: Parameters = [
            "query" : txt
        ]

        // SwiftyJSON : Work with Alamofire
        AF.request(url2, method: .post, parameters: parameter2, headers: header)
            .validate()
            .responseJSON { response in
                switch response.result {
                case .success(let value):
                    let json = JSON(value)

					// 감지한 언어 결과 저장
                    langType = json["langCode"].stringValue


                    /* ========== 번역 작업 ========== */
                    // 최종 url
                    let url = "https://openapi.naver.com/v1/papago/n2mt"

                    // header는 동일하기 때문에 생략

                    // parameter (source : 감지 api의 결과)
                    let parameter: Parameters = [
                        "source": langType,
                        "target": "en",
                        "text": txt
                    ]

                    // SwiftyJSON : Work with Alamofire
                    AF.request(url, method: .post, parameters: parameter, headers: header)
                        .validate()
                        .responseJSON { response in
                            switch response.result {
                            case .success(let value):
                                let json = JSON(value)

								// 번역 결과 저장
                                self.secondTextView.text = json["message"]["result"]["translatedText"].stringValue
                            case .failure(let error):
                                print(error)
                            }
                        }

                case .failure(let error):
                    print(error)
                }
            }
}

그 다다음

  • 멘토님께 질문드렸을 때, completionHandler 키워드를 알려주셔서,
    구글링해서 비슷하게 코드를 작성해보았다
  • 훨씬 깔끔해진 것 같다
  • 참고1, 참고2
/* ========== 언어 감지 ========== */
// 매개변수로 클로저를 넣어서, api 호출 성공 시 해당 클로저가 실행되게 한다
func detectLang(completion: @escaping (String, String) -> Void) {
        
        guard let txt = firstTextView.text else { return }
        let header: HTTPHeaders = [
            "X-Naver-Client-Id" : APIKey.naver,
            "X-Naver-Client-Secret" : APIKey.naverSecret
        ]
        var langType = ""
     
        // 최종 url
        let url2 = "https://openapi.naver.com/v1/papago/detectLangs"

        let parameter2: Parameters = [
            "query" : txt
        ]

        // SwiftyJSON : Work with Alamofire
        AF.request(url2, method: .post, parameters: parameter2, headers: header)
            .validate()
            .responseJSON { response in
                switch response.result {
                case .success(let value):
                    let json = JSON(value)
                    
                    langType = json["langCode"].stringValue
                    
                    // 언어 감지 결과와 번역할 텍스트를 매개변수로 한 completionHandler 실행.
                    completion(langType, txt)
        
                case .failure(let error):
                    print(error)
                }
            }
}
    
    

func translate(_ sender: String, _ sender2: String) {
// 매개변수명 이따구로 쓰지 말자..ㅎ
        
       // 최종 url
       let url = "https://openapi.naver.com/v1/papago/n2mt"
        let header: HTTPHeaders = [
            "X-Naver-Client-Id" : APIKey.naver,
            "X-Naver-Client-Secret" : APIKey.naverSecret
        ]


       // parameter
       let parameter: Parameters = [
           "source": sender,
           "target": "en",
           "text": sender2       ]

       // SwiftyJSON : Work with Alamofire
       AF.request(url, method: .post, parameters: parameter, headers: header)
           .validate()
           .responseJSON { response in
               switch response.result {
               case .success(let value):
                   let json = JSON(value)

                   self.secondTextView.text = json["message"]["result"]["translatedText"].stringValue
               case .failure(let error):
                   print(error)
               }
           }
}


/* ========== 함수 실행 ========== */
@IBAction func translateButtonTapped(_ sender: UIButton) {
        detectLang(completion: translate)
        // 클로저를 요 안에서 바로 선언해도 되긴 한데,
        // 처음 써보는거라 함수로 따로 선언해두고, 해당 함수를 인자로 넣어주었다
}

api 보낼 때

  • 키나 쿼리에다가 억지로 큰따옴표 붙여놨는지 확인해보자
  • 안붙여야 정상적으로 데이터를 받을 수 있었다

Pagination

  • 대량의 데이터와 리소스를 분할해서 가져온다. (주로 서버 데이터&리소스)
  • 사용자의 스크롤 시점에 맞춰서 구현한다

종류

Offset Based Pagination

  • 몇 번째 페이지에서 몇 개의 데이터를 보여줄지 쿼리스트링으로 전달
  • 페이지 단위로 정보 요청하는 사이에 서버 데이터 변하면
    중복 데이터가 나올 수 있음
  • 서버의 데이터 변화가 적은 구조인 경우에 사용한다

CursorPagination

  • 클라이언트가 가지고 있는 마지막 데이터를 기준으로 다음 데이터 조회
  • 사용자가 조회한 시점 이후에 최신 데이터 추가되어도 조회가 어려움
  • 현재 페이지 기준 전/후의 데이터를 호출하는 방식이기 때문에
    정보 건너뛰고 중간 페이지에 대한 값을 얻기 힘들다

구현 방법

1. TableView willDisplayCell Method

func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
}
  • 화면에 보이지 않는 셀에서도 호출될 수 있는 가능성이 있다 -> 권장 x
  • 마지막 셀을 스크롤 할 때의 시점을 명확하게 파악하기 어려움

2. UIScrollViewDelegateProtocol - 스크롤뷰의 Offset 활용

func scrollViewDidScroll(_ scrollView: UIScrollView) {
}
  • 참고
  • 스크롤뷰의 스크롤 상태를 감지할 수 있는 메서드
  • scrollView.contentSize.height
    scrollView.content.offSet.Y
    유사해지는 지점을 노린다

Example

  • 처음엔 count를 100으로 설정하고,
    scrollView.contentSize.height
    scrollView.content.offSet.Y의 차이가 1000까지 줄어들었을 때,
    count를 100 늘려준다
  • 근데 scrollView.contentSize.height는 처음부터 고정일 줄 알았는데,
    얘도 스크롤할수록 조금조금씩 늘어나다가 max를 찍는 것 같다

//
//  ScrollTestViewController.swift
//  0809hw
//
//  Created by 임승섭 on 2023/08/09.
//

import UIKit

class ScrollTestViewController: UIViewController {

    @IBOutlet var mainTableView: UITableView!
    
    var count = 100
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        mainTableView.delegate = self
        mainTableView.dataSource = self
    }
}

extension ScrollTestViewController: UITableViewDelegate, UITableViewDataSource {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "ScrollTestTableViewCell") as! ScrollTestTableViewCell
            
        cell.mainLabel.text = "\(indexPath.row)"
        return cell
    }
}

extension ScrollTestViewController: UIScrollViewDelegate {
    func scrollViewDidScroll(_ scrollView: UIScrollView) {
        
        if (scrollView.contentSize.height - scrollView.contentOffset.y < 1000) {
            print("=============== 카운트 증가!! ===============")
            count += 100
            mainTableView.reloadData()
        }
        
        print(scrollView.contentSize.height, scrollView.contentOffset.y)
    }
}

3. UITableViewDataSourcePrefetching Protocol

func tableView(_ tableView: UITableView, prefetchRowsAt indexPaths: [indexPath]) {
}
  • 서버 통신과 같은 비동기 상황에서 pagination 쉽게 구현
  • 용량이 큰 이미지를 다운받아서 테이블뷰에 보여줄 때 효과적이다
  • 셀이 화면에 보이기 전, 필요한 리소스 다운
    (cellForRowAt이 호출되기 전에 미리 필요한 데이터 로딩)
  • cellForRowAt이 호출되면 prefetchRowAt에서 미리 로딩해둔 리소스와 데이터 표현
  • 사용자가 보고있는 셀 이후에 표시될 셀을 판단하고 호출
    • 사용자가 스크롤하지 않으면 호출x
    • 두 번째 파라미터를 통해 배열로 전달

구조체 배열 돌 때(?)

  • for loop을 항상 인덱스로만 사용해왔어서 실수가 좀 있었다
  • struct Movie 타입의 배열이 있다고 했을 때, 해당 배열의 원소에 접근하고, 그 원소의 구조체 요소 값을 바꾸려고 할 때는 반드시 인덱스로 접근해야 한다
  • 이게 뭔소린지 나도 잘 이해가 안되는데, 코드로 보면 바로 느낌이 온다
  • 이거때문에 진짜 돌아버릴 뻔 했당

바보 코드

for item in list {
    if (item.title == title) {
        item.like.toggle()
    }
}
  • 이 짓을 아무리 해도 list 배열에 있는 애들은 변화가 없을 것이다.
  • 왜냐하면 item 값만 계속 토글하고 있으니까...
  • item은 list랑 상관이 없는 친구니까...

인덱스로 접근

for i in 0...list.count-1 {
	if list[i].title == title {
    	list[i].like.toggle()
    }
}
  • 코드가 조금 정신없긴 하지만, 아직까지 인덱스로 접근하는게 맘 편한건 사실이다..

enumerated()

for (index, item) in list.enumerated() {
	if item.title == title {
    	list[index].like.toggle()
    }
}
  • 둘 다 쓸 수 있는 enumerated() 메서드가 있다
  • 좋다 이거

0개의 댓글