[iOS] 5주차 정리

Zoe·2022년 7월 12일
0

iOS

목록 보기
6/39

5주차 정리


✅ 프로토콜

1️⃣ 개념

  • 규약. 협약. 자격증. 리모콘
  • 왜 필요할까?
  • 클래스와 상속의 단점 : 다중상속 불가능. 하나의 클래스만 상속가능. 상위 클래스의 메모리구조를 따라갈 수 밖에 없음. 필요하지 않은 속성/메서드도 상속... 그리고 클래스에서만(레퍼런스 타입) 상속 가능.
  • 이런 단점들 때문에 프로토콜이 도입됨.
  • 자격증의 개념 : 누구나 딸 수 있음. 다중 채택도 가능.

🌟 상속 구조의 단점

// 상속 구조의 단점
// 예제

class Bird {    
    var isFemale = true    
    func layEgg() {
        if isFemale {
            print("새가 알을 낳는다.")
        }
    }    
    func fly() {
        print("새가 하늘로 날아간다.")
    }
}

class Eagle: Bird {    
    // isFamale
    // layEgg()
    // fly()    
    func soar() {
        print("공중으로 치솟아 난다.")
    }
}
class Penguin: Bird {   
    // isFamale
    // layEgg()    
    // fly()       // 상속 구조에서는 펭귄이 어쩔 수 없이 날개됨 ⭐️
    
    func swim() {
        print("헤엄친다.")
    }
}

// struct가 될 수도 없고(클래스로만 구현가능), 무조건 Bird를 상속해야만 함
class Airplane: Bird {    
    // isFamale
    // layEgg()         // 상속 구조에서는 비행기가 알을 낳게됨 ⭐️
    
    override func fly() {
        print("비행기가 엔진을 사용해서 날아간다")
    }
}

// 플라잉 박물관을 만듦
struct FlyingMuseum {
    func flyingDemo(flyingObject: Bird) {
        flyingObject.fly()
    }
}
let myEagle = Eagle()
myEagle.fly()
myEagle.layEgg()
myEagle.soar()

let myPenguin = Penguin()
myPenguin.layEgg()
myPenguin.swim()
myPenguin.fly()     // 문제 ===> 펭귄이 날개 됨(무조건적인 멤버 상속의 단점)

let myPlane = Airplane()
myPlane.fly()
myPlane.layEgg()         // 문제 ===> 비행기가 알을 낳음

let museum = FlyingMuseum()     // 타입 정의 ===> 오직 Bird 클래스 밖에 안됨
museum.flyingDemo(flyingObject: myEagle)
museum.flyingDemo(flyingObject: myPenguin)
museum.flyingDemo(flyingObject: myPlane)    // Bird를 상속해야만 사용 가능

🌟 프로토콜의 도입

// "fly()"라는 기능을 따로 분리해 내기

protocol CanFly {
    func fly()      // 🌟구체적인 구현은 하지 않음 ===> 구체적인 구현은 자격증을 채택한 곳에서
    // 위의 메서드를 구현해야된다는 요구사항!
}
class Bird1 {   
    var isFemale = true
    
    func layEgg() {
        if isFemale {
            print("새가 알을 낳는다.")
        }
    }

}

class Eagle1: Bird1, CanFly {    // "CanFly" 자격증을 채택
    
    // isFemale
    // layEgg()
    
    func fly() {
        print("독수리가 하늘로 날라올라 간다.")
    }
    
    func soar() {
        print("공중으로 활공한다.")
    }
}


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

// 구조체에서 채택도 가능
struct Airplane1: CanFly {
    func fly() {
        print("비행기가 날아간다")
    }
}

// 박물관을 만듦
struct FlyingMuseum1 {
    func flyingDemo(flyingObject: CanFly) {     // 중요한 기능 ===> 프로토콜을 타입으로 인식
        flyingObject.fly()
    }
}

let myEagle1 = Eagle1()
myEagle1.fly()
myEagle1.layEgg()
myEagle1.soar()

let myPenguin1 = Penguin1()
myPenguin1.layEgg()
myPenguin1.swim()
//myPenguin1.fly()     // 더이상 펭귄이 날지 않음

let myPlane1 = Airplane1()
//myPlane1.layEgg()         // 더이상 비행기가 알을 낳지 않음
myPlane1.fly()

