Swift/개념 & 응용

[Swift] where문 활용법

유정주 2023. 3. 27. 12:00
반응형

서론

 

where은 Swift의 특정 제약을 거는 키워드입니다.

 

비슷한 효과를 내는 문법은 if문입니다.

if문에 비해 where문은 낯설게 느껴질 수 있는데요.

where문을 이용하면 가독성은 유지하면서 코드 길이를 줄일 수 있다는 사실을 알고 계시나요?

 

이번 포스팅에서는 그 방법에 대해 알아보겠습니다.

 

for문과 where

where문을 사용하면 for문 안의 if문을 없애고 코드를 줄일 수 있습니다.

아래는 0부터 100 사이의 5의 배수를 출력하는 코드입니다.

 

//if문
for i in 0..<100 {
    if i % 5 == 0 {
        print(i)
    }
}

//where문
for i in 0..<100 where i % 5 == 0 {
    print(i)
}

 

if문은 for문 내부에 작성되기 때문에 코드가 길고 수행되는 로직이 한 눈에 들어오지 않습니다.

where문은 range 바로 옆에 조건이 작성되고, 내부에는 수행되는 로직만 들어가 있습니다.

따라서 코드가 짧고 어떤 로직이 수행되는지 한 눈에 알 수 있다는 장점이 있습니다.

 

위 코드처럼 특정 조건에만 수행한다면 where문이 if문보다 명확합니다.

반대로 if-else가 필요한 상황이라면 where문보다 if문이 적절합니다.

 

Generic과 where

where문을 이용해 Generic 타입에 제약을 줄 수 있습니다.

func isEqual<T: Equatable>(_ a: T, _ b: T) -> Bool {
    return a == b
}

func isEqualWithWhere<T>(_ a: T, _ b: T) -> Bool where T: Equatable {
    return a == b
}

isEqual과 isEqualWithWhere는 모두 T에 특정 제약을 추가했습니다.

두 개의 차이는 미미하기 때문에 어떤 것을 선택해도 좋을 것 같습니다.

다만, Generic 타입 개수와 제약이 많아질수록 where문을 사용하는게 Return 타입을 포함해서 함수 타입을 파악하기 쉽습니다.

 

Protocol과 where

protocol과 where문을 함께 사용하면 가독성 뿐만 아니라 중복 코드를 줄일 수 있습니다.

protocol을 채택하는 특정 타입에만 기본 구현, 메서드 정의를 할 수 있기 때문입니다.

 

간단한 예시로 알아봅시다. (간단해서 좀 억지스러울 수 있음 ㅎㅎ;;)

protocol Itemable {
    associatedtype Item
    var value: Item { get set }
    func equal(_ a: Item) -> Bool
}

Itemable 프로토콜은 value 변수와 equal 메서드를 가지고 있습니다.

equal 메서드는 Itemable 타입의 value와 a가 같은지 판단하는 메서드입니다.

equal의 동작은 특별할 거 없이 == 연산자로 판단하는 것뿐이기 때문에 Equatable만 채택한다면 모든 구현 내용이 같습니다.

(Int == Int, Double == Double, String == String 처럼요)

 

이럴 때 where문을 사용하면 효과적입니다.

extension Itemable where Item: Equatable {
    func equal(_ a: Item) -> Bool {
        return self.value == a
    }
}

where을 이용해 Item이 Equatable을 준수하고 있는 경우 equal 메서드의 기본 구현을 할 수 있습니다.

 

class Item: Itemable {
    var value: Int
    init(value: Int) {
        self.value = value
    }
}

var items: [Item] = [Item(value: 1), Item(value: 2), Item(value: 3)]

print(items[0].equal(1)) //true

덕분에 Item 클래스는 직접 equal 메서드를 구현하지 않았어도 equal 메서드를 사용할 수 있게 되었습니다.

 

iOS 사용 예시

간단한 예시말고 실제로 iOS에서 사용될 수 있는 예시가 궁금하실 수 있습니다.

iOS에서는 아래의 Alertable 프로토콜을 예로 들 수 있습니다.

import UIKit

protocol Alertable {
    func showAlert(title: String, message: String, preferredStyle: UIAlertController.Style = .alert, completion: (() -> Void)? = nil)
}

extension Alertable where Self: UIViewController {
    func showAlert(
        title: String = "",
        message: String,
        preferredStyle: UIAlertController.Style = .alert,
        completion: (() -> Void)? = nil
    ) {
        let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
        alert.addAction(UIAlertAction(title: "OK", style: UIAlertAction.Style.default, handler: nil))
        self.present(alert, animated: true, completion: completion)
    }
}

Alertable은 showAlert 메서드를 통해 Alert를 띄울 수 있습니다.

where문을 이용해 UIViewController 타입에게 showAlert의 기본 구현을 제공했습니다.

덕분에 Alert가 필요한 ViewController는 Alertable을 채택만 한다면 showAlert 메서드를 사용할 수 있습니다.

 

Alert가 필요한 ViewController만 채택해주면 되니

상속을 이용하는 방법보다 유연하고 가볍겠네요 :)

 

Enum과 where

마지막으로 enum에서 where을 사용해 안정성을 높이는 방법을 소개하겠습니다.

switch grade {
case 0..<60:
    print("F")
case 60..<70:
    print("D")
case 70..<80:
    print("C")
case 80..<90:
    print("B")
case 90...100:
    if (grade % 5 == 0 && grade >= 90) {
        print("A")
    } else {
        print("A-")
    }
default:
    fatalError("Invalid grade")
}

위 코드는 90 ~ 100 범위에서 5의 배수는 A, 이외는 A-를 부여하는 코드입니다.

grade는 Int 타입이므로 default를 작성해야 하고, 이때 fatalError로 예외처리를 해주었네요.

만약 grade가 100을 초과하면 런타임 에러가 발생할 수 있다는 것입니다.

 

where을 이용해 이런 런타임 에러 가능성을 없앨 수 있습니다.

let grade = 89
switch grade {
    case 0..<60:
        print("F")
    case 60..<70:
        print("D")
    case 70..<80:
        print("C")
    case 80..<90:
        print("B")
    case 90...100 where grade % 5 == 0:
        print("A")
    default:
        print("A-")
}

90 ~ 100 범위 case에 where을 추가하여 5의 배수일 때 A를 부여하도록 했습니다.

그리고 default에 A- 등급을 부여하도록 했어요.

이를 통해 fatalError 코드를 삭제할 수 있고, 런타임 에러 가능성이 사라졌습니다.

(100 초과가 들어오게 하지 않게 하는게 제일 좋긴 하지만요 ㅎㅎ;;)

 

마무리

이번 포스팅에서는 where문의 다양한 활용 방법에 대해 알아보았습니다.

저는 프로토콜과 where을 함께 사용하는게 가장 매력적이었습니다.

중복되는 코드를 줄여줘서 프로젝트 구조에 가장 큰 기여를 하는 것 같아서요 ㅎㅎ

얼른 where문에 익숙해져서 가독성과 코드 길이를 함께 챙길 수 있었으면 좋겠습니다.

 

감사합니다!

 

참고

https://levelup.gitconnected.com/mastering-where-keyword-in-swift-2d3f01314f24


아직은 초보 개발자입니다.

더 효율적인 코드 훈수 환영합니다!

공감 댓글 부탁드립니다.

 

 

반응형