Objective-C Runtime Part 3 – Swift & Objc Runtime

Objective-C Runtime Part 3 – Swift & Objc Runtime

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.

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

Leave a Reply

%d bloggers like this: