TIL(230216)

Youth·2023년 2월 16일
0

1.keypath를 이용해 고차함수 사용해보기

유튜브에서 keypath관련한 영상을 추천해주길래 공부를 해봤다
https://youtu.be/2Inr35G-tW4
여기서 나오는게 이제 filter나 map함수에 key path를 이용하는 방법이다

예를 들어보자면

struct Company {
    enum CompanyType {
        case startUp
        case huge
    }
    let name: String
    let type: CompanyType
    let isPublic: Bool
    let stockPrice: Int
}

let companies: [Company] = [
    .init(name: "aa", type: .huge, isPublic: false, stockPrice: 145),
    .init(name: "bb", type: .huge, isPublic: false, stockPrice: 200),
    .init(name: "cc", type: .startUp, isPublic: true, stockPrice: 50),
    .init(name: "dd", type: .startUp, isPublic: true, stockPrice: 39),
    .init(name: "ee", type: .huge, isPublic: true, stockPrice: 200)
]

이렇게 구조체를 선언하고 인스턴스들의 배열을 comapnies라는 변수에 저장한다

대기업중에 공기업이 아니며 주식이 150이상인 회사의 이름을 출력하려고 한다면

  1. 대기업인 회사를 filter로 걸러준다
  2. 공기업인 회사를 filter로 걸러준다
  3. 주식이 150이상인 회사를 filter로 걸러준다
  4. 걸러진 요소의 이름을 map해준다

고차함수를 4번을 써야한다 사실 4번쓰면되는거아닌가?라고 생각했었고 그냥 고차함수쓰는걸 개인적으로 좀 즐겨(?)하는 편이라서 코드를 작성해보면

companies
.filter{ $0.type == .huge }
.filter{ !$0.isPublic }
.filter{ $0.stockPrice > 150 }
.map{ $0.name }

이렇게 작성하면된다. 그런데 이렇게 작성하면 약간 귀찮은점은 대괄호를 써야한다는점...?
사실 그점빼고 불편한점은 없는데 새로운방식이 있다고 하니까 궁금해서 사용해봤다

companies
.filter(\.type == .huge)
.filter(!\.isPublic)
.filter(\.stockPrice > 150)
.map(\.name)

딱히 코드가 줄어서 깔끔해졌다거나 그런느낌은 없지만 대괄호를 안사용해도 된다는 장점은 있는거같다 하지만 문제는
첫번째 필터와 두번째 필터 세번째 필터를 사용하면 아마 오류가 발생할거다
keypath에는 enum의 case를 비교하고 true가 아닌 false를 필터링하고 크고작고를 비교하는 기능이 없기때문에다 그래서 이와같은 기능을 사용하기 위해서는 몇가지의 함수가 추가로 필요하다

func > <T, V: Comparable>( _ leftHandSide: KeyPath<T, V>, _ rightHandSide: V) -> ((T) -> Bool) {
    return { $0[keyPath: leftHandSide] > rightHandSide }
}

func == <T, V: Equatable>(lhs: KeyPath<T, V>, rhs: V) -> ((T) -> Bool) {
    return { $0[keyPath: lhs] == rhs }
}


prefix func !<T>(keyPath: KeyPath<T, Bool>) -> ((T) -> Bool) {
    return { !$0[keyPath: keyPath] }
}

굳이 이렇게까지 추가해가면서 사용해야할 필요가 있을까? 싶기는 하지만 미리 선언해놓고 편하게 쓰려면 쓸수는 있을거같다

2.codingKey의 역할

api를 사용할때 json으로 데이터를 받으면 보통 postman에서 돌려보고 복사해서
quicktype홈페이지에서 swift 구조체로 바꿔주는 과정을 거친다(보통)
그러다보면 자동으로 바꿔준 구조체에 늘 codingKey라는게 존재한다

// 실제 API에서 받게 되는 정보

struct MusicData: Codable {
    let resultCount: Int
    let results: [Music]
}

// 실제 우리가 사용하게될 음악(Music) 모델 구조체
// (서버에서 가져온 데이터만 표시해주면 되기 때문에 일반적으로 구조체로 만듦)

