Swift | 빠르게 빠르게 작성하고 기억하기
iOS 개발자로 성장하면서 빠르게 기록해야 하는 내용을 담아냅니다.
한결
Automatic Reference Count (ARC)
iOS 앱에서 메모리의 힙 영역을 관리하는 프로세스 (참조하고 있는 것의 갯수를 관리하는 방식)
힙 영역에는 참조되어 관리하는 요소들이 코드 영역에서 올라온다. (대표적인 참조형은 클래스의 인스턴스나 클로저가 될 수 있겠다.)
힙 영역에 참조할 것이 생성되면 RC를 증가시키고, 해당 인스턴스의 변수나 참조형이 더 이상 역할을 하지 않는다면 RC를 감소시키고 자체적으로 메모리에서 해제시킨다.
컴파일 타임에 RC에 대한 관리가 된다는 점이 주요점이다. (런타임단에서 관리되는 가비지 컬렉터와 차이가 있다.)
강한 참조
위 코드에서 변수 a에 nil을 재할당해도 Person의 인스턴스는 deinit을 하지 않는다. 이건, 변수 b가 a에 담겼던 Person의 인스턴스를 강하게 참조하고 있기 때문이다.
Person 인스턴스를 deinit하려면 b = nil 로 RC를 다 감소시키고, 참조하고 있는 요소를 다 없애줘야 한다. (강함 참조의 경우)
약한 참조
weak 키워드로 다른 인스턴스를 참조시키는 경우, 참조 시점에 RC를 증가시키지 않는다.
참조하는 인스턴스가 해제될 경우, 주소값 자체를 지운다.
weak로 선언한 변수의 경우 컴파일 타임에서 nil이 할당될 수 있기 때문에,
- 반드시
변수로(var 키워드) 선언해야 하고(재할당이 될 수 있도록)
- 타입을
옵셔널로 정의해줘야 한다.
weak로 선언한 참조 관계는 참조가 해제되는 순서가 중요할 수 있다.
특정 하나의 클래스 멤버만 다른 참조 타입을 weak로 바라볼 경우 deinit 되는 시점을 잘 체크해야 한다.
See more...
Reaction
Comment
Share
한결
Result Enum Type
실패와 성공의 케이스만 가지는 @frozen된 열거형 타입이다.
제네릭으로 성공, 실패의 케이스에 사용될 '타입'을 선언해서, associated value로 저장해준다.
네트워크 통신을 하는 API 코드에서 completionHanlder의 인자로 Result Enum Type을 지정해주면, 내부 응답 핸들링 코드에서 편하게 사용해볼 수 있다.
of: T.Type 을 인자로 둔 이유는, fetch<T> 메서드 자체를 호출하는 환경에서 Result<T, Error> 열거형에 T라고 하는 Decodable한 타입을 맵핑해주기 어려워서이다.
Alamofire의 API를 활용할 때에도 decodeOf 등으로 decoding 할 데이터 타입을 먼저 맵핑해준다.
Reaction
Comment
Share
한결
Observable 패턴
View와 Model을 연결해주는 ViewModel에서 데이터 '값'의 변화를 포착해서 원하는 동작을 만들어내는 클로저를 맵핑하기 위해서 Observable이라고 하는 클래스를 자주 사용한다.
View에서 다루고자 하는 값을 ViewModel에서 관리하고 → Observable한 input 값 설정
View에서는 이 값을 변화시키려고 하는 액션(ex. 텍스트필드 입력, 버튼 터치, 테이블뷰 업데이트 등)을 ViewModel에게 인지시킨다. → Observable한 input 값 변경 + 클로저 맵핑
Model에서는 View에 보여줄 데이터를 가지고 있고(가지고 있다기 보다는 데이터와 관련된 로직을 관리하는) 이 데이터를 View에 전달하기 위해서 ViewModel을 활용한다.
Observable한 output 값 설정
ViewModel에서는 View에서 들어온 액션 + 변경된 값을 기반으로 내부 로직(Model 로직에 기반한)을 돌려 View가 원하는 형태의 값으로 반환해준다. → Observable한 output 값 변경 후 반환
Reaction
Comment
Share
한결
최적화 수준
Debug
최적화 수준은 낮지만 개발자 친화적인 모드로 관리됨
Incremental 방식 - Swift에서는 각각의 파일 개별적으로 컴파일 진행 (파일 단위로 컴파일을 한 다음 파일간의 관계성을 도출)
Release
최적화 수준이 높음
Whole Module Optimization (전체 모듈 최적화) - 전체 파일을 하나의 파일로 관리하여 파일간의 관계성을 만들어 실행하는 컴파일링 방식
💡
컴파일 최적화의 핵심은?
:
서로의 관계가 연결될 필요가 없다면, 최대한 파일간의 연결성을 차단시키는 것
→ final 키워드 같이, 클래스의 추후 상속을 단절시킬 수 있는 키워드를 잘 활용하자!
→ private 키워드 같이, 외부에서 바로 멤버나 메서드를 건드리지 못하도록 제어하자!
→ *Dynamic Dispatch 상태인 메서드를 Static Dispatch 형태로 설정해보자!
*Method Dispatch: 함수를 어떻게 실행시킬지에 대한 명세
컴파일 단계에서 함수가 확정되는지 vs 런타임 단계에서 함수가 실행되는지에 대한 여부
Dynamic Dispatch - 런타임 단계에서 함수 확정 - class (오버라이딩을 하거나 상속을 시킬 수 있어서)
Static Dispatch - 컴파일 단계에서 함수 확정 - struct, enum 내부에서 정의한 메서드
컴파일 단계에서 어떤 시점에 어떤 함수가 어떻게 실행될 지 정해져서 직접 호출하는 메서드
Reaction
Comment
Share
한결
Swift - queryString에 한글 퍼센트 인코딩하기
특정 외부 API를 통해 데이터를 한글 키워드로 검색할 경우 쿼리스트링에 키워드 자체가 %가 들어간 이상한 형태로 깨지는 경우를 본 적 있을 것이다. 이런 인코딩 방식을 Percent-Encoding 이라고 한다.
URL을 기반으로 네트워크 통신을 하기 위한 또 하나의 규약이라고 볼 수 있다. 미리 정해진 인코딩 방식에 따라 한글도 %가 들어간 특이한 16진수 값으로 맵핑된다.
iOS 앱을 만들어 가면서 분명 한글로 검색하여 외부 데이터를 패칭해와야 하는 경우가 많았다. 그럴 때 queryString에 퍼센트 인코딩을 적용하는 방법을 간단히 기록으로 남긴다.
텍스트 필드로 받아온 String 값이 nil인지 확인한다.
String 값이 있다면 .addingPercentEncoding 메서드로 한 번 인코딩 해주고 API 통신을 하는 코드에 넣어준다.
끗! 아주 간단하다.
Reaction
Comment
Share
한결
URLSession
Request
Configuration
shared ===> delegation 형태로 응답을 받을 수 없음
default ===> delegation 형태로 응답 받을 수 있음
ephemeral
background
Task (어떤 요청 처리인지?)
data get (json, media ..)
upload (formType, content-type ..)
download
Response - 응답을 어떻게 받을거?
completion handler - closure
success/failure 형태로 응답 상태를 한 번에 확인
한 번에 확인할 수 있기 때문에 큰 용량의 데이터를 얼마나 어떻게 받아오고 있는지 캡쳐하기 쉽지 않다. (⇒ delegation 방식을 사용함)
See more...
Reaction
Comment
Share
한결
DispatchQueue.global().async, DispatchGroup 으로 비동기 컨트롤하기
DispatchQueue.global() 로 백그라운드 스레드에서 task들이 동작할 수 있도록 넘겨줄 수 있다.
여기서 동기/비동기(sync/async) 방식을 정해줄 수 있다.
동기 방식으로 넘겨줄 경우, 결국 백그라운드 스레드에서 앞선 작업들이 다 진행되는 것을 main 스레드는 기다려야 하기 때문에, main 스레드가 동기적으로 task를 처리하는 것과 크게 다르지 않다.
비동기 방식으로 넘겨줄 경우, 넘겨준 작업의 시작과 종료를 기다리지 않고 main 스레드가 일을 진행할 수 있다. 다만 다른 스레드의 일이 언제 끝날지 알 겨를이 없다.
그래서, DispatchGroup을 통해서 각각의 DispatchQueue.gloabl().async 작업의 task 시작 시점(enter)과 종료 시점(leave)을 그룹 단위로 ± 할 수 있게 정해줄 수 있다.
DispatchQueue.gloabl().async(group: groupName) { 클로저 } 형태로, 비동기 코드를 그룹으로 쉽게 묶을 수 있다.
dispatchGroup.enter() dispatchGroup.leave() 의 호출로 그룹내 특정 비동기 코드가 종료되었음을 감지할 수 있는 장치를 만들어준다.
dispatchGroup.notify(queue: someQueue) 로 특정 스레드에 그룹의 비동기 task가 모두 종료되었음을 알려준다. 보통 main 스레드에서 비동기적 통신에 따른 UI를 그리기 때문에 큐에 .main을 반영해준다.
Reaction
Comment
Share
한결
Project Setting - 외부 Font 사용 설정하기
1.
프로젝트에서 사용하고 싶은 Font 파일을 준비한다.
2.
프로젝트 파일에 Font 파일을 추가한다.
3.
프로젝트 설정 > Build Phase > Copy Bundle Resources 항목에 Font 파일이 잘 등록되었는지 확인한다.
4.
info.plist에 Fonts provided by application 속성을 등록하고, Font 파일 이름을 정확하게 기입한다.
5.
사용하고자 하는 코드에서 정확한 폰트 패밀리 네이밍을 반영해준다.
*폰트 패밀리에 등록된 폰트의 정확한 이름은 쉬운 반복문으로 사용해볼 수 있다.
Reaction
Comment
Share
한결
UIKit - 쌓여있는 VC를 앱 사용성에 맞게 dismiss 시키고 특정 VC 보여주기
SceneDelegate의 scene 메서드 내부 구현처럼, 앱을 마치 처음 켠 것처럼 만들어버린다.
SceneDelegate에 정의한 window 객체를 불러와서, 새롭게 띄우려고 하는 VC를 갈아끼우는 느낌으로 코드가 동작한다.
viewDidLoad가 다시 돌기 때문에, 앱 전체의 테마를 바꾼다던지 로그인 여부 자체를 변경하는 것 등을 관리할 때 이전에 쌓은 페이지들을 다 dismiss 해주는게 좋을 수 있다.
Reaction
Comment
Share
한결
UIKit - CollectionView 가로 스크롤 UI
커머스 앱이나 배달 앱 등 비슷한 카테고리로 동일한 데이터를 좁은 View 안에서 보여주기 위해서 가로의 끝이 언제인지 알 수 없는 가로 스크롤 UI를 사용하곤 한다. iOS에서는 UICollectionView로 이런 가로 UI를 쉽게 생성할 수 있다.
컬랙션뷰를 구현하기 위해서는 delegate, datasource에 대한 위임, 커스텀 Item에 대한 등록 역시 필요하다. (이 부분은 테이블뷰 구현과 동일하다.)
Swift 자체에서 시스템 테이블로 제공하는 형식이 없다보니, 테이블뷰 구현과 다르게 컬랙션 아이템(테이블에서는 셀), 컬랙션 틀 자체에 대한 레이아웃을 랜더링 하기 전에 반드시 설정해줘야 한다.
*설정하지 않고 빌드해보면 아래와 같은 에러를 마주할 수 있다.
"UICollectionView must be initialized with a non-nil layout parameter"
UICollectionViewFlowLayout 클래스로 생성한 인스턴스에서 컬랙션 아이템, 틀에 대한 레이아웃을 지정할 수 있다. (내장 멤버 변수를 조정하여 설정 가능하다.)
itemSize - 말 그대로 각 아이템의 사이즈를 결정해준다.
scrollDirection - 당연히 스크롤 방향이다.
minimumLineSpacing - 컬랙션 뷰 내부에서 아이템들 간의 간격을 결정한다.
sectionInset - 컬랙션 뷰 내부의 아이템 그룹의 Edge에 대한 간격을 결정한다.
위와 같이 설정을 한다면, 아래 첨부된 영상처럼 가로 스크롤이 가능한 UI 구현이 가능하다.
00:07
Reaction
Comment
Share
한결
UIKit - Pagination (a.k.a. 무한 스크롤) - 계속 수정 예정
유저의 스크롤 시점에 따른 데이터 조회를 위해서 iOS에서는 페이지네이션을 기본으로 한다.
웹에서의 페이지네이션과는 그 방법 자체가 조금 다르다. 웹에서는 조회 가능한 데이터셋을 기준에 따라 정말 '페이지'로 구분해서 버튼 형식으로 구분했었던 것 같다. iOS에서의 페이지네이션은 웹에서 '무한 스크롤'이라고 부르는 형태를 페이지네이션이라고 지칭한다.
스크롤을 기반으로 데이터 조회 함수를 호출하는 시점을 조절하는 것이 핵심이다. iOS 화면이 스크롤 되어서 아래의 컨텐츠 뷰 영역이 뜨기 전에 미리 데이터를 fetching 하는 방식이 필요하다.
Off-Set 기반
주의사항: 데이터 fetching 중에, 데이터 전체 갯수에 변동이 (새로운 데이터가 fetching 중간에 갱신되는 경우) 있는 경우 원하는 결과가 되지 않을 수 있다.
Cursor 기반
스크롤 전, 후에 데이터가 어떻게 되는지의 차이를 이용하여 구현하게 된다.
특정 데이터 인스턴스에 Previous, Next의 키로 이전, 이후의 데이터셋을 미리 정해두고 데이터를 fetching 하는 방식이다.
채팅, SNS에서
Reaction
Comment
Share
한결
UIKit - 스토리보드 기반에서 코드 기반으로 시뮬레이터 작업 세팅하기
💡
스토리보드 + 코드 기반에서 코드 기반으로만 iOS 개발 작업을 할 수 있는 세팅 방법을 순서대로 기록합니다.
1. MainStoryboard 파일을 삭제한다.
사실 파일을 삭제할 필요까지는 없지만, 앞으로 사용할 일이 없기 때문에 굳이 남겨둘 이유도 없다.
2.
info.plist 파일에서 Storyboard Name 을 지워준다.
3.
프로젝트 설정 > Build Settings > Info.plist Values 항목에서 스토리보드 항목을 지워준다.
4.
SceneDelegate.swift 파일에서 scene 함수 내부에서 Window 객체에 엔트리로 연결하려고 하는 ViewController 인스턴스를 맵핑해준다.
Reaction
Comment
Share
한결
Swift - tableView.delegate = self 에서 프로토콜 타입을 할당해야 하는 자리에 self를 할당하는 이유는?
Swift 프로토콜에는 Protocol as Type, Delegation이라는 개념이 적용되기 때문이다.
Protocol as Type
Protocol's Delegation
클래스에 Protocol을 연결하여 위임자를 자기 자신의 인스턴스로 할당할 수 있다.
물론 동일 프로토콜을 적용한 다른 클래스의 인스턴스로 위임하는 것도 가능!
여기서는 tableView 라고 하는 프로퍼티의 일을 SomeView의 인스턴스가 직접 위임받아 처리할게! 라는 뜻으로 이해하면 된다.
Reaction
Comment
Share
한결
UIKit - View Class에 프로토콜로 클래스 이름이 할당된 타입 프로퍼티 지정하기
TableView에 커스텀셀을 등록하거나, 다른 Storyboard의 뷰를 연결하기 위해서 identifier 값을 지정하게 된다. 이럴경우 (나의 경우에는) 뷰 클래스 내부에 identifier라는 타입 프로퍼티를 지정해주는 편인데, 매번 클래스를 선언할 때마다 나의 타이핑 오류를 감안하기는 번거롭다. protocol, extension 을 이용하면, 프로젝트 단위로 이런 번거로움을 쉽게 해소할 수 있다.
1.
protocol은 특정 클래스나 구조체 등에 형태의 제약을 걸어주는 역할을 한다.
프로토콜을 적용하는 클래스가 반드시 identifier라는 타입 프로퍼티를 가지게 강제해본다.
2.
extension은 특정 클래스나 구조체 등의 기존 역할을 확장해주는 역할을 한다.
강제의 개념이라기 보다는
이런이런 역할도 할 수 있어! 로 기능을 확장하는 느낌이다.
extension에 미리 만들어둔 ViewClassIdentifier 프로토콜을 연결한다.
identifier라고 하는 타입 프로퍼티 작성을 강제했기 때문에 extension 선언 시에 반드시 작성해줘야 한다.
연산형 프로퍼티에 따로 getter, setter 를 부여하지 않으면 디폴트로 getter로 역할을 한다.
반환하는 부분에 describing: self 를 통해서 확장하는 View 클래스 자기 자신의 이름으로 값이 맵핑된다. (아주 나이스하다!)
3.
해당 extension이 적용된 View 객체들은 코드 생성시에 해당 프로퍼티를 초기화 하는 과정을 개발자가 직접 거치지 않아도 바로 타입 프로퍼티 형태로 접근이 가능하다.
Reaction
Comment
Share
한결
UIKit - 원하는 방향으로 layer.cornerRadius 부여하기
UIImageView, UIView 등등에 원하는 방향(좌상단, 우상단, 좌하단, 우하단)에만 radius 처리를 하는 방법은 간단하다. 전체에 cornerRadius 값을 주고, 부여하고자 하는 포인트를 masked하면 된다. 뭔가 피그마에서 이미지 틀을 만들고 그 안에 이미지를 마스킹 하는 것과 비슷하다.
아래에 깎으려고 하는 코너의 값 자체를 넣어뒀는데, 사실 외울 필요가 전혀 없다.
생각해보면, 뷰에서 좌측은 X좌표가 뷰의 가장 minimum한 값이고 뷰의 하단은 Y좌표가 뷰의 가장 maximum한 값이기 때문이다. 그래서 머리속으로 깎으려고 하는 코너의 위치가 어떻게 되는지 생각만 하면 된다.
좌상단: .layerMinXMinYCorner
우상단: .layerMaxXMinYCorner
좌하단: .layerMinXMaxYCorner
우하단: .layerMaxXMaxYCorner
Reaction
Comment
Share
한결
XCode StoryBoad 상 세그웨이를 이용한 화면전환이 아닌 코드로 화면 전환하기
왜 코드로?
스토리보드에서 세그웨이를 연결해서 하면 화면간 연결이 편하지 않을까?
⇒ 세그웨이를 끌어다가 연결하는건 편하지만, 연결을 위한 버튼이나 테이블 뷰의 셀 등을 직접 만들어서 화면으로 하나하나 연결해줘야 하는 불편함이 생긴다. 물론 연결되는 화면을 일일이 그려야 한다는 번거로움도 커진다.
화면 전환?
전환되는 방식을 먼저 고려하자 (정답은 없다.)
특정 콘텐츠(메뉴)의 상세 정보 ⇒ 보통 우측에서 Show 방식으로
특정 콘텐츠(메뉴)의 기존과 다른 정보 ⇒ 보통 아래에서 위로 Modal 방식으로
그러면 어떻게?
1.
스토리보드 가져오기
2.
스토리보드 내 전환하려는 화면 가져오기
3.
화면 띄우기
See more...
Reaction
Comment
Share
한결
[Swift - 문법]
구조체의 연산 프로퍼티
다른 (저장형) 인스턴스 프로퍼티를 이용해서 말 그대로 '연산'을 담당하는 구조체의 속성.
초기화를 하지 않고, 구조체 자체에서 연산을 하기 때문에 메모리의 힙/스택 영역을 차지하지 않음.
함수(메서드)는 아닌데 마치 함수처럼 작성함.
생성한 인스턴스에서 멤버 프로퍼티를 조회하는 것처럼 사용하면 됨.
let 키워드로 선언은 안되고 무조건 var 키워드로 선언해야 함.
Reaction
Comment
Share
한결
Swift Extension으로 반복적인 코드 관리하기
Extension은 기존에 존재하는 타입을 확장하여 기능성을 높여준다. (Add functionality to an existing type.)
Class, Struct는 물론이고 Protocol, View에 대한 인스턴스 메서드나 계산이 들어간 인스턴스 프로터피도 새롭게 추가할 수 있다.
Reaction
Comment
Share
한결
ATS Policy
HTTP와 HTTPS와 같은 Apple(App) Transport Security 정책을 말한다.
iOS 10+ 에서 적용된 보안 정책으로, 쉽게 생각하면 데이터를 받아오는 URL 구조에서 보안정책을 무시한 내용이 담겨있으면 (ex. http로 통신하는 URL) 앱에서 로드를 거부한다.
물론 프로젝트 plist에서 ATS > Allow Arbitrary Loads 설정을 YES로 강제하면 HTTP 프로토콜 내에서도 통신이 가능하게 허용할 수는 있다.
Reaction
Comment
Share
한결
XIB과 NIB 파일
XIB: XML Interface Builder
XML은 내가, 그리고 모두가 알고 있는 것과 동일하게 그 마크업 언어가 맞다.
XML로 되어 있는 인터페이스를 만드는 도구를 XIB라고 이해하면 쉽겠다.
스토리보드 형태로 되어 있어서 쉽게 인터페이스를 디자인하고 개발할 수 있다. 물론 코드 파일과 연결시켜서 코드베이스로도 통제할 수 있다.
NIB
XIB에서 정의된 인터페이스의 뷰나 액션이 정의된 XML 기반의 컴파일된 파일이다.
쉽게 생각해서 XIB가 컴파일된 파일이라고 보면 된다.
요 아티클에서 알아본 파일 관리 시스템에서 앱 번들 디렉토리에 저장되어, 런타임이 실행되는 시점에 불러와져서 화면을 그린다. 그 화면이 켜질때 불러와진다.
앱 개발에 사용하는 방식은 학습 내용 정리 블로그에 아티클로 정리할 예정이다.
Reaction
Comment
Share
한결
UIViewController + UITableView vs. TableViewController
TableViewController에서 테이블 형태의 뷰를 만들어가면,
이후 앱 디자인이 변경될 경우 더 많은 작업을 해야 할 수 있겠다. 혹은 ViewController자체를 바꿔야 할 수도 있을 것 같다.
또한, 자유롭게 UI 요소를 컨트롤하기 어렵다는 한계를 마주해야 한다.
그래서 보통,
UIViewController 위에다가 UITableView 요소를 올리는 방식으로 개발 작업의 자유도를 높인다.
물론, 이렇게 작업할 경우 상속 관계의 차이가 있어서 테이블 작업을 할 때의 방식이 차이가 날 수 있다. (그냥 난다라고 보면 된다.)
그러면, UIViewController + UITableView 조합으로 코드는 어떻게 작성할까?
1.
UIViewController의 특정 위치에 UITableView를 얹는다. 필요에 따라서 TableView 객체를 Outlet 속성으로 잡는다.
2.
UIViewController 클래스를 상속받은 클래스에 TableView를 다루기 위한 두 개의 프로토콜을 연결한다.
UITableViewDelegate
UITableViewDataSource
3.
viewDidLoad 메서드에서 TableView의 두 프로토콜을 클래스 인스턴스 자체로 설정해준다.
See more...
Reaction
Comment
Share
Share
한결
UIKit - 원하는 방향으로 layer.cornerRadius 부여하기
UIImageView, UIView 등등에 원하는 방향(좌상단, 우상단, 좌하단, 우하단)에만 radius 처리를 하는 방법은 간단하다. 전체에 cornerRadius 값을 주고, 부여하고자 하는 포인트를 masked하면 된다. 뭔가 피그마에서 이미지 틀을 만들고 그 안에 이미지를 마스킹 하는 것과 비슷하다.
아래에 깎으려고 하는 코너의 값 자체를 넣어뒀는데, 사실 외울 필요가 전혀 없다.
생각해보면, 뷰에서 좌측은 X좌표가 뷰의 가장 minimum한 값이고 뷰의 하단은 Y좌표가 뷰의 가장 maximum한 값이기 때문이다. 그래서 머리속으로 깎으려고 하는 코너의 위치가 어떻게 되는지 생각만 하면 된다.
좌상단: .layerMinXMinYCorner
우상단: .layerMaxXMinYCorner
좌하단: .layerMinXMaxYCorner
우하단: .layerMaxXMaxYCorner
👍