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 페이지네이션
- 대량의 데이터와 리소스를 분할에서 가져오는 방법.
- 서버의 데이터와 리소스를 다룰 때 사용.
- 사용자의 스크롤 시점에 맞춰 페이지네이션 기능을 구현.
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 고려사항
- 최대 page 상한 조건문
- 15페이지 까지 밖에 지원하지 않기 때문에 그에 대한 조건 처리
- 검색어의 페이지가 적은 상황에 대한 처리
- 항상 default 페이지 상한이 마지막 페이지라는 이유는 없다. 따라서 서버 response에서 마지막 페이지에 대한 정보를 확인 할 수 있으면 확인해야 함
- 마지막 페이지인지 여부를 프로퍼티에 저장하고, prefetching시 마지막 페이지 프로퍼티 조건을 따져야 함
- isEnd가 있다면 page를 굳이 체크해야 할까..?
- 기능을 제공하는 API의 제약조건을 확인해야함
- 마지막 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 구현하기
- tableView.rowheight = UITableView.automaticDimension
- tableView.estimatedRowHeight : 기본 높이 지정
- 셀에서 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는 다음주차때,,
'SeSAC' 카테고리의 다른 글
8.21 ~ 8.22 TIL(SeSAC iOS 3기) (0) | 2023.08.27 |
---|---|
8.14 ~ 8.18 TIL (1) | 2023.08.22 |
7.31 ~ 8.4 TIL (0) | 2023.08.09 |
7.24 ~ 7.28 TIL (0) | 2023.08.02 |
7.20 ~ 21 TIL (0) | 2023.07.26 |