🍎 아이-였-μ—μŠ€/🐀 μŠ€μœ„ν”„νŠΈ

[Swift] Method Dispatch (2): 더 λ³΅μž‘ν•œ μƒν™©μ˜ Method Dispatch

κ°œλ°œν•˜λŠ” ν›ˆμ΄ 2021. 12. 26. 18:33

일단 정리.. Static Dispatch, Dynamic Dispatch

μ§€λ‚œ ν¬μŠ€νŠΈμ—μ„œ Static Dispatch와 Dynamic Dispatchλ₯Ό μ •λ¦¬ν•΄λ³΄μ•˜λŠ”λ°μš”, Static DispatchλŠ” μΈμŠ€ν„΄μŠ€μ—μ„œ λ©”μ„œλ“œκ°€ 호좜될 λ•Œ μ‹€μ œλ‘œ μ–΄λ–€ λ©”μ„œλ“œκ°€ ν˜ΈμΆœλ μ§€ 컴파일 νƒ€μž„μ— κ²°μ •ν•  수 μžˆλŠ” κ²½μš°μ˜€μŠ΅λ‹ˆλ‹€. 그리고 Dynamic DispatchλŠ” λŸ°νƒ€μž„μ— vtableμ΄λΌλŠ” ν…Œμ΄λΈ”μ— μ‹€μ œ μ‹€ν–‰ν•  λ©”μ„œλ“œμ˜ μ£Όμ†Œλ₯Ό μ°Ύμ•„ κ²°μ •ν•˜λŠ” κ²½μš°μ˜€μŠ΅λ‹ˆλ‹€.

 

λŸ°νƒ€μž„μ— ν…Œμ΄λΈ”μ„ λ£©μ—…ν•΄μ•Όν•˜λŠ” μ˜€λ²„ν—€λ“œ λ•Œλ¬Έμ— Dynamic DispatchλŠ” μ„±λŠ₯에도 뢀정적인 영ν–₯을 μ€€λ‹€λŠ” 것도 μ§šμ–΄λ΄€μ—ˆμ£ . ν•˜μ§€λ§Œ 상속과 λ‹€ν˜•μ„±μ„ μ‚¬μš©ν•˜κΈ° μœ„ν•΄μ„œλŠ” Dynamic Dispatch의 μ‚¬μš©μ΄ ν•„μˆ˜μ μ΄μ—ˆμŠ΅λ‹ˆλ‹€. 

 

κ²°κ΅­ μ§€λ‚œ ν¬μŠ€νŠΈμ—μ„œλŠ” ꡬ쑰체, μ—΄κ±°ν˜• λ“±, κ°’ νƒ€μž… μΈμŠ€ν„΄μŠ€λ“€μ€ Static Dispatch, μ°Έμ‘° νƒ€μž… μΈμŠ€ν„΄μŠ€μΈ ν΄λž˜μŠ€λŠ” Dynamic Dispatch둜 결둠이 λ‚¬μ—ˆλŠ”λ°μš”, μ’€ 더 λ³΅μž‘ν•œ 상황을 κ³ λ―Όν•΄λ΄…μ‹œλ‹€.

 

의문점 1: ν”„λ‘œν† μ½œμ„ μ±„νƒν•˜λŠ” κ΅¬μ‘°μ²΄λŠ” 어쩔건데?

μŠ€μœ„ν”„νŠΈμ— 직접적인 상속은 클래슀만이 κ°€λŠ₯ν•˜μ§€λ§Œ, κ΅¬μ‘°μ²΄λ‘œλ„ μƒμ†μ˜ κ°œλ…μ„ μ μš©ν•  수 μžˆλŠ” 방법이 μžˆμ—ˆμŠ΅λ‹ˆλ‹€. λ°”λ‘œ ν”„λ‘œν† μ½œμ˜ μ‚¬μš©μ΄μ£ .

class Test {
    func printTest() {
        print("test")
    }
}

class TestA: Test {
    override func printTest() {
        print("testA")
    }
}

class TestB: Test {
    override func printTest() {
        print("testB")
    }
}

let tests = [Test(), TestA(), TestB()]

for test in tests {
    print(type(of: test))
    test.printTest()
}

μ§€λ‚œ ν¬μŠ€νŠΈμ—μ„œ μ‚¬μš©ν–ˆλ˜ 이 ν΄λž˜μŠ€λ“€μ„ ꡬ쑰체와 ν”„λ‘œν† μ½œλ‘œ λ°”κΏ”λ³Όκ²Œμš”!

 

protocol TestPrintable {
    func printTest()
}

struct Test: TestPrintable {
    func printTest() {
        print("test")
    }
}

struct TestA: TestPrintable {
    func printTest() {
        print("testA")
    }
}

struct TestB: TestPrintable {
    func printTest() {
        print("testB")
    }
}