let museum1 = FlyingMuseum1()
museum1.flyingDemo(flyingObject: myEagle1)
//museum1.flyingDemo(flyingObject: myPenguin1)   // 더이상 "CanFly"자격증이 없는 펭귄은 날지 못함
museum1.flyingDemo(flyingObject: myPlane1)
  • 상속하는 클래스를 먼저 써야 함. 그 다음 채택할 프로토콜을 씀. 다중 상속이 안되기 때문에 첫 번째 나오는 것이 상속할 클래스임을 알 수 있음.
  • 프로토콜을 타입으로 인식. 프로토콜도 타입.

2️⃣ 속성의 요구사항 정의

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"   
    var name: String = "삼성티비"  
    static var type: String = "리모콘"
}
let myTV = TV()
myTV.id
myTV.name
TV.type

// 타입 속성 ===================================
// 1) 저장 타입 속성으로 구현

class SmartPhone: RemoteMouse {
    var id: String {
        return "777"
    }
    
    var name: String {
        get { "아이폰" }
        set { }
    }
    
    static var type: String = "리모콘"     // 타입 저장 속성은 (상속은 되지만) 재정의 원칙적 불가능
}


// 2) 계산 타입 속성으로 구현

class Ipad: RemoteMouse {
    var id: String = "777"
    
    var name: String = "아이패드"
    
    class var type: String {       // 타입 계산 속성은 재정의 가능 (class키워드 가능)
        get { "리모콘" }
        set { }
    }
}
  • 정의 -> 채택 -> 구현
  • 속성 정의 : 반드시 get set 붙임. 반드시 var로 선언. 저장 속성/계산 속성으로 모두 구현 가능
  • {get} -> 저장 속성:let/var, 계산 속성:읽기(get)/읽기쓰기(get/set)
  • {get set} -> 저장 속성:var, 계산 속성:읽기쓰기(get/set)
  • static : 저장 타입 속성에서 static키워드로만 구현 가능. 클래스에서 채택 시 static/class로 모두 구현 가능

3️⃣ 메서드의 요구사항 정의

  • 구조체에서는 저장속성을 변경하는 것이 불가능 -> mutating 필요.
  • 생성자 요구사항 : 반드시 required

4️⃣ 타입으로써의 프로토콜

  • 프로토콜은 타입이다. 일급객체로 취급
  • 변수에 할당할 수 있음
  • 함수를 호출할 때, 프로토콜을 파라미터로 전달할 수 있음
  • 함수에서 프로토콜을 반환할 수 있음
    🌟 프로토콜 타입 취급의 장점
  • 공통된 타입으로 뽑아내서 저장할 수 있음
  • 함수의 파라미터로 쓰일 수 있음
// 프로토콜의 타입 취급의 장점 - 1 ⭐️

let electronic: [Remote] = [tv, sbox]      // 프로토콜의 형식으로 담겨있음


for item in electronic {   // 켜기, 끄기 기능만 사용하니 타입캐스팅을 쓸 필요도 없음 (다만, 프로토콜에 있는 멤버만 사용가능)
    item.turnOn()
}




// 프로토콜의 타입 취급의 장점 - 2 ⭐️

func turnOnSomeElectronics(item: Remote) {
    item.turnOn()
}

turnOnSomeElectronics(item: tv)
turnOnSomeElectronics(item: sbox)

🌟is / as

// 1) is연산자 ==============================

// 특정타입이 프로토콜을 채택하고 있는지 확인
tv is Remote
sbox is Remote


// 프로토콜 타입으로 저장된 인스턴스가 더 구체적인 타입인지 확인 가능
electronic[0] is TV
electronic[1] is SetTopBox




// 2) as연산자 ==============================

// 업캐스팅(as)
let newBox = sbox as Remote
newBox.turnOn()
newBox.turnOff()



// 다운캐스팅(as?/as!)
let sbox2: SetTopBox? = electronic[1] as? SetTopBox
sbox2?.doNetflix()

//(electronic[1] as? SetTopBox)?.doNetflix()

5️⃣ 프로토콜의 확장

  • 반복되는 코드를 줄일 수 있음!
  • 중복구현을 피하기 위함!
protocol Remote {
    func turnOn()
    func turnOff()
}

