[Swift] - case let


case let


https://stevenpcurtis.medium.com/ive-finally-mastered-case-let-in-my-swift-code-7b1737cf52c3

JSON 문자열을 다운받아서 결과를 화면에 출력하는 코드가 있다고 해보자. 이 때 사용하는 completionHandler에는 JSON 데이터를 올바르게 잘 받은 경우에 ((Users) -> Void)? 클로저를 리턴한다.

Result 타입을 리턴하는 것은 복잡도를 증가시킨다. 백엔드나 그 외에서 리턴된 에러에 대해서도 생각해봐야 한다. 이런 에러를 핸들링 하기 위해 viewModel을 사용할 수 있다. ViewModel이 아래의 클로저를 리턴해서 사용하게 할 수 있다.

var closure: ((Result<Users, Error>) -> Void)?

구현은 아래와 같이 될 것이다.

viewModel.closure = { result in
  switch result {
    case let .success(users):
      print(users.data.map{ "\($0.firstName)"}.joined(separator:"\n"))
    case .failure(_):
      break
  }
}

위 코드에서 문제는 무엇이냐면 fail일 때의 case를 핸들링하고 있지만, 여기서 아무 작업도 하고 있지 않기 때문에 break를 작성한 것인데, 별로 이 코드에 도움이 되지 않는다. 이는 아래와 같이 개선될 수 있다.

viewModel.closure = { result in
  if case let .success(users) = result {
      print(users.data.map{ "\($0.firstName)"}.joined(separator:"\n"))
  }
}

정리하자면, Result 타입으로 내려오는 결과를 switch문으로 success / failure로 case를 나눠 구분 했지만 failure case의 코드가 의미가 없어 이를 if case let 문으로 바꾼 것 같다. 그런데 case let 문 자체에 대한 설명이 아니어서 아래에는 이에 대한 내용을 정리했다.

case let

Swift에서 case는 switch 뿐만 아니라 if, guard, for, while문과 함께 조건절을 완성하는 구문으로 활용도가 높다.

위에서 봤던 코드를 다시 보자.

if case let x = y {
  ...
}

위와 같은 형식을 띄고 있는데, 이 구문은 아래와 같은 코드이다.

switch y {
  case let x: ...
  default: ()
}

그래서 switch 문을 굉장히 단순화 시킬 수 있던 것이다. 예제를 좀 더 보자.

enum Media {
    case book(title: String, author: String)
    case movie(title: String, director: String)
    case website(url: String)
}

let m = Media.movie(title: "movie", director: "director")

switch m {
case let Media.movie(title: title, director: _):
        print("movie with \(title)")
default: ()
}

// same

if case let Media.movie(title: title, director: _) = m {
    print("movie with \(title)")
}

또한 여기에 추가적인 조건을 붙일 수 있다.

if case let Media.movie(title: title, director: _) = m, title != "title" {
    print("movie with \(title)")
}

if문 뿐만 아니라 guard 문도 사용할 수 있다.

enum NWResponse {
    case response(String, Int)
    case error(Error)
}

func processNWResponse(_ response: NWResponse) {
    guard case let .response(string1, codeNum) = response, let resp = string1 as String?, 200..<300 ~= codeNum else {
        return
    }
    
    print(resp)
}

이런 식으로 guard case let 문을 사용해서 굉장히 복잡해 보이는 조건도 처리하기 쉽게 만들 수 있다.

Optional pattern

Swift에서 바인딩을 할 때는 옵셔널 값을 바인딩 하는 지를 잘 살펴봐야 한다. if case let 문에서도 바인딩하는 값이 옵셔널일 경우도 주의해야 하는데, 옵셔널 값을 받는 것을 옵셔널 패턴이라고 한다.

let optional: Int? = 12

// optional enum의 .some case에 매칭
if case .some(let x) = optional {
    print(x)
}

// ?를 사용해서 optional unwrapping(optional pattern)
if case let x? = optional {
    print(x)
}

if case let .some(x) = optional {
    print(x)
}

참고로 위의 세 print문에서는 모두 12란 값이 출력된다.

  • 참고
  • https://townpharm.tistory.com/12