반응형

Swift class vs final class 비교

Swift 개발을 오랫동안 해오신 분이라면, class와 final class의 차이를 구분하는 것이 단순한 문법적 선택을 넘어 아키텍처 설계, 성능 최적화, 코드 안정성에 직결된다는 것을 느끼셨을 겁니다.
이 글에서는 클래스 선언에서 자주 마주치는 두 가지 키워드인 class와 final class를 세심하게 비교하고, 어떤 상황에서 어떤 선택이 더 적절한지, 체계적으로 정리해보겠습니다

1. 기본 개념


class

  • Swift에서 참조 타입(Reference Type) 을 나타냅니다.
  • 상속(inheritance) 이 가능하며, 오버라이드(override) 도 허용됩니다.
  • Objective-C의 NSObject 기반 객체 모델과 호환됩니다.

final class

  • 클래스 선언 앞에 final 키워드를 붙여 상속을 금지한 클래스입니다.
  • 즉, 이 클래스를 다른 클래스가 상속받을 수 없습니다.
  • 내부 메서드, 프로퍼티, 서브스크립트 등도 자동으로 final로 취급됩니다.

 

2. 문법 예시

class Animal {
    func sound() {
        print("Some sound")
    }
}

class Dog: Animal {
    override func sound() {
        print("Woof!")
    }
}

final class Cat {
    func sound() {
        print("Meow")
    }
}

// ❌ 오류: 'Cat' cannot be subclassed
// class PersianCat: Cat { }

 

3. 주요 차이점 요약

항목 class final class
상속 가능 여부 가능 불가능
오버라이드 가능 여부 가능 불가능
런타임 디스패치 Dynamic Dispatch (vtable lookup) Static Dispatch
성능 약간 느림 빠름 (컴파일 타임 최적화 가능)
코드 안정성 상속 시 예측 어려움 구조 고정, 예측 가능
사용 목적 재사용성과 확장성 안정성과 불변성

 

4. 런타임 동작 차이 (Dispatch 방식)

Swift의 메서드 호출은 크게 세 가지 디스패치 방식으로 동작합니다.

디스패치 방식 설명 예시
Static Dispatch 컴파일 시 호출 대상이 결정됨 struct, enum, final class
Dynamic Dispatch (vtable) 런타임에 실제 타입 확인 후 호출 일반 class
Message Dispatch (objc_msgSend) Objective-C 런타임 기반 @objc 메서드 또는 Objective-C 호환 클래스

 

5. 성능 비교

측정 항목 class final class
메서드 호출 오버헤드 런타임 vtable 탐색 필요 컴파일 시 결정
인스턴스 생성 속도 동일 동일
함수 인라인 최적화 불가능 가능 (컴파일러가 inline 처리 가능)
ARC 동작 동일 동일

📈 결론: 성능 차이는 미세하지만, 반복 호출이 많은 코드(예: 렌더링 루프, CoreAnimation 관련 클래스)에서는 final이 실질적으로 도움이 됩니다.

 

6. final의 장점


✅ (1) 성능 최적화

  • 컴파일러가 정적 디스패치(static dispatch) 로 변환 가능.
  • 메서드를 인라인 처리하여 CPU 브랜치 예측 비용 감소.

✅ (2) 코드 안정성 향상

  • 서브클래싱을 통한 예기치 않은 오버라이드 방지.
  • 유지보수 시 예측 가능한 동작 보장.

✅ (3) 의도 명확성

  • “이 클래스는 상속하지 말라”는 명시적 신호로 작용.
  • 프레임워크 설계 시, 외부 개발자가 잘못 확장하는 것을 방지.

 

7. final의 단점


⚠️ (1) 상속 불가능

  • 재사용 구조를 상속으로 구현해야 할 때 제한됨.
  • 예: UIKit에서 UIView를 상속받아 커스텀 뷰 작성이 필요한 경우 final 사용 불가.

⚠️ (2) 테스트 어려움 (Mocking 제한)

  • 단위 테스트에서 final 클래스는 Mocking / Subclassing이 불가능합니다.
  • 예를 들어, final class APIClient는 MockAPIClient로 대체할 수 없습니다.

 

해결 방법: 프로토콜 기반 설계(Dependency Injection)로 전환.

protocol APIClientProtocol {
  func fetchData()
  }
}

final class APIClient: APIClientProtocol {
  func fetchData() { … }
}

// Test
class MockAPIClient: APIClientProtocol {
  func fetchData() { /* mock data */ }
}

 

9. 요약 정리

항목 class final class
상속 가능 불가능
오버라이드 가능 불가능
디스패치 Dynamic (vtable) Static (inlined 가능)
성능 일반적 약간 더 빠름
Mocking 가능 불가능
코드 안정성 중간 매우 높음
사용 예시 커스텀 UI, 추상 클래스 서비스, 매니저, 유틸리티

 

💡 결론

  • “확장과 재사용”이 목적이면 → class
  • “안정성과 성능”이 목적이면 → final class

Swift 개발 철학상, Apple은 “명시적 설계(intentional design)” 을 강조하기 때문에

상속하지 않을 클래스는 항상 final을 붙이는 것이 권장됩니다.

 

반응형
Posted by 까칠코더
,