Rx-MVVM(1): Cấu trúc project – lib manager

Bài viết được sự cho phép của tác giả Lê Xuân Quỳnh

Vậy là bạn đã đọc qua về phần detail của khóa học rồi đúng không? Nếu chưa hãy đọc nó tại menu lập trình IOS nhé. Dù sao, chúng ta nên nhắc lại mục đích của hàng loạt bài viết liên quan tới Rx và MVVM trước. Để chúng ta biết chúng ta sẽ đạt được gì sau khi học.

  12 Packages và Libraries của Go cực kì mạnh mẽ mà bạn cần phải biết
  67 tools, libraries và resources giúp Web developer "dễ thở" hơn
Rx-MVVM(1): Cấu trúc project – lib manager
Cây thư mục project

Hãy xem tác giả đã bố trí như nào.

  1. Thư mục Application
  2. Thư mục Common
  3. Thư mục Configs
  4. Thư mục Extensions
  5. Thư mục Managers
  6. Thư mục Models
  7. Thư mục Modules
  8. Thư mục Networking
  9. Thư mục Third Party
  10. Thư mục Resources

Vậy là có tổng 10 thư mục. Chúng ta hãy tìm hiểu lần lượt từng thư mục nhé

1. Thư mục Application

Thư mục này có 3 file:

File AppDelegate.swift:

Hãy để ý dòng code này:

 let libsManager = LibsManager.shared

Ở đây tác giả tạo 1 singleton có tên là LibsManager. Nếu ai chưa biết về singleton, thì nôm na nó là 1 lớp được khởi tạo duy nhất 1 lần, và cho dù có gọi hàm tạo đi bao lần nữa nó cũng chỉ tạo có 1 lần thôi. Người ta hay dùng singleton cho những thứ chỉ được tạo 1 lần duy nhất, như quản lý AppDelegate, loading app… Ai chưa hiểu hãy giành 30 phút để google singleton là gì?

Vậy lớp LibsManager dùng để làm gì? Chúng ta hãy vào class này xem nhé:

Để ý đoạn code này:

if UserDefaults.standard.object(forKey: Configs.UserDefaultsKeys.bannersEnabled) == nil {
            bannersEnabled.accept(true)
        }
Rx-MVVM(1): Cấu trúc project – lib manager
lúc mới chạy app lần đầu

Bạn chạy app lần đầu, hình ảnh này sẽ xuất hiện. Còn các lần tiếp theo, nó không xuất hiện nữa. Và chính dòng code trên điều khiển việc đó.

Các dòng code tiếp theo, liên quan đến Rx:

 bannersEnabled.skip(1).subscribe(onNext: { (enabled) in
            UserDefaults.standard.set(enabled, forKey: Configs.UserDefaultsKeys.bannersEnabled)
            analytics.set(.adsEnabled(value: enabled))
        }).disposed(by: rx.disposeBag)

Bắt đầu mù tịt vì đụng đến Rx rồi. Bắt đầu khó nha. Đến đây thì chúng ta bắt buộc phải đọc về Rx để hiểu nó đang làm gì. Vậy thì hãy để ý tới biến bannersEnabled. Nó được khởi tạo dạng Rx chứ không phải biến thông thường bằng câu lệnh:

let bannersEnabled = BehaviorRelay(value: UserDefaults.standard.bool(forKey: Configs.UserDefaultsKeys.bannersEnabled))

Chúng ta thấy, BehaviorRelay là 1 hàm, có value phía trong là 1 giá trị kiểu boolean. Đọc về help của Xcode như sau:

Summary
BehaviorRelay is a wrapper for BehaviorSubject.
Discussion
Unlike BehaviorSubject it can’t terminate with error or completed.

Đến đây thì đọc không hiểu mô tê gì cả. Bắt buộc chúng ta phải đọc sample ở đâu đó trên mạng. OK, vậy chúng ta cùng tìm hiểu nhé.

Đầu tiên chúng ta tải source tại đây:

https://github.com/codetoanbug/sampleRx/tree/master

