Method Dispatch
- 클래스 / 프로토콜의 메서드가 실행되는 방식(Method Dispatch)에 대한 이해
- 공식문서에 나온 내용이 아닌 자료들을 통한 것이므로 무조건적인 신뢰는 하지 말 것
- 스위프트가 함수를 실행시키는 방법(3가지 방법을 모두 사용)
- 컴파일 타입 - [Direct/Static Dispatch]
- 컴파일 시점에 코드 자체에 함수의 메모리 주소 삽입 또는 함수의 명령 코드를 해당 위치에 코드를 심음(in-line)
- 가장 빠름
- 값 타입(구조체/열거형)에 사용
- 상속 / 다형성의 장점을 누릴 수 없음
- 런타임 - [Table Dispatch] , [Message Dispatch]
- Table Dispatch
- 함수의 포인터를 배열형태로 보관 후에 실행
- 중간 속도
- 클래스(Virtual Table), 프로토콜(Witness Table)에서 사용
- Message Dispatch
- 상속구조를 모두 훑은 뒤에, 실행할 메서드 결정
- 가장 느림
- 주로 Objective-C 클래스에서 사용
- Objective-C 런타임에 의존
- 컴파일 타입 - [Direct/Static Dispatch]
구분 | 본체(Initial Declaration) | Extension | 비고 |
Value Type(Struct) | Direct Dispatch | Direct Dispatch | - |
Protocol | Table Dispatch (Witness Table) |
Direct Dispatch (메서드 디폴트 구현 제공) |
본체의 요구사항 메서드를 Witness Table로 구현 (프로토콜을 채택한 타입마다 테이블을 만듦) |
Class | Table Dispatch(Virtual Table) *final 키워드 - Direct |
Direct Dispatch (상속시 재정의 불가 원칙) *@objc dynamic - Message |
*@objc dynamic 키워드를 통해, Message Dispatch로 바뀌면 extension내의 메서드 재정의 가능 |
@objc dynamic | Message Dispatch | Message Dispatch | - |
프로토콜의 메서드 디스패치
- 프로토콜 - Witness Table
import UIKit
protocol MyProtocol {
func method1() // 요구사항 - Witness Table
func method2() // 요구사항 - Witness Table
}
extension MyProtocol {
// 요구사항의 기본 구현 제공
func method1() { print("Protocol - Witness Table method1") }
func method2() { print("Protocol - Witness Table method2") }
// 필수 요구사항은 아님 --> Direct Dispatch
func anotherMethod() {
print("Protocol Extension - Direct Method")
}
}
- 클래스 - Virtual Table
import UIKit
protocol MyProtocol {
func method1() // 요구사항 - Witness Table
func method2() // 요구사항 - Witness Table
}
extension MyProtocol {
// 요구사항의 기본 구현 제공
func method1() { print("Protocol - Witness Table method1") }
func method2() { print("Protocol - Witness Table method2") }
// 필수 요구사항은 아님 --> Direct Dispatch
func anotherMethod() {
print("Protocol Extension - Direct Method")
}
}
class FirstClass: MyProtocol {
func method1() { print("Class - Virtual Table method1") }
func method2() { print("Class - Virtual Table method2") }
func anotherMethod() { print("Class - Virtual Table method3") }
}
/**==============================================================
[Class Virtual Table]
- func method1() { print("Class - Virtual Table method1") }
- func method2() { print("Class - Virtual Table method2") }
- func anotherMothod() { print("Class - Virtual Table method3") }
=================================================================**/
/**==============================================================
[Protocol Witness Table]
- func method1() { print("Class - Virtual Table method1") } // 요구사항 - 우선순위 반영⭐️
- func method2() { print("Class - Virtual Table method2") } // 요구사항 - 우선순위 반영⭐️
=================================================================**/
let first = FirstClass()
first.method1() // Class - Virtual Table method1
first.method2() // Class - Virtual Table method2
first.anotherMothod() // Class - Virtual Table method3
let proto: MyProtocol = FirstClass()
proto.method1() // Class - Virtual Table method1 (Witness Table)
proto.method2() // Class - Virtual Table method2 (Witness Table)
proto.anotherMothod() // Protocol Extension - Direct method
- 함수란 것은 결국 CPU가 실행될 수 있는 형태의 명령어
- 명령어는 코드 영역에만 존재
- 코드 영역에서 CPU가 한 줄 한 줄 읽으면서 자기가 실행될 명령어들을 실행시킴
- 데이터, 힙, 스택 → 데이터를 저장하는 영역(값, 메모리 주소)
중첩 타입
- 타입 내부에 타입을 선언하는 것
- 왜 사용함?
- 특정 타입 내에서만 사용하기 위함
- BStruct는 AClass와 관계가 있고, AClass 없이는 의미가 없을 수 있음 (그래서 범위를 명확히 한정)
- 타입 간의 연관성을 명확히 구분하고, 내부 구조를 디테일하게 설계 가능
- 특정 타입 내에서만 사용하기 위함
- 중첩 타입을 배우는 목적
- 중첩타입으로 선언된 API 들을 볼줄 알아야함 ⭐️
- → DateFomatter.Style.full
- → 중간 타입에 대문자가 나오면, 중첩타입임을 인지
- 실제, 앱을 만들때 중첩 선언을 잘 활용해야함 (타입 간의 관계 명확성)
- 하나의 타입의 내부 구조(계층 관계 등)를 디테일하게 설계 가능
import UIKit
class AClass {
struct Bstruct {
enum Cenum {
case aCase
case bCase
struct Dstruct {
}
}
var name: Cenum
}
}
// 타입 선언과 인스턴스의 생성
let aClass: AClass = AClass()
let bStuct: AClass.Bstruct = AClass.Bstruct(name: .bCase)
let cEnum: AClass.Bstruct.Cenum = AClass.Bstruct.Cenum.aCase // 열거형은 케이스선택
let dStruct: AClass.Bstruct.Cenum.Dstruct = AClass.Bstruct.Cenum.Dstruct()
- Swift 공식 문서의 예제 (실제 사용 방법)
import UIKit
struct BlackjackCard {
enum Suit: Character {
case spades = "♠"
case hearts = "♡"
case diamonds = "♢"
case clubs = "♣"
}
enum Rank: Int {
case two = 2, three, four, five, six, seven, eight, nine, ten
case jack, queen, king, ace // No rawValue
struct Values {
let first: Int, second: Int?
}
var values: Values {
get {
switch self {
case Rank.ace :
return Values(first: 1, second: 11)
case .jack, .queen, .king:
return Values(first: 10, second: nil)
default:
return Values(first: self.rawValue, second: nil)
}
}
}
}
let rank: Rank, suit: Suit
// (읽기) 계산속성
var description: String {
get {
var output = "\(suit.rawValue) 세트, "
output += "숫자 \(rank.values.first)"
if let second = rank.values.second {
output += " 또는 \(second)"
}
return output
}
}
}
let card1 = BlackjackCard(rank: .ace, suit: .spades)
print("1번 카드 : \(card1.description)")
let card2 = BlackjackCard(rank: .five, suit: .diamonds)
print("2번 카드 : \(card2.description)")
let heartsSymbol: Character = BlackjackCard.Suit.hearts.rawValue
let suit = BlackjackCard.Suit.hearts
- 실제 API의 사용 예시
import UIKit
let formatter = DateFormatter()
// dataStyle 변수의 타입 확인해보기
formatter.dateStyle = .full
formatter.dataStyle = DateFormatter.Style.none
/**
- var dateStyle: Style { get set } (타입 확인)
- var dateStyle: DateFormatter.Style { get set } (내부 정의)
**/
// 타입 확인
let setting1: DateFormatter.Style = DateFormatter.Style.full
let setting2: DateFormatter.Style = DateFormatter.Style.medium
// 어던 타입과 관계가 있는지 명확해 보인다.
struct DateFormatters {
var style: Style
enum Style {
case full
case long
case medium
case none
}
}
var dateStyle1 = DateFormatters(style: .full)
dateStyle1 = DateFormatters(style: Style.full)
dateStyle1.style = Style.full
dateStyle1.style = .full
[self] vs [Self]
구분 | self | Self |
의미 | 인스턴스를 가르킴 | 타입을 가르킴 |
설명 | 모든 인스턴스들이 암시적으로 생성하는 속성 | Self가 특정한 타입을 가르키는 것은 아ㅣㄴ고, 특정 타입 내부에서 사용하여 해당 타입을 가르킴 |
가르키는 대상 예시 | "hello", 7 ... | String, Int ... |
사용 목적과 예시 | 1) 인스턴스 내부에서 인스턴스의 속성을 더 명확하게 가르키기 위해 사용 self.name = name 2) 값타입(구조체/열거형)에서 인스턴스 자체의 값을 치환할때 사용 가능(클래스에서는 사용 불가) mutating func reset() { self = Calculator() } 3) 타입속성/메서드에서 사용하면, 타입자체를 가르킴 static func doPrinting() { print("\(self.club) } 4) 타입 인스턴스를 가르킬때, 타입 자체의 뒤에 붙여서 사용(타입자체를 외부에서) let a: MyClass.Type = MyClass.self (메타타입부분 참고) |
1) 특정 타입 내부에서 타입을 선언하는 위치에 사용 extension Int { static let zero: Self = 0 } 2) 특정 타입 내부에서 타입속성/타입메서드를 지칭하는 위치에서 타입 대신 사용 Int.zero -> Self.zero 3) 프로토콜에서 채택하려는 타입을 지칭가능 (범용적으로 사용 가능함을 의미) protocol Some { var name: Self { get set } |
반응형
'iOS > Swift' 카테고리의 다른 글
Swift No.24 (0) | 2024.02.02 |
---|---|
Swift No.23 (1) | 2024.01.31 |
Swift No.21 (1) | 2024.01.28 |
Swift No.20 (1) | 2024.01.27 |
Swift No.19 (1) | 2024.01.25 |