프로토콜?
특정 기능, 역할을 하기 위한 프로퍼티, 메소드, 기타 요구사항 등의 청사진
프로토콜 준수, 채택
- 구조체, 클래스, 열거형은 프로토콜을 채택하여 특정 기능, 역할을 하기위한 프로토콜의 요구 사항을 실제로 구현할 수 있다.
- "청사진" 이라는 의미에 걸맞게 정의를 하고 설계도만 제공할 뿐 스스로 기능을 구현하지는 않는다.
- 구현은 프토토콜을 채택한 구조체, 클래스, 열거형에서 구현한다.
프로토콜 구문
protocol 프로토콜명 {
//기능 정의
}
- 프로토콜을 채택하려면 사용자 정의 타입 뒤에 콜론을 붙이고 프로토콜명을 위치시킨다.
- 프로토콜이 여러개일 경우, 콤마로 나열할 수 있다.
- 클래스의 경우, 상속받은 superclass가 있다면 나열된 목록의 제일 앞에 위치시켜준다.
struct 구조체명: 프로토콜명1, 프로토콜명2 { }
class 클래스명: 프로토콜명1, 프로토콜명2 { }
// 상속받은 경우
class 클래스명: 부모클래스명, 프로토콜명1, 프로토콜명2 { }
Property Requirements
- 프로토콜에서는 프로퍼티가 저장 프로퍼티인지, 연산 프로퍼티인지 구분 짓지않는다.
- 프로퍼티의 이름과 타입을 지정해주고,gettable, settable를 명시한다.
- 프로토콜 내부에 정의한 프로토콜은 항상 var이다.
protocol Person {
var height: Int { get set }
var weight: Int { get }
var name: String { get }
static var age: Int { get set }
}
struct Jiseong: Person {
var _height = 100
var height: Int {
get {
return _height
}
set {
self._height = newValue
}
}
var _weight = 90
var weight: Int { // 프로토콜 프로퍼티에선 get만 명시되어 있지만, 원한다면 set도 가능하다.
get {
return _weight
}
set {
self._weight = newValue
}
}
let name: String = "Jiseong"
static var age: Int = 54
}
- 프로퍼티는 저장 프로퍼티나, 연산 프로퍼티 둘중 하나로 구현할 수 있다. -> 상관없다는 뜻
- 프로토콜 내부의 프로퍼티가 get만 명시되어 있는 경우, get만 되는 것이 아니다. "get(읽기)은 꼭 해야하고, set(쓰기)은 너가 추가하고싶음 추가해라" 라는 의미를 갖고있다.
- get만 명시되어 있는 경우엔, 채택한 사용자 정의 타입에서 변수가 아닌 상수로 정의할 수 있지만, get, set 둘다 명시되어 있는 프로퍼티의 경우 상수로 정의할 수 없고, get, set 모두 구현을 해야한다.
Method Requirements
- 프로토콜에서는 인스턴스 메소드와 타입메소드를 정의할 수 있다.
- 메소드 역시 메소드명과, 반환값만 지정할 수 있고, 구현부는 작성할 수 없다.
- 메소드 파라미터의 기본값도 지정할 수 없다. 파라미터의 타입 지정은 가능하다.
- mutating 키워드를 사용하여 인스턴스 프로퍼티를 변경할 수 있다는 것을 표현할 수 있다.
- 물론 mutating 키워드는 값타입에서만 사용할 수 있다.
protocol Person {
static func run()
func buy(what: String) -> String
mutating func eat()
}
class Jiseong: Person {
static func run() {
print("running")
}
func buy(what: String) -> String {
return "\(what) 구매 완료 "
}
func eat() {
print("yummy")
}
}
If you mark a protocol instance method requirement as mutating, you don’t need to write the mutating keyword when writing an implementation of that method for a class. The mutating keyword is only used by structures and enumerations.
- 프로토콜에선 mutating 키워드를 사용하여 인스턴스 프로퍼티를 변경할 수"도" 있는 메소드로 정의했으므로 클래스(참조타입)에서 해당 메소드 구현을 작성할땐 mutating을 작성해줄 필요가 없다.
Initializer Requirements
- 프로토콜 내부에 이니셜라이저를 정의할 수 있다.
protocol Person {
var sex: String { get }
init(sex: String)
}
class Jiseong: Person {
var sex: String
required init(sex: String) {
self.sex = sex
}
}
let test = Jiseong(sex: "man")
print(test.sex)
- 특정 이니셜라이저를 프토토콜에 정의하였기 때문에 해당 프로토콜을 채택하는 사용자 정의 타입은 이니셜라이저를 꼭 구현해야한다. 그렇기에 required(필수) 키워드가 붙는다.
- 실패가능한 이니셜라이저도 정의할 수 있다.
- 특정 프로토콜의 이니셜라이저를 구현하고, 상속받은 SuperClass의 이니셜라이저도 구현해야 하는 경우, SubClass의 이니셜라이저에 required와 override 키워드를 붙여줘야 한다.
protocol SomeProtocol {
init()
}
class SomeSuperClass {
init() {
// initializer implementation goes here
}
}
class SomeSubClass: SomeSuperClass, SomeProtocol {
// "required" from SomeProtocol conformance; "override" from SomeSuperClass
required override init() {
// initializer implementation goes here
}
}
Protocols as Types
프로토콜은 타입이므로, 아래와 같이 타입 사용이 허용되는 곳에 프로토콜을 타입으로서 사용할 수 있다.
- 함수, 메소드, 이니셜라이저의 파라미터 타입 혹은 리턴 타입
- 상수, 변수, 프로퍼티의 타입
- 배열, 딕셔너리의 원소타입
제네릭의 타입에 어떤 프로토콜을 준수하는 타입만 받는 등의 제약을 거는데 사용할 수도 있다.
Optional Protocol Requirements
본래 프로토콜에 정의한 메소드, 프로퍼티는 프로토콜을 채택한 사용자 정의타입에서 필수적으로 구현해야한다.
하나라도 빼먹는다면 아래와 같은 컴파일 오류를 야기한다.
하지만 @objc를 사용하면 필수 구현이 아닌 선택적으로 구현할 수 있게끔 할 수 있다.
- @objc 키워드를 프로토콜 앞에 붙이고, 메소드, 프로퍼티에는 @objc optional을 붙인다.
- @objc 키워드를 붙인 프로토콜은 클래스만이 채택할 수 있다. 그렇기 때문에 @objc 키워드를 붙인 프로토콜엔 mutating 키워드를 사용할 수 없다.
또한 프로토콜 내부 프토퍼티나, 메소드 파라미터나 반환값의 타입으로 사용자 정의타입을 사용 할 수 있는데, @objc키워드를 붙인 프로토콜은 사용자 정의 타입을 사용하지 못한다.
이를 해결하기 위해선 사용하고싶은 사용자 정의 타입에 NSObject를 상속받으면 되는데, 이는 클래스에만 상속이 가능하다는 단점이 있다.
@objc 키워드를 붙인 예시
@objc protocol Person {
var sex: String { get }
init(sex: String)
static func run()
@objc optional func buy(what: String) -> String
@objc optional func eat()
}
class Jiseong: Person {
var sex: String
required init(sex: String) {
self.sex = sex
}
static func run() {
print("running")
}
func buy(what: String) -> String {
return "\(what) 구매 완료 "
}
}
@objc optional키워드가 붙은 eat 메소드는 선택적 구현을 할 수 있기에 구현해주지않아도 오류가 발생하지않는다.
또 하나의 방법이 있다.
Extension을 활용한 방법이다. (기본 구현)
@objc는 사용자 정의타입도 사용하지 못하고, 클래스에만 채택받을 수 있다는 단점이 있었는데, 이를 해결할 수 있다.
struct type {
var name: String
}
protocol Person {
var name: type { get }
var sex: String { get }
init(sex: String)
static func run()
func buy(what: String) -> String
func eat()
}
extension Person {
func eat() { print("yummy") }
}
class Jiseong: Person {
var sex: String
var name: type = type(name: "Jiseong")
required init(sex: String) {
self.sex = sex
}
static func run() {
print("running")
}
func buy(what: String) -> String {
return "\(what) 구매 완료 "
}
}
let jiseong = Jiseong(sex: "man")
print(jiseong.name) // type(name: "Jiseong")
print(jiseong.buy(what: "drink")) // drink 구매 완료
jiseong.eat() // yummy
사용자 정의 타입을 사용할 수 있게 됐다.
eat은 Extension에서 이미 구현이 되었으므로 프로토콜을 채택한 타입에서 구현하지 않아도 된다.
extension Person {
func eat() {}
}
eat을 미리 구현하고싶지않고, 선택적 구현으로만 활용하고싶다면 위 코드처럼 Extension에서 빈 구현으로 만들어주면 된다.
'iOS > Swift' 카테고리의 다른 글
Swift) 순환 참조 , strong, weak (0) | 2022.02.13 |
---|---|
Swift) ARC (0) | 2022.02.13 |
Swift) removeAll() vs [] (0) | 2022.02.13 |
Swift) map (0) | 2022.02.12 |
Swift) DoubleStack - Queue (0) | 2022.02.12 |
댓글