Sau đó các bạn mở file RxSample.xcworkspace và xem code trong file ViewController

       let bag = DisposeBag() //0

        let bannersEnabled = BehaviorRelay<String>(value: "code")//1

        bannersEnabled.accept("toan")//2
        bannersEnabled.accept("bug")//3

        let subscriber = bannersEnabled.subscribe { (event) in
            print("event = \(event)")
        } //4

        bannersEnabled.accept("viet")//5
        bannersEnabled.accept("nam")//6

        subscriber.disposed(by: bag)//7

Giải thích từng dòng:

Dòng 1: Các bạn khởi tạo 1 biến bannersEnabled có kiểu là BehaviorRelay. Đúng ra cái này không phải biến thông thường như các bạn đã quen, là nó có giá trị kiểu số 1,2 hay là kiểu string abc.. Mà thằng này có 1 đặc điểm là biến có thẻ phát ra 1 giá trị nào đó theo thời gian thực, cụ thể mình cho nó phát ra được các giá trị string. Giá trị khởi đầu là “code”

Dòng 2, 3: Mình đang ném 2 cái giá trị string “toan” và “bug” vào biến này để nó phát ra – nó như 1 cái đài phát thanh đang nói ra 2 câu đó.

Dòng 4: Mình khởi tạo biến subscriber, biến này dùng để lắng nghe các sự kiện phát ra từ biến bannersEnabled, giống như bạn chọn đúng kênh của đài phát thanh để nghe âm thanh trên kênh. này. Và câu lệnh print để hiển thị những gì mà nó nghe được.

Dòng 5, 6: tương tự dòng 2,3 mình lại phát ra 2 câu nói “viet” và “nam”.

Dòng 7: Các bạn hiểu nôm na là quy tắc dọn dẹp bộ nhớ tự động. Nó dựa vào biến dòng số 0 để dọn dẹp lại, và nôm na nó bắt buộc luôn khi các bạn khởi tạo các subscriber thì đều phải nhờ ông bag này dọn dẹp bộ nhớ khi không dùng nữa. Hiểu thế cho đơn giản.

Kết quả như sau:

event = next(bug)
event = next(viet)
event = next(nam)

Tại sao chúng ta chỉ nghe được 3 câu trên. Đơn giản do thằng bannersEnabled nó bắt đầu nghe tính từ thời điểm cách thời điểm nó đăng ký trước đó 1 tín hiệu, nghĩa là bắt đầu nghe từ từ bug trở đi về sau.

Vậy làm sao ta chỉ muốn nghe mỗi 2 từ viet nam thôi, cách đơn giản là thêm dòng lệnh này thay thế:

let subscriber = bannersEnabled.skip(1).subscribe { (event) in
            print("event = \(event)")
        }

skip(n) ở đây nghĩa là bỏ đi n giá trị phát ra tính từ thời điểm trước đăng ký 1 sự kiện cho đến về sau. Các bạn có thể thay đổi con số để loại bỏ. Ví dụ skip(2) thì nó bỏ từ bug và từ viet, còn nghe được mỗi từ nam.

Ồ! Vậy bạn đã hiểu các biến trên làm gì chưa? Bọn nó phát ra sự kiến, xử lý sự kiện trong hàm subscribe.

Quay trở lại hàm trong dự án của chúng ta, đoạn code trên:

let bannersEnabled = BehaviorRelay(value: UserDefaults.standard.bool(forKey: Configs.UserDefaultsKeys.bannersEnabled))//0
if UserDefaults.standard.object(forKey: Configs.UserDefaultsKeys.bannersEnabled) == nil {//1
            bannersEnabled.accept(true)//2
        }

        bannersEnabled.skip(1).subscribe(onNext: { (enabled) in
            UserDefaults.standard.set(enabled, forKey: Configs.UserDefaultsKeys.bannersEnabled)//3
            analytics.set(.adsEnabled(value: enabled))//4
        }).disposed(by: rx.disposeBag)//5

Dòng 1: lệnh if nếu như kiểm tra cái bannersEnabled chưa có giá trị(app chưa được mở bao giờ) thì gán giá trị bannersEnabled.accept(true) để phát ra tín hiệu rằng nó chưa mở banner bao giờ(dòng 2).

Dòng 3: Nó đăng ký 1 subscriber và bỏ đi 1 skip để loại bỏ giá trị khởi tao ban đầu(dòng 0), chỉ bắt đầu nghe ngóng từ dòng số 2 trở đi.

Vậy khi dòng 2 phát ra sự kiện thì các dòng 3, 4 được thực thi, và nếu mình đoán không nhầm thì nó làm 2 hành động: 1 là gán giá trị để bật banner lên, và chắc chắn ở đâu đó có 1 thằng lắng nghe khi giá trị này thay đổi, để hiển thị UI như hình trên. 2 là nó bật quảng cáo lên, và cũng tương tự, ở đâu đó nhiều thằng subscriber cũng lắng nghe sự thay đổi của biến này để hiển thị UI quảng cáo(adsEnabled nghĩa là bật quảng cáo, từ ads là quảng cáo, enabled là bật lên).

Vậy chúng ta đã hiểu hết logic đống loằng ngoằng trên rồi đúng không? Các bạn bắt đầu hình dung được là Rx sẽ là công cụ giúp chúng ta phát tín hiệu và lắng nghe tín hiệu. Vậy nó có ưu điểm gì:

Ví dụ khi chúng ta bật ads lên, thì mọi UI ở khắp mọi nơi khi hiển thị cho người dùng không cần quan tâm bạn thay đổi ở màn hình nào, đều đồng bộ được giá trị. Nếu bạn làm UIKit thuần, bạn sẽ phải vất vả dùng NSNotificationCenter hay là Protocol hay là closure function để báo hiệu giá trị thay đổi cho các màn khác. Và giả sử bạn có 5 cái màn khác nhau, sub view có, parrent view có, thì câu chuyện đồng bộ hóa dữ liệu trở nên khủng khiếp biết nhường nào 🙁

OK, Rx đáng để học đúng không nào? Vậy chúng ta cùng xem các đoạn code khác nhé.

Chúng ta xem tiếp file LibsManager nhé.

Đoạn code:

func setupLibs(with window: UIWindow? = nil) {
        let libsManager = LibsManager.shared
        libsManager.setupCocoaLumberjack()
        libsManager.setupAnalytics()
        libsManager.setupAds()
        libsManager.setupTheme()
        libsManager.setupKafkaRefresh()
        libsManager.setupFLEX()
        libsManager.setupKeyboardManager()
        libsManager.setupActivityView()
        libsManager.setupDropDown()
        libsManager.setupToast()
    }

Ở đây, hàm setupLibs có các chức năng sau:

setupCocoaLumberjack – vậy nó là gì? Nó là thư viện CocoaLumberjack:

https://github.com/CocoaLumberjack/CocoaLumberjack

Như dòng giới thiệu:

CocoaLumberjack is a fast & simple, yet powerful & flexible logging framework for Mac and iOS.

Vậy nó là 1 thư viện log, chức năng là ghi lại những hoạt động của người dùng trong ứng dụng. Ví dụ người dùng bấm nút, chuyển màn hình, gõ text… bất cứ action nào trên ứng dụng của mình. Điều này nhằm cho developer đánh giá được người dùng hay xem phần nào, màn nào bị crash không, … rất nhiều thứ hay ho mà chúng ta có thể thống kê được qua thư viện này. Nhằm cải thiện app tốt hơn hay triển khai các hoạt động marketing cho app…

Còn về chi tiết, chúng ta sẽ giới thiệu ở 1 vài viết khác về CocoaLumberjack , hoặc bạn có thể tự tìm hiểu dần qua google.

setupAnalytics là gì? Ở đây chúng ta đang nói đến Firebase của Google. Nó có nhiều chức năng lắm, và cụ thể là làm server, realtime, bắn notification cho app, tracking app, quảng cáo… Rất nhiều tính năng mà Google cung cấp cho chúng ta. Và tất nhiên như thông lệ, Google vẫn hay bài cho dùng thử giới hạn, dùng quá thì phải pay 😀 Đúng là ông lớn nên biết làm kinh tế quá ha.

Cụ thể nó như nào, chúng ta sẽ nghiên cứ 1 topic sâu về Firebase sau các bạn nhé.

setupAds là gì? cái tên tiếng Anh cũng nói rằng nó là cài đặt cho phần quảng cáo trong app rồi đúng không. Và ở đây chúng ta dùng quảng cáo của Google.

