Thậm chí iOS App của bạn được viết bằng swift hoàn toàn, thì bên trong nó đều sử dụng tới Objc Runtime. Bởi vì những thư viện cốt lõi đều được viết bằng Objc như Foundation, UIKit, …. Mà gắn với Objc thì tất nhiên phải có Objc Runtime rồi.
Bài viết hôm nay tập trung nói về ứng dụng dùng Swift của bạn sử dụng Objc Runtime như thế nào.
Series này được chia thành nhiều phần.
- Phần 1: Khái niệm, ưu nhược điểm.
- Phẩn 2: Chúng ta có thể làm gì với objc runtime.
- Phần 3: Swift & Objc Runtime
- Phần 4: Inside Objc Class
- Phần 5: Message dispatch
1. Sử dụng một cách tự động mà bạn không thấy
Có thể bạn không để ý, nhưng Objc Runtime sẽ tự động được dùng khi bạn kế thừa một class sử dụng Objc.
Ta xét đoạn code sau:
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
print("Hello swift")
}
}
Sau khi compile xong thì chúng ta sẽ dùng tool nm để xem list symbol trong chương trình của chúng ta.
nm <đường dẫn tới file binary sau khi compile> | grep "ViewController" | xcrun swift-demangle
Để chứng minh điều đó, chúng ta gọi command trên. Ta sẽ thấy có một dòng có kết quả như: OBJC_METACLASS
$_DemoSwift.ViewController
Như vậy khi kế thừa một class Obj, thì cũng sinh ra một Meta class để chứa thông tin phương thức sử dụng Objc của class này.
Sử dụng objc runtime khi override objc method
nm <đường dẫn tới file binary sau khi compile> | grep "viewDidLoad" | xcrun swift-demangle
Sau khi dùng command trên được kết quả sau:
0000000100001be0 T DemoSwift.ViewController.viewDidLoad() -> () // s1 0000000100001c90 t @objc DemoSwift.ViewController.viewDidLoad() -> () // s2
- s1 chính là symbol chứa code swift trong method
viewDidLoad
ta viết bên trên. - s2 là symbol có chứa code mà sẽ gọi sang symbol s1. Method này sẽ được lưu thông tin trong
OBJC_METACLASS
$_DemoSwift.ViewController
Như vậy khi override function từ framework dùng objc thì sẽ tạo ra 1 symbol có cờ @objc để Objc Runtime có thể gọi tới. Và sau đó sẽ gọi sang code swift của chúng ta sử dụng call
instruction thay vì sử dụng objc_msgSend
.
2. Sử dụng do bạn chủ động sử dụng nó.
2.1 Sử dụng hàm API của Objc Runtime
Bạn có thể chủ động sử dụng các hàm API của Objc Runtime như bên Objc để làm các việc như Associete Objects, Method Swizzling …
2.2 Sử dụng khi dùng @objc cho function
Khi sử dụng #selector bạn phải thêm cờ @objc tại đầu của function, bởi vì selector là một tính năng của Objc. Vậy khi bạn đặt cờ @objc ở đầu function thì chuyện gì sẽ xảy ra. Chúng ta lại thử viết code như sau và dùng công cụ nm để xem sau khi compile chuyện gì sẽ xảy ra.
class UIHandle {
@objc func viewDidFirstAppear() {
print("First appear")
}
}
nm <đường dẫn tới file binary sau khi compile> | grep "viewDidFirstAppear" | xcrun swift-demangle
Dùng command trên sau khi compile code ta được:
0000000100001fa0 T DemoSwift.UIHandle.viewDidFirstAppear() -> () // s1 0000000100002090 t @objc DemoSwift.UIHandle.viewDidFirstAppear() -> () // s2 0000000100004934 S method descriptor for DemoSwift.UIHandle.viewDidFirstAppear() -> () // s3
- s1: Symbol này chứa code đã được compile trong đoạn code swift trên
- s2: Symbol này chứa code có cờ @objc để Objc Runtime lookup và gọi tới.
- s3: Symbol này chứa method decriptor của method này. Mục đích của cái này mình cũng chưa nắm rõ để làm gì.
3.3 Sử dụng khi dùng @objc ở property
Khi bạn sử dụng @objc ở property trong class. Compile cũng sẽ tạo ra các symbol get/set tương tự như property của Objc.
3.4 Sử dụng khi gọi method viết bằng objc
Khi code swift gọi những method được viết bằng objc thì chúng sẽ gọi method đó bằng cách gọi objc_msgSend
thay vì dùng call
instruction. Chúng ta thử đoạn code sau:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
viewDidAppear(false)
}
}
Sau khi compile xong ta tiến hành disassembly method viewDidLoad
bằng Hopper ta thu được:
00000001000018f0 push rbp ; CODE XREF=-[_TtC9DemoSwift14ViewController viewDidLoad]+31 00000001000018f1 mov rbp, rsp 00000001000018f4 sub rsp, 0x40 00000001000018f8 mov qword [rbp+var_8], 0x0 0000000100001900 mov qword [rbp+var_8], r13 0000000100001904 mov rdi, r13 ; argument "instance" for method imp___stubs__objc_retain 0000000100001907 mov qword [rbp+var_20], r13 000000010000190b call imp___stubs__objc_retain 0000000100001910 xor ecx, ecx 0000000100001912 mov edi, ecx 0000000100001914 mov qword [rbp+var_28], rax 0000000100001918 mov dword [rbp+var_2C], ecx 000000010000191b call _$s9DemoSwift14ViewControllerCMa 0000000100001920 mov rdi, qword [rbp+var_20] 0000000100001924 mov qword [rbp+var_18], rdi 0000000100001928 mov qword [rbp+var_10], rax 000000010000192c mov rsi, qword [0x100006250] ; @selector(viewDidLoad), argument "selector" for method imp___stubs__objc_msgSendSuper2 0000000100001933 lea rdi, qword [rbp+var_18] ; argument "super" for method imp___stubs__objc_msgSendSuper2 0000000100001937 mov qword [rbp+var_38], rdx 000000010000193b call imp___stubs__objc_msgSendSuper2 0000000100001940 mov rdi, qword [rbp+var_20] ; argument "instance" for method imp___stubs__objc_release 0000000100001944 call imp___stubs__objc_release 0000000100001949 mov rsi, qword [0x100006258] ; @selector(viewDidAppear:), argument "selector" for method imp___stubs__objc_msgSend 0000000100001950 mov rdi, qword [rbp+var_20] ; argument "instance" for method imp___stubs__objc_msgSend 0000000100001954 mov edx, dword [rbp+var_2C] 0000000100001957 call imp___stubs__objc_msgSend 000000010000195c add rsp, 0x40 0000000100001960 pop rbp 0000000100001961 ret
ở dòng 26 có gọi method imp___stubs__objc_msgSend
để gọi method viewDidAppear bằng Objc Runtime.
Tuy nhiên khi bạn gọi method swift có chứa cờ @objc thì chúng vẫn được gọi một cách bình thường( Sử dụng call
instruction).
Tổng kết
Cho dù bạn có viết ứng dụng bằng Swift thì chương trình của bạn cũng vẫn tương tác với Objc Runtime như bình thường. Bởi vì các Framework vẫn được viết bằng Objc. Khi nào Apple không dùng các Framework đó nữa mà viết lại toàn bộ bằng Swift thì mới dùng sử dụng Objc Runtime một cách tự động ở trong ứng dụng của bạn.
Tham khảo thêm:
https://nshipster.com/swift-objc-runtime/
https://developer.apple.com/documentation/swift/using_objective-c_runtime_features_in_swift