[Swift] 프로토콜 [1]

임승섭·2023년 6월 25일
0

Swift

목록 보기
12/35

프로토콜

기본

클래스 & 상속의 단점

  • 다중상속이 불가능하다. 즉, 하나의 클래스만 상속 가능
  • 기본적으로 상위 클래스의 메모리 구조를 따라감. 즉, 필요하지 않은 속성/메서드도 상속됨
  • 상속은 클래스(레퍼런스 타입)에서만 가능

프로토콜

  • 쉽게 자격증, 리모콘
  • 클래스와 상속의 단점을 보완한다
  • 구체적인 구현을 하지 않고 (메서드 선언만 한다),
    구체적인 구현은 자격증을 채택한 곳에서 (반드시) 한다
  • 프로토콜 자체를 타입으로 사용 가능하다
    • 예를 들어, 함수의 매개변수 타입을 프로토콜로 써주면
      "해당 프로토콜을 채택한 class 또는 struct 타입의 인스턴스"를 인자로 넣어줄 수 있다
/*(1)*/
protocol SomeProtocol { 
	func playPiano()	// 구체적인 구현하지 않는다.
}

struct MyStruct: SomeProtocol {
	func playPiano() {
    	// 구체적인 구현
    ]
}

class MyClass: SomeProtocol {
	func playPiano() {
    	// 구체적인 구현
    }
}



/*(2)*/
protocol CanFly {
	func fly()
}

class Bird1 {
	var isFemale = true
    
    func layEgg() {
    	if isFemale {
        	print("새가 알을 낳는다.")
        }
    }
}

class Eagle1: Bird1, CanFly {	// 상속할 클래스를 먼저 쓰고, 뒤에 프로토콜들을 쓴다
	// isFemale	(저장속성)
    // layEgg	(메서드)
    
    func fly() {	// 구체적인 구현
    	print("독수리가 하늘로 날아올라 간다")
    }
    
    func soar() {
    	print("공중으로 활공한다.")
    }
}

class Penguin1: Bird1 {
	// isFemale
    // layEgg()
    
    func swim() {
    	print("물 속을 헤엄칠 수 있다.")
    }
}

struct Airplane1: CanFly {
	func fly() {
    	print("비행기가 날아간다")
    }
}

struct FlyingMuseam1 {
	// (중요) 프로토콜을 타입으로 사용 
    // -> CanFly 자격증을 딴 class, struct를 인수로 사용할 수 있다.
	func flyingDemo(flyingObject: CanFly) {		
    	flyingObject.fly()
    }
}

문법

  • 정의 -> 채택 -> 구현
  • 여러 개의 프로토콜 채택 가능
  • 요구사항
    • 속성 요구사항
    • 메서드 요구사항 (메서드, 생성자, 서브스크립트)
/*(1). 정의*/
protocol MyProtocol {
	func doSomething() -> Int
}

class FamilyClass { }


/*(2). 채택*/
class MyClass: FamilyFlass, MyProtocol {
	func doSomething() -> Int {
    	/*(3). 구현*/
    	return 7
    }
}

struct MyStruct: MyProtocol {
    func doSomething() -> Int {
    	return 7
    }
}


enum MyEnum: MyProtocol {
    func doSomething() -> Int {
    	return 7
    }
}

속성 요구사항

  • 속성의 뜻에서 var로 선언해야 한다 (let x)
  • get, set 키워드를 통해 읽기/쓰기 여부 설정 (최소한의 요구사항)
  • 저장 속성/계산 속성 모두 구현 가능
    • 프로토콜 생긴거만 보고는 저장속성인지 계산속성인지 구분할 수 없다
protocol RemoteMouse {

	// 생긴걸 보면, 구체적인 구현 사항이 없다. 당연히 기본값도 넣어줄 수 없다
    // "최소한의 요구사항" -> 최소한 이 정도는 구현해야 해
	var id: String { get }				// ===> (채택하는 곳에서 가능한거) let 저장속성 / var 저장속성 / 읽기 계산속성 / 읽기,쓰기 계산속성
    var name: String { get set }		// ===> var 저장속성 / 읽기,쓰기 계산속성
    static var type: String { get set } // ===> 타입 저장 속성 (static)   ===> 타입 계산 속성 (class)

struct TV: RemoteMouse {
	var id: String = "456"				// let으로 선언도 가능
    var name: String = "삼성티비"
    static var type: String = "리모콘"

메서드 요구사항

  • 메서드의 헤드부분(인풋/아웃풋)만 요구사항 (중괄호는 필요 없음)
  • 타입 메서드로 제한하려면, static 키워드
    • 채택하는 쪽에서 static / class 키워드 모두 사용 가능
  • mutating : struct에서 저장속성 변경하는 경우, struct도 채택 가능하도록 허락한다
/*(1). 정의*/
protocol RandomNumber {
	static func reset()
    func random() -> Int
	mutating func doSomething()		// 구조체에서는 원래 저장속성 변경이 안되는데, 변경을 허락해준다는 키워드
    								// 클래스에서는 그냥 쓰면 된다
}


/*(2). 채택, (3). 구현*/
class Number: RandomNumber {

	class func reset() {		// static으로 해도 되고 class로 해도 된다
    	print("다시 세팅")
    }
    
