SeSAC

8.7 ~ 8.11 TIL

예스코치 2023. 8. 15. 23:14

1.  overriding vs overloading

- overriding: 서브 클래스에서 슈퍼 클래스의 메서드, 프로퍼티, 서브스크립트를 재정의 하는 것.

- overloading: 매개변수, 반환 타입의 차이로 동일한 이름의 함수를 구분하는 것, 동일한 이름의 함수를 중복 선언하는 것.

 

2. URL의 구조

http://news.naver.com:80/article/3234234?lang=ko&page=1 

 

scheme

  • http://
  • 네트워크  통신에 사용할 프로토콜
  • http, https, ftp 등

host

  • news.naver.com

port

  • - :80

path

  • /article/3234234

query string

  • ?lang=ko&page=1

 

URL 허용 문자

  • URL에서는 기본적으로 ASCII 코드 값만 사용 가능.
  • 한글, 특수문자와 같이 ASCII 코드에 해당하지 않는 문자는 인코딩이 필요.

 

URL Encoding(PercentEncoding)

URL에 문자를 표현하는 문자 인코딩 방법, 16진수 값으로 인코딩

if let query = "고래밥".addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) {
   let url = "https://openapi.naver.com/v1/search/movie.json?query=\(query)&display=15&alert=16"
}

 

 

MIME type

  • Multipurpose Internet Mail Extensions 
  • 전송된 데이터의 다양성을 명시하기 위한 매커니즘(무슨 타입인지)
  • application/x-www-form-urlencoded: 데이터를 URL인코딩 방식으로 인코딩해서 전송하는 방식

 

3. ATS(App Transport Security)

  • 앱이 서버에 전송하는 데이터에 대한 보안 설정
  • HTTP 프로토콜을 사용하는 외부 서버와 통신하기 위한 설정
  • HTTPS의 경우 데이터 패킷암호화하여 전송하기 때문에 보안상 안정
  • IPv6: 도메인이 아닌 일반 IP기반의 주소를 사용하는 경우. 128비트 주소를 사용하는 인터넷 프로토콜로, 16년 12월 이후 모든 앱 내에서 이를 사용할 경우 리젝 사유에 포함되고 있음

모든 도메인에 대한 HTTP 통신 허용

특정 도메인만 선택적으로 HTTP 통신 허용

  • Exception Domain → Type Dictionary로 변경 → 키에 도메인 주소를 입력하되, 맨 앞의 www는 제외
  • NSExceptionAllowsInsecureHTTPLoads: 해당 도메인에 대해 HTTP 접속을 허용할 지 여부 결정
  • NSIncludesSubdomains: 해당 도메인의 서브 도메인까지 설정에 포함할 것인지 결정하는 역할

 

4. Enum을 활용한 API Key, Value 관리

  • Struct + Static으로 한다면 언젠가 해당 타입이 초기화 될 수 있기 때문에, 초기화를 할 수 없는 Enum으로 구현해서 위의 상황을 방지함.
  • 변경되지 않는 열거형으로 관리하는게 효율적일 수 있음.

 

5. Alamofire 사용기

1. Header 추가하기

func callRequest() {
    let url = "https://dapi.kakao.com/v2/search/vclip?query=엔믹스"
    let header: HTTPHeaders = ["Authorization" : "KakaoAK \(APIKey.kakaoKey)"]

    AF.request(url, method: .get, headers: header).validate().responseJSON { response in
        switch response.result {
        case .success(let value):
            let json = JSON(value)
            print("JSON: \(json)")
        case .failure(let error):
            print(error)
        }
    }
}
  • header를 따로 상수로 빼서 선언하고, 메서드에서 넣어주면 깔끔하다

2. URL Encoding

URL에 한글이 포함될 경우, 다음과 같은 에러가 발생한다.

invalidURL(url: "https://dapi.kakao.com/v2/search/vclip?query=엔믹스")

이를 인식하기 위해선 Encoding을 거쳐야 함.

func callRequest() {
    let text = "엔믹스".addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)!
    let url = "https://dapi.kakao.com/v2/search/vclip?query=\(text)"
    let header: HTTPHeaders = ["Authorization" : "KakaoAK \(APIKey.kakaoKey)"]

    AF.request(
        url,
        method: .get,
        headers: header
    )
    .validate()
    .responseJSON { response in
        switch response.result {
        case .success(let value):
            let json = JSON(value)
            print("JSON: \(json)")
        case .failure(let error):
            print(error)
        }
    }
}

3. 상태코드 기반 네트워크 에러 처리