setupTheme – phần này khá mới trong thời gian gần đây, khi mà các ứng dụng bắt đầu quan tâm tới thị giác của người dùng nhiều hơn. Làm sao mà họ dùng app đỡ hại mắt, và dùng trong đêm tối sẽ theo 1 chế độ màu khác với dùng ban ngày. Người ta đẻ ra dark-mode và light-mode, chính ông lớn apple cũng chơi cuộc chơi này rồi. Nếu bạn đang cầm trên tay 1 thiết bị IOS 13, bạn sẽ hiểu được vào ban đêm, nhiều ứng dụng tự nhiên chuyển màu tối, đễ đơ hại mắt hơn. Hay thậm chí facebook cũng đã bật chế độ này trên appweb của họ rồi. Nó rất nhiều thứ hay ho để học, và trong ứng dụng này chúng ta chỉ cần bấm nút switch đơn giản để chuyển qua lại giữa 2 chế độ.

setupKafkaRefresh – Cái này là gì? Thư viện ở đây:

https://github.com/OpenFeyn/KafkaRefresh

Như readme mà họ viết, thì nó đơn giản là dạng loading khi bạn vuốt xuống ở table để cập nhật data mới, hay gọi là pull to refresh mà các bạn hay thấy trên nhiều ứng dụng. Trông cũng cool ngầu đấy chứ:

một dạng loading của thư viện

setupFLEX – Nó là gì?

OKie, chúng ta lại tìm hiểu thư viện của nó nào:

https://github.com/Flipboard/FLEX

Đọc readme:

FLEX (Flipboard Explorer) is a set of in-app debugging and exploration tools for iOS development. When presented, FLEX shows a toolbar that lives in a window above your application. From this toolbar, you can view and modify nearly every piece of state in your running application.

Chi tiết, bạn đọc readme tiếng Anh, còn ai không thích tiếng anh thì mình tìm thấy 1 bài viết khá đầy đủ ở đây. Nôm na nó là thư viện cho phép xem thông tin mọi thứ của app mà ta đang làm, bao gồm view, thông số biến, hàm, log hay database…

setupKeyboardManager – nghe tên cũng biết là quản lý bàn phím rồi đúng không?

Xem detail thư viện tại:

https://github.com/hackiftekhar/IQKeyboardManager

Như readme viết, thường khi chúng ta build ứng dụng sẽ hay gặp tình trạng bàn phím che mất chữ, không nhìn thấy được. Và thư viện này giúp chúng ta giải quyết vấn đề đó mà không phải viết bất kỳ 1 dòng code nào. Nghe ra gì phết. Okie vậy chúng ta cứ dùng thôi.

setupActivityView – vào phần chi tiết chúng ta thấy 1 đối tượng NVActivityIndicatorView.

Thư viện:

https://github.com/ninjaprox/NVActivityIndicatorView


Hình ảnh thư viện

Như vậy thì chúng ta cũng biết được là nó làm gì rồi đúng không? Đó là các hiệu ứng chờ đợi trên app rất đẹp mắt và cute.

setupDropDown – control bổ sung cho IOS

Thư viện:

https://github.com/AssistoLab/DropDown


Ví dụ về dropDown

Vậy cũng dễ hình dung nó là control bổ sung do IOS mặc định không có.

setupToast – chắc chắn là các thông báo đẩy như android, control bổ sung cho IOS.

Thư viện:

https://github.com/scalessec/Toast-Swift

Rx-MVVM(1): Cấu trúc project – lib manager
Các ví dụ về thông báo đẩy Toast

Khá ok!

Vậy là chúng ta đã dạo qua 1 vòng về file LibsManager – quản lý toàn bộ library của App. Theo mình đây là cách rất thông minh để có thể tập trung lại các thư viện, dễ quản lý và dễ sửa lỗi khi cần.

2. Thư mục Managers

Chúng ta cùng nghiên cứu về cách quản lý các thư viện của tác giả ở bài viết tiếp theo.

Bài viết gốc được đăng tải tại codetoanbug.com

Có thể bạn quan tâm:

Xem thêm Tìm việc Project manager hấp dẫn trên TopDev