서론
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
아직은 초보 개발자입니다.
더 효율적인 코드 훈수 환영합니다!
공감과 댓글 부탁드립니다.