API 응답 유효성 검사로, 통과시킬 statusCode 값이나 범위를 지정해서 해당 값의 상태코드가 전달되면 디코딩 로직을 수행함

AF.request(
    url,
    method: .get,
    headers: header
)
.validate(statusCode: 200...500) // ⭐️
.responseJSON { response in
    switch response.result {
    case .success(let value):
        let json = JSON(value)
		
        ...
        

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

 

6. Pagination 페이지네이션

  • 대량의 데이터와 리소스를 분할에서 가져오는 방법.
  • 서버의 데이터와 리소스를 다룰 때 사용.
  • 사용자의 스크롤 시점에 맞춰 페이지네이션 기능을 구현.

보통 페이지네이션 관련 항목이 API 문서에 있다

iOS의 Pagination 방법

1. TableView willDisplayCell Method

  • 테이블뷰가 특정 셀을 그리려는 시점에 호출되는 메서드
func tableView(
    _ tableView: UITableView,
	willDisplay cell: UITableViewCell,
	forRowAt indexPath: IndexPath
) { }
  • 프리패칭 → willDisplayCell → cellForRowAt
  • 단점
    • 화면에 보이지 않는 셀에서도 호출될 수 있는 가능성이 있어서 권장되는 방식은 아님.
    • 마지막 셀을 스크롤 할 때의 시점을 명확하게 설정하기 어려움.

 

2. UIScrollViewDelegate Protocol(offset 이용)

  • 보편적으로 많이 사용하는 방법!
  • ScrollViewDelegate를 위임받아서 사용.
  • 스크롤뷰의 스크롤 상태를 감지할 수 있는 메서드를 활용해 페이지네이션을 구현.
  • scrollView.contentSize.height ↔ scrollView.content.offsetY
func scrollViewDidScroll(_ scrollView: UIScrollView) { }

3. UITableViewDataSourcePrefetching

  • 서버 통신과 같은 비동기 상황에서 페이지네이션을 쉽게 구현할 수 있음.
  • 용량이 큰 이미지를 다운받아 테이블뷰셀에 보여주려고 하는 경우 등에 효과적.
  • 스크롤 성능이 향상됨.

3.1. 동작구조

 

UITableViewDataSourcePrefetching | Apple Developer Documentation

A protocol that provides advance warning of the data requirements for a table view, allowing you to start potentially long-running data operations early.

developer.apple.com

  • cellForRowAt 메서드가 호출되기 전에 미리 필요한 데이터를 로딩!
    • prefetchRowAt에서 필요한 데이터를 다운 받는 등의 작업을 진행.
    • 비동기 처리가 필요함.
  • cellForRowAt 메서드가 호출되면 prefetchRowAt에서 미리 로딩한 리소스와 데이터를 표현함.
  • 사용자의 빠른 스크롤 등으로 화면에 셀을 보여줄 필요가 없는 경우, cancelPrefetchingForRowsAt가 호출되고, 여기서 관련 작업을 취소하도록 구현할 수 있음.

 

3.2. prefetchRowsAt

  • 셀에 표시할 데이터를 미리 다운로드하여, 화면에 데이터가 표현될 때까지의 시간 지연을 최소화할 수 있음.
// 셀이 화면에 보이기 직전에 필요한 리소스를 미리 다운 받는 기능
func tableView(_ tableView: UITableView, prefetchRowsAt indexPaths: [IndexPath]) {

    for indexpath in indexPaths {
        if videoList.count - 1 == indexpath.row {
            pageNumber += 1
            callRequest(query: searchBar.text!, page: pageNumber)
        }
    }
}

 

3.3. cancelPrefetchingForRowsAt

  • 빠른 스크롤을 통해 지나가는 셀에 대해서 로직을 취소하는 작업이 필요할 수 있음.
  • 네트워크 호출 횟수와 리소스를 덜 사용할 수 있음.
  • 매우 빠르게 스크롤을 내리면, 프리패칭 하던 셀을 취소시킴
// 취소 기능: 직접 취소하는 기능을 구현해주어야 함!
func tableView(_ tableView: UITableView, cancelPrefetchingForRowsAt indexPaths: [IndexPath]) {
    print("====취소: \(indexPaths)")
}

 

다음 페이지 패칭하는 시점?

  • 마지막 셀 3~4개 전의 셀이 보여질때 다음 페이지에 대한 데이터를 패칭!
  • 페이지 정보는 프로퍼티로 가지고 있어야 함
  • UITableViewDataSourcePrefetching

 

Pagination 고려사항

  1. 최대 page 상한 조건문
    1. 15페이지 까지 밖에 지원하지 않기 때문에 그에 대한 조건 처리
  2. 검색어의 페이지가 적은 상황에 대한 처리
    1. 항상 default 페이지 상한이 마지막 페이지라는 이유는 없다. 따라서 서버 response에서 마지막 페이지에 대한 정보를 확인 할 수 있으면 확인해야 함
    2. 마지막 페이지인지 여부를 프로퍼티에 저장하고, prefetching시 마지막 페이지 프로퍼티 조건을 따져야 함
  3. isEnd가 있다면 page를 굳이 체크해야 할까..?
    1. 기능을 제공하는 API의 제약조건을 확인해야함
    2. 마지막 page임에도 isEnd가 정상적으로 들어오지 않는 경우도 많음
      • ex) 보여주는 페이지는 15개 까지 뿐이지만, 실제로 보여줄 수 있는 데이터는 14만개로 엄청 많음. page의 limit은 서버에서 그냥 걸어주는거
      • 따라서 pagination시 isEnd와 page 둘다 대응해야한다!!

 

7. Protocol을 활용한 재사용 셀 식별자 강제

protocol ReusableViewProtocol {
    static var identifier: String { get }
}

extension UITableViewCell: ReusableViewProtocol {
    static var identifier: String {
        return String(describing: self)
    }
}

extension UIViewController: ReusableViewProtocol {
    static var identifier: String {
        return String(describing: self)
    }
}
  • identifier를 가지고 있는 ReusableViewProtocol을 생성
  • String(describing: self) - 인스턴스의 이름을 문자열로 변환
  • extension으로 ReusableViewProtocol이 필요한 타입들은 채택하도록 구현

 

8. UIView 원으로 만들기

override func viewDidLoad() {
    super.viewDidLoad()

    first.backgroundColor = .black
    first.layer.cornerRadius = first.frame.width / 2

}

Storyboard에서 ViewController를 생성한 경우, 기기에 따라서 원이 이상하게 나오더라... 왜??

 

🔥 화면을 그릴때, frame.width가 storyboard에서 선택한 기기 화면 크기로 계산이 되기 때문!!

 

해결방법

override func viewDidLoad() {
    super.viewDidLoad()

    first.backgroundColor = .black

    DispatchQueue.main.async { [weak self] in
        guard let self else { return }
        self.first.layer.cornerRadius = self.first.frame.width / 2
    }
}

main 스레드에서 진행하면, 빌드하는 기기의 크기로 제대로 계산하게 된다.

 

✅ 사용자 눈에 보여지기 전에! viewDidLoad 가 수행됨

스토리보드에 디자인된 크기 기준으로 로직이 돌아가기 때문에,

실제 기기의 비율에 맞게 반영되지 않았던 것!

따라서, viewDidLoad 가 아니고 사용자 눈에 보여지는 시점에 설정해야 된다!!

override func viewDidLoad() {
        super.viewDidLoad()

        first.backgroundColor = .black

        DispatchQueue.main.async { [weak self] in
            guard let self else { return }
            self.first.layer.cornerRadius = self.first.frame.width / 2
        }
    }
}
  • DispatchQueue.main.async 에서 수행하면 돌아가는 시점이 가장 늦음. 여기서 수행하면 원하는 비율로 잡힌다.

 

