스위프트에는 문법에 응용할 수 있는 다양한 종류의 패턴이 있습니다. 여러 패턴을 잘 숙지해두면 스위프트 코드의 양을 줄이는 효과는 물론, 스위프트 문법을 잘 활용할 수 있습니다.
스위프트의 패턴은 크게 두 종류로 나뉩니다.
와일드카드 패턴은 와일드카드 식별자(_)를 사용한다는 것으로 ‘이 자리에 올 것이 무엇이든 간에 상관하지 마라’라는 뜻입니다. 즉, 와일드카드 식별자가 위치한 곳의 값은 무시합니다.
let value: String = "ABC"
switch value {
case _: // -> 어떤 값이 와도 상관없기에 항상 실행됩니다.
print(value)
}
// ABC
var optionalValue: String? = "ABC"
func printResult(_ value: String?) {
switch optionalValue {
case "ABC"?: print(optionalValue)
case _?: print("이건 값은 있는데 ABC가 아니네")
case nil: print("이 값은 nil입니다.")
}
}
printResult(optionalValue)
// Optional("ABC")
optionalValue = "ㅇㄹㅁ"
printResult(optionalValue)
// 이건 값은 있는데 ABC가 아니네
optionalValue = nil
printResult(optionalValue)
// 이 값은 nil입니다.
let tupleData = ("Peter", 30, "남자")
switch tupleData {
case ("Peter", _, _): print("이 사람은 피터군요.")
case (_, _, _): print("위 케이스 외에 언제든지 실행됩니다.")
}
// "이 사람은 피터군요."
식별자 패턴은 변수 또는 상수의 이름에 알맞는 값을 어떤 값과 매치시키는 패턴을 말합니다.
let someValue: Int = 40
someValue 상수를 선언하는 동시에 someValue에 40이라는 값을 할당하려고 합니다. 이때 someValue의 타입인 Int와 할당하려는 40의 타입이 매치가 된다면 someValue는 40이라는 값의 식별자가 되므로 식별자 패턴이 성립됩니다.
값 바인딩 패턴은 변수 또는 상수의 이름에 매치된 값을 바인딩 하는 것입니다.
위에 설명한 식별자 패턴은 값 바인딩 패턴의 일종입니다.
예를 들어 튜플을 확인해봅시다.
enum Gender: String {
case 남자, 여자
}
let person = ("피터", 30, Gender.남자)
switch person {
case let (name, age, gender): print("이름: \(name), 나이: \(age), 성별: \(gender.rawValue)")
}
// 이름: 피터, 나이: 30, 성별: 남자
아래 코드는 위 코드와 동일한 작동을 합니다.
switch person {
case (let name,let age,let gender): print("이름: \(name), 나이: \(age), 성별: \(gender.rawValue)")
}
// 와일드카드 패턴과 결합하여 유용하게 사용될 수도 있습니다.
switch person {
case (let name, _, let gender): print("이름: \(name), 성별: \(gender.rawValue)")
}
let value: (Int, Double) = (1, 2)
print(value)
열거형 케이스 패턴은 값을 열거형 타입의 case와 매치시킵니다. 열거형 케이스 패턴은 switch 구문읜 case 레이블과 if, while, guard, for-in 구문의 case 조건에서 볼 수 있습니다.
일단 범위 연산자와 String의 경우에서 확인해보겠습니다.
let someValue: Int = 30
if case 0...100 = someValue {
print("\(someValue)는 0 이상 100 이하의 값입니다. ")
}
// 30는 0 이상 100 이하의 값입니다.
let otherValue: String = "가나다"
if case "가나다" = otherValue {
print(otherValue)
}
// 가나다
열거형으로 확인해볼까요?
enum MainDish {
case 파스타(맛: String)
case 피자(도우: String, 토핑: String)
case 치킨(순살입니까: Bool)
case 백반
}
var dinner = MainDish.파스타(맛: "크림")
func 이거피자입니까(dish: MainDish) {
guard case .피자(let dough, let topping) = dish else {
print("이건 피자가 아니잖아!!!!")
return
}
print("이건 \(dough)도우에 \(topping)이 들어간 피자야아 ")
}
이거피자입니까(dish: dinner)
// 이건 피자가 아니잖아!!!!
dinner = .피자(도우: "씬", 토핑: "페퍼로니")
이거피자입니까(dish: dinner)
// 이건 씬도우에 페퍼로니이 들어간 피자야아
var dinner = MainDish.파스타(맛: "크림")
var dishes = [MainDish]()
dishes.append(dinner)
dinner = .피자(도우: "씬", 토핑: "페퍼로니")
dishes.append(dinner)
dinner = .치킨(순살입니까: true)
dishes.append(dinner)
while case .치킨(let withBorn) = dinner {
let value = withBorn ? "순살" : "순살 아님"
print("\(value) 치킨입니다.")
break
}
// 순살 치킨입니다.
dinner = .백반
dishes.append(dinner)
for dish in dishes {
switch dish {
case .파스타(맛: let 맛):
print(맛)
case .피자(도우: let 도우, 토핑: let 토핑):
print("\(도우) \(토핑)")
case .치킨(순살입니까: let 순살입니까):
print(순살입니까 ? "순살 치킨": "순살 아닌 치킨")
case .백반:
print("그냥 백반입니다.")
}
}
// 크림
// 씬 페퍼로니
// 순살 치킨
// 그냥 백반입니다.
옵셔널 패턴은 옵셔널 도는 암시적 추출 옵셔널 열거형에 감싸져 있는 값을 매치시킬 때 사용합니다. 옵셔널 패턴은 식별자 패턴 뒤에 물음표를 넣어 표기하며 열거형 케이스 패턴과 동일한 위치에 자리합니다.
let optionalValue: Int? = 31
if case .some(let value) = optionalValue {
print(value)
}
// 31
if case let value? = optionalValue {
print(value)
}
// 31
func hasValue(_ optionalValue: Int?) {
guard case let .some(value) = optionalValue else {
print("None")
return }
print(value)
}
hasValue(optionalValue)
// 31
let arrayOptional = [nil, 2, 3, 4, nil, 400]
for case let value? in arrayOptional {
print(value)
}
// 2
// 3
// 4
// 400
타입캐스팅 패턴은 is 패턴과 as 패턴이 있습니다.
타입캐스팅 패턴은 타입캐스팅을 하거나 타입을 매치시킵니다.
is 패턴은 프로그램 실행 중에 값의 타입이 is 우측에 쓰여진 타입 또는 그 타입의 자식클래스 타입이면 값과 매치시킵니다. is 패턴은 타입캐스팅에 사용되는 as 연산자와 비슷한 역할을 하지만 반환된 결괏값은 신경쓰지 않는다는 차이가 있습니다.
as 패턴은 프로그램 실행 중에 값의 타입이 as 우측에 쓰여진 타입 또는 그 타입의 자식클래스 타입이면 값과 매치시킵니다. 만약 매치된 값의 타입은 as 패턴이 원하는 타입으로 캐스팅됩니다
let someValue: Any = 100
switch someValue {
case is String: print("이건 String입니다.")
case let value as Int: print(value + 1)
default: print("Int도 String도 아닙니다.")
}
// 101
표현 패턴은 표현식의 값을 평가한 결과를 이용하는 것입니다. 표현 패턴은 switch 구문의 case 레이블에서만 사용할 수 있습니다.
표현 패턴은 스위프트 표준 라이브러리의 패턴 연산자인 ~= 연산자의 연산 결과가 true로 반환하면 매치시킵니다. ~= 연산자는 같은 타입의 두 값을 비교할 때 == 연산자를 사용합니다. 표현 패턴은 정숫값과 정수의 범위를 나타내는 Range 객체와 매치시킬 수도 있습니다.
표현 패턴은 매우 유용한 패턴 중 하나입니다. 그 이유는 ~= 연산자를 중복 정의하거나 ~= 연산자를 새로 정의하거나 또는 자신이 만든 타입에 ~= 연산자를 구현해준다면 자신이 원하는 대로 패턴을 완성시킬 수 있기 때문입니다.
var point = (1, 2)
switch point {
case (0, 0): print("원점에 있습니다.")
case (-2...2, -2...2): print("\(point)는 원점에 가깝습니다.")
default: print(point)
}
// (1, 2)는 원점에 가깝습니다.
point = (0, 0)
switch point {
case ("0", "0"): print("원점에 있습니다.") // String으로 바뀌었습니다.
default: print(point)
}
// 원점에 있습니다.
전역 메서드로 구현을 한다는게 신기하네요
struct Person {
var name: String
var age: Int
}
let peter: Person = .init(name: "Peter", age: 30)
func ~= (pattern: String, value: Person) -> Bool {
return pattern == value.name
}
func ~= (pattern: Person, value: Person) -> Bool {
return pattern.name == value.name && pattern.age == value.age
}
switch peter {
case Person(name: "Peter", age: 100): print("이 사람은 피터입니다.")
case "Peter": print("Hello Peter")
default: print("어데 김씹니꺼?")
}
// Hello Peter
자료 출처: 야곰 스위프트 프로그래밍 3판
"이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다."