Swift 표준 라이브러리에 정의된 중요한 프로토콜이 있다. 다음은 사용 가능한 몇 가지 프로토콜 목록이다.
Equatable, Comparable, Hashable, Numeric, CaseIterable
이 프로토콜들은 시스템과 신속한 언어에 의해 수행되는 기본 프로세스를 담당한다고 한다.
Equatable
이 프로토콜을 사용하면 시스템은 데이터타입을 == 과 != 를 사용하여 값을 비교하는데 사용된다.
해당 Equatable 프로토콜을 채택하게 된다면 아래와 같은 형태로 특정 값을 비교하는 연산(==)을 구현할 수 있다.
다음 코드에서는 Employees 타입의 인자를 받는 value1과 value2의 age값을 비교하여 true와 false를 반환시킬 수 있다.
struct Employees: Equatable {
var name: String
var age: Int
static func == (value1: Employees, value2: Employees) -> Bool {
return value1.age == value2.age
}
}
let employee1 = Employees(name: "John", age: 32)
let employee2 = Employees(name: "George", age: 32)
let message = employee1 == employee2 ? "Equal" : "Different"
print(message) // "Equal"
- 사용했을때 장점은 코드를 간결하게 짤 수 있다.
추가로 Equatable 프로토콜을 채택하는 제네릭 함수가 정의할 수 있다. 이 함수는 두 개의 Equatable 타입을 비교하고 그 결과에 따라 "equal" 또는 "different" 라는 문자열을 반환한다.
struct Employees: Equatable {
var name: String
var age: Int
}
func compareValues<T: Equatable>(value1: T, value2: T) -> String {
let message = value1 == value2 ? "equal" : "different"
return message
}
let employee1 = Employees(name: "George", age: 55)
let employee2 = Employees(name: "Robert", age: 55)
let result = compareValues(value1: employee1, value2: employee2)
print("The values are \(result)") // "The values are different"
Numeric
이 프로토콜은 함수가 받은 값의 데이터 유형이 산술을 지원해야 한다고 결정한다.
아래 예제에서 제너릭 함수인 calculateResult 는 Numeric 프로토콜을 채택하기 때문에 인자값으로 받을 value1 과 value2 는 반드시 수학적인 기능을 수행할 수 있는 타입이여야 한다.
func calculateResult<T: Numeric>(value1: T, value2: T) {
print(value1 + value2) // 7.5
}
calculateResult(value1: 3.5, value2: 4)
Comparable
이 프로토클을 사용하면 Equatable과 달리 부등호(>, >=, <, <=)로 비교현산이 가능해지며 sequence나 collection등에서 정렬 등의 추가 기능을 사용할 수 있게 된다. Comparable 프로토콜은 Equatable을 상속받기 때문에 == 연산 또한 구현을 해야하며 추가적으로 < 연산을 구현해줘야 한다.
struct Employees: Comparable {
var name: String
var age: Int
static func > (value1: Employees, value2: Employees) -> Bool {
return value1.age > value2.age
}
static func < (value1: Employees, value2: Employees) -> Bool {
return value1.age < value2.age
}
static func >= (value1: Employees, value2: Employees) -> Bool {
return value1.age >= value2.age
}
static func <= (value1: Employees, value2: Employees) -> Bool {
return value1.age <= value2.age
}
}
let employee1 = Employees(name: "George", age: 32)
let employee2 = Employees(name: "Robert", age: 55)
if employee1 > employee2 {
print("\(employee1.name) is older")
} else {
print("\(employee2.name) is older") // "Robert is older"
}
Hashable
이 프로토콜은 Hasher 를 통해서 Int 타입의 해쉬 값을 만들어 낼 수 있도록 만들어주는 프로토콜입니다. 예를들어 ForEach문을 사용할 때 identifiable를 순수하도록 만들어서 사용할 수 있지만 id를 생성해야 합니다. 하지만 이때 id를 생성하는 대신 Hashable를 사용하여 해쉬 값을 만들어 사용할 수 있다는 것 입니다.
[String] 데이터를 ForEach문으로 사용할 수 있는 것은 String타입은 Hashable 프로토콜을 준수하고 있기 때문입니다. Hashable은 각 String의 "ONE", "TWO", "THREE", "FOUR", "FIVE" 문자열들이 각각 고유의 id를 각각의 데이터마다 보유하고 있다는 의미입니다.
let data: [String] = [
"ONE", "TWO", "THREE", "FOUR", "FIVE"
]
var body: some View {
VStack {
ForEach(data, id: \.self) { item in
Text(item)
.font(.headline)
}
}
}
Hashable 프로토콜을 준수하면 고유의 Hash id 를 갖는다고 했는데 다음과 같이 문자열에 해당하는 랜덤 값이 생성되는 것을 확인할 수 있으며 만약 문자열이 같다면 hashValue의 값도 같다는 것을 확인할 수 있습니다. (문자열에 따라 생성된다는 것을 재확인)
let data: [String] = [
"ONE", "TWO", "THREE", "FOUR", "FIVE", "FIVE"
]
var body: some View {
VStack {
ForEach(data, id: \.self) { item in
Text(item.hashValue.description)
.font(.headline)
}
}
}
만약 String이 아니라 데이터 모델인 경우에는 Hashable 프로토콜을 준수하도록 만들어야 ForEach문에서 사용할 때 문제가 발생하지 않습니다. Hashable을 채택한다는 것은 Hashable의 부모 프로토콜이 Equatable이기 때문에 Equatable도 준수합니다. 또한 Hashable을 사용하려면 반드시 hash() 메서드를 구현해야합니다.
hash 메서드는 다음과 같이 hasher.combine(value)로 해시 값이 정해지는데 name를 가지고 해시 값을 만드는 것을 의미합니다.
struct CustomModel: Hashable {
let name: String
func hash(into hasher: inout Hasher) {
hasher.combine(name)
}
}
struct ContentView: View {
let data: [CustomModel] = [
CustomModel(name: "ONE"),
CustomModel(name: "TWO"),
CustomModel(name: "THREE"),
CustomModel(name: "FOUR"),
CustomModel(name: "FIVE")
]
var body: some View {
VStack {
ForEach(data, id: \.self) { item in
Text(item.name)
.font(.headline)
}
}
}
}
더 복잡한 고유의 해시 값을 갖게 만들어 중복이 발생하는 것을 줄이기 위해서는 다른 값들과 함께 섞어서 해시값을 만들 수 있습니다.
struct CustomModel: Hashable {
let name: String
let country: String
func hash(into hasher: inout Hasher) {
hasher.combine(name + country)
}
}
CaseIterable
이 프로토콜을 이용하면 .allCases를 아래처럼 사용하여 모든 enum에 case들에 접근할 수 있게 된다.
enum Departments: CaseIterable {
case mail
case marketing
case managing
}
var message = ""
for department in Departments.allCases {
message += "\(department) "
}
print(message) // "mail marketing managing "
'SwiftUI' 카테고리의 다른 글
Gradient (0) | 2023.11.06 |
---|---|
Extensions (0) | 2023.11.06 |
Definition of Protocols (0) | 2023.11.05 |
Managing a Shared Resource Using a Singleton (0) | 2023.11.05 |
Dictionary (0) | 2023.11.05 |