struct Music: Codable {
    // 2.내가 원하는 value이름으로 바꾼다
    let songName: String?
    let artistName: String?
    let albumName: String?
    let previewUrl: String?
    let imageUrl: String?
    private let releaseDate: String?
    
    // 네트워크에서 주는 이름을 변환하는 방법 (원시값)
    // (서버: trackName ===> songName)
    enum CodingKeys: String, CodingKey {
        // Codingkey프로토콜 채택
        case songName = "trackName"
        case artistName
        case albumName = "collectionName"
        case previewUrl
        case imageUrl = "artworkUrl100"
        case releaseDate
    }
    
}

1.서버에서 주는 이름 "trackName" 근데 나는 trackName보다는 songName이 나을거같아라고 생각했을때
2.case를 songName으로 바꾸고 rawValue를 서버에서주는 String으로 설정

위와같은 역할을 한다 codingKey의 중요성을 예전에 한번 느꼈던적이있는데
아카데미에서는 보통 codingKey 없이 사용했었다 근데 그때 Info였다 이걸 swift에서는 info로 사용하고 서버에서는 Info로 주는바람에 어디서 틀린건지 자꾸 decoding에러가 발생해서 원인을 찾는데 꽤나 힘들었던 경험이있다

이제는 coding키를 왜쓰는지 아니까 잘 사용해서 decoding오류를 발생하는 일이 없게 해야할거같다.

3.ResultType의 장점

네트워킹 공부를하니까 error handling에 관한 내용이나 코드들을 매번 보게 된다. 그중에서 자주보는게 보통은 throw로 에러를 던지고 do catch로 받는 방식인데 Result타입이라는걸 예시코드에서 자주보게 되길래 간단하게 정리를 해보려고 한다

@frozen enum Result<Success, Failure> where Failure : Error

애플공식홈페이지에는 이렇게 소개가 되어있고

case success(Success)
A success, storing a Success value.
case failure(Failure)
A failure, storing a Failure value.

Result는 Generic으로 선언된 Enum타입이다 그리고 각각의 Case는 연관값을 가지게 되는데 각각의 연관값은 Result를 선언할때 Generic으로 명시해준 타입들의 값을 넣어주면된다

enum McDonaldOrderError: Error {
     case invalidSelection
     case LackOfMoney
     case outOfStock
}

Error 프로토콜을 채택한 에러타입을 이렇게 명시하고나서

func orderMcDonaldMenu(orderedMenu: Hamburger) -> Result<Bool, McDonaldOrderError> {
    if orderedMenu.name != "BigMac" {
        return .failure(.invalidSelection)
    }
    if orderedMenu.price > myMoney {
        return .failure(.LackOfMoney)
    }
    if orderedMenu.count == 0 {
        return .failure(.outOfStock)
    }
    
    return .success(true)
}

이렇게 error handling이 가능하다
한번 해석을 해보자면 Generic에서 Success Type Parameter에는 Bool타입을, Failure Type Parameter에는 만들어둔 에러를 넣어준다 그렇게되면
Result자체가 .success 와 .failure case두개를 가지고 있는 Enum이기때문에 .success와 .failure로 분기처리가 가능하고 연관값을 가지고있어서 각각의 case에 연관값을 넣어주면되는데 연관값은 generic의 Type Parameter들을 알맞게 넣어준다 그렇게되면 오류여부와 연관값들을 함께 전달할수 있게된다.

장점은 가독성이라고한다
기존 throw를 사용한 do catch문은 error를 던지기만하고 do catch하는쪽에서 어떻게 처리를하는지를 다시 읽어야하기때문에 가독성에있어서 조금 불편할수있다
하지만 Result를 사용한다면 데이터처리과정에서 에러를 어떻게 핸들링할지를 한번에 볼수있기때문에 가독성측면에서 장점이될수있다
do catch없이 result를 switch문으로 활용이 가능하다

profile
AppleDeveloperAcademy@POSTECH 1기 수료, SOPT 32기 iOS파트 수료

0개의 댓글