Contents
Chào bạn đến với Fx Studio. Đã có rất nhiều thứ mới xuất hiện trong thế giới Rx khi chúng ta cùng nhau khám phá từng chủ đề một. Và tiếp tục mang tới sự hack não cho bạn thêm nữa, thì bài viết này sẽ lại nói về một khái niệm mới. Đó là Delegate Proxy.
Trước khi đi vào bài thì chúng ta phụ đạo lại một chút kiến thức về Protocol & Delegate trước. Bạn tham khảo 2 link sau đây:
Còn nếu mọi thứ đã ổn rồi, thì …
Bắt đầu thôi!
Chuẩn bị
-
- Xcode 12
- Swift 5.3
- RxSwift 5.0
Trong bài này, việc demo code cũng không liên quan nhiều tới Project ở các bài trước. Nên bạn có thể tự tạo một Project hoặc Playground đơn giản để demo. Ngoài ra, bạn có thể checkout source code demo ở đây.
-
- Link: checkout
- Thư mục:
/Examples/BasicRxSwift
Delegate Proxy
Đôi khi những class của Cocoa không phải lúc nào cũng có các function trong không gian ReactiveX. Lúc này, bạn cần nâng cao thêm skill mới để biến Non-Rx thành Rx. Và cũng trong cách bài trước (Binding Observables) bạn đã cũng đã tự custom được Binder đơn giản cho properties của 1 class. Và nó cũng đủ đáp ứng yêu cầu của bạn rồi.
Nhưng vẫn còn một thế lực nữa trong giới iOS mà bạn cần tiêu diệt, đó là …
Protocol
Mà đại diện chính của Protocol là Delegate. Nó được sử dụng khá là nhiều. Thậm chí trở thành chuẩn chung cho các class trong Cocoa framework, như UITableViewDelegate, UICollectionViewDelegate … Nên việc nắm bắt được các function của các Delegate cũng là một công việc phải làm được trong thế giới Rx.
Từ đó, khái niệm Delegate Proxy ra đời. Ta sẽ có một Observable được uỷ quyền, nhằm giúp cho việc truyền các sự kiện từ các function của các Protocol Delegate tới được các Subscriber. Nói dễ hiểu thì như sau:
Biến đổi Protocol Delegate thành Observable.
Cũng để cho việc truyền đạt tư tưởng này tới bạn một cách nhanh nhất. Thì mình sẽ tập trung vào việc Rx hoá cho các Protocol Delegate của class CLLocationManager. Và hi vọng từ đó, bạn có thể ngộ ra được chân lý & ánh sáng của cuộc đời này.
Khái quát thì công việc của chúng ta sẽ bao gồm 5 bước:
-
- Create Extensions
- Register
- Extension ReactiveX
- Method Invoked
- Implement
1. Create Extensions
Bắt đầu bằng việc tạo file *.swift
với tên là CLLocationManager+Rx.swift, để định nghĩa các phương thức & thuộc tính nằm trong không gian Rx
. Trong file này chúng ta có 2 phần:
- Extension ReactiveX
- Delegate Proxy
Với Extension ReactiveX là để đảm bảo được việc mở rộng trong không gian Rx cho class CLLocationManager. Class phải được mở rộng Reactive<Base>
và nó lại kế thừa tới ReactiveCompatible protocol .
Với Delegate Proxy là một khái niệm mới và bạn sẽ tạo một class mới. Delegate Proxy sẽ là lớp trung gian, đối tượng này sẽ đảm nhận việc nhận dữ liệu và phát lại dưới dạng Observable.
Sự kết hợp của Delegate Proxy và Reactive sẽ biến các extension
của các class trong Cocoa trở thành Rx hoá.
Delegate proxy + = AClass.rx... Reactive<AClass>
Quay lại chủ đề chính của chúng ta. Tạo 2 extension như sau:
CLLocationManager
extension CLLocationManager: HasDelegate { public typealias Delegate = CLLocationManagerDelegate }
Extension này sẽ báo cho Rx biết class CLLocationManager có Delegate. Bạn cần nhớ là CLLocationManager & CLLocationManagerDelegate là 2 thực thể khác nhau. Chỉ giống nhau cái tên phía trước thôi.
RxCLLocationManagerDelegateProxy
class RxCLLocationManagerDelegateProxy: DelegateProxy<CLLocationManager, CLLocationManagerDelegate>, DelegateProxyType, CLLocationManagerDelegate { }
Class này sẽ là Proxy cho class CLLocationManager. Và ngay sau đó thì 1 đối tượng Observable sẽ được tạo ra và có subscription tới. Điều này đơn giản hoá bới HasDelegate protocol (được cung cấp bởi RxCocoa). Nếu không làm công việc này, thì phải cần phải khai báo thêm 2 function sau cho việc kế thừa DelegateProxyType. Đó là:
public static func currentDelegate(for object: ParentObject) -> Delegate? { return object.delegate } public static func setCurrentDelegate(_ delegate: Delegate?, to object: ParentObject) { object.delegate = delegate }
Thầm cảm ơn tới HasDelegate protocol. Em nó giúp cho bạn giản đi một nữa công việc rồi.
2. Register
Như trên, ta đã có 1 class Proxy thì sẽ cần phải có các phương thức sau:
class RxCLLocationManagerDelegateProxy: DelegateProxy<CLLocationManager, CLLocationManagerDelegate>, DelegateProxyType, CLLocationManagerDelegate { static func registerKnownImplementations() { } static func currentDelegate(for object: AnyObject) -> Any? { } static func setCurrentDelegate(_ delegate: Any?, to object: AnyObject) { } }
Do 2 function về delegate đã được giải quyết với HasDelegate Protocol rồi. Nên ta sẽ xoá nó đi và quản tâm tới em register
thôi. Nhưng mà việc đầu tiên cần phải làm vẫn là khai báo thêm 1 thuộc tính cho Base (CLLocationManager).
weak public private(set) var locationManager: CLLocationManager?
Vì chúng ta có 1 Property mới nên nó cần phải được khởi tạo. Do đó, ta phải thêm 1 function init
nữa.
public init(locationManager: ParentObject) { self.locationManager = locationManager super.init(parentObject: locationManager, delegateProxy: RxCLLocationManagerDelegateProxy.self) }
Bạn cần chú ý tới:
ParentObject
nó chính làBase
và cũng là CLLocationManager trong ví dụ của chúng ta.- Sau đó gán thuộc tính với tạo với tham số
ParentObject
- Cuối cùng gọi
super.init
để hoàn thiện việc khởi tạo
Khi các việc khởi tạo đã xong thì tới việc chính là đăng ký các function delegate và uỷ quyền cho class Proxy này.
static func registerKnownImplementations() { self.register { RxCLLocationManagerDelegateProxy(locationManager: $0) } }
Nó sẽ register
cho tất cả và đăng ký tới tất cả các function của Delegate (CLLocationManagerDelegate). Nhằm đưa dữ liệu từ đối tượng Base (CLLocationManager) đến các đối tượng Observable được kết nối tới.
Đây là cách mở rộng (extension) 1 class bằng delegate proxy pattern từ RxCocoa.
3. Extension ReactiveX
Bây giờ, ta tiếp tục mở rộng thêm không gian Rx từ struct chính là Reactive cho class CLLocationManager. Để có thể sử dụng .rx
huyền thoại. Thêm 1 extension như sau:
public extension Reactive where Base: CLLocationManager { }
Chúng ta đã quen thuộc với cách tạo thêm function hoặc property trong không gian Reactive này rồi. Bạn hãy nhớ lại phần custom Binder nha. Và tất nhiên, chúng ta cần tới đối tượng Proxy để làm cầu nối trung gian rồi. Bạn tiếp tục thêm đoạn code sau.
var delegate : DelegateProxy<CLLocationManager, CLLocationManagerDelegate> { return RxCLLocationManagerDelegateProxy.proxy(for: base) }
Trong đó:
delegate
là đối tượng DelegateProxy- Khởi tạo nó bằng class Proxy được khai báo ở trên
- Với tham số cho
.proxy
chính làbase
(là thực thể CLLocationManager được sử dụng.rx
)
4. Method Invoked
Giờ là phần cuối cùng, bạn muốn dùng tới function nào trong Delegate (CLLocationManagerDelegate) thì hãy mời nó vào để cùng quẩy.
Nếu bạn xem nhiều tutorial khác trên mạng thì khúc này sẽ rối não lắm. Kinh nghiệm xương máu mà mình đúc kết được. Tới đây thì bạn nào logic như thế nào thì sẽ có cách riêng để tự triển khai tương ứng.
Trong ví dụ demo này, ta cần lấy được location
mới nhất được update từ đối tượng CLLocationManager. Trước tiên, bạn xác định cần dùng function nào trong CLLocationManagerDelegate. Ta chọn em này:
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation])
OKAY rồi, chúng ta lại quay về extension Reactive ở trên và thêm đoạn code sau:
var didUpdateLocation: Observable<[CLLocation]> { return delegate.methodInvoked(#selector(CLLocationManagerDelegate.locationManager(_:didUpdateLocations:))) .map { parameters in return parameters[1] as! [CLLocation] } }
Tiến hành phân tích chúng nó một chút
didUpdateLocation
là một Observable với kiểu giá trị trả về là một Array CLLocation- Sử dụng chính Delegate Proxy bằng việc dùng method
methodInvoked
. Nó như một lời mời gọi function của CLLocationManagerDelegate mà ta đã chọn ở trên. - Vì là của Objective-C nên bạn sử dụng
#selector
và cung cấp đúng function đã chọn - Kết quả trả về sẽ là 1 Observable với kiểu như thế này
Observable<[Any]>
Trong đó[Any]
là một mãng được tạo từ các tham số trong function của CLLocationManagerDelegate. - Cuối cùng ta dùng
map
để đưa[Any]
thành[CLLocation]
. Tất nhiên là không thể được ngay lập tức. Vì function của CLLocationManagerDelegate có tới 2 tham số. - Xác định từ trước thì tham số thứ 2
parameters[1]
sẽ là[CLLocation]
. Nên chỉ việc return nó trongmap
là xong.
Cuối cùng cũng xong các bước tạo mệt mỏi này. Hi vọng bạn sẽ kiên trì để hết và kĩ. Nếu chỉ cần lơ là một tí thì lại không hiểu chi hết. Mọi việc cứ copy dán mù quán. Mình đã gãy tại đây tới 3 lần rồi.
Đau!
5. Implement
Mọi thứ setup đã xong. Bước này chỉ là cách dùng nó thôi. Tại WeatherCityViewController bạn khai báo thêm 1 property CLLocationManager. Nhớ import CoreLocation
trước nha.
private let locationManager = CLLocationManager()
Về mặt UI, bạn thêm 1 UIButton và tạo IBOutlet cho nó. Ta đặt tên là locationButton
. Button này chịu trách nhiệm gọi việc startUpdateLocation
. Thay vì tạo 1 IBAction thì ta cũng dùng luôn ControlEvent cho nó xịn sò.
Tại viewDidLoad
bạn thêm đoạn code sau:
locationButton.rx.tap .subscribe(onNext: { [weak self] in guard let self = self else { return } self.locationManager.requestWhenInUseAuthorization() self.locationManager.startUpdatingLocation() }) .disposed(by: bag)
Sử dụng rx.tap
thì mọi thứ có vẻ trông pro lên liền. Công việc chỉ còn là subscribe
và handle vào trong đó. Với 2 việc chính
requestWhenInUseAuthorization()
để xin cấp quyềnstartUpdatingLocation()
bắt đầu việc tracking location theo GPS
Tiếp theo, ta cần subscribe
tới đối tượng locationManager
để đăng ký tới function update Location, mà đã dày công setup ở trên. Thêm tiếp đoạn code sau nha:
locationManager.rx.didUpdateLocation .subscribe(onNext: { locations in print(locations) }) .disposed(by: bag)
Bạn hãy build project và nhấn vào locationButton
để xem kết quả đúng như ta mong muốn không. Nếu ra được kết quả thì chúc mừng bạn đã vượt qua skill khó này. Ví dụ kết quả như sau:
Khá là EZ phải không nào! Từ bây giờ trở đi, chúng ta sẽ quên đi việc implement các function của các Delegate protocol đi là vừa rồi.
Cùng nhau quẩy với Rx nào!
Tạm kết
- Khái niệm về Delegate Proxy
- Mở rộng không gian Rx cho các class
- Custom Delegate Proxy class
Okay! Tới đây thì mình xin kết thúc bài viết này. Nếu có gì thắc mắc hay góp ý cho mình thì bạn có thể để lại bình luận hoặc gởi email theo trang Contact.
Cảm ơn bạn đã đọc bài viết này!
Related Posts:
Written by chuotfx
Hãy ngồi xuống, uống miếng bánh và ăn miếng trà. Chúng ta cùng nhau đàm đạo về đời, về code nhóe!
3 comments
Leave a Reply Cancel reply
Fan page
Tags
Recent Posts
- Charles Proxy – Phần 1 : Giới thiệu, cài đặt và cấu hình
- Complete Concurrency với Swift 6
- 300 Bài code thiếu nhi bằng Python – Ebook
- Builder Pattern trong 10 phút
- Observer Pattern trong 10 phút
- Memento Pattern trong 10 phút
- Strategy Pattern trong 10 phút
- Automatic Reference Counting (ARC) trong 10 phút
- Autoresizing Masks trong 10 phút
- Regular Expression (Regex) trong Swift
You may also like:
Archives
- September 2024 (1)
- July 2024 (1)
- June 2024 (1)
- May 2024 (4)
- April 2024 (2)
- March 2024 (5)
- January 2024 (4)
- February 2023 (1)
- January 2023 (2)
- November 2022 (2)
- October 2022 (1)
- September 2022 (5)
- August 2022 (6)
- July 2022 (7)
- June 2022 (8)
- May 2022 (5)
- April 2022 (1)
- March 2022 (3)
- February 2022 (5)
- January 2022 (4)
- December 2021 (6)
- November 2021 (8)
- October 2021 (8)
- September 2021 (8)
- August 2021 (8)
- July 2021 (9)
- June 2021 (8)
- May 2021 (7)
- April 2021 (11)
- March 2021 (12)
- February 2021 (3)
- January 2021 (3)
- December 2020 (3)
- November 2020 (9)
- October 2020 (7)
- September 2020 (17)
- August 2020 (1)
- July 2020 (3)
- June 2020 (1)
- May 2020 (2)
- April 2020 (3)
- March 2020 (20)
- February 2020 (5)
- January 2020 (2)
- December 2019 (12)
- November 2019 (12)
- October 2019 (19)
- September 2019 (17)
- August 2019 (10)
Vô tình đọc được bài viết này khá hữu ích, cảm ơn bạn đã chia sẻ. Tiện đây cho mình hỏi trong CLLocationManagerDelegate có 1 func locationManagerShouldDisplayHeadingCalibration thì mình mapping sang Rx như thế nào vậy ạ? mình có search nhưng vẫn chưa biết cách implement cho hàm này.
Theo nguyên tắc thì function trên của bạn có return về value (khác void) nên bạn phải dùng Forward Delegate thêm. Bạn đọc thêm tại bài viết này: https://fxstudio.dev/rxcocoa-basic-forward-delegate/
Hiện tại mình đang bận nên chưa check được. Ngoài ra thì còn có nhiều cách để mở rộng không gian Reactive cho function đó.
Cảm ơn bạn nhiều nhé 👍