    func random() -> Int {
    	return Int.random(in: 1...100)
    }
}



// 1) 정의
protocol Togglable {
	// mutating 키워드는 메서드 내의 속성 변경의 의미를 가진다 (당연히 클래스에서도 사용 가능하다)
    mutating func toggle()        
}

// 2) 채택 / 3) 구현
enum OnOffSwitch: Togglable {
    case on
    case off
    
    mutating func toggle() {		// 구조체와 열거형 모두 "값 타입"이기 때문에 mutating 키워드가 필요하다
        switch self {   // .on   .off
        case .off:
            self = .on
        case .on:
            self = .off
        }
    }
}

var s = OnOffSwitch.off
s.toggle()		// on
s.toggle()		// off
// 껐다 켰다 하는 메서드를 만들었다


class BigSwitch: Togglable {
    var isOn = false	// 저장 속성 하나 만들어 놓고
    
    func toggle() {      // mutating 키워드 필요없음 (클래스 이기 때문)
        isOn = isOn ? false : true	// 삼항연산자
    }
}

메서드 요구사항 - 생성자 요구사항

  • 클래스는 상속을 고려해야 하기 때문에, 반드시 생성자 앞에 required 붙여줘야 한다
    -> 필수 생성자로 구현해야 한다!!
    • 그럼 그 클래스를 상속하는 하위 클래스는 반드시 해당 생성자를 구현해줘야 한다.
      • 이 때, 하위 클래스에서 다른 생성자를 생성하지 않았으면
        필수 생성자는 자동 상속된다
  • 구조체는 상속이 없기 때문에, required 키워드 필요 없다
  • final을 붙여서 상속을 막으면 required 키워드 필요 없다
  • 클래스에서 반드시 지정생성자로 구현할 필요 없고, 편의생성자로 구현도 가능하다
    • 프로토콜은 단순히 "최소한의 요구사항"이기 때문에
      내 맘대로 편의 생성자로 구현해도 아무 문제 없다
protocol SomeProtocol {     // 생성자를 요구사항으로 지정 가능
    init(num: Int)
}

// 예제 - 1 ======================
class SomeClass: SomeProtocol {
    required init(num: Int) {
        // 실제 구현
    }
    
    // 편의생성자로 구현 가능 -> 지정생성자 호출해야겠지
    required convenience init() {}
}

class SomeSubClass: SomeClass {
    // 하위 클래스에서 생성자 구현 안하면 필수 생성자는 자동 상속
    // required init(num: Int)
    
    // 만약 생성자를 구현한다면,
    init() {
    	//	
    }
    required init(num: Int) {	// 필수 생성자 구현해줘야 한다.
    	//
    }
    
}

// 예제 - 2 ======================

protocol AProtocol {
    init()
}

class ASuperClass {
    init() {
        // 생성자의 내용 구현
    }
}

class ASubClass: ASuperClass, AProtocol {
    // AProtocol을 채택함으로 "required" 키워드 필요하고, 상속으로 인한 "override(재정의)" 재정의 키워드도 필요
    // 클래스 상속으로 인한 override, 프로토콜로 인한 required
    required override init() {
        // 생성자의 내용 구현
    }
}

실패가능 생성자

  • init?() 요구사항 -> init() (실패불가능)/ init?() (실패가능)/ init!() (강제로 벗기기)로 구현 가능
  • init() 요구사항 -> init?() 로 구현 불가능

    실패 가능이 더 크고, 그 속에 실패불가능이 포함된 구조

protocol AProto {
	/*(1). 실패가능으로 정의*/
    init?(num: Int)     
    
    /*(2). 실패 불가능으로 정의*/
    init(num: Int)
}

struct AStruct: AProto {  // Failable/Non-failable 모두 요구사항을 충족시킴
    
    /*(1)*/
    init?(num: Int) {}	// 당연히 실패 가능으로 정의 가능
    
    init(num: Int)  {}		// 실패 불가능으로 정의 가능
    
    init!(num: Int)  {}     // 이것도 괜찮음
    
    /*(2)*/
    //init?(num: Int) {}	// 범위가 더 넓기 때문에 얘는 불가능
    
    init(num: Int)  {}		// 실패 불가능 가능
    
    //init!(num: Int)  {}     // 거의 비슷한 범위이기 때문에 이것도 괜찮음 -> 실무에서 거의 이럴 일 없음.
}

// 클래스에서 채택 - required 키워드
class AClass: AProto {
    required init(num: Int) {}
}

메서드 요구사항 - 서브스크립트 요구사항

  • get, set 키워드를 통해 읽기/쓰기 여부 설정
  • get -> 읽기, 읽기/쓰기 모두 구현 가능 (최소한의 요구사항)
  • get/set 키워드 -> 반드시 읽기/쓰기 모두 구현
protocol DataList {
	subscript(idx: Int) -> Int { get}
}

struct DataStructure: DataList {
	
    /*1*/
    subscript(idx: Int) -> Int {    // 읽기 전용 서브스크립트로 구현한다면
        get {		// get만 있으니까 생략 가능
            return 0
        }
    }
    
    /*2*/
    subscript(idx: Int) -> Int {    // (최소한만 따르면 됨)
        get {
            return 0
        }
        set {                 // 구현은 선택
            // 상세구현 생략
        }
    }
}

0개의 댓글