✅ @inlinable이란?
@inlinable은 컴파일러에게 특정 함수나 계산 속성을 인라인 최적화할 수 있다고 알려주는 키워드
인라인 최적화: 함수 호출 대신 함수의 실제 코드를 호출 지점에 직접 삽입하는 것
// 일반적인 함수 호출
func square(_ x: Int) -> Int {
return x * x
}
let result = square(5) // 함수 호출 오버헤드 발생
// @inlinable로 최적화된 경우
@inlinable
func square(_ x: Int) -> Int {
return x * x
}
let result = 5 * 5 // 컴파일러가 직접 코드 삽입
✅ @inlinable의 특징
- 성능 향상: 함수 호출 오버헤드 제거
- 모듈 경계 최적화: 다른 모듈에서도 인라인 최적화 가능
- ABI 안정성: 함수 구현이 공개 인터페이스의 일부가 됨
✅ 언제 사용해야 할까?
사용하기 좋은 경우
// 1. 작고 자주 호출되는 함수
@inlinable
public func square(_ x: Int) -> Int {
return x * x
}
// 2. 단순한 계산 프로퍼티
@inlinable
public var isEven: Bool {
return self % 2 == 0
}
// 3. 수학적 연산
@inlinable
public func distance(_ a: CGPoint, _ b: CGPoint) -> CGFloat {
let dx = a.x - b.x
let dy = a.y - b.y
return sqrt(dx * dx + dy * dy)
}
❌ 사용하지 말아야 하는 경우
// 1. 복잡한 비즈니스 로직
func loadBooks() {
let result = bookRepository.loadBooks()
// 복잡한 로직들...
} // @inlinable 부적합
// 2. 상태 변경이 있는 메서드
func selectBook(at index: Int) {
selectedBookIndex = index
// side effect 존재
} // @inlinable 부적합
// 3. 큰 함수들
func processLargeData() {
// 100줄 이상의 코드
} // 코드 크기 증가로 성능 저하 가능
✅ 실제 프로젝트에서의 적용
// BookViewModel에서 적용 가능한 예시
final class BookViewModel {
// ✅ 단순한 계산만 하므로 적용 가능
@inlinable
var totalBooksCount: Int {
return books.count
}
@inlinable
var bookImageName: String {
return "harrypotter\(selectedBookIndex + 1)"
}
// ❌ 복잡한 로직이므로 부적합
var releaseDate: String {
return DateFormatter.enDateFormatter.string(from: currentBook?.releaseDate ?? Date())
}
}
✅ lazy란?
lazy는 프로퍼티가 처음 접근될 때까지 초기화를 지연시키는 키워드
lazy의 동작 원리
class Example {
// 즉시 초기화 (객체 생성 시점)
let immediateProperty = ExpensiveObject()
// 지연 초기화 (처음 접근할 때)
lazy var lazyProperty = ExpensiveObject()
}
let example = Example()
// 이 시점에서 immediateProperty는 이미 생성됨
// lazyProperty는 아직 생성되지 않음
let value = example.lazyProperty // 이 시점에서 생성됨
✅ 언제 사용해야 할까?
lazy 사용이 적합한 경우
1. UI 컴포넌트 (가장 일반적)
class ViewController: UIViewController {
private lazy var titleLabel = UILabel().then {
$0.textAlignment = .center
$0.font = .systemFont(ofSize: 24, weight: .bold)
$0.numberOfLines = 0
}
private lazy var tableView = UITableView().then {
$0.delegate = self
$0.dataSource = self
$0.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
}
private lazy var customButton = UIButton().then {
$0.setTitle("Tap Me", for: .normal)
$0.backgroundColor = .blue
$0.layer.cornerRadius = 8
}
}
2. 포맷터 및 헬퍼 객체
class DataManager {
private lazy var dateFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.dateStyle = .medium
formatter.timeStyle = .short
return formatter
}()
private lazy var numberFormatter: NumberFormatter = {
let formatter = NumberFormatter()
formatter.numberStyle = .currency
formatter.locale = Locale.current
return formatter
}()
private lazy var jsonDecoder: JSONDecoder = {
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
return decoder
}()
}
3. 네트워크 및 데이터베이스 연결
class NetworkManager {
private lazy var urlSession: URLSession = {
let configuration = URLSessionConfiguration.default
configuration.timeoutIntervalForRequest = 30
return URLSession(configuration: configuration)
}()
private lazy var coreDataStack: CoreDataStack = {
return CoreDataStack(modelName: "DataModel")
}()
}
4. 복잡한 계산이나 데이터 변환
class AnalyticsManager {
private var rawData: [DataPoint] = []
private lazy var processedData: [ProcessedDataPoint] = {
return rawData.map { dataPoint in
// 복잡한 변환 로직
return ProcessedDataPoint(
value: dataPoint.value * 1.5,
timestamp: dataPoint.timestamp,
category: categorize(dataPoint)
)
}
}()
}
❌ lazy 사용을 피해야 하는 경우
// 1. 자주 변경되는 값
var currentTemperature: Double // lazy 부적합
// 2. 가벼운 계산
let spacing: CGFloat = 16 // lazy 불필요
// 3. 항상 필요한 값
let identifier = "CellIdentifier" // lazy 불필요
✅ let vs lazy var의 차이점
1. 불변성 (Immutability)
// let - 완전 불변
private let titleLabel = UILabel()
// titleLabel = UILabel() // ❌ 컴파일 에러
// lazy var - 변경 가능
private lazy var titleLabel = UILabel()
// titleLabel = UILabel() // ✅ 가능 (하지만 위험!)
2. Thread Safety
// let - Thread Safe
private let titleLabel = UILabel() // 여러 스레드에서 접근해도 안전
// lazy var - Thread Safe하지 않음
private lazy var titleLabel = UILabel() // 동시 접근 시 위험
3. 메모리 관리
// let - 객체 생성 후 참조 변경 불가
private let titleLabel = UILabel() // 강한 참조 고정
// lazy var - 참조 변경 가능
private lazy var titleLabel = UILabel() // 나중에 다른 객체로 교체 가능
✅ 실제 프로젝트 적용 예시
Before: let 사용
class BookViewController: UIViewController {
// 현재 코드
private let titleLabel = UILabel().then {
$0.textAlignment = .center
$0.font = .systemFont(ofSize: 24, weight: .bold)
$0.numberOfLines = 0
}
private let seriesStackView = UIStackView().then {
$0.axis = .horizontal
$0.spacing = 8
$0.distribution = .fillEqually
}
private let bookDetailView = BookDetailView()
}
After: lazy 적용
class BookViewController: UIViewController {
// lazy로 변경
private lazy var titleLabel = UILabel().then {
$0.textAlignment = .center
$0.font = .systemFont(ofSize: 24, weight: .bold)
$0.numberOfLines = 0
}
private lazy var seriesStackView = UIStackView().then {
$0.axis = .horizontal
$0.spacing = 8
$0.distribution = .fillEqually
}
private lazy var bookDetailView = BookDetailView()
}
✅ 주의사항
1. 실수로 재할당하지 않기
private lazy var titleLabel = UILabel()
func someMethod() {
titleLabel = UILabel() // ❌ 기존 UI 연결이 끊어짐!
}
2. self 참조 시 retain cycle 주의
private lazy var button = UIButton().then { [weak self] in
$0.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside)
}
✅ UI 컴포넌트에서의 메모리 사용 패턴
class ViewController: UIViewController {
// let: 객체 생성 시점에 모든 UI 컴포넌트 생성
private let label1 = UILabel() // 즉시 생성
private let label2 = UILabel() // 즉시 생성
private let label3 = UILabel() // 즉시 생성
// lazy: viewDidLoad에서 실제 사용될 때만 생성
private lazy var lazyLabel1 = UILabel() // 지연 생성
private lazy var lazyLabel2 = UILabel() // 지연 생성
private lazy var lazyLabel3 = UILabel() // 지연 생성
override func viewDidLoad() {
super.viewDidLoad()
// 이 시점에서 lazy var들이 생성됨
view.addSubview(lazyLabel1)
}
}
✅ 적용 판단 기준
@inlinable
// ✅ 적용하기
@inlinable var isDataLoaded: Bool { !books.isEmpty }
@inlinable func bookCount() -> Int { books.count }
// ❌ 적용하지 않기
func loadBooks() { /* 복잡한 로직 */ }
func updateUI() { /* 상태 변경 */ }
lazy
// ✅ lazy 적용
private lazy var expensiveFormatter = DateFormatter()
private lazy var complexView = CustomComplexView()
// ❌ lazy 불필요
private let simpleSpacing: CGFloat = 16
private let identifier = "CellID"
✅ 정리
@inlinable | lazy | |
목적 | 함수 호출 오버헤드 제거 | 메모리 사용 패턴 최적화 |
적용 대상 | 작고 자주 호출되는 함수/프로퍼티 | 생성 비용이 높고 사용되지 않을 수 있는 객체 |
주의사항 | 큰 함수에 사용하면 오히려 성능 저하 | Thread Safety 없음, 재할당 가능 |
'iOS' 카테고리의 다른 글
[iOS] Coordinator 패턴으로 화면 전환 로직 분리하기 (0) | 2025.07.10 |
---|---|
[iOS] 클로저에서 async/await로 리팩토링하기 (1) | 2025.07.08 |
[iOS] Unit Test 기본 개념과 적용 (0) | 2025.06.23 |
[iOS] MVVM 패턴에서 데이터 로딩 책임 분리 (0) | 2025.06.18 |
[아키텍처] Clean Architecture + MVVM (0) | 2025.06.13 |