
swiftui tutorial 의 예시 코드를 공부하다보면, Struct 뒤에 ~able 로 붙은 것들이 보인다.
이건 무엇이며 각각 어떤 역할을 할까?
Hashable과 Codable은 스위프트 언어에서 프로토콜(Protocol)이다.
예시코드에서 각각의 의미
1. Hashable:
Hashable 프로토콜은 객체를 고유한 해시값으로 매핑할 수 있도록 해주는 프로토콜이다. 해시값은 객체를 식별하는 데 사용되며, 해시 함수를 통해 계산된다. Hashable 프로토콜을 채택하면 해당 객체를 Set이나 Dictionary의 키로 사용할 수 있다. 또한, Hashable 프로토콜을 준수하는 객체는 집합(Set)과 같은 자료 구조에서 중복된 요소를 제거할 수 있다. Landmark 구조체의 인스턴스가 Hashable 프로토콜을 준수하므로, 이 객체는 Set이나 Dictionary의 키로 사용할 수 있는 것이다.
2. Codable:
Codable 프로토콜은 객체를 다른 데이터 형식으로 변환하고, 다시 해당 형식에서 객체로 변환하는 데 사용된다. 즉, Codable을 준수하는 객체는 데이터를 직렬화(Serialization)하고, 역직렬화(Deserialization)할 수 있다. 이는 객체를 JSON, XML, Property List 등과 같은 외부 데이터 형식으로 변환하거나, 외부 데이터를 객체로 변환할 때 유용하다. Codable을 사용하면 객체를 인코딩(Encoding)하거나 디코딩(Decoding)하는 작업을 간편하게 수행할 수 있다. Landmark 구조체는 Codable 프로토콜을 준수하므로, 이 객체를 JSON 데이터로 인코딩하거나 JSON 데이터에서 디코딩할 수 있다.
프로토콜의 의미
프로토콜(Protocol)은 스위프트 언어에서 특정 기능 또는 역할을 정의하는 추상적인 청사진이다. protocol은 method, property 및 다른 멤버의 집합으로 구성되어 있다. Class, Struct, Enum 등의 타입은 프로토콜을 채택하여 해당 프로토콜이 요구하는 기능을 구현할 수 있다.
프로토콜은 코드의 재사용성과 유연성을 높여주는 역할을 한다. 다양한 타입이 동일한 프로토콜을 구현할 수 있기 때문에, 이들을 일관된 방식으로 다룰 수 있다. 또한, 프로토콜을 사용하면 타입 간의 결합도를 낮추고, 의존성을 관리하기 쉽게 만들어준다.
몇 가지 주요 프로토콜
1. Equatable
Equatable 프로토콜은 타입의 인스턴스들을 서로 비교할 수 있도록 해주는 프로토콜이다. 이를 준수하는 타입은 동등 비교 연산자(==)를 사용하여 인스턴스들을 비교할 수 있다.
2. Comparable
Comparable 프로토콜은 타입의 인스턴스들을 순서대로 비교할 수 있도록 해주는 프로토콜이다. 이를 준수하는 타입은 비교 연산자(<, >, <=, >=)를 사용하여 인스턴스들을 비교하고 정렬할 수 있다.
3. CustomStringConvertible
CustomStringConvertible 프로토콜은 타입의 인스턴스를 문자열로 표현하기 위한 기능을 정의하는 프로토콜이다. 이를 준수하는 타입은 description이라는 속성을 구현하여 인스턴스를 문자열로 변환할 수 있다.
4. UITableViewDelegate, UITableViewDataSource
UITableViewDelegate와 UITableViewDataSource 프로토콜은 테이블 뷰를 관리하고 사용자의 입력에 대응하기 위한 기능을 정의하는 프로토콜이다. UITableViewDelegate는 테이블 뷰의 동작을 커스터마이즈하고, UITableViewDataSource는 테이블 뷰의 데이터 소스를 제공하는 역할을 한다.
5. URLSessionDelegate, URLSessionDataDelegate
URLSessionDelegate와 URLSessionDataDelegate 프로토콜은 네트워크 작업을 관리하고 응답 데이터를 처리하는 기능을 정의하는 프로토콜이다. URLSessionDelegate는 네트워크 작업의 상태 변화를 처리하고, URLSessionDataDelegate는 응답 데이터를 수신하고 처리하는 역할을 한다.
이 외에도 다양한 프로토콜이 스위프트에서 제공되며, 필요에 따라 직접 프로토콜을 정의하여 사용할 수도 있다.
1. Equatable
struct Point: Equatable {
var x: Int
var y: Int
}
let point1 = Point(x: 3, y: 5)
let point2 = Point(x: 3, y: 5)
if point1 == point2 {
print("두 점은 동일합니다.")
} else {
print("두 점은 다릅니다.")
}
위의 예시에서 `Point` 구조체는 `Equatable` 프로토콜을 준수하고 있다. 이로 인해 두 개의 `Point` 인스턴스를 `==` 연산자로 비교할 수 있게 된다.
2. CustomStringConvertible
struct Person: CustomStringConvertible {
var name: String
var age: Int
var description: String {
return "이름: \(name), 나이: \(age)세"
}
}
let person = Person(name: "John", age: 30)
print(person)
위의 예시에서 `Person` 구조체는 `CustomStringConvertible` 프로토콜을 준수하고 있다. 이로 인해 `person` 인스턴스를 `print` 함수로 출력할 때, `description` 속성이 사용되어 "이름: John, 나이: 30세"라는 문자열이 출력된다.
3. UITableViewDelegate
class MyTableViewController: UIViewController, UITableViewDelegate {
// ...
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
// 특정 셀이 선택되었을 때 실행되는 로직
}
// ...
}
위의 예시에서 `MyTableViewController` 클래스는 `UIViewController`를 상속하고, `UITableViewDelegate` 프로토콜을 준수하고 있다. 이로 인해 해당 클래스는 테이블 뷰의 동작을 커스터마이즈하기 위한 메서드를 구현할 수 있다. `didSelectRowAt` 메서드는 특정 셀이 선택되었을 때 실행되는 로직을 구현한 예시이다.
이렇게 다양한 상황에서 프로토콜을 사용하여 기능을 정의하고 필요한 곳에서 적절한 타입에 프로토콜을 채택하면 코드의 재사용성과 유연성을 높일 수 있다.
아직도 프로토콜을 왜! 써야하는지 잘 모르겠다면
프로토콜 사용할 때
protocol Drawable {
func draw()
}
struct Circle: Drawable {
func draw() {
print("원을 그립니다.")
}
}
struct Rectangle: Drawable {
func draw() {
print("사각형을 그립니다.")
}
}
func drawShapes(_ shapes: [Drawable]) {
for shape in shapes {
shape.draw()
}
}
let circle = Circle()
let rectangle = Rectangle()
drawShapes([circle, rectangle])
위의 코드는 Drawable이라는 프로토콜을 정의하고, Circle과 Rectangle 구조체가 이 프로토콜을 준수하도록 구현하였다.
그리고 drawShapes 함수는 Drawable 프로토콜을 준수하는 객체들의 배열을 인자로 받아 그림을 그리는 기능을 수행한다.
프로토콜을 사용함으로써 얻을 수 있는 이점은 다음과 같다
- 다양한 도형을 그리는 함수 drawShapes에서 Drawable 프로토콜을 준수하는 모든 객체를 받을 수 있다. 즉, Circle과 Rectangle 이외에 다른 구조체나 클래스도 drawShapes 함수에 전달할 수 있다.
- 새로운 도형을 추가하기 위해 Drawable 프로토콜을 준수하는 구조체를 추가로 구현하면 된다. 기존의 코드를 수정할 필요가 없으며, 이미 구현된 drawShapes 함수를 그대로 사용할 수 있다.
- Drawable 프로토콜을 준수하는 객체들은 draw 메서드를 구현해야 한다. 이로써 해당 객체들은 도형을 그리는 공통된 기능을 가지게 된다.
프로토콜을 사용하지 않을 때
struct Circle {
func draw() {
print("원을 그립니다.")
}
}
struct Rectangle {
func draw() {
print("사각형을 그립니다.")
}
}
func drawShapes(_ shapes: [Any]) {
for shape in shapes {
if let drawable = shape as? Drawable {
drawable.draw()
}
}
}
let circle = Circle()
let rectangle = Rectangle()
drawShapes([circle, rectangle])
위의 코드는 Drawable 프로토콜을 사용하지 않고 Circle과 Rectangle 구조체를 구현한 예시이다.
drawShapes 함수는 Any 타입의 객체 배열을 인자로 받고, 객체가 Drawable 프로토콜을 준수하는지 검사한 후에 그림을 그리는 기능을 수행한다.
프로토콜을 사용하지 않는 경우에는 다음과 같은 문제점이 있다
drawShapes 함수는 Any 타입의 배열을 받아 객체의 타입을 검사해야 한다. 이로 인해 타입 안정성이 떨어지고, 실행 시간에 타입 캐스팅이 필요하게 된다.
새로운 도형을 추가하기 위해서는 drawShapes 함수의 구현을 수정해야 한다. 이미 구현된 함수를 변경하는 것은 오류 가능성을 높이고 기존 코드와의 호환성을 해치는 요소가 된다.
객체들은 draw 메서드를 구현해야 하지만, 이를 강제할 수 있는 방법이 없다. 따라서, 그렇지 않은 객체를 drawShapes 함수에 전달하면 오류가 발생할 수 있다.
결론
이와 같이 프로토콜을 사용하면 코드의 유연성, 재사용성, 확장성이 향상되며, 인터페이스의 명시화와 협업을 원활하게 할 수 있다!
'Apple Developer Academy > 🎇 Swift' 카테고리의 다른 글
| [Swift] how to create multiple preview (45) | 2023.06.08 |
|---|---|
| [Swift] 반복문에서 id: \.id 의 의미 (0) | 2023.06.08 |
| [Swift] load(_:) method (fetching JSON data) 뜯어보기 (0) | 2023.06.07 |
| [SwiftUI] 헷깔리는 State, Binding, ObservedObject, EnvironmentObject 총정리 (0) | 2023.05.31 |
| [SwiftUI Tutorials] SwiftUI Essentials - Creating and Combining Views (0) | 2023.03.26 |
댓글