let tests: [TestPrintable] = [Test(), TestA(), TestB()]

for test in tests {
    test.printTest()
}

μ΄λ ‡κ²Œ ν•˜λ©΄ λ™μΌν•œ 좜λ ₯이 λ‚˜μ˜€κ² μ£ ? 이제 tests λ°°μ—΄μ˜ μš”μ†Œ νƒ€μž…μ€ ν”„λ‘œν† μ½œμ˜ νƒ€μž…μΈ TestPrintable이 λ˜κ² λ„€μš”.

 

자, 그럼 λ‹€μ‹œ 의문점이 μƒκΉλ‹ˆλ‹€. ν”„λ‘œν† μ½œλ§Œ 보고 μ‹€μ œ κ΅¬ν˜„μ²΄κ°€ λ­”μ§€ μ–΄λ–»κ²Œ μ•Œμ•„..?

 

컴파일 νƒ€μž„μ—λŠ” TestPrintable의 μ„ μ–Έλ§Œ μ•Œκ³ μžˆκ³ , μ •μ˜λ₯Ό μ°Ύμ•„κ°€λ €λ©΄ 각 μΈμŠ€ν„΄μŠ€κ°€ κ°€μ§„ λ©”μ„œλ“œ μ •μ˜λ₯Ό μ°Ύμ•„κ°€μ•Όκ² μ£ ? κ²°κ΅­ μ΄λ²ˆμ—λ„ Dynamic Dispatchκ°€ λ˜κ² λ„€μš”.

 

ν”„λ‘œν† μ½œμ„ μ±„νƒν•œ ꡬ쑰체의 Dynamic Dispatch

ꡬ쑰체도 vtable을 μ“Έ 것 κ°™μ§€λ§Œ, κ·Έλ ‡μ§€ μ•ŠμŠ΅λ‹ˆλ‹€. κ΅¬μ‘°μ²΄λŠ” 곡톡쑰상인 μΈμŠ€ν„΄μŠ€κ°€ μ—†μœΌλ‹ˆκΉŒμš”.

 

κ·Έλž˜μ„œ μ΄λ²ˆμ—λŠ” μƒˆλ‘œμš΄ ν…Œμ΄λΈ”μΈ Protocol Witness Table(PWT)이 μ‚¬μš©λ©λ‹ˆλ‹€.

 

Protocol Witness Table은 ν”„λ‘œν† μ½œμ„ μ±„νƒν•˜λŠ” 각 ꡬ쑰체 μΈμŠ€ν„΄μŠ€κ°€ ν•˜λ‚˜μ”© κ°€μ§€κ³ , νŠΉμ •ν•œ λ©”μ„œλ“œμ— λŒ€ν•œ μ‹€μ œ κ΅¬ν˜„μ„ 이 ν…Œμ΄λΈ”μ— μ—°κ²°μ‹œμΌœλ‘‘λ‹ˆλ‹€. 

WWDCμ—μ„œ λ°œν‘œν•œ μžλ£Œμ—μ„œλŠ” μ΄λ ‡κ²Œ ν‘œν˜„ν•˜κ³  μžˆμ–΄μš”. 배열에 λ‹΄κΈ΄ μΈμŠ€ν„΄μŠ€λ“€μ΄ 내뢀에 PTWλ₯Ό κ°€μ§€κ³  여기에 μ‹€μ œ κ΅¬ν˜„μ— λŒ€ν•œ 링크가 λ§Œλ“€μ–΄μ Έ μžˆλŠ” 것이죠.

 

μ΄λ ‡κ²Œ ν•˜λ©΄ λŸ°νƒ€μž„μ— λ°°μ—΄ μš”μ†Œκ°€ κ°€μ§„ ν…Œμ΄λΈ”μ„ ν•œ 번 μ½μ–΄μ„œ μ½”λ“œλ₯Ό μ°Ύμ•„κ°€μ•Όν•˜λ‹ˆ Dynamic Dispatchκ°€ 되겠죠!

 

의문점 2: ν”„λ‘œν† μ½œμ„ Extension ν•΄μ„œ λ©”μ„œλ“œκ°€ μ •μ˜λ˜μ–΄ μžˆλ‹€λ©΄?

이런 κ²½μš°λŠ” μ–΄λ–¨κΉŒμš”?

protocol TestPrintable {
}

extension TestPrintable {
    func printTest() {
        print("hello")
    }
}

struct Test: TestPrintable {
    func printTest() {
        print("test")
    }
}

struct TestA: TestPrintable {
    func printTest() {
        print("testA")
    }
}

struct TestB: TestPrintable {
    func printTest() {
        print("testB")
    }
}

let tests: [TestPrintable] = [Test(), TestA(), TestB()]

for test in tests {
    test.printTest()
}

