Protocol
Protocol
•
특정 역할을 하기 위한 메소드, 프로퍼티, 기타 요구사항 등의 청사진? 약속? 규약?
•
프로토콜은 일급 객체
•
클래스, 구조체, 열거형에서 사용할 수 있음
•
저장프로퍼티인지 연산프로퍼티인지 명시하지 않음
•
실제 구현은 사용하는 곳에서!
•
이름과 타입 그리고 gettable, settable한지 명시 ( 프로퍼티는 항상 var로 선언 )
•
메소드를 만들 시, { } 는 적지 않고 반환값도 명시 할 수 있음
•
mutating 을 사용하여 인스턴스에 변화를 줄 수 있음 → 값 타입에서만 가능
•
프로토콜을 무조건 따라야 한다..? optional 하게 만들 수는 없나? → 가능하다..!
◦
@objc 를 protocol 앞에 붙이고,
@objc optional var xxx: String { get set } 하면 된다!
⇒ 프로토콜을 채택 시, 다음과 같은 에러를 표시! fix 를 눌러주면 바로 Person 프로토콜을 따르도록 수정 됨
protocol Person {
var name: String { get }
var age: Int { get set }
func sleep(time: Int) -> String
mutating func work(month: Int)
}
struct Worker: Person {
let name: String
var age: Int
var salary: Int
var money: Int
func sleep(time: Int) -> String {
"\(time)시간을 잤네..."
}
mutating func work(month: Int) {
self.money += (self.salary * month)
}
}
var worker = Worker(name: "Lee", age: 31, salary: 400, money: 1000)
print(worker.sleep(time: 9)) // 9시간을 잤네...
worker.work(month: 8)
print(worker.money) // 4200
Swift
복사
위 예시와 동일한 Person 프로토콜을 class 에서 받으면,
class 이기 때문에, init 혹은 name, age 의 값을 설정하라는 에러가 난다.
struct 와 다른 점은, work 메서드에서 mutating 이 빠져있다는 것.
→ class 는 참조 타입으로 mutating 사용을 하지 않는다.
아까 위에서, var 로 설정해야한다고 했는데, class 와 struct 의 name 은 let 으로 받았다.
프로토콜을 채택하는 곳에서, 저장 프로퍼티 or 연산 프로퍼티 를 결정하게 되는데,
연산 프로퍼티는 애초에 let 이 될 수 없다. ( 값이 변화할 것이니까! )
그래서 애초에 규약을 let 으로 만들 수 없다고 생각한다.
그럼 채택할 때, 다 let 으로 할 수 있는거야?
→ Nope, 프로토콜에서 { get } 으로 했을 때, 가능하다.
→ And, { get } 으로 설정해도 채택하는 곳에서, getter 만 가지고 있는 연산 프로퍼티로 사용 가능
⇒ 따라서, var name: String { get } 으로 설정한 프로퍼티는, 프로토콜이 채택된 곳에서 let 으로 사용이 가능했다.
protocol 도 일급 객체 ?
Swift 에서 클로저 / 함수 외에도, 프로토콜 또한 일급 객체 이다..
→ protocol 이 독립적인 하나의 타입으로 사용될 수 있기 때문 !
protocol Person {
var name: String { get }
}
Swift
복사
•
변수 / 상수 타입으로 선언
⇒ 근데, 프로토콜의 인스턴스를 생성 할 수가 없다..
⇒ 타입 캐스팅 !!
Person 으로 타입 캐스팅이 되어있기 때문에,
student 의 school 에는 접근이 되지 않음
Person 프로토콜을 채택하지 않은 Homeless 구조체는
타입캐스팅을 위해서 as! 를 사용하라고 한다.
as 나 as? 는 에러
→ 결국 에러
•
인자 값, 반환 값 으로 사용
프토토콜을 채택한, struct, class, enum 을 프로토콜 타입으로 캐스팅 해서 사용한다 !
doAssignment 함수에 Person 프로토콜 타입을 인자 값, 반환 값 으로 되어있다.
student 를 인자로 넣었지만,
함수에서 리턴으로 인스턴스 값을 변경해서 student2 에 반환해주었다.
출력 시, 값이 잘 변경된 것을 볼 수 있다.
→ student 의 값은 어떻게 되었나?
→ 당연히 변경되지 않는다. struct 는 값 타입이니깐!
그리고, student2 또한 Person 프로토콜 타입으로 캐스팅 되었기 때문에 school 에 접근은 불가하다.
Protocol - extension
프로토콜에서는 메서드의 선언부만을 정의하는데…!
sleep 함수처럼, 반환 값이나.. 내부 로직이 똑같은 경우는 이렇게 반복되는 코드가 나오는데..
→ 그럴때 나올 수 있는게 extension
같은 protocol 을 채택한, 여러 구조체중에 한두개만 함수 내부 로직이 다를 것 같다..?
→ 그럼 해당 구조체 내부에 오버로딩이나, override 표시 없이 그냥 재정의 해주면 된다.
추가로 extension 으로, 프로퍼티에도 기본값을 줄 수 있다!
(연산 프로퍼티만 가능)
Generic
Generic
지정된 타입에 의존하지 않기 위해 사용 !
함수나, 타입에 사용 할 수 있음 !
제네릭 함수 예시
•
기존 코드
각 파라미터의 타입을 지정하기 때문에, 같은 로직의 함수를 여러개 만들어서 각각 사용
•
Generic 사용
이처럼 제네릭을 사용하여, 오버로딩을 하지 않고 불필요한 코드 중복을 제거할 수 있음 !
타입 파라미터는 T 로 정해져 있는 것은 아니지만,
단일 대문자를 추천한다고 한다 ( 가독성을 위해 ! )
제네릭 타입 예시
•
기존 코드
•
Generic 사용
제네릭 확장 - extension
위와 같은 Stack 구조체에서, top 아이템을 가져오는 프로퍼티를 확장해서 만들기
Stack 의 제네릭 타입이 T 로 선언이 되어있기 때문에,
확장한 프로퍼티 ( 함수도! ) 에서 T 타입을 사용해야 한다 !
→ Optional 하게 표시한 이유는 items.last 가 nil 일 수 있기 때문 -
제네릭 타입 제약
평소에 사용되는 여러 표현과 자료구조는 대부분 일정한 프로토콜을 준수하고 있음
→ Dictionary 는 (key, value) 를 갖고 있는데, key 는 Hashable 준수해야 함
→ Hashable 을 들어가보니, Equatable 을 채택하고 있다
Equatable 은 가끔씩 마주치는데,
우리가 제네릭 타입을 사용할때,
연산자 == 를 사용하려면 값을 비교할 수 있어야 한다는 에러가 나오기도 한다.
이때, <T: Equatable> 처럼 제약을 주기도 한다.
Equatable 처럼 프로토콜으로 제약을 줄 수 있지만,
클래스 제약을 제네릭 타입에 주는 것도 가능하다.
코드를 작성하면서 마주치게 되는 에러들이 많은데,
그럴때 그냥 fix 하고 넘기기보다, 내부에 선언을 한번 알아보는 것도 좋겠다 !
Associatedtype - 프로토콜에서의 제네릭
associatedtype
프로토콜을 제네릭하게 사용하는 방법
프로토콜 에서 사용되는 플레이스 홀더 타입
•
사용 방법
protocol Container {
associatedtype Item
var element: Item { get }
mutating func append(_ item: Item)
}
/*
protocol Container {
associatedtype T
var element: T { get }
mutating func append(_ item: T)
}
*/
Swift
복사
→ 프로토콜 사용 타입에서, typealias 로 지정
struct Bag: Container {
typealias Item = String
var element: String
mutating func append(_ item: String) { }
}
Swift
복사
// typealias 생략 -> 타입 추론 도 가능
struct Bag: Container {
var element: String
mutating func append(_ item: String) { }
}
Swift
복사
→ 프로토콜 사용 타입에서, associatedtype 이 사용된 자리에 타입 지정
struct Box<T>: Container {
var element: T
mutating func append(_ item: T){ }
}
Swift
복사
[ 추가 ]
앞서 설명한듯이 프로토콜은 assoiciatedtype 을 사용하여 제네릭을 사용한다.
위 오류 예시처럼,
프로토콜 자체에 제네릭을 선언하지도 않았고 assoiciatedtype 도 사용하지 않았지만,
아래 사진처럼, 프로토콜에 메서드를 선언할때, 메서드에 제네릭을 선언 할 수는 있음 !
associatedtype + 제약
•
타입 제약
struct Box<T: Equatable>: Container {
var element: T
mutating func append(_ item: T){ }
}
Swift
복사
•
where 절 사용
struct Box<T>: Container where T: Equatable {
var element: T
mutating func append(_ item: T){ }
}
Swift
복사
•
where 절에서는 associatedtype과 관련된 제약등 더 다양한 제약을 추가할 수 있음
protocol Container {
associatedtype Item
mutating func append(_ item: Item)
var count: Int { get }
subscript(i: Int) -> Item { get }
}
func allItemsMatch<C1: Container, C2: Container>
(_ someContainer: C1, _ anotherContainer: C2) -> Bool
where C1.Item == C2.Item, C1.Item: Equatable {
if someContainer.count != anotherContainer.count {
return false
}
for i in 0..<someContainer.count {
if someContainer[i] != anotherContainer[i] {
return false
}
}
return true
}
Swift
복사
C1 과 C2 컨테이너는 동일한 타입의 컨테이너일 필요는 없다.
하지만, 동일한 타입의 아이템들을 가지고 있어야 함!
→ 타입 제약과 제네릭 where절의 조합으로 표현
associatedtype을 사용한 프로토콜은 타입으로 사용이 불가 ?
protocol Container {
associatedtype Item
mutating func append(_ item: Item)
var count: Int { get }
subscript(i: Int) -> Item { get }
}
struct C: Container {
var arr = [String]()
typealias Item = String
var count: Int { arr.count }
mutating func append(_ item: String) {
arr.append(item)
}
subscript(i: Int) -> String {
arr[i]
}
}
Swift
복사
→ 이와 같이 나와서 any 를 붙여 보았는데….
안되는 것 같다..
Swift 5.7 에서 아래 예시를 봤는데 저 any 부분과, 어떻게 다르게 사용하는 것인지 알고 싶다..
protocol IntCollection: RangeReplaceableCollection where Self.Element == Int {}
extension Array : IntCollection where Element == Int {}
var array: any IntCollection = [3, 1, 4, 1, 5]
array.append(9)
print(array) // [3, 1, 4, 1, 5, 9]
Swift
복사
⇒ 이것저것 찾아보다보니, some & any 에 대해 학습을 해야하는걸 알았다..
Existential any …..
Opaque some ….
🥹
조금은 이해했는데, 아직은….! ㅎㅎ 수목금 중에 공유해보자..