Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
Tags
- collectionview
- uitableview
- UICollectionView
- 스위프트
- tableView
- RxCocoa
- map
- Clean Code
- uiscrollview
- clean architecture
- 리펙터링
- Observable
- SWIFT
- UITextView
- Refactoring
- Xcode
- Protocol
- rxswift
- 리팩토링
- swift documentation
- ios
- 클린 코드
- 애니메이션
- 리펙토링
- HIG
- swiftUI
- combine
- Human interface guide
- MVVM
- ribs
Archives
- Today
- Total
김종권의 iOS 앱 개발 알아가기
[iOS - swift] KeyPath 이해하기 (map(\.), 디코딩 map atKeyPath) 본문
KeyPath 이해하기
- Key의 개념은 Objective-C에서 프로퍼티에 접근할 때 사용하는 개념
- 단순히 Key는 문자열 값이고, 이 문자열 값을 가지고 프로퍼티에 접근하는 방식
- 구체적인 개념은 이전 포스팅 글 참고
- KeyPath는 Root라는 타입으로부터 구체적인 Value Type으로의 key의 경로를 의미
ex) KeyPath 개념 이해하기
- 이전 포스팅 글에서 알아본 KVC (Key Value Coding)에서 key값으로 프로퍼티에 접근하지만 또다른 방법으로 KeyPath를 통해 접근이 가능
// KVC에서 Key값으로 접근하는 방법
class Person: NSObject { // NSObject 서브클래싱
@objc var name: String? // @objc 어노테이션
}
// KVC - key로 프로퍼티 값 접근
let person = Person()
person.value(forKey: "name") // nil
person.setValue("jake", forKey: "name")
person.value(forKey: "name") // "jake"
- KeyPath를 통해 접근하는 방법
- KeyPath의 개념에 대입하여 이해: KeyPath는 Root라는 타입(=Person타입)으로부터 구체적인 Value Type(프로퍼티타입)으로의 key의 경로를 의미
- keyPath로 인스턴스 안 프로퍼티에 접근이 가능
person[keyPath: \Person.name]
- Root 타입은 생략 가능
person[keyPath: \.name]
KeyPath를 직접 정의하여 사용하는 방법
(KeyPath를 사용하지 않은 경우)
- School의 him, han에 메소드를 통해서 갖고오고 싶은 경우 각각 getKim(), getHan()을 정의하여 사용
struct PersonInfo {
var name: String
var age: Int
}
struct School {
var kim: PersonInfo
var han: PersonInfo
func getKim() -> PersonInfo {
return self.kim
}
func getHan() -> PersonInfo {
return self.han
}
}
let kim = PersonInfo(name: "jake", age: 12)
let han = PersonInfo(name: "jack", age: 13)
let school = School(kim: kim, han: han)
(KeyPath를 사용한 경우)
- KeyPath를 직접 정의하여 사용 KeyPath<School, PersonInfo>
- Root타입이 School이고, Value타입이 프로퍼티의 타입인 PersonInfo
extension School {
func getSchool(keyPath: KeyPath<Self, PersonInfo>) -> PersonInfo {
self[keyPath: keyPath]
}
}
print(school.getSchool(keyPath: \.kim))
print(school.getSchool(keyPath: \.han))
KeyPath를 사용하는 이유
- KVC(Key Value Coding)에서는 프로퍼티에 접근할 때 문자열 key값을 이용했지만, KeyPath를 이용하면 문자열이 아닌 프로퍼티 이름으로 접근이 가능 -> runtime error 방지에 유리
// KeyPath를 사용하지 않은 경우 -> 문자열을 직접 입력하므로 오타날 가능성이 존재
person.value(forKey: "name")
// KeyPath를 사용한 경우
person[keyPath: \.name]
- swift에서는 KeyPath를 통해 'syntactic sugar' (간결한 코드)를 사용할 수 있는 테크닉이 존재
ex1) keyPath 예제 - map
struct SomeModel {
let name: String
let age: Int
}
let someModel1 = SomeModel(name: "jake", age: 12)
let someModel2 = SomeModel(name: "lee", age: 32)
let res1 = [someModel1, someModel2].map(\.name)
ex2) keyPath 예제 - compactMap
Optional인 경우, \.?.으로 접근
let someModel3: SomeModel? = SomeModel(name: "han", age: 20)
let someModel4: SomeModel? = nil
let res1 = [someModel1, someModel2].map(\.name)
let res2 = [someModel3, someModel4].compactMap(\.?.name)
ex3) RxSwift를 사용하고, 서버로부터 받은 특정 모델을 디코딩하고 싶은 경우
// 예시로 사용할 json
{
"items":[
{
"name":"jake",
"age": 12
},
{
"name":"jake",
"age": 30
},
]
}
- 사용하는쪽에서 keyPath를 사용하지 않으면 아래처럼 모델을 생성
- 사용하는쪽에서 map(MyResponseModel.self)과 같이 사용
- 불필요한 users프로퍼티가 존재하여, 사용하는 쪽에서도 users.로 접근해야하는 경우가 발생
// 사용하는쪽에서 keyPath x
struct MyResponseModel: Codable {
struct User: Codable {
let name: String
let age: Int
}
enum CodingKeys: String, CodingKey {
case users = "items"
}
let users: [User]
}
- 사용하는쪽에서 keyPath를 사용하면 모델을 더욱 단순화하고 불필요한 users 프로퍼티를 삭제
- 사용하는쪽에서 map([MyResponseModel2].self, atKeyPath: "items")과 같이 사용
// 사용하는쪽에서 keyPath o
struct MyResponseModel2: Codable {
let name: String
let age: Int
}
사용하는쪽 예시) map(_:atKeyPath:)로 사용
// https://stackoverflow.com/questions/55847632/rxswift-api-response
func getMostPopularRepositories(byLanguage language: String) -> Observable<[Repository]> {
let encodedLanguage = language.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryAllowed)!
let provider = MoyaProvider<Github>(plugins: [NetworkLoggerPlugin(verbose: true)])
let parameters = [“q”: “language:\(encodedLanguage)“,“sort”: “stars”]
let request = provider.rx.request(.repositories(parameters)).asObservable()
let pRepos = request.map([Repository].self, atKeyPath: “items”)
return pRepos
}
ex4) RxSwift를 사용하고, 서버로부터 받은 특정 모델을 디코딩하고 싶은 경우 2번째
{
"profile":{
"user_id":"abcd",
"secret":{
"age":12
}
}
}
- keyPath 사용 x
- 불필요한 profile 프로퍼티가 존재 (사용하는 쪽에서도 myResponseModel3.profile.으로 접근해야하는 경우가 발생)
struct MyResponseModel3: Codable {
struct User: Codable {
struct Secret: Codable {
let age: Int
}
enum CodingKeys: String, CodingKey {
case userID = "user_id"
case age
}
let userID: String
let age: Int
}
let profile: [User]
}
// map(MyResponseModel3.self)
- KeyPath 사용 o
struct MyResponseModel4: Codable {
struct Secret: Codable {
let age: Int
}
enum CodingKeys: String, CodingKey {
case userID = "user_id"
case age
}
let userID: String
let age: Int
}
// map([MyResponseModel4].self, atKeyPath: "profile")
cf) atKeyPath에는 키를 연달아서 접근 가능 (items.someSubName1.someSubName2)
* 전체 코드: https://github.com/JK0369/ExKeyPath
* 참고
https://stackoverflow.com/questions/55847632/rxswift-api-response
'iOS 응용 (swift)' 카테고리의 다른 글
Comments