궁금한 내용을 검색해보세요!
이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다.
서근 개발노트
티스토리에 팔로잉
SWIFT/Grammar

Swift : 기초문법 [프로토콜#4 - 익스텐션, 컬렉션 타입, 상속]

서근
QUOTE THE DAY

-
Written by SeogunSEOGUN

반응형

본 게시글은 yagom님과Zedd0202님의 게시글을 참고하여 작성되었습니다.

 

익스텐션 채택 및 준수

  • 기존 타입의 소스코드에 접근할 수 없는 경우에도 마찬가지로, 기존 프로토콜 익스텐션(Extension) 하여 새 프로토콜을 채택하고 준수할 수 있다.
  • 익스텐션은 기존 타입에 새로운 프로퍼티 및 메서드, 하위 스크립트를 추가할 수 있기 때문에 프로토콜에서 요구할 수 있는 모든 요구사항을 추가할 수 있다.
 protocol Person {
    var name: String { get }
}

위 프로토콜은 읽기전용 프로퍼티만 요구하고 있다. 그렇기에 모든 종류의 프로퍼티를 만족시킬 수 있다. 이것을 Extension을 사용하면 아래와 같다.

protocol Person {
    var name: String { get }
}

extension String: Person {

}

String 타입을 익스텐션 시켜주고, Person을 채택했다. 이제 준수를 해야 할 차례이다.

 

다시 한번 말하지만 읽기 전용(getter) 이므로 모든 종류의 프로퍼티에서 요구사항을 충족시킬 수 있다.

extension String: Person {
    var name: String {
       return "안녕하세요 \(self) 입니다!" // self = String을 의미
    }
}

이렇게 연산프로퍼티로 정의해 줬는데, 주의할 점은 extension 안에는 저장 프로퍼티로 만들면 안 된다!

extension String: Person {
    var name: String // 저장 프로퍼티 이므로 오류!
}

이제 이 익스텐션은 다음과 같이 호출 가능하다.

protocol Person {
    var name: String { get }
}

extension String: Person {
    var name: String {
        return "안녕하세요 \(self) 입니다!"
    }
}

var myName: String = "서근"
print(myName.name) //안녕하세요 서근 입니다!

myNameString 타입으로 extenstion의 요구를 충족해서 성공적으로 호출 가능한 것!

 

또 다른 예제를 들어보자면..

protocol calculator {
    var returnDouble: Double { get }
    mutating func add(number: Int)
}

extension Int: calculator {
    var returnDouble: Double {
        //Double타입을 'self'즉, Int 타입으로 반환
        return Double(self)
    }
    mutating func add(number: Int) {
        self += number
    }
}

var number = 25
number.add(number: 20)  //number은 25이기 때문에 25 + 20 = 45
number.returnDouble //45

calculator 프로토콜에 returnDouble 읽기 전용 프로퍼티와, mutating 함수가 존재한다. 

 

그리고 Int 타입을 익스텐션 후, calculator를 채택했다. 이제 준수 고고!

 

returnDouble을 연산 타입으로 정의해줘야 하기에 return Double(self) 즉, Double 타입을 익스텐션 타입인 Int로 리턴 해준다는 의미!

 

그리고 Int는 값 타입 이기 때문에 값이 변경될 때는 반드시 mutating키워드를 사용해야 함! 그래서 이 add 메서드는 self에 파라미터로 전달받은 값을 더하는 코드가 된다.

 

이것을 호출하면 var number = 25로써 number.returnDouble(25)가 됐고, add 파라미터로 (20)을 줬으니, 25 + 20이 됨! 결과적으로 number.returnDouble(45)가 된다!

프로토콜 타입 컬렉션

이 전 게시글에서 타입으로서의 프로토콜을 봤는데, 컬렉션(배열, 세트, 딕셔너리, 튜플)에 저장될 타입으로 프로토콜을 사용할 수 있다.

var someArr = [calculator]() //calculator protocol

//extension의 Int가 calculator을 채택하고 준수했기 때문에,
//Int(append(0)) 추가 가능
someArr.append(0) //[0]
someArr.append(1) //[0, 1]

이렇게 배열 또는 딕셔너리를 만들고, 아래와 같이 배열 안에 추가할 수 있다.

 

그리고, 컬렉션에 저장될 수 있다는 의미는 for문을 보면 이해할 수 있다.

var someArr = [calculator]()
someArr.append(0) //[0]
someArr.append(1) //[0, 1]
someArr.append(5)

for index in someArr {
    print(index.returnDouble)
}

//0.0
//1.0
//5.0

프로토콜의 상속과 클래스 전용 프로토콜(Inheritance)

프로토콜은 하나 이상의 프로토콜을 상속받아 기존 프로토콜의 요구사항보다 더 많은 요구사항을 추가할 수 있다.

 

프로토콜 상속 문법은 클래스의 상속 문법과 유사하지만, 여러 개의 상속된 프로토콜을 쉼표로 구분하여 나열하는 옵션이 존재한다.

protocol calculator {
    var  returnDouble: Double { get }
    mutating func add(number: Int)
}

protocol InheritingProtocol: calculator {
    mutating func remove(number: Int)
}

struct Seogun: InheritingProtocol {
    var myDouble: Double = 20.0
    var returnDouble: Double {
        return myDouble
    }
    mutating func add(number: Int) {
        myDouble += Double(number)
    }
    mutating func remove(number: Int) {
        myDouble -= Double(number)
    }
}

var seons: Seogun = Seogun()
seons.add(number: 10) // 20 + 10 = 30
seons.remove(number: 12) // 30 - 12 = 18

calculator 프로토콜 외에 InheritingProtocolcalculator를 상속받았기 때문에 calculator의 요구사항을 구현해줄 필요가 없다.

 

그리고 상속된 요구사항에 요구사항을 추가할 수 있으니까!  mutating func remove(number: Int) 요구사항을 추가해줬다.

 

그다음으론 Seogun이라는 구조체를 만들어 InheritingProtocol을 채택해서 위 두 개의 프로토콜 요구사항을 모두 구현해준 것이다. 

 

또 다른 간단한 예제를 보자!

protocol Readable {
    func read()
}

protocol Writeable {
    func write()
}

protocol ReadSpeakable: Readable {
    func speak()
}

protocol ReadWriteSpeakable: Readable, Writeable {
    func speak() 
}

ReadSpeakable 프로토콜은 Readable 프로토콜을 상속받았고, ReadWriteSpeakable 프로토콜은 ReadableWriteable 프로토콜을 상속받았다. 

class SomeClass: ReadWriteSpeakable {
    func read() {
       print("Read")
    }
    func write() {
       print("Write")
    }
    func speak() {
       print("Speak")
    }
}

SomeClassReadWriteSpeakable을 채택 했기때문에 세 프로토콜이 요구하는 read(), write(), speak() 메서드를 모두 구현해야만 한다.

 

만약 SomeClassReadSpeakable을 채택했다면? Readable 프로토콜의 read() 메서드와 ReadSpeakable 프로토콜의 speak() 메서드를 구현해주면 된다!

Class Only 프로토콜

프로토콜의 상속 리스트에 class 키워드를 추가해서 프로토콜이 클래스 타입에만 채택될 수 있도록 제한할 수도 있다. 클래스 전용 프로토콜로 제한하기 위해서는 프로토콜의 상속 리스트의 맨 처음에 class 키워드를 위치시키면 된다.

                         //class 타입에만 채택 가능
protocol ReadWriteSpeakable: class, Readable, Writeable {
    //추가 요구사항
}

class SomeClass: ReadWriteSpeakable {
    func read() {}
    func write() {}
    func speak() {}
}

// 오류! ReadWriteSpeakable 프로토콜은 클래스 타입에서만 채택 가능!
struct SomeStruct: ReadWriteSpeakable {
    func read() {}
    func write() {}
    func speak() {}
}

Extension 프로토콜

프로토콜을 사용하면 어떤 메서드가 있어야 하는지 설명할 수 있지만 내부에 코드를 제공하지는 않는다.

 

Extension을 사용하면 메서드 내부에 코드를 제공할 수 있지만, 하나의 데이터 형식에만 영향을 준다. 동시에 많은 형식에 메서드를 추가할 수 없다.

 

프로토콜 익스텐션은 이 두 가지 문제를 모두 해결한다.

 

예를 들어 다음은 일부 이름을 포함하는 배열과 집합이다.

let pythons = ["Eric", "Graham", "John", "Michael", "Terry", "Terry"]
let beatles = Set(["John", "Paul", "George", "Ringo"])

Swift의 Array와 Set는 모두 Collection이라는 프로토콜을 준수하므로, 우리는 그 프로토콜에 확장자를 작성하여 컬렉션을 깔끔하게 출력하는  summarize() 메서드를 추가할 수 있다.

extension Collection {
    func summarize() {
        print("There are \(count) of us:")

        for name in self {
            print(name)
        }
    }
}

이제 Array와 Set 모두에 이 방법이 적용되므로 아래 코드로 호출할 수 있다.

pythons.summarize()
beatles.summarize()

/*
There are 6 of us:
Eric
Graham
John
Michael
Terry
Terry
There are 4 of us:
John
Ringo
Paul
George
*/

 

 

다음 게시글은 프로토콜 마지막 포스팅으로 찾아뵙겠습니다 :)

 

 

읽어주셔서 감사합니다 🤟

 

 

 

 


잘못된 내용이 있으면 언제든 피드백 부탁드립니다.


서근


위처럼 이미지 와 함께 댓글을 작성할 수 있습니다.