ν”„λ‘œν† μ½œμ„ extensionν•΄μ„œ κΈ°λ³Έ λ©”μ„œλ“œλ₯Ό μ •μ˜ν•΄λ‘κ³  싀행을 ν•΄λ³΄μ•˜μŠ΅λ‹ˆλ‹€.

 

κ²°κ³ΌλŠ”,

hello
hello
hello

μ΄λ ‡κ²Œ κΈ°λ³Έ λ©”μ„œλ“œκ°€ μ‹€ν–‰λ©λ‹ˆλ‹€. μ™œλƒν•˜λ©΄ λ°°μ—΄μ˜ μš”μ†Œ νƒ€μž…μ΄ TestPrintable이기 λ•Œλ¬Έμ— 이 νƒ€μž…μ— λŒ€ν•œ λ©”μ„œλ“œκ°€ κ³§λ°”λ‘œ μ‹€ν–‰λ˜λŠ” 것이죠. 각 μΈμŠ€ν„΄μŠ€μ— μ •μ˜λœ printTestλŠ” ν”„λ‘œν† μ½œλ‘œλΆ€ν„° 채택받은 λ©”μ„œλ“œκ°€ μ•„λ‹™λ‹ˆλ‹€.

 

μ‹€μ œλ‘œ XCodeμ—μ„œ μžλ™μ™„μ„±λ„ 지원해주지 μ•Šλ„€μš”γ…Žγ…Ž

 

μ›λž˜λŠ” μ΄λ ‡κ²Œ ν•΄μ€˜μ•Όν•˜λŠ”λ° 말이죠..

 

즉, 이런 κ²½μš°λŠ” μ–Έμ œλ‚˜ ν”„λ‘œν† μ½œμ˜ κΈ°λ³Έ λ©”μ„œλ“œλ₯Ό μ‹€ν–‰ν•˜λŠ” Static Dispatch둜 λ™μž‘ν•©λ‹ˆλ‹€.

 

의문점 3: ν”„λ‘œν† μ½œμ— μΈν„°νŽ˜μ΄μŠ€κ°€ 있고, κΈ°λ³Έ λ©”μ„œλ“œκ°€ Extension으둜 μ •μ˜λœ κ²½μš°λŠ”?

μ•žμ„  μ˜ˆμ‹œμ—μ„œ μΈν„°νŽ˜μ΄μŠ€λ§Œ μΆ”κ°€ν•΄λ³΄κ² μŠ΅λ‹ˆλ‹€.

protocol TestPrintable {
    func printTest()
}

extension TestPrintable {
    func printTest() {
        print("hello")
    }
}

struct Test: TestPrintable {
    func printTest() {
        print("test")
    }
}

struct TestA: TestPrintable {
    func printTest() {
        print("testA")
    }
}

struct TestB: TestPrintable {
    func printTest() {
        print("testB")
    }
}

let tests: [TestPrintable] = [Test(), TestA(), TestB()]

for test in tests {
    test.printTest()
}

μ΄λ²ˆμ—λŠ” ν”„λ‘œν† μ½œμ— μΈν„°νŽ˜μ΄μŠ€κ°€ μ‘΄μž¬ν•˜κ³ , 이λ₯Ό μ±„νƒν•˜λŠ” κ΅¬μ‘°μ²΄μ—μ„œ 각각 μžμ‹ μ˜ κ΅¬ν˜„μ²΄λ₯Ό κ°€μ§€κ³  μžˆμŠ΅λ‹ˆλ‹€. 이런 κ²½μš°μ— μ½”λ“œλ₯Ό 싀행해보면 κ²°κ³ΌλŠ” μ•„λž˜μ²˜λŸΌ λ‚˜μ˜€λŠ”λ°μš”,

test
testA
testB

μ΄λ²ˆμ—λŠ” ν”„λ‘œν† μ½œμ„ μ±„νƒν•œ μΈμŠ€ν„΄μŠ€λ“€μ˜ λ©”μ„œλ“œκ°€ μ‹€ν–‰λ©λ‹ˆλ‹€. 각 λ©”μ„œλ“œλ“€μ΄ ν”„λ‘œν† μ½œμ„ 받은 κ΅¬μ‘°μ²΄μ—μ„œ μ •μ˜λ˜κ³  μžˆλŠ” 상황이기 λ•Œλ¬Έμ— 이런 κ²½μš°λŠ” ν”„λ‘œν† μ½œμ„ μ±„νƒν•œ ꡬ쑰체의 λ©”μ„œλ“œκ°€ μš°μ„ μ μœΌλ‘œ μ‹€ν–‰λ©λ‹ˆλ‹€.

 

λ”°λΌμ„œ 이런 κ²½μš°λŠ” Dynamic Dispatch μž…λ‹ˆλ‹€.

 

