Tag: Swift

  • iOS/Swift – View Controller Lifecycle

    iOS/Swift – View Controller Lifecycle

    Lời mở đầu

    Nói về ViewController thì chắc hẳn tất cả iOS Developer đều biết đến và đã sử dụng rất nhiều. Nhưng đối với các bạn mới bắt đầu với iOS, mọi người thường không chú ý nhiều đến vòng đời của ViewController, dẫn đến mắc phải một số lỗi không đáng có.
    Bài viết này mình sẽ giới thiệu cho các bạn mới bắt đầu với iOS về vòng đời của View Controller và cách sử dụng để tránh những lỗi không đáng có.

    View Controller Lifecycle là gì?

    View Controller lifecycle là vòng đời của một view controller được tính từ lúc nó được nạp vào bộ nhớ (RAM) cho tới khi nó bị giải phóng khỏi bộ nhớ.

    Phân tích vòng đời của View Controller

    Dưới đây là sơ đồ về vòng đời của nó:

    Như các bạn đã thấy, trên sơ đồ này có khá nhiều trạng thái mà các bạn chắc dã nhìn thấy rất nhiều nhưng một số thì không phải không 😀
    Và nó cũng là các phương thức tương ứng được gọi tự động trong vòng đời của View Controller

    OK, bây giờ chúng ta sẽ đi vào chi tiết.

    loadView

    Phương thức này được gọi khi View hiện tại đang bằng nil. Cơ bản nó sẽ đưa View mà bạn tạo trong phương thức này vào view của ViewController.

    NOTE: Phương thức này được sử dụng khi View Controller được tạo bằng code. Nếu chúng ta tạo View Controller từ file .xib hoặc storyboard thì tốt nhất không sử dụng hương thức này.

    viewDidload:

    Phương thức này được gọi một lần duy nhất trong vòng đời của ViewController. Nó được gọi khi tất cả các view đã được load vào bộ nhớ(RAM).

    Ứng dụng:
    1. Khi bạn muốn cài đặt giao diện người dùng (User Interface)
    2. Những công việc mà bạn muốn nó chỉ chạy duy nhất một lần trên View Controller này.

    viewWillAppear:

    Phương thức này được gọi mỗi lần trước khi nội dung của View được thêm vào view hierarchy của ứng dụng.

    Ứng dụng:
    Vì phương thức này sẽ được gọi mỗi lần trước khi View được xuất hiện nên nó thường dùng khi bạn muốn 1 công việc nào đó luôn được gọi mỗi khi View Controller đó hiển thị trên màn hình.
    VD: Kiểm tra kết nối mạng, kiểm tra service state, add observer Notification v.v.

    NOTE:
    • Tránh làm các công việc mà bạn chỉ muốn thực hiện nó một lần trong vòng đời của View Controller trong phương thức này.
    • Nếu bạn add observer notification ở hàm này thì cần remove notification ở phương thức viewDidDisappear:. Để tránh trường hợp khi quay trở lại màn hình này hàm add observer notification sẽ được đăng kí một lần nữa -> nó sẽ thực thi hàm trong #selector nhiều lần.
    • Với tương tác tầng Application (VD: Bấm Home, show notification center, show control center… ) rồi trở lại ứng dụng thì sẽ không kích hoạt phương thức này mà nó sẽ kích hoạt các phương thức của UIApplication.

    viewDidAppear:

    Phương thức này được gọi mỗi lần sau khi nội dung của View được thêm vào view hierarchy của ứng dụng.

    Ứng dụng:
    Thường được sử dụng để lưu dữ liệu, bắt đầu animation, bắt đầu chơi Video hoặc âm thanh hoặc thu thập dữ liệu từ network.

    NOTE:
    • Tương tự như viewWillAppear, với tương tác tầng Application (VD: Bấm Home, show notification center, show control center… ) rồi trở lại ứng dụng thì sẽ không kích hoạt phương thức này mà nó sẽ kích hoạt các phương thức của UIApplication.
    • Nếu bạn add observer notification ở hàm này thì cũng phải xóa notification ở viewDidDisappear:

    viewWillDisappear:

    Phương thức này được gọi trước khi view được xóa khỏi view hierarchy. View vẫn còn trên view hierarchy nhưng chưa được xóa.

    Ứng dụng:
    Thường dùng để quản lí các timer, ẩn bàn phím, hủy các network request và lưu lại các trạng thái.

    viewDidDisappear:

    Phương thức này được gọi sau khi view của ViewController được xóa khỏi view hierarchy.

    Ứng dụng:
    Thường sử dụng để hủy việc lắng nghe các thông báo (Notification) hoặc các cảm biến của thiết bị các trên màn hình này.

    deinit:

    Phương thức này được gọi trước khi một view controller bị xóa khỏi bộ nhớ.

    Ứng dụng:
    Thường được sử dụng để xóa tài nguyên mà view controller đã được phân bổ nhưng không được giải phóng bới ARC(Automatic reference counting)

    NOTE:
    Hãy nhớ rằng một view controller không còn hiển thị trên màn hình nữa không có nghĩa là nó đã được giải phóng. Ngay cả khi màn hình bị tắt nếu nó vẫn còn trong bộ nhớ thì nó vẫn hoạt động như thường.

    didReceiveMemoryWarning:

    Phương thức này được gọi khi bộ nhớ (RAM) của máy gần đầy. Và iOS không tự động di chuyển dữ liệu từ bộ nhớ sang không gian ổ cứng hạn chế của nó.

    Ứng dụng:
    Xóa một số đối tượng ra khỏi bộ nhớ.

    NOTE:
    Hãy nhớ rằng nếu bộ nhớ của ứng dụng vượt quá một ngưỡng nhất định, iOS sẽ tắt ứng dụng của bạn. Và nó trông giống như là ứng dụng bị crash


    Cảm ơn mọi người đã theo dõi bài viết!
    Mọi ý kiến đóng góp mọi người hãy comment xuống phía dưới để mình có thể thay đổi cho bài biết được tốt hơn. :v

  • Swift: Map, Flat Map, Filter and Reduce

    Swift: Map, Flat Map, Filter and Reduce

    Xin chào mọi người.
    Trong swift có một số tính năng rất hay đó là Higher Order Function. Nó có một số hàm như là map, CompactMap, Filter and Reduce được sử dụng cho các kiểu dữ liệu dạng collection.

    Khởi tạo giá trị mẫu

    struct Person {
        let name: String
        let age: Int
        let pets: [String]
    }
    struct Pet {
        let name: String
        let age: Int
    }
    
    var peopleArray = [Person(name: "Jack", age: 11, pets: ["Dog", "Cat"]),
                       Person(name: "Queen", age: 12, pets: ["Pig"]),
                       Person(name: "King", age: 13, pets: [])]

    MAP

    Trước khi sử dụng chúng ta cùng tìm hiểu về syntax của hàm này trước nhé:

    let resultCollection = inputCollection.map { (elementOfCollection) -> ResultType in
       return ResultType()
    }

    Nhìn vào đoạn code trên ta có thể hiểu hàm này sẽ trả về cho ta một collection có kiểu dữ liệu là ResultType( Một kiểu dữ liệu bất kì mà bạn mong muốn, nó có thể là Int, Double, String …)

    OK, Giờ chúng ta đi vào code mẫu để dễ hiểu hơn

    Dạng đầy đủ:

    var ages = peopleArray.map { (person) -> Int in
        return person.age
    }
    print(ages)
    // OUTPUT: [11, 12, 13]

    Ngoài dạng thông thường thì hàm này còn có thể viết dưới dạng rút gọn như sau:

    let ages = peopleArray.map({ $0.age })
    print(ages)
    // OUTPUT: [11, 12, 13]

    $0: ở đây được hiểu là argument đầu tiên của function map, trong trường hợp này nó sẽ đại diện cho 1 phần tử trong mảng có kiểu dữ liệu là Person

    Ứng dụng:
    Hàm này nên được sử dụng khi mà bạn muốn tạo một collection mới có kiểu dữ liệu khác từ 1 collection hiện tại

    Flat Map

    let resultCollection = inputCollection.flatMap { (elementOfCollection) -> [ResultType] in
       return [ResultType]
    }

    Cũng giống như hàm map, hàm flatMap cũng trả về 1 collection nhưng flat map sẽ bỏ qua các tầng (nếu có).
    Để dễ hiểu hơn chúng ta sẽ đi vào ví dụ sau:

    Sử dụng hàm Map:

    let pets = peopleArray.map({ $0.pets })
    print(pets)
    //OUTPUT: [["Dog", "Cat"], ["Pig"], []]

    Dạng đầy đủ:

    let flatPets = peopleArray.flatMap { (person) -> [String] in
        return person.pets
    }
    print("Flat pets: \(flatPets)")
    // OUTPUT: ["Dog", "Cat", "Pig"]

    Dạng rút gọn:

    let flatPets = peopleArray.flatMap({ $0.pets })
    print("flatPets: \(flatPets)")
    // OUTPUT: flatPets: ["Dog", "Cat", "Pig"]

    Có thể thấy flat map sẽ loaị bỏ hết các tầng collection bên trong và chuyển về 1 collection chỉ còn 1 tầng duy nhất thay vì collection chứa collection như khi sử dụng hàm Map.
    Ngoài ra hàm flatMap cũng bỏ đi các giá trị collection empty hoặc nil.

    NOTE: Hàm flatMap chỉ trả về giá trị đúng như mong đợi khi kiểu của nó là non-optional.


    Vậy trong trường hợp khai báo kiểu optional thì sao: let pets: [String]?
    Trong trường hợp này hàm Flat Map sẽ trả về giá trị như hàm Map vì vậy chúng ta sẽ call flatMap thêm 1 lần nữa như sau:

    let flatPetsShort = peopleArray.flatMap({ $0.pets }).flatMap({ $0 })
    print("flatPetsShort: \(flatPetsShort)")
    //OUTPUT: flatPetsShort: ["Dog", "Cat", "Pig"]

    Ứng dụng:
    Flat Map thường sử dụng trong trường hợp lọc các giá trị nil ra khỏi collection hoặc chuyển 1 collection nhiều tầng thành collection 1 tầng

    Reduce

    Cấu trúc hàm Reduce

    let result = inputCollection.reduce(initialValue) { (result, nextElement) -> ResultType in
        return a value that can be computed in the next element.
    }

    Hàm reduce sẽ duyệt lần lượt các phần tử trong collection và trả về kết quả dựa trên initialValue(Giá trị khởi tạo) và phép tính ở hàm return.

    Bây giờ chúng ta đi vào ví dụ để dễ hiểu hơn:

    Bài toán cụ thể: Cần tính tổng số tuổi của mọi người trong collection bằng hàm Reduce thì chúng ta sẽ làm như sau:

    Dạng đầy đủ:

    let ageTotal = peopleArray.reduce(0) { (result, personNext) -> Int in
        return result + personNext.age
    }
    print(ageTotal)
    // OUTPUT: 36

    Dạng rút gọn:

    let ageTotal2 = peopleArray.map({ $0.age }).reduce(0, +)
    print(ageTotal2)
    // OUTPUT: 36

    Ở dạng rút gọn, để sử dụng được hàm Reduce trong trường hợp này. Chúng ta cần sử dụng hàm Map để tạo ra một collection mới, chứa các phần tử là tuổi của tất cả mọi người trước, rồi sử dụng hàm Reduce dạng rút gọn để thực hiện.

    Ứng dụng:
    Bạn nên sử dụng hàm Reduce khi bạn muốn kết hợp các phần tử trong collection

    Filter

    Cấu trúc hàm Filter

    let result = inputCollection.filter { (elementOfCollection) -> Bool in 
        return (Conditions)
    }

    Hàm Filter sẽ trả về kết quả là 1 collection chứa tất cả các phần tử thỏa mãn điều kiện (Conditions)

    Bây giờ chúng ta sẽ đi vào ví dụ cho dễ hiểu hơn:
    Bài toán của chúng ta là cần lọc ra được tất cả các Person có số tuổi > 11.

    Chúng ta sẽ sử dụng hàm Filter dạng đầy đủ như sau:

    let result = peopleArray.filter { (person) -> Bool in
        return person.age > 11
    }
    print("result: \(result)")

    Dạng rút gọn:

    let results = peopleArray.filter({ $0.age > 11 })
    print(results)

    Ứng dụng:
    Sử dụng khi bạn cần giải quyết bài toán lọc các phần tử trong collection có điều kiện xác định

    NOTE:

    print("Cảm ơn mọi người đã theo dõi bài viết của mình.\n
    Mọi đóng góp cũng như góp ý, mọi người hãy comment ở phía dưới để mình có thể hoàn thiện bài viết tốt hơn.") 

    THANK YOU!

  • Swift—Design patterns: Multicast Delegate

    Swift—Design patterns: Multicast Delegate

    Multicast delegate

    Khái niệm

    • Chúng ta đều biết rằng delegate là mối quan hệ 1 – 1 giữa 2 objects, trong đó 1 ọbject sẽ gửi data/event và object còn lại sẽ nhận data hoặc thực thi event
    • Multicast delegate, về cơ bản chỉ là mối quan hệ 1 – n, trong đó, 1 object sẽ gửi dataevent đi, và có n class đón nhận data, event đó.

    Ứng dụng của multicase delegate, lúc nào thì dùng?

    • Dùng multicast delegate khi mà bạn muốn xây dựng một mô hình delegate có mối quan hệ 1 – nhiều.
    • Ví dụ: bạn có 1 class chuyên lấy thông tin data và các logic liên quan, và bạn muốn mỗi khi data của class được update và muốn implement các logic tương ứng của class này trên nhiều view/view controller khác nhau. Thì multicast delegate có thể dùng để xử lý bài toán này.

    So sánh với observer và notification

    • Tại sao không dùng observer: chủ yếu chúng ta dùng obverver để theo dõi data hoặc event nào đó xảy ra, và thực hiện logic sau khi nó đã xảy ra. Còn nếu dùng delegate, chúng ta còn có thể xử lý data hoặc event, hay nói cách khác, chúng ta có thể quyết định xem có cho phép event đó xảy ra hay không, ví dụ như các delegate của Table view như tableView:willSelectRowAtIndexPath:
    • Tại sao không dùng Notification thay vì multicast delegate? Về cơ bản, notification là thông tin một chiều từ 1 object gửi sang nhiều object nhận, chứ không thể có tương tác ngược lại. Ngoài ra, việc gửi data từ object gửi sang các object nhận thông qua userInfo thực sự là một điểm trừ rất lớn của Notifiction. Hơn nữa, việc quản lý Notification sẽ tốn công sức hơn là delegate, và chúng ta khá là khó khăn để nhìn ra mối quan hệ giữa object gửi và nhận khi dùng Notification.

    Implementation

    • Vì Swift không có sẵn phương pháp tạo multicast delegate nên chúng ta cần phải tạo ra 1 class helper, nhằm quản lý các object muốn nhận delegate cũng như gọi các method delegate muốn gửi.

    Đầu tiên, chúng ta tạo class helper như dưới:

    class MulticastDelegate<ProtocolType> {
        private let delegates: NSHashTable<AnyObject> = NSHashTable.weakObjects()
        
        func add(_ delegate: ProtocolType) {
            delegates.add(delegate as AnyObject)
        }
        
        func remove(_ delegateToRemove: ProtocolType) {
            for delegate in delegates.allObjects.reversed() {
                if delegate === delegateToRemove as AnyObject {
                    delegates.remove(delegate)
                }
            }
        }
        
        func invokeDelegates (_ invocation: (ProtocolType) -> Void) {
            for delegate in delegates.allObjects.reversed() {
                invocation(delegate as! ProtocolType)
            }
        }
    }
    

    Class này là helper class có các method là add:(:) dùng để add và remove các object muốn nhận delegate. Ngoài ra nó có method invokeDelegates(:)->Void để gửi method delegate sang toàn bộ các object muốn nhận delegate.

    Tiếp theo, define protocol (các delegate method) muốn implement:

    protocol SampleDelegate: class {
        func sendSampleDelegateWithoutData()
        func sendSampleDelegate(with string: String)
    }
    
    extension SampleDelegate {
        func sendSampleDelegateWithoutData() {        
        }
    }
    

    Ở đây, protocol SampleDelegate có 2 method là để ví dụ thêm rõ ràng rằng multicast delegate có thể thoải mái gửi các delegate tuỳ ý. Phần extention của SampleDelegate chỉ là để khiến cho method sendSampleDelegateWithoutData trở thành optional, không cần phải "conform" đến SampleDelegate. Đây là cách khá Swift, thay vì dùng cách sử dụng @objC và keywork optional

    Tiếp theo, define ra class sẽ gửi các method của delegate

    class SampleClass {
        var delegate = MulticastDelegate<SampleDelegate>()
        
        func didGetData() {
            delegate.invokeDelegates {
                $0.sendSampleDelegate(with: "Sample Data")
            }
        }
    }
    

    Ở đây, có thể thấy rằng delegate của class "SampleClass" thực chất là object của helpper "MulticastDelegate", và nó chỉ chấp nhận delegate là objects của các class mà conform đến protocol "SampleDelegate"

    Khai báo vài class conform đến protocol "SampleDelegate"

    class ReceivedDelegate1: SampleDelegate {
        func sendSampleDelegate(with string: String) {
            print("ReceivedDelegate === 1 === \(string)")
        }
        
        deinit {
            print("deinit ReceivedDelegate1")
        }
    }
    
    class ReceivedDelegate2: SampleDelegate {
        func sendSampleDelegate(with string: String) {
            print("ReceivedDelegate === 2 === \(string)")
        }
        
        deinit {
            print("deinit ReceivedDelegate2")
        }
    }
    

    OK, bây giờ test thử:

    let sendDelegate = SampleClass()
    let received1 = ReceivedDelegate1()
    sendDelegate.delegate.add(received1)
    
    do {
        let received2 = ReceivedDelegate2()
        sendDelegate.delegate.add(received2)
        sendDelegate.didGetData()
    }
    print("Đợi cho object received2 trong block do được release")
    DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
        sendDelegate.didGetData()
    }
    
    

    Sau khi run đoạn source code này, ta được log như dưới:

    ReceivedDelegate === 2 === Sample Data
    ReceivedDelegate === 1 === Sample Data
    Đợi cho object received2 trong block do được release
    deinit ReceivedDelegate2
    ReceivedDelegate === 1 === Sample Data
    

    Giờ cùng phân tích:

    Trong block do thì object "sendDelegate" đã append được 2 delegate objects vào biến delegate của nó, tiến hành send delegate thì ta thấy rằng cả object của cả 2 class ReceivedDelegate1ReceivedDelegate2 đều nhận được.

    Sau block do thì object received2 sẽ được release, vì việc release sẽ tốn một chút thời gian cho nên chúng ta sẽ thử thực hiện việc send delegate sau khoảng thời gian 1s, sau khi received2 đã được release (bằng cách check log của method deinit)

    Lúc này, ta thấy rằng chỉ có object của class ReceivedDelegate1 là còn nhận được delegate, object của class ReceivedDelegate2 đã bị release nên không còn nhận được object nữa. Như vậy, cách làm này vẫn đảm bảo các delegate vẫn là weak reference, không gây ra leak memory.

    Đề làm được điều này thì ta đã sử dụng NSHashTable.weakObjects() để lưu weak reference đến các delegate được gán vào biến delegates của helper MulticastDelegate. Do đó đảm bảo được việc keep weak reference của class helper, nhằm tránh memory leak.

    Ví dụ xem file: https://github.com/nhathm/swift.sample/tree/master/DesignPatterns/MulticastDelegate.playground

  • iOS — Play RTSP Streaming

    iOS — Play RTSP Streaming

    Hướng dẫn build IJK Player để play RTSP streaming

    Table of Contents

    Chuẩn bị môi trường

    • Cài đặt Homebrew ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
    • Cài git brew install git
    • Cài yasm brew install yasm
    • Clone IJKPlayer từ github:
      • git clone https://github.com/RioV/ijkplayer.git ijkplayer-ios
        • Note: ở đây dùng /RioV/ijkplayer bởi vì đang tìm hiểu thấy IJKPlayer có issue, vậy nên folk sang một bản khác để tiện fix bug
        • Note: chú ý checkout source code về folder mà tên không có space, ví dụ: IJK Player => NG, IJK-Player => OK. Việc này sẽ ảnh hưởng đến tiến trình build lib, nếu như có space thì build sẽ bị lỗi.
      • cd ijkplayer-ios
      • git checkout -B latest k0.8.8 version lấy theo release tag của IJKPlayer Release Nếu sau này sửa lỗi lib ở branch develop thì sẽ là git checkout -B develop

    Build lib IJKPlayer

    • cd config
    • rm module.sh
    • ln -s module-lite.sh module.sh -> việc này sẽ bỏ module.sh default và thay vào đó sử dụng moule-lite.sh nhằm giảm binary size
    • Để build lib support RTSP thì cần chỉnh sửa file module-lite.sh như sau:
      • Xoá: export COMMON_FF_CFG_FLAGS="$COMMON_FF_CFG_FLAGS --disable-protocol=rtp"
      • Add: export COMMON_FF_CFG_FLAGS="$COMMON_FF_CFG_FLAGS --enable-protocol=rtp"
      • Add: export COMMON_FF_CFG_FLAGS="$COMMON_FF_CFG_FLAGS --enable-demuxer=rtsp" (nên để trong section của demuxer)
    • cd ..
    • ./init-ios.sh
    • cd ios
    • ./compile-ffmpeg.sh clean
    • Sửa file ijkplayer-ios/ios/compile-ffmpeg.sh
      • Chuyển:

        FF_ALL_ARCHS_IOS6_SDK="armv7 armv7s i386" FF_ALL_ARCHS_IOS7_SDK="armv7 armv7s arm64 i386 x86_64" FF_ALL_ARCHS_IOS8_SDK="armv7 arm64 i386 x86_64"

        Thành

        FF_ALL_ARCHS_IOS8_SDK="arm64 i386 x86_64"

    • ./compile-ffmpeg.sh all
      • Note: Với câu lệnh ./compile-ffmpeg.sh all thì rất dễ xảy ra lỗi nếu như source code đang ở trong directoy có chứa space. Ví dụ: working directory là /Documents/JLK Player thì sẽ lỗi, để fix thì chuyển thành /Documents/IJKPlayer

    Tích hợp IJKPlayer vào project

    • Add IJKPlayer vào project: File -> add File to "Project" -> chọn ijkplayer-ios/ios/IJKMediaPlayer/IJKMediaPlayer.xcodeproj
    • Chọn: Application’s target.
      • Vào: Build Phases -> Target Dependencies -> Chọn IJKMediaFramework
      • Chọn IJKMediaPlayer.xcodeproj, chọn target IJKMediaFramework và build.
      • Vào: Build Phases -> Link Binary with Libraries -> Thêm:
        • libc++.tbd
        • libz.tbd
        • libbz2.tbd
        • AudioToolbox.framework
        • UIKit.framework
        • CoreGraphics.framework
        • AVFoundation.framework
        • CoreMedia.framework
        • CoreVideo.framework
        • MediaPlayer.framework
        • MobileCoreServices.framework
        • OpenGLES.framework
        • QuartzCore.framework
        • VideoToolbox.framework

    Sample

    Sử dụng đoạn source code sau để play thử RTSP stream bằng IJKPlayer

    import UIKit
    import IJKMediaFramework
    
    class IJKPlayerViewController: UIViewController {
        var player: IJKFFMoviePlayerController!
        override func viewDidLoad() {
            super.viewDidLoad()
    
            let options = IJKFFOptions.byDefault()
            let url = URL(string: "rtsp://170.93.143.139/rtplive/470011e600ef003a004ee33696235daa")
            guard let player = IJKFFMoviePlayerController(contentURL: url, with: options) else {
                print("Create RTSP Player failed")
                return
            }
    
            let autoresize = UIView.AutoresizingMask.flexibleWidth.rawValue |
                UIView.AutoresizingMask.flexibleHeight.rawValue
            player.view.autoresizingMask = UIView.AutoresizingMask(rawValue: autoresize)
    
            player.view.frame = self.view.bounds
            player.scalingMode = IJKMPMovieScalingMode.aspectFit
            player.shouldAutoplay = true
            self.view.autoresizesSubviews = true
            self.view.addSubview(player.view)
            self.player = player        
            self.player.prepareToPlay()
        }
    }