extension Remote {                         // (요구사항의 메서드 우선순위 적용 - 프로토콜 메서드 테이블 만듦)
    func turnOn() { print("리모콘 켜기") }    // 1. (채택)구현시 해당 메서드 2. 기본 메서드
    func turnOff() { print("리모콘 끄기") }   // 1. (채택)구현시 해당 메서드 2. 기본 메서드
    
    func doAnotherAction() {               // (요구사항 메서드 X - 테이블 만들지 않음)
        print("리모콘 또 다른 동작")            // 타입에 따른 선택 (Direct Dispatch)
    }
}

class Ipad: Remote {
    func turnOn() { print("아이패드 켜기") }

    func doAnotherAction() { print("아이패드 다른 동작") }
}
/**=================================================
 [Class Virtual 테이블]
 - func turnOn()          { print("아이패드 켜기") }
 - func turnOff()         { print("리모콘 끄기") }
 - func doAnotherAction() { print("아이패드 다른 동작") }
====================================================**/

let ipad: Ipad = Ipad()
ipad.turnOn()           // 클래스 - V테이블
ipad.turnOff()          // 클래스 - V테이블
ipad.doAnotherAction()  // 클래스 - V테이블
// 아이패드 켜기
// 리모콘 끄기
// 아이패드 다른 동작

/**=====================================
 [Protocol Witness 테이블] - 요구사항
 - func turnOn()  { print("아이패드 켜기") }
 - func turnOff() { print("리모콘 끄기") }
========================================**/
let ipad2: Remote = Ipad()
ipad2.turnOn()           // 프로토콜 - W테이블
ipad2.turnOff()          // 프로토콜 - W테이블
ipad2.doAnotherAction()  // 프로토콜 - Direct (직접 메서드 주소 삽입)
// 아이패드 켜기
// 리모콘 끄기
// 리모콘 또 다른 동작

struct SmartPhone: Remote {
    func turnOn() { print("스마트폰 켜기") }

    func doAnotherAction() { print("스마트폰 다른 동작") }
}
/**=====================================
 [구조체] - 메서드 테이블이 없음
========================================**/

// 본래의 타입으로 인식했을때
var iphone: SmartPhone = SmartPhone()
iphone.turnOn()             // 구조체 - Direct (직접 메서드 주소 삽입)
iphone.turnOff()            // 구조체 - Direct (직접 메서드 주소 삽입)
iphone.doAnotherAction()    // 구조체 - Direct (직접 메서드 주소 삽입)
// 스마트폰 켜기
// 리모콘 끄기
// 스마트폰 다른 동작

/**=====================================
 [Protocol Witness 테이블] - 요구사항
 - func turnOn()  { print("스마트폰 켜기") }
 - func turnOff() { print("리모콘 끄기") }
========================================**/

// 프로토콜의 타입으로 인식했을때
var iphone2: Remote = SmartPhone()
iphone2.turnOn()            // 프로토콜 - W테이블
iphone2.turnOff()           // 프로토콜 - W테이블
iphone2.doAnotherAction()   // 프로토콜 - Direct (직접 메서드 주소 삽입)
// 스마트폰 켜기
// 리모콘 끄기
// 리모콘 또 다른 동작
  • 구조체는 원래부터 메서드 테이블이 없음. 코드의 주소를 찾아가는 방식.
  • 애플에서 프로토콜을 자유롭게 사용하기 위해 만든 방식 : 프로토콜을 채택한 구조체의 경우 크기에 따라 스택 또는 힙에 저장됨. 크기가 커지면 힙.

✅ 메서드 디스패치

  • 더 좋은 개발자가 되기 위함
  • 스위프트가 함수를 실행시키는 방법
  • 다이렉트, 테이블, 메세지
  • 메서드라는 것은 cpu에 대한 명령어. 실제로 코드영역에만 존재. 이미 저장되어 있는 함수를 실행시키려면 메서드 주소가 필요한 것. 이걸 찾아가는 방법이라고 이해하면 됨.
  • 다이렉트 : 컴파일 시점에 코드 자체에 함수의 메모리 주소 삽입. 또는 함수의 명령 코드를 복사. 가장 빠름.
  • 테이블 디스패치 : 함수의 포인터(메모리주소)를 배열형태로 보관 후 실행. 클래스와 프로토콜에서 사용. 클래스는 virtual. 프로토콜은 witness
profile
iOS 개발자😺

0개의 댓글