Author: Hoang Anh Tuan

  • How to make a super app?

    How to make a super app?

    Ngày nay, các ứng dụng Super app ngày càng phổ biến. Đứng ở phía người dùng, super app là 1 app mà "thứ gì cũng có". Sự xuất hiện của Super app hướng đến sự tiện lợi dành cho người dùng khi mà họ có thể làm mọi thứ trên cùng 1 ứng dụng, từ việc thanh toán tiền điện, nước, đặt xe, đặt vé máy bay, đặt phòng, shopping, …

    Còn đứng ở trên góc nhìn của 1 developer thì sao? Làm thế nào để có thể đưa 1 app vào thành 1 ứng dụng nhỏ bên trong 1 app khác? Nếu đưa nhiều ứng dụng vào trong 1 super app như vậy, size của super app chắc chắn sẽ phải tăng lên rất nhiều. 1 app thông thường sẽ có size từ khoảng 50-150MB. Vậy tại sao 1 super app như shopee, grab, … có rất nhiều mini app lại chỉ có size 300MB?… Đó là 2 trong những câu hỏi mình đã từng băn khoăn về super app và chưa có câu trả lời. Sau 1 khoảng thời gian trực tiếp tham gia phát triển 1 super app, mình đã hiểu hơn về cách phát triển 1 super app.

    Nội dung

    • Super app app, mini app là gì?
    • Nguồn gốc của Super app?
    • Lợi ích của super app?
    • Tạo ra 1 super app?
    • Tổng kết
    • Reference

    Super app, mini app là gì?

    2 thuật ngữ super app và mini app đi song hành cùng với nhau.

    Super app là những app cung cấp nhiều services khác nhau, và những service này có thể được làm từ những team khác nhau.

    Mỗi service mà super app cung cấp được coi là 1 mini app (hay mini programs).

    Lợi ích của super app

    Đối với người dùng

    • 1 Ứng dụng cung cấp nhiều services cho người dùng => Người dùng có thể trải nghiệm nhiều loại dịch vụ, tiện ích ngay trên cùng 1 ứng dụng mà không cần phải chuyển app, hay download 1 app mới. Ví dụ: Zalo là 1 super app, gồm nhiều mini app như: Thanh toán tiền điện/nước, mua vé máy bay, nạp thẻ điện thoại, đặt xe, shoping, … => Người dùng không cần phải down thêm nhiều app mà chỉ cần 1 app là đủ.
    • Tiết kiệm dung lượng điện thoại vì không cần tải các app khác.

    Đối với công ty

    • Chia sẻ được lượng người dùng giữa các super app và mini app với nhau. Ví dụ: Zalo là 1 super app, chứa Lazada là 1 mini app nhỏ => Lazada có thể hưởng được lượng user hiện có của Zalo.
    • super app trở nên sinh động, nhiều tính năng hơn để cạnh tranh với các app khác.
    Công ty xây dựng 1 hệ sinh thái lớn mạnh, thu hút người dùng

    Super App và Mini App kết nối với nhau thế nào?

    Hãy thử suy nghĩ 1 vài câu hỏi sau đây trước khi đọc câu trả lời.

    Câu hỏi 1: Làm thế nào để SuperApp có thể mở được 1 MiniApp?
    Câu trả lời đó là source code của super app phải chứa code của mini app. Nhưng source code của mini app này sẽ được đóng gói dưới dạng framework.
    Team MiniApp sẽ build service mà họ muốn được tích hợp vào super app thành framework, và gửi cho team super app để team super app embed vào source code của họ. Các framework này có những yêu cầu sau:

    • Phải cung cấp 1 public function trả ra 1 View – view này là đầu vào của mini app.
    • Team MiniApp sẽ chỉ build phần service mà họ muốn đưa vào super app thành framework, chứ không build toàn bộ application của họ thành framework. Bởi nếu build toàn bộ app thành framework -> size của framework sẽ tăng lên đáng kể -> size của super app cũng sẽ tăng lên 1 tương đương. Vậy thì mỗi super app sẽ có size lên đến hàng nghìn MB chứ không còn là 300MB nữa.
      Ví dụ: Zalo muốn đưa Service Zalo Pay vào 1 super app khác, thì họ sẽ chỉ build service này thành framework, và đưa cho bên super app tích hợp, chứ ko phải build cả app Zalo thành 1 framework.

    Câu hỏi 2: Nếu mỗi mini app cung cấp 1 kiểu function có tên khác nhau, thì super app phải xử lí thế nào?
    Team SuperApp sẽ tạo ra 1 Interface / Protocol. Interface này sẽ là chuẩn chung cho toàn bộ MiniApp. Sau đó, team SuperApp sẽ đóng gói interface này thành 1 framework, và đưa cho phía mini app implement.

    Triển khai

    Step 1: Xây dựng 1 interface làm chuẩn chung cho các mini app, và dóng gói nó lại thành 1 framework và đưa cho mini app sử dụng.

    Step 2: Team MiniApp implement interface đó, và sau đó build source code thành framework và gửi cho team SuperApp.

    Step 3: SuperApp lấy ra input view mà team MiniApp đã cung cấp để hiển thị.

    Kết quả:

    Tổng kết:

    • Bài viết này chỉ xây dựng một demo đơn giản về xây dựng 1 super app để đem lại 1 cái nhìn tổng quan về cách thức super app và mini app kết nối với nhau.
    • Trên thực tế, để xây dựng 1 super app còn gặp nhiều khó khăn khác như: Authen giữa super app và mini app, thanh toán giữa super app và mini app, … Đó cũng là những bài toán hay cần phải giải quyết.

    Reference

    https://hoangatuan.medium.com/create-a-xcframework-for-ios-986c4fc1421e: Cách tạo framework
    https://www.brandsvietnam.com/congdong/topic/27240-WeChat-da-khoi-nguon-thuat-ngu-Super-App-nhu-the-nao: Lịch sử của super app

    Source code:

  • Method Swizzling in Swift

    Method Swizzling in Swift

    Table of Contents

    • Problems?
    • What is method swizziling?
    • Swizzling CocoaTouch class
    • Swizzling custom Swift class
    • Note
    • References

    Problems?

    If you meet one of these situations, how will you handle?

    • Firebase SDK only provides you a function named "showLoginView()" to present a LoginViewController. The problem is all of view controllers in your app use a custom background color? So how can we set background color for LoginViewController?
    • Firebase SDK saves value to UserDataDefault, but you expect that all keys must have a prefix, for example is "FPT". How can you do this?

    Method swizzling comes to rescue

    Defination

    So what is method swizzling?

    Method swizzling is the process of changing the implementation of an existing selector at runtime.

    Speak in a easy-to-understand way, method swizzling acts like swap(a, b) function. It will takes implementation of function 1 and function 2 and swap.

    Swizzling

    Use swizzling to solve the problem:

    So with method swizzling, we can change the implementation of viewDidLoad in LoginViewController to our custom implementation that calls change backgroundColor.

    Swizzling CocoaTouch class

    To swizzle, you just need to follow some steps:

    1. Create a new method with your custom implementation.
    2. Get default method selector reference.
    3. Get new method selector reference.
    4. Use objective-C runtime to switch the selectors.

    Let’s swizzle:

    First, create a demo view controller and create new method with your custom implementation.

    class ViewController: UIViewController {
        override func viewDidLoad() {
            super.viewDidLoad()
            debugPrint("Call default view did load")
        }
    }
    
    extension UIViewController {
        // 1 
        @objc func viewDidLoadSwizzlingMethod() {
            // 2 
            self.viewDidLoadSwizzlingMethod()
            
            // 3 
            debugPrint("Swizzleeee. Call NEW view did load ")
            view.backgroundColor = .yellow
        }
    }
    
    
    1. Create new method with custom implementation
    2. If you add this line, it will call new implementation first, then call default implementation. If. you don’t add this line, it will call new implentation only.
    3. Your custom implementation

    Next, create a function where the swizzle takes place.

    extension UIViewController {
    ...
         static func startSwizzlingViewDidLoad() {
            // 1
            let defaultSelector = #selector(viewDidLoad)
            let newSelector = #selector(viewDidLoadSwizzlingMethod)
    
            // 2
            let defaultInstace = class_getInstanceMethod(UIViewController.self, defaultSelector)
            let newInstance = class_getInstanceMethod(UIViewController.self, newSelector)
            
            // 3
            if let instance1 = defaultInstace, let instance2 = newInstance {
                debugPrint("Swizzlle for all view controller success")
                method_exchangeImplementations(instance1, instance2)
            }
        }
    }
    1. Create 2 selectors of default method and new method.
    2. Create 2 references of 2 selectors by using class_getInstanceMethod.
    3. Use Objective-C runtime to “swaps implementation” of 2 selectors.

    The final step is call the function startSwizzlingViewDidLoad. We must swizzle before the viewController call it’s default viewDidLoad.
    Here, I will swizzle at AppDelegate to make all ViewControllers in apps change backgroundColor to yellow.

    class AppDelegate {
        func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
            UIViewController.startSwizzlingViewDidLoad()
            return true
        }
    
        ...
    }

    As you can see, all of our view controllers will be set backgroundColor to yellow color.

    Swizzling custom Swift class

    To use method swizzling with your Swift classes, you just need to do:

    • The methods you want to swizzle must have the dynamic attribute.
    • Flow the steps like swizzle CocoaTouch class
    class GST {
        
        @objc dynamic func workFromHome() {
            print("Working...")
        }
        
        @objc dynamic func swizzleWorkFromHome() {
            print("Playing from home...")
        }
        
        static func startSwizzling() {
            let defaultInstance = class_getInstanceMethod(GST.self, #selector(GST.workFromHome))
            let newInstance = class_getInstanceMethod(GST.self, #selector(GST.swizzleWorkFromHome))
            
            if let instance1 = defaultInstance, let instance2 = newInstance {
                method_exchangeImplementations(instance1, instance2)
            }
        }
    }

    And the results:

    Note

    1. If you swizzle multiple times default method, that default method will have the implementation of the lastest swizzle method.

    Example:

    • You swizzle viewDidLoad with your custom method in your AppDelegate.
    • Firebase swizzle viewDidLoad with its custom method when FirebaseSDK init in your app => after AppDelegate.
    • When a ViewController init => It will takes the implementation of Firebase’s custom method instead of your, because Firebase swizzle after you swizzle.
    1. If you are shipping a framework which is used by hundreds of apps, better not to use swizzling in this case. If you must use swizzling, you should added it to the framework’s document.

    References:

  • Composition over Inheritance

    Composition over Inheritance

    Content

    • Vấn đề về sử dụng Inheritance
    • Disadvantages of Inheritance
    • Composition là gì?
    • Sử dụng Composition để thay thế Inheritance.

    Vấn đề về sử dụng Inheritance

    Inheritance là 1 trong những core concept của OOP. 1 vài lợi ích mà Inheritance mang lại đó là:

    • Code reusebility: Các lớp con có các properties và functions của lớp cha -> Có thể giảm sự duplicate code giữa các lớp con bằng cách đặt các phần code bị duplicate vào lớp cha.
    • Code dễ đọc và dễ hiểu hơn.
    • Inheritance đại diện cho mối quan hệ IS-A relation ship -> Các lớp con có thể thay thế cho lớp cha

    Mặc dù Inheritance được sử dụng rộng rãi, nhưng liệu nó có phải là 1 concept mạnh mẽ nên áp dụng mọi lúc mọi nơi? Hãy cùng suy ngẫm về 1 ví dụ sau:

    • SkyBird và MountainBird là 2 class kế thừa từ class Bird. SkyBird và MountainBird có chung hành vi eat() và fly(), do đó chúng ta đặt 2 phương thức này ở lớp Bird để reuse code.
    • Hành vi jug() là khác nhau nên chúng ta sẽ tự override lại ở lớp con.

    Nghe có vẻ ổn. Bây giờ, khách hàng muốn chúng ta thêm 2 class là WildBird và CloudBird. Chúng có hành vi fly() khác so với lớp Bird, vì vậy chúng ta sẽ override lại hành vi fly(), từ đó có thiết kế:

    Nhưng vấn đề ở đây là WildBird and CloudBird có cùng hành vi fly(). Nếu chúng ta thiết kế như Diagram ở trên thì chúng ta đã tạo ra 1 sự duplicate code hành vi fly giữua lớp WildBird và CloudBird.

    Bạn có thể nghĩ 1 cách để tránh duplicate code đó là tạo 1 class cha cho WildBird và CloudBird, và đặt hành vi fly() chung vào đó, như sau:

    Khá OK. Nhưng giờ khách hàng tiếp tục muốn 1 số hành vi khác như WildBird và SkyBird có chung hành vi eat() mới – khác hành vi eat của Bird, CloudBird và MoutainBird có chung hành vi jug(), …

    Nếu bạn tiếp tục cố gắng tạo ra các lớp cha mới thì bạn sẽ tạo ra 1 kiến trúc ngày càng mở rộng, càng rối cho hệ thống của bạn. Ngoài ra bạn còn phải sửa lại kiến trúc hiện có -> Dẫn đến sửa lại rất nhiều class, code hiện có (Vi phạm nguyên tắc OCP).

    Disadvantages of inheritance

    Giờ thì hãy cùng điểm qua 1 vài nhược điểm của kế thừa:

    • 1 object chỉ có thể kế thừa từ 1 lớp cha.
    • Dễ dàng tạo ra 1 kiến trúc lớn, phức tạp
    • Thay đổi 1 function ở lớp cha -> Ảnh hưởng tới toàn bộ lớp con.
    • Thường thì chúng ta cố gắng nhét tất cả các func chung vào lớp cha -> Dẫn đến lớp cha bị phình to, đồng thời có những func của lớp cha mà lớp con này không cần dùng đến. (Điều này rất dễ thấy trong chính project hiện tại của các bạn).
    • Khi mở rộng source code thường dẫn đến phải thay đổi các code hiện có.

    Composition là gì?

    Khác với Inheritance đại diện cho quan hệ IS-A giữa 2 class, thì Composition đại diện cho quan hệ HAS-A giữa 2 class:

    • Composition có nghĩa là "thành phần". Ở ví dụ trên, Wheel là 1 thành phần của Car, nói cách khác, Car chứa 1 instance kiểu Wheel.
    • Có thể hiểu Composition như việc xếp hình. Thay vì đặt các chức năng vào đối tượng gốc, bạn sẽ tách các chức năng ra thành các đối tượng riêng lẻ, sau đó ghép các thành phần đó lại để bổ sung thêm chức năng cho đối tượng gốc của bạn.
    Thay vì đặt các login run, start,… vào đối tượng gốc Car thì ta sẽ tách ra thành các thành phần riêng lẻ

    Lợi ích của Composition:

    • Giải quyết được vấn đề tạo ra 1 big hirachy khi dùng kế thừa.
    • Dễ dàng reuse code giữa các đối tượng 1 cách linh hoạt.
      Ví dụ: Ta có thể reuse các logic turnOn, turnOff giữa các loại xe mà không cần tạo class cha.
    • Khi thêm mới/ thay đổi các hành vi của đối tượng thì không cần phải thay đổi quá nhiều code hiện có, chỉ cần viết thêm code và thay thế các hành vi mới vào hành vi hiện tại. (Đảm bảo nguyên lí SRP và OCP).
    • Để reuse code mà có liên quan đến UI kéo thả thì sử dụng Inheritance là bất khả thi, còn Composition vẫn có thể giải quyết được 😉

    Áp dụng Composition để giải quyết vấn đề ở đầu bài viết.

    1. Tạo ra các interface cho các hành vi fly và eat lần lượt là IFlyBehaviour và IEatBehaviour.
    2. Các class FlyHighBehaviour và FlyLowBehaviour conform protocol IFlyBehaviour, chúng sẽ khai báo lại phương thức fly() với các hành vi riêng. Ngoài ra có thể tạo thêm các class có phương thức fly khác tùy theo yêu cầu bài toán.
    3. Class EatLeafBehaviour conform protocol IEatBehaviour, khai báo lại phương thức eat riêng. Ngoài ra có thể tạo thêm các class có phương thức eat khác tùy theo yêu cầu bài toán.
    4. WildBird vừa có thể bay và ăn nên nó sẽ chứa 2 instance kiểu IFlyBehaviour và IEatBehaviour; Penguin không thể bay nên chỉ cần chứa 1 instance kiểu IEatBehaviour.

    Code triển khai:

    Khi cần thêm các hành vi fly mới, chỉ cần viết thêm các class conform protocol IFlyBehaviour.
    Khi cần thêm các hành vi eat mới, chỉ cần viết thêm các class conform protocol IEatBehaviour.
    Ví dụ tạo 2 đối tượng WildBird có hành vi Fly khác nhau. Các loài chim với các hành vi fly/eat khác thì chỉ cần khởi tạo class chim đó với các hành vi mong muốn
    Khởi tạo đối tượng Penguin không có hành vi bay, và hành vi ăn là ăn cá.

    Kết luận:

    • Composition đem lại nhiều lợi ích hơn, tuy nhiên không phải là luôn luôn thay thế Inheritance = Composition.
    • Sử dụng Inheritance khi bạn thật sự cần dùng đến nó, chứ không nên chỉ vì mục đích reuse code.

    Reference

    https://betterprogramming.pub/inheritance-vs-composition-2fa0cdd2f939

    Cách sử dụng composition với protocol extension: https://medium.com/commencis/reusability-and-composition-in-swift-6630fc199e16 Cách này khá hay nhưng bị 1 nhược điểm là func của protocol không thể để là private.

  • Basic CAShapeLayer iOS (P3)

    Basic CAShapeLayer iOS (P3)

    Lâu k gặp chủ đề gì hay ho để viết, ngồi viết nốt bài trong series basic CAShapeLayer đang bỏ dở từ lâu vậy =)) Bài viết này sẽ nói về cách sử animation cơ bản với CAShapeLayer.

    Content

    • Khởi tạo 1 animation
    • Các thuộc tính của animation
    • Lưu í

    Khởi tạo 1 animation

    Khởi tạo 1 animation. 1 animation có dạng CABasicAnimation.

    let animation = CABasicAnimation(keyPath: )

    Bởi vì 1 shapeLayer có rất nhiều thuộc tính khác nhau để áp dụng animation, nên khi khởi tạo 1 CABasicAnimation thì ta phải truyền keyPath vào để xác định xem sẽ thực hiện animation trên thuộc tính nào.
    Có vẻ API này đã rất lâu rồi nên Apple không define keyPath bằng enum 🙁 Nên ta phải truyền keyPath bằng 1 string 🙁
    Tìm trên mạng thì đây là các thuộc tính mà CAShapeLayer support để animate:

    Sau khi khởi tạo 1 animation, chỉ việc add animation đó vào shapeLayer của bạn.

    shapeLayer.add(animation, forKey: nil)

    Các thuộc tính của animation:

    Ta có thể cấu hình thêm cho animation 1 chút bằng các thuộc tính của CABasicAnimation như: duration, fromValue, toValue, timingFunction, fillMode, isRemovedOnCompletion, …

    duration, timingFunction, repeatCount, autoReverse

    • duration: set thời gian chạy animation
    • timingFunction: set gia tốc
    • repeatCount: số lần lặp lại animation
    • autoReverse: có reverse lại sau khi kết thúc animation hay không
    • beginTime: Thời gian bắt đầu chạy animation
    func createSquare() {
        let shapeLayer = CAShapeLayer()
            
        shapeLayer.lineWidth = 2.0
        shapeLayer.fillColor = UIColor.clear.cgColor
        shapeLayer.strokeColor = UIColor.red.cgColor
        let openCirclePath = UIBezierPath(arcCenter: CGPoint(x: 100, y: 200),
                                          radius: 60.0,
                                          startAngle: 0.0,
                                          endAngle: CGFloat.pi * 2,
                                          clockwise: true)
            
        shapeLayer.path = openCirclePath.cgPath
        view.layer.addSublayer(shapeLayer)
            
        let animation = CABasicAnimation(keyPath: "strokeEnd")
        animation.beginTime = CACurrentMediaTime() + 0.3
        animation.fromValue = 0.0
        animation.toValue = 1.0
        animation.duration = 2
        animation.repeatCount = HUGE
        animation.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
        shapeLayer.add(animation, forKey: nil)
    }

    fromValue, toValue

    Tương tự như giải thích ở bài trước, nó sẽ quyết định xem animation chạy % nào. Dưới đây là lần lượt 2 trường hợp chạy animation với fromValue = 0 và fromValue = 0.5

    fillMode

    Trước hết, hay thay đoạn animation ở trên bằng đoạn animation này và quan sát kết quả:

    let animation = CABasicAnimation(keyPath: "strokeColor")
    animation.beginTime = CACurrentMediaTime() + 2
    animation.fromValue = UIColor.blue.cgColor
    animation.toValue = UIColor.cyan.cgColor
    animation.duration = 2
    animation.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
    shapeLayer.add(animation, forKey: nil)

    Ở ví dụ trên, mình chạy animation đổi màu và bắt đầu sau 3s. Màu của layer lần lượt biến đổi như sau:

    • Đầu tiên shapeLayer có strokeColor là màu đỏ
    • Khi bắt đầu animation thì được set là màu blue, animate đến màu cyan
    • Khi kết thúc animation thì strokeColor lại thành màu đỏ

    Thuộc tính fillMode cho phép bạn kiểm soát behavior của animation tại thời điểm bắt đầu và kết thúc của animation. fillMode gồm: forward, backward, both và removed. Có các tác dụng như sau:

    • removed: giá trị default của fillMode. Effect của animation sẽ bị remove khi animation kết thúc -> Lí do đầu tiên khiến strokeColor quay về thành màu đỏ
    • backward: Hiển thị khung hình đầu tiên của animation ngay lập tức. Ở trường hợp này sẽ hiển thị màu xanh ngay lập tức.
    • forward: Giữ lại khung hình cuối cùng của animation cho đến khi bạn remove animation.
    • both: kết hợp forware và backward.

    set fillMode cho animation = .both và quan sát kết quả:

    isRemovedOnCompletion

    Wait? Tại sao set fillMode = .both hoặc .forward mà vẫn bị reset về màu đỏ ban đầu vậy?

    Việc set fillMode = forward chỉ có tác dụng giữ EFFECT cuối cùng vẫn được giữ ở animation, nhưng animation đã bị remove khỏi layer khi thực hiện xong rồi =))

    Vì vậy để giữ cho animation không bị remove thì bạn sẽ phải set đồng thời cả 2 thuộc tính:

     animation.fillMode = .forward
     animation.isRemovedOnCompletion = false
    Uy tín luôn

    Lưu ý

    #1
    Khi app xuống background thì animation sẽ bị remove khỏi layer -> Các animation sẽ bị mất. Để tránh tình trạng này thì chỉ cần set thuộc tính sau cho animation:

    animation.isRemovedOnCompletion = false

    #2

    • Có rất nhiều keyPath để set cho CABasicAnimation, bạn có thể tham khảo thêm ở đây.
    • Tuy nhiên, chỉ những thuộc tính mình để cập ở trên mới được dùng cho CAShapeLayer.
      Việc dùng các keyPath khác để dùng cho CAShapeLayer có thể đem lại những animation không được như ý muốn: ví dụ như 1 vài animation rotate, …
    • Nếu muốn kết hợp các animation không support CAShapeLayer với các shapeLayer thì có thể trick bằng cách add shapeLayer lên 1 UIView, rồi add animation lên layer của view đó.
    loadingView.layer.addSublayer(shapeLayer)
    let rotationAnimation = CABasicAnimation(keyPath: "transform.rotation.z")
    loadingView.layer.add(rotationAnimation, forKey: nil)

    #3
    CABasicAnimation có các delegate animationDidStart và animationDidStop để handle thêm nếu muốn nhưng sẽ k đề cập ở bài viết này.

    #4
    CABasicAnimation thì có 1 vài thuộc tính để custom, và cũng chỉ animate đơn giản được bằng fromValue và endValue.
    Để có thể custom được nhiều hơn thì có thể sử dụng thêm keyFrame Animation cho ShapeLayer. // Có thể mình sẽ viết ở 1 bài khác

  • How to write clean code (P1)

    How to write clean code (P1)

    Mặc dù lâu không viết gì vì đang bận làm đồ án, nhưng sau khi khi hoàn thành 1 task về refactor vài nghìn dòng code đã là động lực để em phải ngoi lên viết bài viết này, để chia sẻ về cách viết code mà bản thân đang áp dụng.
    Phần 1 của bài viết sẽ giới thiệu về cách cải thiện code đơn giản và dễ dàng nhất: Đặt tên

    Nội dung bài viết

    • Tầm quan trọng của clean code
    • Meaningful names

    Tầm quan trọng của clean code

    Tại sao clean code lại quan trọng?

    • Những dòng code cũng chính là 1 bản design document, vì vậy nếu code được viết 1 cách gọn gàng, dễ hiểu thì khi 1 người mới đọc code thì sẽ dễ dàng nắm được logic, flow của code – Sếp HoaND1 said.
    • Việc viết code 1 cách clean sẽ giảm thiểu thời gian dể người khác, hoặc chính bạn sau 1 thời gian đọc lại, có thể nhanh chóng hiểu được; tránh gây ra những hiểu lầm về mặt logic.
    Clean code cũng giúp tránh việc đồng nghiệp của bạn phải thốt lên “WTF” nữa

    Meaningful names

    Việc chọn 1 cái tên sao cho truyền tải đủ ý định của biến, hàm, class, … đôi khi sẽ mất nhiều thời gian, những nó sẽ tiết kiệm được nhiều thời gian hơn so với thời gian bị mất.
    Một cái tên tốt nên thể hiện tại sao biến này, hàm này, … tồn tại, nó có tác dụng thế nào, được sử dụng thế nào mà không cần tốn thêm quá nhiều thời gian để đi tìm hiểu chúng làm gì.

    let temp: Int = 0
    func check() -> Bool {}

    Ví dụ với những cái tên kiểu này, đồng nghiệp của bạn sẽ phải lặn lội mọi ngóc ngách nơi biến, hàm được gọi để có thể hình dung 1 cái nhìn mơ hồ xem chúng có tác dụng gì… Hãy biết thương đồng nghiệp của bạn.
    Ok, giờ thì đi tìm hiểu 1 vài cách đặt tên cho tốt

    Coding Conventions

    Trong swift, có 1 vài coding conventions cơ bản trong việc đặt tên như:

    • Tên biến, tên class nên là danh từ, tên hàm nên bắt đầu bằng 1 động từ
    • Sử dụng camel case (tránh dùng snakeCase)
    • Viết hoa chữ cái đầu cho các kiểu dữ liệu type, protocol, viết thường cho các thứ khác. …

    Hãy chọn những cái tên cụ thể với hành vi

    func sortListObject() {}

    Như tên hàm này không được cụ thể lắm, vì qua cái tên không thể hiện được là object sẽ sort theo kiểu gì?
    Nếu sort theo tên thì nên sửa lại thành sortListObjectById() chẳng hạn, hoặc sortListObjectByName() nếu sort theo tên.

    Nếu có điều gì quan trọng về 1 biến, 1 hàm mà người đọc nên biết, thì nên thêm thông tin đó vào tên.

    var id: String // "af84ef845cd8"
    -> var hexID: String

    Tránh những cái tên chung chung

    Những cái tên mơ hồ như temp, tmp, i, j, … trong đa số trường hợp thường không thể hiện được quá nhiều thông tin. Vì vậy, hãy chọn 1 cái tên truyền tải nhiều ý nghĩa hơn thay vì chúng.

    Cũng có 1 vài trường hợp những cái tên chung chung có thể chấp nhận. Ví dụ như:

    if left < right {
       let temp = left
       left = right
       right = left
    }

    Trong những trường hợp như thế này, việc sử dụng tên “temp” cũng ổn. Bởi mục đích của nó là lưu trữ tạm thời,với thời gian tồn tại chỉ vài dòng, nó cũng không có nhiệm vụ nào khác. Nó không được chuyển sang chức năng khác hoặc được đặt lại, hoặc sử dụng nhiều lần. Và quan trọng hơn là người khác vẫn có thể dễ dàng hiểu.

    Hãy chọn những cái tên ý nghĩa. Nếu bạn định sử dụng những cái tên chung chung như “temp”, “tmp”, … hãy chắc chắn rằng có 1 lí do hợp lí cho việc đó.

    Tránh những tên quá dài

    • Khi chọn tên, nên tránh những cái tên quá dài, bởi vì chúng rất khó đọc và khó để nhớ
    newNavigationControllerWrappingViewControllerForDataSourceOfClass
    • Đôi khi những cái tên cũng chứa những thông tin bị thừa, mà nếu bỏ đi thì cũng không ảnh hưởng gì tới ý nghĩa.
    func convertToString() 
    func toString() // Vẫn truyền tải đủ ý nghĩa

    Tránh những cái tên hiểu lầm

    • Tránh đặt những tên kiểu getXYZ(), sizeXYZ(), … nếu bên trong thân hàm xử lý những logic có độ phức tạp tính toán lớn, bởi những cái tên này thường dễ khiến người đọc hiểu lầm là hàm này "lightweight" nên dùng 1 cách thường xuyên.
    // Tên hàm gây hiểu lầm
    func getNumberOfSelectedObjects() -> Int {
       Loop hundreds time to calculate ...
    }
    • 1 vài kiểu gây hiểu lầm khác như đặt tên biến là objectIndexs nhưng lại trả về kiểu Object, …
    • Những cái tên trả về kiểu boolean thì tên biến, hàm nên bắt đầu bằng các từ như is, has, can, should, … để làm cho rõ nghĩa.
    • 1 số cái tên thông dụng khác như max, min cho giới hạn; first, last cho thứ tự,…

    Kết luận

    Hãy cố gắng chọn những cái tên truyền đạt đầy đủ ý nghĩa và dễ hiểu đối với mọi người.

    Tham khảo

    Sách "The Art of Readable Code" – O’Reilly
    Sách "Clean Code" – Uncle Bob

  • CoreData with MultiThreading

    CoreData with MultiThreading

    Nội dung

    • Tóm tắt CoreData
    • Sử dụng CoreData với MultiThreading
    • Debug CoreData MultiThreading
    • Kết luận

    Tóm tắt về CoreData:

    Các thành phần chính của CoreData:

    • Managed Object Model
    • Managed Object Context
    • Persistent store coordinator

    MultiThreading with CoreData:

    Khi khởi tạo managedObjectContext(MOC) thì sẽ có thể lựa chọn 1 trong 2 loại queue để khởi tạo MOC, đó là:

    • NSMainQueueConcurrencyType (main Thread)
    • NSPrivateQueueConcurrencyType (background Thread)

    NSMainQueueConcurrencyType chỉ có thể được sử dụng trên main queue.
    NSPrivateQueueConcurrencyType tạo ra 1 queue riêng để sử dụng. Vì queue này là private, nên chỉ có thể access queue thông qua hàm perform(_:)performAndWait(_:) của MOC .

    Nếu ứng dụng sử dụng nhiều thao tác data processing (parse JSON to data, …) thì việc sử dụng trên main queue sẽ gây ra block main. Khi đó, có thể khởi tạo 1 context dùng private queue và thực hiện xử lí data trên đó.

    Trước khi sử dụng CoreData với MultiThread, chú í đến điều Apple recommend:

    Hãy chắc chắn rằng MOC được sử dụng trên thread(queue) mà chúng được liên kết khi khởi tạo.

    Nếu MOC không được sử dụng trên thread(queue) mà chúng được liên kết, trong trường hợp MOC liên kết với mainQueue nhưng được sử dụng trên background thread, hoặc ngược lại, sẽ khiến app đôi lúc sẽ gặp những lỗi crash lạ.

    Vì vậy để chắc chắn MOC luôn được sử dụng trên thread mà MOC được liên kết, thì có thể sử dụng perform( _:) và performAndWait( _:) như sau:

    • perform( _:) và performAndWait( _:) sẽ tự động đưa đoạn code bên trong nó thực hiện trên queue mà context đó được khởi tạo -> Điều đó sẽ chắc chắn rằng context được sử dụng trên đúng queue.
    • perform(_:) sẽ thực hiện async hàm bên trong nó.
    • performAndWait(_:) sẽ thực hiện sync hàm bên trong nó -> Nó sẽ block thread hiện tại gọi đến hàm đó cho đến khi hàm bên trong thực hiện xong -> Không nên gọi trên main.

    Debug Concurrency:

    Để đảm bảo Context được chạy trên đúng luồng nó được liên kết khi khởi tạo, có thể bật debug CoreData Concurrency như sau:

    • Chọn Edit Scheme -> Run -> Thêm "-com.apple.CoreData.ConcurrencyDebug 1".
    • Khi bật debug này lên, nó sẽ dừng app của bạn lại tại nơi context bị dùng sai Thread.

    Ví dụ:

    • Khởi tạo 1 context bằng private queue:
    private(set) lazy var managedObjectContext: NSManagedObjectContext = {
       let managedObjectContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
       managedObjectContext.persistentStoreCoordinator = persistentStoreCoordinator
       return managedObjectContext
    }()
    • Sử dụng 1 context trên main:
    func createNewEntity() {
       DispatchQueue.main.async {
           let user = User(context: self.manager.managedObjectContext)
           user.name = "Hoang Anh Tuan"
                
           let account = Account(context: self.manager.managedObjectContext)
           account.username = "sunlight"
           account.password = "123"
                
           user.account = account
           account.user = user
                
           do {
               try self.manager.managedObjectContext.save()
           } catch let error {
               print("Save error: \(error.localizedDescription)")
           }
        }
    }
    • Kết quả khi bật debug:
    Có thể sử dụng perform( _:) / performAndWait( _:) để giải quyết tình huống này.

    Kết luận:

    • Nên dùng background thread cho context để tránh block main thread.
    • Nên dùng các hàm perform( _:) và performAndWait( _:) để đảm bảo context được chạy trên đúng luồng.
    • Ngoài cách ở trên thì còn 1 vài cách như sử dụng child/parent context, nhưng sẽ không được đề cập ở bài viết này.
  • Working with EAAcessory & NSStream

    Working with EAAcessory & NSStream

    Nội dung:

    • EAAcessory là gì?
    • Cấu hình project để làm việc với EAAcessory.
    • Lấy thông tin accessory
    • Tạo stream để gửi & nhận data
    • Gửi data đến accessory
    • Nhận data từ accessory

    EAAcessory là gì?

    • 1 đối tượng kiểu EAAcessory đại diện cho 1 thiết bị ngoại vi đang kết nối với app thông qua Lightning connector hoặc Bluetooth.
    • EAAcessory gồm các thuộc tính chứa các thông tin quan trọng về thiết bị ngoại vi: isConnected, name, manufacturer, serialNumber, protocols mà thiết bị ngoại vi dùng, firmware version, …
    • Ngoài ra, cũng có thể lấy macAddress của thiết bị ngoại vi:
     let macAddress = accessory.value(forKey: PrinterConstant.MACAddress) as? String
    • Từ những thông tin đó, bạn có thể mở 1 session tới thiết bị ngoại vi để trao đổi dữ liệu.

    Config Project:

    • Không phải cứ kết nối Bluetooth là app sẽ scan thấy hoặc lấy được thông tin của thiết bị ngoại vi đó.
    • Để app có thể giao tiếp với thiết bị ngoại vi, thì app cần khai báo các protocols mà thiết bị ngoại vi đó hỗ trợ vào Info.plist.

    Đọc thông tin accessory:

    • Sau khi config project và kết nốt với thiết bị ngoại vi thông qua Bluetooth, có thể bắt đầu đọc thông tin của thiết bị ngọai vi:

    Đầu tiên, import ExternalAccessory:

    import ExternalAccessory
    • Đọc thông tin:
    let listAccessoryAvailable = EAAccessoryManager.shared().connectedAccessories
    accessory = listAccessoryAvailable.first
    print(accessory?.name)
    print(accessory?.serialNumber)
    print(accessory?.protocolStrings)
    ...

    Tạo socket để gửi/nhận data:

    • Để gửi/nhận data đến thiết bị ngoại vi thì cần mở 1 socket đến nó.
    func openSession() {
        guard let accessory = self.accessory else {
            return
        }
            
        guard let protocolString = accessory.protocolStrings.first else {
            return
        }
            
        session = EASession(accessory: accessory, forProtocol: protocolString)
        if session != nil {
            session?.inputStream?.delegate = self
            session?.inputStream?.open()
            session?.inputStream?.schedule(in: .current, forMode: .default)
                
            session?.outputStream?.delegate = self
            session?.outputStream?.open()
            session?.outputStream?.schedule(in: .current, forMode: .default)
        }
    }

    conform to StreamDelegate để handle event của socket:

    extension ViewController: StreamDelegate {
        func stream(_ aStream: Stream, handle eventCode: Stream.Event) {
            switch eventCode {
            case .openCompleted:
                print("Open session complete")
            case .hasBytesAvailable:
                print("Has bytes available")
            case .hasSpaceAvailable:
                print("Has space available")
            case .errorOccurred:
                let error = aStream.streamError
                print("Error occur")
            case .endEncountered:
                print("End stream")
                aStream.close()
                aStream.remove(from: .current, forMode: .default)
            default:
                return
            }
        }
    }

    Note:

    • Gửi data đến thiết bị ngoại vi thông qua outputStream, nhận data từ thiết bị ngoại vi thông qua inputStream.

    Gửi data:

    • Sau khi đã open được session, bắt đầu gửi data sang thiết bị ngoại vi thông qua outputStream:
    guard let outputStream = session?.outputStream else {
        return
    }
    
    outputStream.write(data, maxLength: 128)

    There is no firm guideline on how many bytes to write at one time. Although it may be possible to write all the data to the stream in one event, this depends on external factors, such as the behavior of the kernel and device and socket characteristics. The best approach is to use some reasonable buffer size, such as 512 bytes, one kilobyte, or a page size (four kilobytes) – Apple

    Nhận data:

    • Khi thiết bị ngoại vi gửi dữ liệu đến app của bạn, StreamDelegate sẽ trigger event hasSpaceAvailable, khi đó app sẽ đọc data thông qua inputStream:
    func stream(_ aStream: Stream, handle eventCode: Stream.Event) {
        switch eventCode {
    ...
            case .hasBytesAvailable:
                print("Has bytes available")
                
                let dataBuffer = [UInt8](repeating: 0, count: 128)
                guard let inputStream = session?.inputStream else {
                    return
                }
                while inputStream.hasBytesAvailable {
                    inputStream.read(UnsafeMutablePointer<UInt8>(mutating: dataBuffer), maxLength: 128)
                    print("Read Data: \(dataBuffer)")
    ...
         }
    }

    Xử lí notification khi connect/disconnect với thiết bị ngoại vi:

    • Để nhận được notification, app cần phải đăng kí nhận thông báo trước:
    EAAccessoryManager.shared().registerForLocalNotifications()
    • Tiếp theo là add Observer khi 1 accessory connect/disconnect.
    NotificationCenter.default.addObserver(self,
                                           selector: #selector(self.accessoryDidConnect(notification:)),
                                           name: NSNotification.Name.EAAccessoryDidConnect,
                                            object: nil)
    NotificationCenter.default.addObserver(self,
                                           selector: #selector(self.accessoryDidDisconnect(notification:)),
                                           name: NSNotification.Name.EAAccessoryDidDisconnect,
                                           object: nil)
    • Lấy thông tin của thiết bị ngoại vi thay đổi trạng thái:
    guard let connectedAcessory = notification.userInfo?[EAAccessoryKey] as? EAAccessory else {
        return
    }

    Scan Accessory Available:

    • App có thể scan các accessory available ở gần và kết nối Bluetooth đến chúng từ trong app mà không cần phải mở System.
    • Đầu tiên, show view scan Accessory Available:
    EAAccessoryManager.shared().showBluetoothAccessoryPicker(withNameFilter: nil) { (error) in
        
    }
    • Bạn có thể tạo predicate để filter các accessory available.
    Nguồn: internet
    • Chọn 1 accessory để thực hiện kết nối, hàm sẽ trả về error nếu xảy ra lỗi; nếu không có lỗi tức là bạn đã connect thành công.
  • Debug Conflict Constraint iOS

    Debug Conflict Constraint iOS

    Có thể bạn đã từng gặp lỗi constraint bị breaking với kiểu log như thế này:

    Thường thì Xcode sẽ tự loại bỏ 1 constraint để view không bị conflict nữa. Điều này sẽ dẫn đến UI hiển thị trên màn hình đúng hoặc không, tùy thuộc vào việc Xcode loại bỏ constraint nào.

    Tuy nhiên, kể cả trong trường hợp UI hiển thị đúng, thì chúng ta vẫn nên đi fix cái lỗi này.

    Đối với những màn hình phức tạp, nhiều subview, việc biết view nào đang bị lỗi constraint là khó có thể phán đoán được.

    Vì vậy ở bài viết này, hãy cùng nhau đi đọc đống log kia để biết view nào đang bị conflict constraint 🙂

    Tìm xem view nào bị lỗi bằng cách đọc địa chỉ

    Để í đến dòng log sau:

    Đoạn log này hướng dẫn rằng code sẽ recover bằng cách loại bỏ constraint 0x60000336d7c0 của view có địa chỉ 0x7fccafe06a10.
    Giờ thì bắt đầu đi tìm view có địa chỉ 0x7fccafe06a10:

    • Chọn Show Debug Navigator (Command + 7).
    • Chọn View Memory Graph Hierachy
    • Nhập địa chỉ của View cần tìm vào phần Filter.
    • Chọn vào View có địa chỉ cần tìm, Xcode sẽ hiển thị kết quả như sau:
    • Click chuột phải vào View, chọn Quick Look để xem đó là View nào:
    • Với Quick Look, Xcode sẽ hiển thị View đang bị conflict constraint:

    Đổi màu background của View bằng lldb:

    Nếu trong trường hợp có quá nhiều View giống nhau, và bạn vẫn chưa thể xác định đó là view nào?
    -> Dùng lldb để đổi màu background view đó để có thể xác định dễ hơn.

    Đầu tiên, pause chương trình lại và thực hiện lệnh sau ở cửa sổ lldb:

    expression [(UIView*)0x7fccafe06a10 setBackgroundColor:[UIColor redColor]]
    • Với lệnh này, chọn Quick Look với View vừa rồi thì View đã chuyển sang màu đỏ rồi, nhưng vẫn chưa hiển thị trên màn hình simulator/device.
    • Để View đổi màu ngay lập tức trên simulator/device, thực hiện tiếp 1 câu lệnh sau:
    expression (void)[CATransaction flush]

    OK, khi đó thì View bị lỗi constraint đã ngay lập tức đổi màu rồi.

    Xem tất cả constraint của View:

    • Mở Debug View Hỉeachy.
    • Tìm và chọn view bị lỗi constraint.
    • Chọn Show the size inspector (Option + Command + 5)
    • Khi đó, ở mục Constraint thì sẽ hiển thị tất cả constraint đang gắn với view đó. Cuối cùng thì chỉ cần bỏ đi constraint nào không cần thiết là được.

    Vừa leading, vừa centerX, vừa set width gây ra bị conflic constraint. Bỏ width constraint hoặc leading constraint tùy vào mục đích.

    Tham khảo: https://medium.com/ios-os-x-development/dynamically-modify-ui-via-lldb-expression-1b354254e1dd

  • [RxSwift] Traits

    Contents

    • Traits là gì?
    • Các loại Traits
      • Single
      • Completable
      • Maybe

    Traits là gì?

    Traits là observables nhưng với 1 phạm vi hành vi hẹp hơn so với các Observables thông thường.

    Lợi ích của Traits:

    • Vì phạm vi hẹp hơn, nên khi sử dụng Traits thì sẽ làm cho người đọc clear hơn về í nghĩa của task.
    • Traits luôn được observe và subcribe trên Main.

    Các loại Traits:

    • Trait trong RxSwift gồm 3 loại, đó là: Single, Completable

    Single:

    • Single sẽ chỉ emit ra duy nhất 1 event, và event đó phải thuộc 1 trong 2 kiểu .success(value) hoặc .error.
    • .success(value) về bản chất là sự kết hợp của .next và .complete event của Observable.
    • Sau khi emit event, single sẽ tự terminate.

    Demo

    enum DevideError: Error {
        case divideByZero
    }
    func divide(number1: Int, number2: Int) -> Single<Int> {
        return Single<Int>.create(subscribe: { observer in
            if number2 == 0 {
                 observer(.error(DevideError.divideByZero))
            } else {
                 let result = number1 / number2
                 observer(.success(result))
            }
            return Disposables.create()
        })
    }
    divide(number1: 10, number2: 2)
         .subscribe { (event) in
              switch event {
               case .success(let result):
                    print(result)
               case .error( let error):
                    print(error)
          }
    }

    Vì number2 khác 0, nên khi thực hiện hàm divide 10 cho 2 thì sẽ trả về 1 Single và nó sẽ emit ra .success(5).

    Completable:

    • Completable sẽ chỉ emit ra duy nhất 1 event, và event đó phải thuộc loại .completed hoặc .error.
    • Sau khi emit ra event thì sẽ tự động terminate.
    func divide(number1: Int, number2: Int) -> Completable {
        return Completable.create(subscribe: { observer in
            if number2 == 0 {
                observer(.error(DevideError.divideByZero))
            } else {
                let result = number1 / number2
                observer(.completed)
            }
            return Disposables.create()
        })
    }

    Maybe:

    • Maybe là sự kết hợp của Single và Completable. Nó có thể emit .success(value), .completed hoặc .error event.
    • Vẫn như 2 loại traits trên, Maybe sẽ chỉ emit ra 1 event duy nhất.
    • Sau khi emit ra event thì sẽ tự động terminate.
    Maybe cho phép bạn emit ra 3 loại event khác nhau.
    func divide(number1: Int, number2: Int) -> Maybe<Int> {
        return Maybe<Int>.create(subscribe: { observer in
            if number2 == 0 {
                observer(.error(DevideError.divideByZero))
            } else {
                let result = number1 / number2
                observer(.success(result))
            }
            return Disposables.create()
        })
    }

    Convert Traits thành Observable và ngược lại

    • Có thể convert 1 traits thành Observable đơn giản như sau:
    single.asObservable()
    • Việc convert từ Observable thành Traits cũng đơn giản như vậy:
    observable.asSingle()

    Tuy nhiên Traits thì chỉ emit ra 1 event, còn Observable của bạn thì có thể sẽ emit ra nhiều event. Vì vậy nếu convert 1 Observable emit ra nhiều event sang Traits thì traits đó sẽ emit ra error. Ví dụ như sau:

    let observable = Observable<String>.of("abc", "def")
    observable.asSingle().subscribe({ event in
        switch event {
        case .success(let value):
            print(value)
        case .error(let error):
            print(error)
        }
    })
    Lỗi được emit ra.

    Khi nào thì sử dụng Single, Completable & Maybe:

    Single:

    • Single có tác dụng cho những tiến trình chỉ chạy 1 lần và sẽ thành công với 1 value, hoặc sẽ bị error.
    • Ví dụ như download File, load data, …

    Completable:

    • Dùng cho những task mà bạn chỉ quan tâm xem nó thành công hay thất bại.
    • Ví dụ: Write file…

    Maybe:

    • Dùng cho những task có thể success hoặc fail, và tùy lúc sẽ emit ra 1 value khi success.
  • [RxSwift] Observable

    Contents

    • Observable là gì?
    • Các loại event
    • Khởi tạo Observable
    • Subcribe
    • Create Observable

    Observable là gì?

    • Observable là phần quan trọng nhất của RxSwift. có thể coi nó như 1 sequence, có tác dụng phát ra (emit) các event, và các object khác có thể lắng nghe những event đó.
    • Các event có thể đính kèm các giá trị với các kiểu dữ liệu quen thuộc như Int, String, … hay các kiểu dữ liệu mà bạn tự tạo ra.
    Hình trên là ví dụ cho các event 1, 2 ,3 lần lượt được emit ra theo thời gian.

    Các loại Event:

    Các event được emit ra thuộc 1 trong 3 loại:

    • next: Khi 1 Observable emit 1 element, thì nó được coi là 1 next event. Next event có thể đính kèm giá trị, và Observable có thể emit nhiều next event trong 1 life cycle của nó.
    • completed: Khi Observable hoàn thành việc emit event của mình, thì nó sẽ emit ra event completed và tự động terminate.
    • error: Khi Observable gặp lỗi trong quá trình emit event, thì nó sẽ emit ra event error và tự động terminate.

    Note:
    Tóm gọn lại, Observable có thể phát ra nhiều next event trong 1 life cycle; ngược lại thì Observable chỉ có thể phát ra 1 event complete hoặc error, sau đó sẽ tự hủy và không phát ra bất cứ event nào nữa.

    Khởi tạo Observable:

    of:

    let observable = Observable.of(1,2,3)
    let observable2 = Observable.of([4,5,6])
    • Khởi tạo bằng func of cùng với các giá trị khởi tạo.
    • Cho mỗi next event, Observable sẽ lần lượt emit ra các element được cung cấp.
    • Các giá trị cung cấp phải cùng kiểu dữ liệu: Cùng Int, String, …
    Sau khi phát ra hết các element, observable sẽ emit ra completed event và terminate.

    from:

    let observable = Observable.from([1,2,3])
    • Khi khởi tạo 1 Observable bằng func from, thì giá trị được khởi tạo mặc định phải thuộc kiểu Array.
    • Cho mỗi event next, Observable sẽ lần lượt emit ra các giá trị trong mảng.

    Sau khi emit hết các phần tử trong mảng, observable sẽ emit ra event completed và terminate.

    Ngoài of và from, thì còn các cách khởi tạo khác như just, never, empty.

    create:

    • Với các cách ở trên, thì observable sẽ tự động emit các event được cung cấp sẵn 1 cách lần lượt và tự emit 1 complete sau khi hoàn thành.
    • Bằng cách sử dụng hàm create để tạo 1 Observable, bạn có thể custom Observable theo cách riêng của ban để có thể emit ra completed hoặc error bất cứ khi nào bạn muốn
    let observable = Observable<String>.create({ observer in
        observer.onNext("Global")
        observer.onNext("Smart")
        observer.onCompleted()
        observer.onNext("Technology")
        return Disposables.create()
    })

    Kết quả được in ra: (Sau khi emit ra event complete thì observable sẽ bị terminate, do đó nó sẽ không emit ra "Technology" nữa.

    Subcribe:

    Chỉ copy đoạn code ở trên và run thì bạn sẽ không thấy có gì được print ra màn hình cả. Tại sao vậy?

    1 Observable chỉ có thể emit ra event khi nó được subcribe.

    Đoạn code ở trên, ta chưa subcribe Observable đó.Vậy làm thế nào để subcribe 1 Observable?

    subcribe event

    Để subcribe tới 1 Observable thì vô cùng đơn giản với hàm subcribe:

    let observable = Observable.of(1,2,3)
    observable.subscribe { (value) in
        print(value)
    }

    Để í phần Summary của hàm subcribe này: Đó là hàm có tác dụng subcribes 1 event. Vì vậy, các giá trị được print ra sẽ có cả kiểu của event được emit ra (next/complete/error).

    Kết quả print ra trên màn hình:

    Vậy nếu bạn muốn chỉ print ra giá trị của value mà không cần print ra kiểu event?

    subcribe element

    • Cách 1: Mỗi event sẽ có thuộc tính element để lấy ra giá trị đính kèm với event đó.
    observable.subscribe { (value) in
        if let element = value.element {
            print(element)
        }
    }
    • Cách 2: Sử dụng hàm subcribe:
    observable.subscribe(onNext: { (element) in
        print(element)
    })

    Theo như summary, hàm subcribe này sẽ subcribe đến element, vì vậy nó sẽ chỉ in ra giá trị của element.
    Cả 2 cách đều cho 1 kết quả như sau:

    Ngoài ra , với hàm trên, bạn có thể thực hiện các hành động khi observable emit ra event completed, error, … như sau:

    let observable = Observable<String>.create({ observer in
        observer.onNext("Global")
        observer.onNext("Smart")
        observer.onCompleted()
        observer.onNext("Technology")
        return Disposables.create()
    }).subscribe(onNext: { (element) in
        print(element)
    }, onCompleted: {
         print("Completed")
    })

    Kết luận:

    Observable là 1 thứ đơn giản nhưng vô cùng quan trọng trong RxSwift.

    Tham khảo: RxSwift – Reactive Programming with Swift v1.1