Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
Tags
- 애니메이션
- Xcode
- tableView
- 리펙토링
- Observable
- swiftUI
- Refactoring
- 리펙터링
- MVVM
- 리팩토링
- UITextView
- uiscrollview
- combine
- collectionview
- ios
- rxswift
- Human interface guide
- map
- clean architecture
- 클린 코드
- Protocol
- HIG
- UICollectionView
- 스위프트
- RxCocoa
- Clean Code
- SWIFT
- uitableview
- ribs
- swift documentation
Archives
- Today
- Total
김종권의 iOS 앱 개발 알아가기
[iOS - swift] Method Swizzling (메소드 스위즐링), 우아하게 dealloc 로그 찍기 (deinit 로그 찍기) 본문
iOS 기본 (swift)
[iOS - swift] Method Swizzling (메소드 스위즐링), 우아하게 dealloc 로그 찍기 (deinit 로그 찍기)
jake-kim 2022. 3. 16. 22:42* 스위즐링 관련 설명이 더욱 추가된 최신 포스팅 글은 이 포스팅 글 참고
사전 지식) Objective-c에서 메소드가 실행되는 과정
- Objective-C는 동적인 언어(dynamic language)
- 동적인 언어: 어떤 코드가 실행될지 런타임에 결정 (실행되는 순간가지 코드의 실행을 미룰 수 있다는 의미)
- 정적인 언어: 어떤 코드가 실행될지 컴파일/링크 시점에 결정
- Objective-C는 런타임에 메세지를 날리고, 그 메세지를 통해 메소드를 찾아서 실행
- message 생성 - [receive message]
- 컴파일러에 의해 컨버젼 - objc_msgSend(receiver, selector, arg1, arg2, ...)
// ex) objective-c 메소드이므로, 런타임 때 코드 실행이 결정
@objc dynamic func someMethod() { ... }
Method Swizzling 개념
* swizzle: "뒤섞다"
- Objective-C에 의해 만들어진 swift언어도 역시 Objective-C의 성격을 가지고 있으므로 메소드의 실행은 런타임에 결정
- 메소드의 실행은 런타임때 결정되므로, 런타임에서 메소드가 실행되기전에 원하는 내용으로 뒤섞기가 가능
- = 런타임 시점에 내가 원하는 코드가 실행되게끔 가능
- 특정 SDK의 메소드가 있을 때, 이 메소드를 변경하고 싶은 경우 매우 유용하게 사용 (기존 SDk의 메소드를 수정할 수 없을 때)
Method Swizzling 구현 방법
- 원본 메소드가 @objc dynamic 메소드여야 가능
import UIKit
class ViewController: UIViewController {
@objc dynamic private func someMethod() {
print("iOS앱 개발 알아가기 - 원본")
}
}
- 위 someMethod() 대신에 다른 메소드를 실행하고 싶은 경우?
- class_getInstanceMethod라는 함수를 통해 메소드 인스턴스를 획득
- method_exchangeImplemtations(_:_:) 함수를 통해 런타임 때 메소드 변경
extension ViewController {
class func swizzleMethod() {
guard
let originalMethod = class_getInstanceMethod(Self.self, #selector(Self.someMethod)),
let swizzledMethod = class_getInstanceMethod(Self.self, #selector(Self.anotherMethod))
else { return }
method_exchangeImplementations(originalMethod, swizzledMethod)
}
@objc private func anotherMethod() {
print("iOS앱 개발 알아가기 - 스위즐링")
}
}
- swizzleMethod 실행 후 이전의 someMethod를 실행하면, anotherMethod가 실행
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
Self.swizzleMethod()
self.someMethod() // iOS앱 개발 알아가기 - 스위즐링
}
@objc dynamic private func someMethod() {
print("iOS앱 개발 알아가기 - 원본")
}
}
응용 - Method Swizzling을 이용한 dealloc 로그 찍기
ex) VC의 deinit 코드에 부분에 아무 코드도 구현하지 않아도, Method Swizzling을 통해 모든 VC가 deinit될 때 로그가 출력되도록 구현
- memory leak을 체크하기 위해서 ViewController의 deinit이 실행될 때 로그를 찍도록하는 기능을 Method Swillzing을 사용하여 구현
- 만약 Method Swizzling이라는 기법을 안쓴다면 모든 ViewController의 deinit마다 로그를 찍어주어야하는 상황
- deinit은 swizzling이 원칙직으로 되지 않으므로, viewDidLoad시에 Deallocator를 내부적으로 할당 해놓고, Deallocator가 deinit되는 시점 (== ViewController가 deinit되는 시점)에 로그를 찍도록 구현
- Deallocator 추가
// Swizzling+UIViewController.swift
import UIKit
final private class Deallocator {
var closure: () -> Void
init(_ closure: @escaping () -> Void) {
self.closure = closure
}
deinit {
closure()
}
}
- viewDidLoad에서 Deallocator 클래스를 retain하도록 설정
- viewDidLoad에서 해당 코드가 호출되도록 swizzling 구현
private var associatedObjectAddr = ""
extension UIViewController {
class func swizzleMethodForDealloc() {
let originalSelector = #selector(viewDidLoad)
let swizzledSelector = #selector(swizzled_viewDidLoad)
guard
let originalMethod = class_getInstanceMethod(Self.self, originalSelector),
let swizzledMethod = class_getInstanceMethod(Self.self, swizzledSelector)
else { return }
method_exchangeImplementations(originalMethod, swizzledMethod)
}
@objc private func swizzled_viewDidLoad() {
let deallocator = Deallocator { print("deinit: \(Self.self)") }
objc_setAssociatedObject(self, &associatedObjectAddr, deallocator, .OBJC_ASSOCIATION_RETAIN)
}
}
- 스위즐링 활성화를 위해서 swizzleMethodForDealloc()를 AppDelegate에서 호출
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
UIViewController.swizzleMethodForDealloc()
return true
}
-> 시스템 내부적으로 구현된 viewDidLoad()도 호출되고, 위에서 정의된 deinit도 호출되어 Method Swizzling 부분을 쉽게 뗴었다 붙였다 할 수 있는 모듈성 증가
* 주의할 점: 시스템에서 정의된 viewDidLoad와 같은 메소드는 swizzling해도, swizzling메소드가 불린 후 viewDidLoad가 호출되는것을 주의 (기존 메소드에 확장 가능에 더욱 용이)
* 전체 코드: https://github.com/JK0369/ExMethodSwizzling
* 참고
'iOS 기본 (swift)' 카테고리의 다른 글
[iOS - swift] UITapGestureRecognizer 제스쳐에서 subviews들은 무시하는 방법 isDescendant(of:), gestureRecognizer(_:, shouldReceive:) (0) | 2022.03.29 |
---|---|
[iOS - swift] @unknown default, @frozen enum 개념 (0) | 2022.03.22 |
[iOS - swift] ArraySlice, SubSequence 개념 (0) | 2022.03.15 |
[iOS - swift] @inlinable 이란? (0) | 2022.03.11 |
[iOS - swift] App Icon (앱 아이콘) 변경 방법, 앱 아이콘 이미지 Generator (0) | 2022.02.13 |
Comments