การทำ Swizzling สำหรับต้องการดัก LifeCycle ของ Class

Apinun (Nun) Wongintawang
te<h @TDG
Published in
2 min readDec 5, 2019
picture by ถ่ายกับเสี่ย by Nuns Swordmanboy

จากที่ประสบปัญหาเกี่ยวกับการดักจับสถานะ ViewWillAppear ซึ่งเป็นสถานะที่เริ่มแสดง View ของ ViewController แต่จะมีบางเคสที่ไม่สามารถดักจับได้เช่น ในการ Present ViewController ที่จะให้แสดง Dialog แจ้งเตือนส่วนใหญ่จะใช้วิธีการตั้งค่า modalPresentationStyle ในการแสดงผลแบบ overFullScreen ซึ่งอาจสั่งจากหน้าไหนก็ได้เพราะส่วนใหญ่ Dialog ViewController จะเน้นการเปิดจากหน้าที่ Presented เป็นหลักทำให้ไม่สามารถควบคุม LifeCycle ของทุกคลาสได้ ข้อมูลของ UImodalPresentationStyle สามารถตรวจสอบการเปิดและปิดของ ViewController ได้มีดังนี้ครับ

from :https://gist.githubusercontent.com/regularberry/da8bc937fe5b7204ade75d64d94933f6/raw/7ee5bf7df3605fa719d8628d70755c6758f53cc3/modalPresentationStyles.csv

วันนี้ขอเสนอวิธีที่เรียกว่า Swizzling ที่สามารถดักจับ LifeCycle ของ ViewController ได้ครับผม

Swizzling เป็นการดักจับ Function ที่เป็น LifeCycle ของ ViewController ซึ่งปกติการดักจับ Function พวกนี้จะไม่สามารถทำได้ตรงๆ จากที่ลองมาหลายวิธีมีดังนี้ครับ

1. ลองพยายามดักจาก Extension​ (ติดปัญหา)

สามารถดักการ Dismiss ได้แต่กลายเป็นว่าคำสั่ง Dismiss ของทุกอันจะต้องมาสั่งในนี้หมดเลยทำให้ส่งผลกับการทำงานของทุกคลาสเลยไม่เหมาะที่จะใช้ครับ

2. ทำ Notification สำหรับใส่ไว้ในคลาสที่มีการ Present แบบ OverFullScreen (ติดปัญหา)

แต่ว่าวิธีนี้ต้องใส่ตัว NotificationCenter ไปทุกคลาสถ้ามี 100 class ก็ต้องใส่ 100 class ไม่อยากนึกถึงตอนมีการปรับเปลี่ยนเงื่อนไขเลยครับจะต้องแก้เยอะแค่ไหนและเทสเยอะแค่ไหน

3. การทำ Swizzling

private let swizzling: (AnyClass, Selector, Selector) -> () = { forClass, originalSelector, swizzledSelector in 
let
originalMethod = class_getInstanceMethod(forClass, originalSelector)
let swizzledMethod = class_getInstanceMethod(forClass, swizzledSelector)
method_exchangeImplementations(originalMethod!, swizzledMethod!)
}
extension UIViewController { //Step 1 static let classInit: Void = { //Step 2
let originalSelector = #selector(viewDidAppear(_:))
let swizzledSelector = #selector(swizzledViewDidAppear(_:))
swizzling(UIViewController.self, originalSelector, swizzledSelector)
}()
@objc func swizzledViewDidAppear(_ animated: Bool) {
print("swizzledViewDidAppear")
swizzledViewDidAppear(animated)
}
}

และต้องนำ UIViewController.classInit ไปใส่ไว้ใน AppDelegate ด้วยนะครับ

จากโค้ดข้างบนเราจะใช้งานตัวแปร swizzling ในการตั้งค่า selector ของฟังก์ชั่นแต่ละคลาส ซึ่งใช้งาน class_getInstanceMethod สำหรับการตั้งค่า Selecter ของ Objective-c และสุดท้ายใช้งาน method_exchangeImplementations เพื่อทำการ implement ทั้งสอง Selector เข้าฟังก์ชั่นตามที่ต้องการ

โดยการใช้งาน swizzling ให้มาดูที่ static let classInit ได้เลยครับ เราจะตั้งค่าตัว selector ในฟังก์ชั่นนั้น โดยการสร้าง selector viewDidAppear ของตัว LifeCycle หลักเข้าไปก่อน จากนั้นจะทำการสร้าง Swizzling ตัว LifeCycle ให้มาฟังก์ชั่นที่เราดักจับไว้จากในโค้ดคือ swizzledViewDidAppear ครับผม

แค่นี้เราจะสามารถดักจับ LifeCycle ได้แล้วครับ

ทดสอบการทำงาน

จะมีหน้าจอการทำงานสองหน้านะครับ หน้าแรกคือหน้า MainViewController และหน้าสอง ชื่อ DialogViewController

MainViewController และ DialogViewController

จะปริ้นค่าดังนี้ครับ

viewDidLoad MainViewController   //เข้าหน้า MainonShowDialog DialogViewController //แสดง DialogswizzledViewWillAppear //แสดง ViewWillAppear ของ overFullScreenswizzledViewDidAppear //แสดง ViewDidAppear ของ overFullScreenonClose DialogViewController //ปิด DialogswizzledViewDidDisappear //แสดง ViewDidDisappear ของ overFullScreen

ประสบความสำเร็จกับการใช้งาน Swizzling ที่ดักจับการทำงาน Life Cycle ของ ViewController กันแล้วนะครับ

ขอบคุณที่ติดตามนะครับ

GitHub

reference

--

--