9. 기타

1. 캡처리스트 with didSet

  • 프로퍼티 옵저버 (didset, willset)은 클로저가 아님
  • 따라서 ARC를 중가시키지 않으므로 캡처리스트가 필요없음
 

Swift - weak self in didSet

I rarely see people using [weak self] in didSet. Is there a reason for this? I tried to use [weak self] in my didSet of a variable: var data: Dictionary<String, Any>! { // [1] didSe...

stackoverflow.com

2. 테이블뷰 dynamic height 구현하기

  1. tableView.rowheight = UITableView.automaticDimension
  2. tableView.estimatedRowHeight : 기본 높이 지정
  3. 셀에서 autolayout으로 스스로 높이를 결정할 수 있게 해야 함
func configureTableView() {
    tableView.register(
        UINib(nibName: MovieCastingTableViewCell.identifier, bundle: nil),
        forCellReuseIdentifier: MovieCastingTableViewCell.identifier
    )
    tableView.register(
        UINib(nibName: MovieOverviewTableViewCell.identifier, bundle: nil),
        forCellReuseIdentifier: MovieOverviewTableViewCell.identifier
    )
    tableView.dataSource = self
    tableView.rowHeight = UITableView.automaticDimension
    tableView.estimatedRowHeight = 80
}

 

 

 

끝. GCD, Closure는 다음주차때,,