의문점 4: 클래슀의 Extension은?

μ΄λ²ˆμ—” 클래슀λ₯Ό Extensionν•˜λŠ” κ²½μš°μž…λ‹ˆλ‹€.

class Test {
    func printTest() {
        print("test")
    }
}

extension Test {
    func printExtension() {
        print("extension")
    }
}

class TestA: Test {
    override func printTest() {
        print("testA")
    }
    override func printExtension() {
        print("extensionA")
    }
}

class TestB: Test {
    override func printTest() {
        print("testB")
    }
}

let tests = [Test(), TestA(), TestB()]

for test in tests {
    test.printExtension()
}

μ΅œμƒμœ„ 클래슀의 Extension에 μƒˆλ‘œμš΄ λ©”μ„œλ“œ μΆ”κ°€ν•˜κ³  TestAμ—μ„œ μ˜€λ²„λΌμ΄λ”©μ„ ν•˜λ €κ³  ν•©λ‹ˆλ‹€.

ν•˜μ§€λ§Œ μ΄λ ‡κ²Œ μ—λŸ¬κ°€ λ‚©λ‹ˆλ‹€.. Extension으둜 λ§Œλ“  λ©”μ„œλ“œλŠ” μ˜€λ²„λΌμ΄λ”©μ΄ λΆˆκ°€λŠ₯ν•˜λ‹€κ³  ν•˜λ„€μš”.. Objective-C λŸ°νƒ€μž„μ„ μ‚¬μš©ν•˜λ©΄ κ°€λŠ₯ν•˜λ‹€κ³  ν•©λ‹ˆλ‹€. 

 

λ§Œμ•½ Objective-C λŸ°νƒ€μž„μ„ μ‚¬μš©ν•˜μ§€ μ•ŠλŠ”λ‹€λ©΄ 항상 μ΅œμƒμœ„ 클래슀의 λ©”μ„œλ“œλ§Œ μ‚¬μš©ν•  수 μžˆμœΌλ―€λ‘œ Static Dispatch κ°€ λ©λ‹ˆλ‹€.

 

정리

https://www.rightpoint.com/rplabs/switch-method-dispatch-table

이 글에 ν‘œλ‘œ 잘 μ •λ¦¬λ˜μ–΄ μžˆμ–΄μ„œ κ·ΈλŒ€λ‘œ κ°€μ Έμ™€λ΄…λ‹ˆλ‹€!

  • 1) κ°’ νƒ€μž…: μˆœμˆ˜ν•œ κ°’ νƒ€μž…, ν”„λ‘œν† μ½œμ„ μ±„νƒν•˜μ§€ μ•ŠλŠ” κ΅¬μ‘°μ²΄λ‚˜ enum은 항상 Static Dispatchμž…λ‹ˆλ‹€. μ–΄λ–€ λ©”μ„œλ“œλ₯Ό μ‹€ν–‰ν•˜μ§€λŠ” λ„ˆλ¬΄λ‚˜ λͺ…ν™•ν•˜λ‹ˆκΉŒμš”!
  • 2) ν”„λ‘œν† μ½œ: ν”„λ‘œν† μ½œμ„ μ±„νƒν•˜λŠ” νƒ€μž…λ“€μ€ 기본적으둜 Dynamic Dispatchμž…λ‹ˆλ‹€. μ—¬κΈ°μ„œ μ‚¬μš©ν•˜λŠ” ν…Œμ΄λΈ”μ€ Protocol Witness Table μ΄κ΅¬μš”. ν•˜μ§€λ§Œ Protocol의 μΈν„°νŽ˜μ΄μŠ€μ— ν¬ν•¨λ˜μ§€ μ•ŠλŠ” κΈ°λ³Έ λ©”μ„œλ“œλ“€μ€ 항상 κΈ°λ³Έ λ©”μ„œλ“œκ°€ 호좜되기 λ•Œλ¬Έμ— Static Dispatch μž…λ‹ˆλ‹€.
  • 3) 클래슀: ν΄λž˜μŠ€λŠ” λ‹€ν˜•μ„±κ³Ό 상속 λ•Œλ¬Έμ— 기본적으둜 Dynamic Dispatch이고, vtable을 μ΄μš©ν•΄μ„œ λ©”μ„œλ“œλ₯Ό μ‹€ν–‰ν•©λ‹ˆλ‹€. 클래슀의 extension으둜 μ •μ˜λœ λ©”μ„œλ“œλŠ” Objective-C λŸ°νƒ€μž„μ„ μ‚¬μš©ν•˜μ§€ μ•ŠμœΌλ©΄ μ˜€λ²„λΌμ΄λ”©ν•  수 μ—†κΈ° λ•Œλ¬Έμ— Static Dispatch μž…λ‹ˆλ‹€.

Reference