where 절
Swift에서 where
절은 특정 패턴과 결합하여 조건을 추가하는 역할을 한다. 조건을 더 추가하고 싶을 때, 특정 타입에 제한을 두고 싶을 때 등 다양한 용도로 사용된다.
언제 어디서든 필요할 때마다 나타나 도와주는 역할을 가지고 있다.
타입에 제약을 주는 것은 개발자가 제네릭 메서드, 첨자, 타입과 관련한 타입 매개변수의 요구사항을 정의할 수 있게 해 준다. 관련 타입들의 요구사항 정의를 위해 개발자는 where
절을 사용할 수 있다.
where
절은 where
키워드를 사용하며, 그 뒤에 관련 타입에 대한 제약사항이나 충족해야 하는 관계 조건을 정의하면 된다. 블록의 타입, 메서드가 시작하는 중괄호({}
) 전에 활용할 수 있다.
where 절의 활용
where 절은 크게 두 가지 용도로 사용된다.
- 패턴과 결합하여 조건 추가
- 타입에 대한 제약 추가
즉, 특정 패턴에 Bool
타입 조건을 지정하거나 어떤 타입의 특정 프로토콜 준수 조건을 추가하는 등의 기능이 있다.
아래 예시는 배열 컬렉션을 for
문으로 순회하는 데 있어서 순회하는 요소의 조건을 짝수로 한정해 출력한다. where
절은 프로토콜 타입의 제약 외로 특정 타입 값을 충족해야 하는 관계 조건을 제약사항으로 둘 수 있다.
값 바인딩, 와일드카드 패턴
let arr: [Int] = [1, 2, 3, 4, 5, 6, 7, 8]
// 값 바인딩, 와일드 카드 패턴
// i 요소가 2로 나웠을때 0일 경우(짝수)일 때만 블록 실행
for i in arr where i % 2 == 0 {
print(i)
}
/*
2
4
6
8
*/
옵셔널 패턴과 결합
where
절은 옵셔널 패턴과도 결합 가능하다.
let arr: [Int?] = [1, 2, 3, 4, 5, 6, 7, 8]
// 옵셔널 패턴과 결합
for case let i? in arr where i % 2 == 0 {
print(i)
}
/*
2
4
6
8
*/
let arrOfOptionalString: [String?] = ["서근", nil, "개발노트", "히", nil, nil]
for case let value? in arrOfOptionalString where value.count > 1 {
print("String 타입을 찾는 중.... '\(value)'")
}
/*
String 타입을 찾는 중.... '서근'
String 타입을 찾는 중.... '개발노트'
*/
타입 캐스팅 패턴과 결합
let anyValue: Any = "ABC"
switch anyValue {
case let value where value is String:
print("value는 String 타입 '\(anyValue)' 이다.")
case let value where value is Double:
print("value는 Double 타입 '\(anyValue)' 이다.")
default:
print("anyValue")
}
//value는 String 타입 'ABC' 이다.
표현 패턴과 결합
var point: (Int, Int) = (1, 2)
switch point {
case (0, 0):
print("원점")
//point 첫번째 값이 1과 다른 값인지 비교하여 값이 같지 않다면 출력
case (-2...2, -2...2) where point.0 != 1:
print("(\(point.0)), (\(point.1))은 원점과 가깝다.")
default:
print("포인트 : (\(point.0), \(point.1))")
}
//포인트 : (1, 2)
제네릭 타입의 연관 타입 제약 추가
protocol Talkable {}
protocol CallToAll {
func callToAll()
}
struct Person: Talkable {}
struct Animal {}
struct WhoCare: Talkable {}
extension Array: CallToAll where Element: Talkable {
func callToAll() {
print("call to all")
}
}
let people: [Person] = []
let cats: [Animal] = []
let whoCare: [WhoCare] = []
people.callToAll()
//cats.callToAll{} Animal 은 Talkable 프로토콜을 준수하지 않았기 때문에 런타임 오류
whoCare.callToAll()
프로토콜, 익스텐션과 결합
프로토콜 익스텐션에 where
절을 사용하게 되면 이 익스텐션이 특정 프로토콜을 준수하는 타입에만 적용될 수 있도록 제약을 줄 수 있다.
즉, 익스텐션이 적용된 프로토콜을 준수하는 타입 중 where
절 뒤에 제시되는 프로토콜도 준수하는 타입만 익스텐션이 적용되도록 제약을 준다는 의미이다.
아래 예제에선 각 extension Selfprintable
위에 where
절을 사용했고, 그 뒤에 이 프로토콜을 준수하는 타입(Self: FixedWidthInteger, Self: SignedInteger
)을 정의했다. 이것이 바로 제약을 준 것이다.
눈치챘겠지만, 여러 프로토콜을 제시하고 싶다면 쉼표(,
)로 구분해주면 된다.
그러니까 FixedWidthInteger
와 1SignedInteger
을 준수하면서 2SelfPrintable
을 준수하는 타입이면 이 메시지를 출력한다는 의미인 것이다.
protocol SelfPrintable {
func printSelf()
}
struct Person: SelfPrintable {}
extension Int: SelfPrintable {}
extension UInt: SelfPrintable {}
extension String: SelfPrintable {}
extension Double: SelfPrintable {}
extension SelfPrintable where Self: FixedWidthInteger, Self: SignedInteger {
func printSelf() {
print("FixedWidthInteger와 SignedInteger을 준수하면서 SelfPrintable을 준수하는 타입 \(type(of:self))")
}
}
extension SelfPrintable where Self: CustomStringConvertible {
func printSelf() {
print("CustomStringConvertible을 준수하면서 SelfPrintable을 준수하는 타입 \(type(of:self))")
}
}
extension SelfPrintable {
func printSelf() {
print("그 외 SelfPrintable을 준수하는 타입 \(type(of:self))")
}
}
소문자 self와 대문자 Self의 차이
self
는 인스턴스 프로퍼티와 인스턴스 메서드를 가리킬 때 사용 되는데, 모든 인스턴스들이 암시적으로 생성하는 프로퍼티이다. 즉, self
는 자기 자신을 가리키는 프로퍼티인데, 클래스, 구조체, 열거형 등 인스턴스 내에서 사용된다. Self
는 타입 프로퍼티와 타입 메서드를 가리킬 때 사용된다. 즉, Self
타입은 특정한 타입이 아닌, 타입의 이름을 반복해서 쓰는 대신 현재 타입을 쓸 수 있도록 도와주는 역할을 한다. 만약 클래스, 구조체, 열거형 등에서 Self
를 사용하면 그 타입을 가리킨다는 의미이다.
이제 위 where
절을 활용한 프로토콜 익스텐션을 호출하려면 아래와 같이 코드를 작성하면 된다.
Int(-8).printSelf()
// FixedWidthInteger와 SignedInteger을 준수하면서 SelfPrintable을 준수하는 타입 Int
UInt(8).printSelf()
// CustomStringConvertible을 준수하면서 SelfPrintable을 준수하는 타입 UInt
String("서근").printSelf()
// CustomStringConvertible을 준수하면서 SelfPrintable을 준수하는 타입 String
Double(8.0).printSelf()
// CustomStringConvertible을 준수하면서 SelfPrintable을 준수하는 타입 Double
Person().printSelf()
// 그 외 SelfPrintable을 준수하는 타입 Person
또 다른 예제를 보자면..
아래 예제는 Numeric
프로토콜을 준수하면서 3Printable
프로토콜을 준수하는 타입만 적용되도록 제약해준 익스텐션이다.
protocol Printable {
func printSelf()
}
extension Int: Printable {}
extension String: Printable {}
// 익스텐션과 프로토콜의 조합
// Numeric프로토콜을 준수하면서 Printable 프로토콜을 준수하는 타입만 적용되도록 제약/
extension Printable where Self: Numeric {
func printSelf() {
print("Numeric을 준수하면서 Printable을 준수하는 타입 \(type(of:self))")
}
}
extension Printable {
func printSelf() {
print("Prinatable만 준수하는 타입 \(type(of:self))")
}
}
마찬가지로 아래와 같이 호출 가능한데, 익스텐션 타입으로 바로 호출도 가능하고 아니면 인스턴스를 만들어서 호출해 줄 수 있다.
Int(5).printSelf()
// Numeric을 준수하면서 Printable을 준수하는 타입 Int
String("서근").printSelf()
// Prinatable만 준수하는 타입 String
let someInt: Int = 10
let someString: String = "서근개발노트"
someInt.printSelf()
// Numeric을 준수하면서 Printable을 준수하는 타입 Int
someString.printSelf()
// Prinatable만 준수하는 타입 String
읽어주셔서 감사합니다 🤟
'SWIFT > Grammar' 카테고리의 다른 글
Swift: 주요 함수 [abs, assert, assertionFailure, debugPrint, dump, fatalError] (0) | 2022.02.08 |
---|---|
Swift : 스위프트의 주요 프로토콜 모음(설명) (0) | 2022.02.07 |
Swift : 고급 문법 [패턴 - Patterns] (0) | 2022.02.05 |
Swift : 기초문법 [제네릭 - Generic] (0) | 2022.02.03 |
Swift : 기초문법 [익스텐션 - Extensions] (0) | 2022.02.03 |