Month: March 2020

  • The Application’s Life Cycle

    The Application’s Life Cycle

    Application’s life cycle – Vòng đời của 1 chương trình. Đây là 1 phần cơ bản nhưng cực kỳ quan trọng trong việc lập trình một ứng dụng.  Tuy có thể coi là 1 kỹ năng trấn phái nhưng không phải ai cũng nắm được rõ và đẩy đủ về vòng đời này (minh chứng là vẫn bị tester bắt nhiều bug về các case abnormal liên quan đến các state của app). Chính vì thế mình viết bài này để có thể giới thiệu 1 cách chi tiết về vòng đời của app và cách sử dụng để tránh những lỗi không đáng có. 

    iOS App life cycle - Brian - Medium


    Một application sẽ có các trạng thái như sau: 
    Not running: Là trạng thái application chưa được bắt đầu hoặc đã chạy nhưng bị terminated bởi system. 
    Inactive: Application đang chạy ở Foreground nhưng không nhận bất cứ sự kiện tương tác nào và cũng không thể xử lý các sự kiện (có thể là bị một vài sự kiện tác động vào trong quá trình chạy, ví dụ như có cuộc gọi đến hay tin nhắn chẳng hạn). 1 app cũng có thể ở trong trạng thái này khi chuyển từ state này sang state khác. 
    Active: Application đang chạy ở Foreground và đang nhận các sự kiện bình thường. Cách duy nhất để đến trạng thái Active là thông qua Inactive. Ở trạng thái này, khi người dùng tương tác với UI, họ có thể nhìn thấy phản hồi cho những hành động của họ.
    Background: Application đang chạy ở background và đang thực thi code. Ở trạng thái này UI của app không được hiển thị nhưng mà nó lại vẫn đang chạy (nếu có đăng ký background task với OS). Hầu hết các app chuyển trạng thái sang suspended thông qua trạng thái này.
    Suspended: Application đang chạy ở background nhưng không thể thực thi code. Thường thì sẽ do chính system sẽ tự động đưa app về trạng thái này và lúc đó app vẫn đang trong memory. Trong trường hợp low memory, hệ thống có thể sẽ tự kill app của mình khi app đang ở trạng thái suspended mà không thông báo gì. 
    Lưu ý rằng: Theo chuẩn của Apple thì chỉ hệ thống mới có thể kill app. 


    Về cơ bản thì 1 application có các trạng thái như trên, và trong app chúng ta cũng có các event tương ứng để được notify khi bắt đầu hay đã vào các trạng thái trên. Các hàm đó được list trong AppDelegate. Các trạng thái chuyển đổi qua lại được gọi là transition giữa các trạng thái.
    – application:willFinishLaunchingWithOptions  ——   Method này được gọi sau khi app của chúng ta khởi chạy thành công. Nó là method đầu tiên được chạy từ app delegate. Chúng ta có thể thực thi các đoạn code nếu khởi chạy thành công.
    – application:didFinishLaunchingWithOptions    ——   Method này được gọi trước khi window của app được hiển thị. Bạn có thể hoàn thiện giao diện của mình và cung cấp root viewcontroller cho window.
    applicationDidBecomeActive ——   Method này được gọi để báo cho app của bạn biết khi nó chuyển trạng thái từ In-Active sang Active hoặc hệ thống và user khơi động app hoặc trong trường hợp user bỏ quan các gián đoạn làm app ngay lập tức chuyển sang In-Active (như là có cuộc gọi đến hoặc tin nhắn). Bạn nên dùng method này để chạy lại các tác vụ đang bị dừng (hoặc chưa chạy) khi app bắt đầu chạy lại.
    applicationWillResignActive ——   Method này được gọi để báo cho app biết rằng nó sắp chuyển từ trạng thái Active sang In-Active . Nó xãy ra khi trường hợp bị gián đoạn (có cuộc gọi tới hoặc SMS) hay là khi user tắt app đi. Bạn nên dùng method này để dừng các task đang chạy hoặc vô hiệu hoá timer trong app, hoặc nhiều thứ khác 
    applicationDidEnterBackground  ——   Method này được gọi để báo cho app biết nó đang không chạy ở dưới foreground. Bạn có khoảng tầm 5 giây để thực thi các task . Trong trường hợp bạn muốn có nhiều thời gian hơn để xử lý, bạn có thể yêu cầu hệ thống cấp cho thời gian thực thi bằng cách gọi hàm beginBackgroundTask(expirationHandler:) . Nếu như method của bạn không được thực thi và trả về trước thời gian hết hạn thì app sẽ bị hệ thống chấm dứt và xoá khỏi bộ nhớ.
    applicationWillEnterForeground  ——   Method này được gọi như là 1 phần trong việc chuyển trạng thái từ Background sang Active. Bạn nên dùng method này để hoàn thành các thay đổi đối với app trước khi nó xuống Background. applicationDidBecomeActive sẽ được gọi ngay khi method này đã hoàn thành việc chuyển trạng thái của app từ In-Active sang Active.
    applicationWillTerminate  ——   Method này được gọi khi app của bạn sắp bị hệ thống khai tử khỏi bộ nhớ. Bạn nên dùng method này để thực thi các tác vụ dọn dẹp. Bạn có tầm khoảng 5 giây để thực thi tác vụ. Nếu hàm của bạn không trả về trước thời gian hết hạn, hệ thống sẽ tự động khai tử app kèm cã task đang thực thi của bạn khỏi bộ nhớ. Method này cũng được gọi trong trường hợp app đang chạy ở dưới background( không bị suspended) nhưng hệ thống lại cần phải huỷ nó vì vài lí do gì đó. Bạn không nên đợi applicationWillTerminate được gọi rồi mới lưu lại data. Trong 1 vài trường hợp hi hữu, applicationWillTerminate sẽ không được gọi trước khi app bị khai tử (Vd như trong trường hợp máy của bạn reboot lại thì method này sẽ không được gọi).

    Đó là tất cả về iOS Application’s life cycle.
    Cảm ơn mọi người đã theo dõi bài viết. Hi vọng bài viết này có thể giúp ích cho các bạn.
    Mọi ý kiến đóng góp các bạn vui lòng comment ở bên dưới để mình có thể hoàn thiện hơn ở các bài viết sắp tới.

    Thanks all from with love <3
    KhanhVD1.

  • iOS/Swift: Lưu dữ liệu bằng Keychain

    iOS/Swift: Lưu dữ liệu bằng Keychain

    Đôi khi phát triển ứng dụng chúng ta sẽ phải lưu lại những dữ liệu nhạy cảm như thông tin người dùng, thẻ ngân hàng … Vậy lúc đó chúng ta sẽ phải cần đến Keychain để lưu các dữ liệu đó. Vậy hôm nay mình sẽ giới thiệu với các bạn phương pháp lưu dữ liệu sử dụng Keychain.

    Tại sao không dùng các phương pháp lưu dữ liệu khác?

    Có thể các bạn đã biết, những thông tin nhạy cảm của người dùng như: thông tin tài khoản(username, pass, token …), thông tin thẻ ngân hàng … nếu để lộ ra ngoài sẽ làm ảnh hưởng rất lớn tời người dùng. Vì vậy nếu chúng ta sử dụng các cách lưu dữ liệu thông thường không có tính bảo mật như: File, Property List(UserDefaults), CoreData, Realm … thì người ta hoàn toàn có thể lấy được nội dung của ứng dụng một cách đơn giản. Về cơ bản các kiểu lưu dữ liệu kia đều sử dụng 1 file để lưu trữ data ở đó mà không sử dụng bất kỳ phương pháp mã hóa nào, vì vậy nó quá dễ dàng để các tin tặc có thể lấy được những dữ liệu bạn lưu trong các file đó.

    Sử dụng Keychain Services API

    Để lưu trữ những dữ liệu nhạy cảm cần tính bảo mật Apple đã cung cấp cho chúng ta Security Services. Keychain services API sẽ giúp chúng ta lưu trữ các dữ liệu này ở trong một encrypted database được gọi là Keychain.

    Để lưu Password vào keychain mình sẽ làm như sau:

        func save(_ password: String, for account: String) {
            let password = password.data(using: String.Encoding.utf8)!
            let query: [String: Any] = [kSecClass as String: kSecClassGenericPassword,
                                        kSecAttrAccount as String: account,
                                        kSecValueData as String: password]
            let status = SecItemAdd(query as CFDictionary, nil)
            guard status == errSecSuccess else { return print("save error")}
        }

    Keychain service cung cấp một số kiểu để xác định nơi lưu trữ phù hợp. kSecClassGenericPassword là một trong số đó, keychain services sẽ hiểu items này là password và cần được mã hóa. Ngoài ra còn có các kiểu khác các bạn tìm hiểu ở ĐÂY.

    SecItemAdd(_:_:) để lưu dữ liệu vào keychain

    Để lấy lại dữ liệu chúng ta đã lưu chúng ta làm như sau:

        func getPassword(for account: String) -> String? {
            let query: [String: Any] = [kSecClass as String: kSecClassGenericPassword,
                                        kSecAttrAccount as String: account,
                                        kSecMatchLimit as String: kSecMatchLimitOne,
                                        kSecReturnData as String: kCFBooleanTrue]
    
            var retrivedData: AnyObject? = nil
            let _ = SecItemCopyMatching(query as CFDictionary, &retrivedData)
    
    
            guard let data = retrivedData as? Data else {return nil}
            return String(data: data, encoding: String.Encoding.utf8)
        }

    Để lấy dữ liệu ra chúng ta sử dụng SecItemCopyMatching() truyền vào đó query mà mình muốn.

    Đối với các bạn lần đầu làm việc với Keychain API thì nó cũng khá lằng nhằng. Nhưng hiện nay đã có khá nhiều thư viện ngon hỗ trợ việc code của các bạn trở nên dễ dàng hơn như: Keychain-swift, Keychain Access

    Mã hóa thông tin

    Ngoài Password ra đôi khi chúng ta cũng cần phải mã hóa một số loại thông tin nhạy cảm khác. Việc mã hóa bằng tay khá phức tạp, vì vậy để hỗ trợ cho việc mã hóa mình thường dùng CryptoSwift.

    Tổng kết

    Mình hi vọng bài viết có thể giúp các bạn khi các bạn gặp phải các bài toán cần phải lưu những dữ liệu nhạy cảm. Khi đó nhớ chọn Keychain nhé. Chúc các bạn thành công.

    Bài viết tiếp theo mình sẽ chia sẻ thêm một tính năng rất hay của keychain đó là Keychain Sharing

  • iOS/Swift: Lưu dữ liệu sử dụng Property List

    iOS/Swift: Lưu dữ liệu sử dụng Property List

    Lời mở đầu

    Cách lưu dữ liệu sử dụng file Property List khá phổ biến trong lập trình ứng dụng iOS, nó thường được dùng để lưu các dữ liệu nhỏ và không cần tính bảo mật cao. Bài viết này mình sẽ chia sẻ với các bạn một số cách để thao tác với Property List.

    Info.plist

    Tất cả các project khi bạn mới tạo XCode sẽ tự tạo ra một file Info.plist, nó làm nhiệm vụ lưu lại các thông tin dự án của bạn. Từ project name, version cho đến các setting cũng như mô tả API của Apple.

    Các thông tin trong file Info.plist sẽ được các API của Apple truy cập vào để lấy thông tin hiển thị lên ứng dụng. (VD: Hiển thị message cho pop up xin quyền truy cập Camera …) cũng như việc xác nhận thông tin của Apple khi bạn Submit ứng dụng lên App Store Connect. Vì vậy file này chỉ nên lưu những thông tin cài đặt của dự án. Nếu bạn muốn lưu dữ liệu dạng này ta có thể tạo một file mới và lưu vào đó.

    Tạo file Property List mới

    Bước 1: Chuột phải vào thư mục bạn muốn lưu file -> New File…

    Bước 2: Đánh ô tìm kiếm phía trên với keyword “Property List” -> Next -> Đặt tên file

    Vậy là bạn đã tạo thành công file Property List. Giờ bạn có thể mở file và điền thông tin mình muốn lưu vào file đó.

    Tuy nhiên nếu sử dụng XCode thì chúng ta chỉ có thể lưu được các dữ liệu như:

    • String
    • Number
    • Bool
    • Data
    • Date
    • Array
    • Dictionary

    Chúng ta cũng có thể lưu kiểu custom object vào file Property List nhưng phải sử dụng code để encode dữ liệu cần lưu sang Data rồi khi sử dụng thì chúng ta decode nó về dữ liệu ban đầu.

    Tạo file Property List bằng code

    Chúng ta có thể tạo file Property List một cách đơn giản hơn bằng cách như sau:

        func save(key: String, value: Any) {
            let myData: NSDictionary = [key: value]
            let fileManager = FileManager.default
            // Đường dẫn lưu file và tên file của bạn
            let path = fileManager.urls(for: .documentDirectory, in: .userDomainMask)[0].appendingPathComponent("MyInfo3.plist")
            // Viết vào file nếu file không tồn tại nó sẽ tự tạo file mới.
            myData.write(to: path, atomically: true)
            print(path)// in ra console để thấy đường dẫn lưu file
        }

    Truy cập vào file Property List bằng code

    Lấy data từ file Property list

        func getMyPlist(key: String) -> Any? {
            // Lấy ra đường dẫn vào file Property List của bạn
            let path = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0].appendingPathComponent("MyInfo.plist")
            // Sử dụng NSDIctionary để lấy nội dung từ URL
            guard let myDict = NSDictionary(contentsOf: path) else { return nil }
            print(path)
            // Trả về giá trị theo key trong Dictionary
            return myDict[key]
        }

    Lưu dữ liệu vào file Property List

        func saveMyPlist(key: String, value: Any) {
            let fileManager = FileManager.default
            // Tạo đường dẫn file
            let path = fileManager.urls(for: .documentDirectory, in: .userDomainMask)[0].appendingPathComponent("MyInfo.plist")
            guard let myDict = NSMutableDictionary(contentsOf: path) else {
                // nếu file chưa tồn tại chúng ta sẽ ghi vào file với bộ key/value đầu tiên.
                let myData: NSDictionary = [key: value]
                myData.write(to: path, atomically: true)
                return
            }
            // nếu file đã tồn tại chúng ta sẽ update dữ liệu
            myDict[key] = value
            myDict.write(to: path, atomically: true)
            print(path)
        }

    Xóa một key trong Property List

        func removeMyPlist(key: String) {
            let fileManager = FileManager.default
            // Tạo đường dẫn file
            let path = fileManager.urls(for: .documentDirectory, in: .userDomainMask)[0].appendingPathComponent("MyInfo.plist")
            guard let myDict = NSMutableDictionary(contentsOf: path) else { return }
            // nếu file đã tồn tại chúng ta sẽ update dữ liệu
            myDict.removeObject(forKey: key)
            myDict.write(to: path, atomically: true)
        }

    Để tương tác dễ hơn với plist bắt buộc chúng ta phải tạo ra các func để quản lý các file đó. Nhưng nếu các bạn không muốn làm những thứ lằng nhằng đó thì chúng ta có thể chuyển qua dùng một file Property List có sẵn mà Apple đã cung cấp đó là sử dụng UserDefault

    UserDefault

    UserDefault là một singleton class nhằm mục đích giúp các nhà phát triển có thể dễ dàng lưu dữ liệu vào file Plist hơn. Mặc định khi bạn sử dụng UserDefault nó sẽ tạo ra một file Plist trong dự án. Từ đó ta có thể thêm, sửa, xóa các key nằm trong file Plist đó dựa trên các hàm mà UserDefault cung cấp.

    UserDefault tương tác trên Property List nên nó cũng phải tuân thủ theo quy tắc của file đó. Vì vậy thông thường chung ta chỉ có thể gán được các giá trị mà file Plist cho phép.

    Để lưu/lấy giá trị bằng UserDefault ta làm như sau:

            let udf = UserDefaults.standard//Tạo instance
            udf.set("11", forKey: "1")// gán giá trị 11 cho key "1"
            udf.synchronize()//nó block thread đang gọi cho đến khi giá trị được gán hoàn tất
            print(udf.value(forKey: "1"))// lấy dữ liệu của key 1 và in ra console

    Như vậy với UserDefault chúng ta có thể làm việc với file Plist một cách đơn giản. Tuy nhiên, đôi khi chúng ta cũng phải lưu lại kiểu dữ liệu mà Plist không hỗ trợ. Lúc này chúng ta cần phải encode nó về Data để có thể lưu được dạng này.

    Lưu ý

    Chúng ta không nên lưu những dữ liệu quá nặng trong Plist, vì file này sẽ tiêu tốn bộ nhớ RAM khi bạn chạy ứng dụng.

    Lưu custom object

    Tạo mới class

    class Employee: NSObject, NSCoding {
        let name: String
        let dob: String
        // hàm khởi tạo
        init(name: String, dob: String) {
            self.name = name
            self.dob = dob
        }
        // init với NSCoder bước này để decode
        required convenience init(coder aDecoder: NSCoder) {
            let name = aDecoder.decodeObject(forKey: "name") as! String
            let dob = aDecoder.decodeObject(forKey: "dob") as! String
            self.init(name: name, dob: dob)
        }
        // hàm này để encode
        func encode(with aCoder: NSCoder) {
            aCoder.encode(name, forKey: "name")
            aCoder.encode(dob, forKey: "dob")
        }
    }

    Lưu Employee vào UserDefault sử dụng NSKeyedArchiver

            let employee = Employee(name: "Dino", dob: "19/09/1999")
            let udf = UserDefaults.standard
            do {
                let encodeData = try NSKeyedArchiver.archivedData(withRootObject: employee, requiringSecureCoding: false)
                udf.set(encodeData, forKey: "emp")
                udf.synchronize()
            } catch {
                print(error)
            }

    Lấy ra Employee từ UserDefault

    let decode = udf.data(forKey: "emp")
    let decodeEmp = NSKeyedUnarchiver.unarchiveObject(with: decode!) as? Employee

    Tổng kêt

    Mình hi vọng bài viết này có thể giúp các bạn sử dụng Property List tốt hơn. Chúc các bạn thành công.

  • The Good, The Bad and the Ugly

    (Một bộ phim kinh điển mà mình cực kỳ thích nên lấy nó làm title cho bài viết này)
    Đây là bản “hồi ký” khi mình tìm solution cho một bài toán, hi vọng nó sẽ giúp ai đó định hướng được đường phải đi.

    Bài toán: mix 2 file audio trên android mà không dùng thêm library nào

    • Suy nghĩ đầu tiên là tìm xem android có chìa API nào ra để mix file ko – bỏ đi, các cậu ko cần search, android ko chìa api nào ra để làm việc này đâu
    • Suy nghĩ thứ 2: Đưa dữ liệu âm thanh(mp3..etc) về dạng raw data và thử mix các bit vào nhau? có vẻ khả thi

    The bad:

    
    private byte[] mBufData1 = null;
    private byte[] mBufData2 = null;
    private ArrayList mBufMixedData = new ArrayList<>();
    
    private void loadMixedData() {
            int length1 = mBufData1.length;
            int length2 = mBufData2.length;
            int max = Math.max(length1, length2);
            int tempSplitSize;
            if (length1 == length2) {
                for (int i = 0; i < length1; i++) {
                    mBufMixedData.add((byte) (mBufData1[i] + mBufData2[i]));
                }
            } else {
                if (length2 > length1) {
                    tempSplitSize = length1;
                } else {
                    tempSplitSize = length2;
                }
                for (int i = 0; i < tempSplitSize; i++) {
                    mBufMixedData.add((byte) (mBufData1[i] + mBufData2[i]));
                }
                if (length2 > length1) {
                    for (int i = tempSplitSize; i < max; i++) {
                        mBufMixedData.add((mBufData2[i]));
                    }
                } else {
                    for (int i = tempSplitSize; i < max; i++) {
                        mBufMixedData.add((mBufData1[i]));
                    }
                }
    
            }
        }

    Chạy được thật, âm thanh đã được mix lại như file karaoke ngoài hàng. Nhưng nhìn source code thì chỉ có đứa mù dở mới ko nhìn thấy vấn đề OOM chắc chắn phát sinh.
    Nếu dừng ở đây – Ok, nó chạy được, chúng ta sẽ giấu đi đoạn OOM kia, kệ cho dự án hót shit vì còn lâu họ mới test ra issue ý, lúc phát hiện chúng ta đã cao chạy xa bay rồi. Ka ka ka
    Chúng ta là những kẻ tồi tệ

    The Ugly

    Cải tiến hơn 1 chút, để tránh OOM, mình ko load hết dữ liệu lên ram nữa mà đưa vào DataOutputStream để write từng bit xuống file

    private void createWaveMixing(String p1, String p2, String p3) throws IOException {
            int size1 = 0;
            int size2 = 0;
            int size1;
            int size2;
    
            FileInputStream fis1 = null;
            FileInputStream fis2 = null;
            try {
                fis1 = new FileInputStream(p1);
                fis2 = new FileInputStream(p2);
                size1 = fis1.available();
                size2 = fis2.available();
                long totalAudioLen = size1;
                if (size1 < size2) {
                    totalAudioLen = size2;
                }
                long totalDataLen = totalAudioLen + WavUtil.LENGTH_EXTENDED;
                long longSampleRate = WavUtil.getSampleRate(p1);//44100
                long totalDataLen = totalAudioLen + WavUtils.LENGTH_EXTENDED;
                long longSampleRate = MediaCodecUtils.getSampleRate(p1);//44100
                int channels = 2;
                long byteRate = WavUtil.RECORDER_BPP * longSampleRate * channels / 8;
                long byteRate = WavUtils.RECORDER_BPP * longSampleRate * channels / 8;
                DataOutputStream out = null;
                try {
                    out = new DataOutputStream(new FileOutputStream(p3));
                    WavUtil.writeWaveFileHeader(out, totalAudioLen, totalDataLen, longSampleRate, channels, byteRate);
                    WavUtils.writeWaveFileHeader(out, totalAudioLen, totalDataLen, longSampleRate, channels, byteRate);
                    out.write(toByteArray(mBufMixedData));
                } catch (Exception e) {
                    Log.e(TAG, "#createWaveMixing():" + e.getMessage(), e);
                } finally {
                    if (out != null) {
                        out.close();
                    }
                }
            } finally {
                if (fis1 != null) {
                    fis1.close();
                }
                if (fis2 != null) {
                    fis2.close();
                }
            }
    
        }
    
        private byte[] toByteArray(ArrayList in) {
            byte[] data = new byte[in.size()];
            for (int i = 0; i < data.length; i++) {
                data[i] = in.get(i);
            }
            return data;
        }
    }

    Cách làm này tránh được OOM, nhưng phải nói là nó chậm, thực sự chậm, chậm kinh khủng. Đâu đó mất khoảng 50 -60 s cho 2 file music raw dài 3 phút.
    Nói chung là chạy được, ko có issue gì cả, chỉ chậm thôi. Chậm thì tự tìm cách mà improve đi, kêu gì – đúng ko các cậu? – lại chả phải quá.
    Nếu dừng ở đây, chúng ta là những gã lừa đảo.

    Nhưng dù sao mình cũng là người vừa đẹp trai lại tốt tính, nên mới xuất hiện tình huống thứ 3

    The Good

    Lần này mình optimize bằng cách sử dụng FileChannel, thay vì handle từng bit một, thì mình bóc một nhóm lớn ra để mix(buôn sỉ mới nhanh giầu)

    private void createWaveMixing(String p1, String p2, String p3) throws IOException {
            int size1;
            int size2;
    
            FileInputStream fis1 = null;
            FileInputStream fis2 = null;
            try {
                fis1 = new FileInputStream(p1);
                fis2 = new FileInputStream(p2);
                size1 = fis1.available();
                size2 = fis2.available();
                long totalAudioLen = size1;
                if (size1 < size2) {
                    totalAudioLen = size2;
                }
                long totalDataLen = totalAudioLen + WavUtils.LENGTH_EXTENDED;
                long longSampleRate = MediaExtractorUtils.getSampleRate(p1);//44100
                int channels = 2;
                long byteRate = WavUtils.RECORDER_BPP * longSampleRate * channels / 8;
                DataOutputStream out = null;
    
                FileChannel fc1 = fis1.getChannel();
                FileChannel fc2 = fis2.getChannel();
                long length1 = fc1.size();
                long length2 = fc2.size();
                try {
                    out = new DataOutputStream(new FileOutputStream(p3));
                    WavUtils.writeWaveFileHeader(out, totalAudioLen, totalDataLen, longSampleRate, channels, byteRate);
                    {
                        ByteBuffer buff1 = ByteBuffer.allocate(BUFFER_SIZE);
                        ByteBuffer buff2 = ByteBuffer.allocate(BUFFER_SIZE);
    
                        ByteBuffer mixedBuffer = null;
                        if (length1 == length2) {
                            while (fc1.read(buff1) > 0) {
                                fc2.read(buff2);
                                mixedBuffer = mixByteBuffer(buff1, buff2);
                                out.write(mixedBuffer.array());
                                buff1.clear();
                                buff2.clear();
                                mixedBuffer.clear();
                            }
                            if (mixedBuffer != null) {
                                mixedBuffer.clear();
                            }
                        } else {
                            if (length2 > length1) {
                                while (fc1.read(buff1) > 0) {
                                    fc2.read(buff2);
                                    mixedBuffer = mixByteBuffer(buff1, buff2);
                                    out.write(mixedBuffer.array());
                                    buff1.clear();
                                    buff2.clear();
                                    mixedBuffer.clear();
                                }
                                while (fc2.read(buff2) > 0) {
                                    out.write(buff2.array());
                                    buff2.clear();
                                }
                            } else {
                                while (fc2.read(buff2) > 0) {
                                    fc1.read(buff1);
                                    mixedBuffer = mixByteBuffer(buff1, buff2);
                                    out.write(mixedBuffer.array());
                                    buff1.clear();
                                    buff2.clear();
                                    mixedBuffer.clear();
                                }
                                while (fc1.read(buff1) > 0) {
                                    out.write(buff1.array());
                                    buff1.clear();
                                }
                            }
    
                        }
    
                    }
                } catch (Exception e) {
                    Log.e("test", "#createWaveMixing():" + e.getMessage(), e);
                } finally {
                    if (out != null) {
                        out.close();
                    }
                    fc1.close();
                    fc2.close();
                }
            } finally {
                if (fis1 != null) {
                    fis1.close();
                }
                if (fis2 != null) {
                    fis2.close();
                }
            }
    
        }

    Kết quả tốc độ tăng gấp 6-8 lần mà lại ko có issue gì cả
    Lần này(dường như) mình sẽ có thể là người tốt

    Cùng 1 bài toán, sẽ có các cách giải khác nhau
    Khoảng thời gian cho phép khác nhau, chúng ta cũng sẽ phải chọn các giải pháp khác nhau(chấp nhận được, chạy có issue, chậm…etc)

    1 ngày làm được ko? được – the bad
    1 tuần làm được ko? được – the ugly
    1 tháng làm được ko? được – the good

    Cái gì cũng sẽ có giá của nó, tùy vào deadline, tùy vào thời điểm, chúng ta hãy cố chọn ra được cách làm tốt nhất

    1 phút dành cho quảng cáo, GDK đã có 1 phiên bản có thể record, mix, nối audio nhé, anh em có thể search gdk-soundutilities

  • [AWS] Remote Debugging ứng dụng Lambda viết bằng Java với Visual Studio Code

    [AWS] Remote Debugging ứng dụng Lambda viết bằng Java với Visual Studio Code

    Debug cũng quan trọng giống như lúc bạn code vậy. Với những bạn làm quen với Lambda thì không phải ai cũng biết làm sao để có thể debug được. Đa phần các bạn sẽ chọn cách in dữ liệu ra màn hình để debug. Hôm nay tôi sẽ hướng dẫn các bạn cách debug ứng dụng viết bằng Lamba nhé.

    Remote Debugging

    Như các bạn đều biết thì để có thể debug được ứng dụng Java thì bạn cần phải Remote tới cổng Debug của trình thực thi Java. Quá trình này được gọi là Remote Debugging. Với ứng dụng Java bình thường các bạn có thể dễ dàng sử dụng các IDE có hỗ trợ Remote Debugging một cách dễ dàng. Với các ứng dụng Lambda bằng Java thì sao?

    Khởi động ứng dụng Lambda với chế độ Remote Debugging

    Trong bài viết Phát triển ứng dụng Lambda bằng Java, tôi đã hướng dẫn các bạn cách sử dụng SAM để chạy các ứng dụng Lambda viết bằng ngôn ngữ Java. Các bạn theo dõi bài viết trên sẽ thấy ứng dụng được chạy trên một máy ảo Java trông một docker container. Để khời động chế để Remote Debugging thì các bạn gõ lệnh sau(các bạn nhớ khởi động Docker trước khi khởi động SAM nhé):

    hieunv@HieuNV sam-app % sam local start-api -d 5858
    Mounting HelloWorldFunction at http://127.0.0.1:3000/hello [GET]
    You can now browse to the above endpoints to invoke your functions. You do not need to restart/reload SAM CLI while working on your functions, changes will be reflected instantly/automatically. You only need to restart SAM CLI if you update your AWS SAM template
    2020-03-30 20:10:33  * Running on http://127.0.0.1:3000/ (Press CTRL+C to quit)
    

    Các bạn sẽ thấy SAM được khởi động và lắng nghe ở cổng 3000. Còn cổng 5858 thì sao? Tại thời điểm này nó chưa được khởi động. Khi bạn access vào http://127.0.0.1:3000/hello thì cổng Remote Debugging 5858 mới được khởi động.

    Invoking helloworld.App::handleRequest (java11)
    
    Fetching lambci/lambda:java11 Docker container image......
    Mounting /Users/hieunv/Projects/hieunv/sam-app/.aws-sam/build/HelloWorldFunction as /var/task:ro,delegated inside runtime container
    Picked up _JAVA_OPTIONS: -agentlib:jdwp=transport=dt_socket,server=y,suspend=y,quiet=y,address=*:5858 -XX:MaxHeapSize=2834432k -XX:MaxMetaspaceSize=163840k -XX:ReservedCodeCacheSize=81920k -XX:+UseSerialGC -XX:-TieredCompilation -Djava.net.preferIPv4Stack=true
    

    Cấu hình Remote Debug trong Visual Studio Code

    Các bạn quay lại Visual Studio Code, vào Tab Debug sau đó chọn create a launch.json file. Tại mục chọn kiểu Debug bạn chon Add Configuration và chọn

    add configuration

    Sau đó các bạn chon Attach To Remote Program

    Attach To Remote Program

    Tiếp đó các bạn sửa lại cấu hình hostName thành localhostport thành 5858(đấy là cổng Remote Debug của trình thực thi Java)

    {
      // Use IntelliSense to learn about possible attributes.
      // Hover to view descriptions of existing attributes.
      // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
      "version": "0.2.0",
      "configurations": [
        {
          "type": "java",
          "name": "Debug (Attach) - Remote",
          "request": "attach",
          "hostName": "localhost",
          "port": 5858
        }
      ]
    }
    

    Đặt break point

    Các bạn quay lại Visual Studio Code và mở mã nguồn muốn debug sau đó đặt break point

    break point

    Khởi động Visual Studio Code Debug bằng các click vào nut Start

    Xem output log bạn sẽ thấy thông báo sau:

    START RequestId: 4f69214b-9a3a-19ef-0137-5081d7caccea Version: $LATEST
    END RequestId: 4f69214b-9a3a-19ef-0137-5081d7caccea
    REPORT RequestId: 4f69214b-9a3a-19ef-0137-5081d7caccea	Init Duration: 58932.30 ms	Duration: 10421.29 ms	Billed Duration: 10500 ms	Memory Size: 512 MB	Max Memory Used: 73 MB
    2020-03-30 20:39:18 127.0.0.1 - - [30/Mar/2020 20:39:18] "GET /hello HTTP/1.1" 500 -
    ``
    

    Sau đó bạn access http://127.0.0.1:3000/hello bằng Postman và quay lại Visual Studio Code

    debug mode

    Như vậy là chúng ta đã debug thành công vào hàm Lambda rồi.

    Cám ơn các bạn đã theo dõi bài viết. Hy vọng bài viết sẽ giúp ích với dự án của các bạn. Chúc các bạn thành công.

  • iOS: Một số cách lưu dữ liệu phổ biến

    iOS: Một số cách lưu dữ liệu phổ biến

    Lời mở đầu

    Đối với các ứng dụng iOS, việc lưu dữ liệu là việc bắt buộc. Việc lưu dữ liệu rất cần thiết đối với các ứng dụng từ lớn cho tới nhỏ. Bạn có thể lưu dữ liệu lớn như các dữ liệu lấy được từ các API, cho đến các trạng thái hoặc các cài đặt của ứng dụng. Bài viết này mình sẽ giới thiệu tổng quát về các cách chúng ta có thể lưu dữ liệu trong iOS. Bài viết này chỉ mang tính giới thiệu cho từng loại thường dùng cho việc gì, các cách thao tác trên từng loại mình sẽ tách ra làm các bài riêng cho các bạn dễ hiểu và đỡ bị ngợp.

    1. Lưu dữ liệu bằng file

    Các dữ liệu cơ bản thường gặp khi sử dụng ứng dụng như String(VD: json, xml ..) và Data(VD: image)

    Đối với những kiểu dữ liệu cơ bản này chúng ta có thể thực hiện thao tác lưu(save), lấy(get) dữ liệu thông qua các hàm đã được cung cấp sẵn trong class đó.

    Chúng ta thường dùng cách này để lưu những dữ liệu như:

    • Lưu lại log của ứng dụng
    • Lưu lại hình ảnh, cache data
    • Lưu lại nội dung có dữ liệu lớn
    • Lưu lại nội dung dạng json hoặc xml

    Về cơ bản thì với cách này các bạn có thể lưu được rất nhiều thứ và nó có thể được quản lý, sắp xếp tùy ý. Do đuôi file không bị cố định nên bạn có thể lưu nội dung lên file có đuôi mình muốn và mở bằng các ứng dụng hỗ trợ.

    Để quản lý thư mục lưu file, chúng ta nên sử dụng NSFileManager. Ngoài ra NSFileManager cũng cung cấp cho chúng ta các hàm để save(lưu), get(lấy) dữ liệu. Nhưng các hàm này bản thân các class như String và Data cũng có rồi nên NSFileManager thường được dùng để quán lý thư mục.

    Hướng dẫn lưu dữ liệu ra file

    2. Lưu dữ liệu phân cấp

    Property Lists

    Là các file có đuôi .plist, dùng để lưu dữ liệu có dạng như một hệ thông phân cấp kiểu Array hoặc Dictionary với dung lượng không quá lớn.

    Trong iOS, Property List chỉ lưu được một số kiểu dữ liệu cơ bản như:

    • String
    • Data
    • Date
    • Number
    • Bool
    • Array
    • Dictionary

    Ngoài ra chúng ta cũng có thể lưu được các object. Để làm điều đó chúng ta cần mã hóa(encode) nó về dạng Data rồi khi cần dùng thì giải mã(decode) dữ liệu trở lại.

    NSUserDefault

    NSUserdefault là một file Property List, đã được Apple viết sẵn 1 class dạng singleton. Nó giúp các iOS developer thuận tiện và tiết kiệm thời gian hơn trọng việc lưu/lấy dữ liệu cần thiết ở file mà NSUserdefault đã tạo sẵn cho mỗi project của bạn.

    Property List thường được dùng để:

    • Lưu dữ liệu đơn giản có dung lượng thấp
    • Lưu lại các setting của ứng dụng
    • Lưu lại các trạng thái của ứng dụng

    3. Lưu dữ liệu sử dụng SQLite

    Đối với các ứng dụng có nhiều dữ liệu phức tạp, với các mối quan hệ chồng chéo giữa các dữ liệu thì việc sử dụng cách 1 với 2 không thể đáp ứng được các yêu cầu về tốc độ tìm kiếm, sắp xếp và thậm chí là hiệu năng về việc dọc và lưu dữ liệu mà ứng dụng yêu cầu. Với những dạng dữ liệu như này chúng ta nên trang bị cho ứng dụng một phương thức tốt hơn đó chính là SQLite.

    SQLite

    SQLite là một hệ thống cơ sở dữ liệu quan hệ nhỏ gọn,hoàn chỉnh có thể cài đặt bên trong các ứng dụng khác.

    Đặc điểm của SQLite là gọn, nhẹ và đơn giản. Không cần cài đặt, không cần cấu hình hay khởi động mà có thể sử dụng ngay. Dữ liệu database cũng được lưu ở một file duy nhất. Đối với các ứng dụng mobile SQLite rất thích hợp để sử dụng.

    SQLite thường được sử dụng khi nào:

    • Khi bạn có một khối dữ liệu lớn cần phải lưu lại
    • Khi bạn có một tập các dữ liệu liên quan móc nối với nhau
    • Khi bạn muốn lưu lại các dữ liệu mà API trả về

    Một số thư viện hay sử dụng

    • FMBD(Flying Meat Database): Là một thư viện hỗ trợ bạn trong việc thao tác với SQLite . Nói cách khác FMDB là một Objecttive-C wrapper của SQLite. Công việc của FMDB là giúp bạn thoải mái hơn trọng việc thực hiện các câu lệnh truy vấn trong SQLite.
    • CoreData: Là một framework được Apple xây dụng để hỗ trợ chúng ta thao tác với SQLite Database theo hướng đối tượng mà không phải quan tâm tới các câu lệnh truy vấn của SQLite. Nó sẽ coi các bản ghi trong SQLite Database như một object, table như class.

    Nên sử dụng FMBD(Flying Meat Database) hay CoreData

    CoreData là một Framework được Apple phát triển để làm việc với SQLite Database vì vậy việc sử dụng CoreData sẽ được khuyến khích hơn cả. CoreData giúp việc viết và đọc code rõ ràng hơn việc sử dụng FMDB để làm việc trực tiếp với SQLite. Nó tốt cho việc bảo trì cũng như phát triển code sau này. Ngoài ra Apple còn hỗ trợ cả việc cache dữ liệu, tích hợp CoreData với UITableView, một class mà hầu hết các ứng dụng đều sử dụng.

    Bạn đọc dữ liệu từ database lấy ra một list các object và hiển thị nó bằng UITableView nó thực sự rất tiện lợi đối với các iOS Dev.

    4. Keychain

    Keychain là một lưu trữ bảo mật với những dữ liệu nhỏ gọn, nhạy cảm như mật khẩu, số tài khoản ngân hàng, mã bảo mật hay một số thông tin mà ta muốn bảo mật không muốn cho người khác biết được.

    Ngoài ra Apple cũng cung cấp một dịch vụ Keychain Sharing để chia sẻ keychain giữa các ứng dụng của cùng một nhà phát triển. Ví dụ như Facebook và Messenger, nếu ta đăng nhập ở Facebook và lưu password vào Keychain thì sang Messenger chúng ta có thể sử dụng password để đăng nhập nhanh ở ứng dụng này.

    5. Realm

    Realm là một database sử dụng core C++ với tham vọng thay thế SQLite với các ưu điểm về tốc độ và dễ sử dụng. Hiện nay Realm rất được ưa chuộng trên thế giới vì nó có rất nhiều ưu điểm vuợt trội so với những cách còn lại.

    Ưu điểm của Realm so với CoreData:

    • Dễ sử dụng hơn
    • Tốc độ query nhanh hơn
    • Quản lý dữ liệu dễ dàng trực quan hơn
    • Tài liệu tham khảo tốt
    • Open source

    Khi nào nên sử dụng Realm cho dự án của bạn:

    • Khi project của bạn cần lưu lại số lượng lớn các bản ghi
    • Khi project của bạn có nhiều dữ liệu móc nối với nhau, có mối quan hệ phức tạp
    • Khi dự án của bạn cần lưu lượng lớn các thông tin từ API

    Tổng kết

    Bài viết này mình đã giới thiệu với các bạn về một số cách lưu dữ liệu phổ biến trong swift. Mỗi khi các bạn muốn lưu một loại dữ liệu nào hãy cân nhắc kiểu dữ liệu nó là gì, mức độ quan trọng của nó như nào. Từ đó bạn có thể chọn được cách lưu dữ liệu tối ưu nhất với loại dữ liệu đó. Các bài viết tiếp theo mình sẽ giới thiệu với các bạn cách triển khai lưu dữ liệu cho từng cách.

    Cảm ơn các bạn đã theo dõi bài viết! Chúc các bạn thành công.

  • ARC in Swift

    ARC in Swift

    Việc quản lí bộ nhớ trong mỗi ứng dụng là rất quan trọng. Nếu ứng dụng của bạn sử dụng dữ liệu bừa bãi 1 cách không cần thiết thì sẽ làm memory của ứng dụng tăng cao, và trong trường hợp xấu có thể dẫn đến crash.
    Swift sử dụng ARC để hỗ trợ developer quản lí ứng dụng của app. Ở bài viết này mình sẽ nói về ARC trong swift.

    Contents:

    • ARC là gì
    • Cách ARC hoạt động
    • Strong reference
    • Xử lí strong reference
    • weak reference

    ARC là gì

    • ARC là viết tắt của Automatic Reference Counting. Swift sử dụng ARC để giúp bạn việc quản lý bộ nhớ của app.
    • ARC sẽ tự động giải phóng bộ nhớ được sử dụng bởi 1 biến khi biến đó không còn được sử dung nữa.
    • Tuy nhiên, để ARC có thể biết được khi nào biến đó không còn dc sử dụng, thì ARC phải biết về các relations giữa biến đó vs các biến khác trong code của bạn.

    Note: ARC chỉ áp dụng cho các biến kiểu class(reference type).

    Cách ARC hoạt động

    • Mỗi khi bạn tạo 1 biến, ARC sẽ tự chia sẻ 1 phần bộ nhớ để lưu trữ biến đó. Khi biến đó không còn được sử dụng, ARC giải phóng biến, lấy lại bộ nhớ để dùng cho những mục đích khác
    • Để xác định 1 biến không còn được sử dụng, ARC sẽ đếm số lượng strong reference hiện tại của biến đó đối với các biến khác. Chỉ khi số lượng strong reference = 0 thì biến đó mới được giải phóng bộ nhớ.

    Vậy strong reference là gì?

    Strong reference

    Mỗi khi bạn gán 1 biến kiểu class cho 1 property, constant hoặc variable, thì những thứ đó tạo 1 strong reference đến biến đó.

    class Car {
        var price: Double
        init(price: Double) {
            self.price = price
        }
        
        deinit {
            print("Car is being deinitialized")
        }
    }
    
    var hondaCar: Car? = Car(price: 50000.0)

    Ở đây, bạn gán biến Car(price: 50000.0) cho varialbe hondaCar
    -> hondaCar đã tạo 1 strong reference đến Car(price: 50000.0)

    Thêm dòng code sau và run thử trên playground:

    hondaCar = nil

    Kết quả thu được ở màn hình console như sau:

    Nếu bạn set honedaCar = nil -> Khi đó Car(price:50000) sẽ không còn strong reference nào trỏ đến -> ARC sẽ giải phóng bộ nhớ cho Car.
    Tiếp tục thử chạy đoạn code sau và quan sát màn hình console:

    Chạy đoạn code trên, bạn sẽ không thấy được print ra "Car is being deinitialized"…
    Vì các biến car1, car2, car3 là reference type, nên ta có sơ đồ relation ship như sau:

    Khi đó nếu chỉ set car1, car2 = nil, thì Car vẫn còn 1 strong reference từ car3 trỏ đến -> ARC sẽ không giải phóng bộ nhớ cho Car trong trường hợp này.

    Strong reference giữa các class

    Trong ví dụ trên, sẽ tạo ra 1 sơ đồ relation giữa các instance như sau:

    • Việc set property departmentowner của 2 instance tuangotham đã tạo ra 2 strong reference trỏ đến nhau.
    • Khi đó Person và Departmen, mỗi thứ sẽ có 2 strong refernce trỏ đến. Vì vậy, khi bạn chỉ set tuan = nilgotham = nil thì Person và Departmen vẫn sẽ không được xóa khỏi bộ nhớ dù không cần dùng đến nữa.
      -> Trường hợp này gọi là retain cycle -> Tạo ra rò rỉ bộ nhớ (memory leaks).

    Xử lí strong reference

    Swift cung cấp 2 cách để xử lí strong reference cycle khi bạn làm việc với các biến kiểu class:

    • weak reference
    • unowned reference

    Weak reference

    • weak refernce: thay vì tạo 1 strong reference, nó sẽ chỉ tạo 1 weak -> ARC sẽ không làm tăng số lượng strong reference trỏ tới biến đó.
    • Sử dụng weak reference khi biến còn lại có thời gian tồn tại ngắn hơn(thường bị giải phóng bộ nhớ trước)
    class Person {
        weak var department: Department?
        
        deinit {
            print("Person is being deinitialized")
        }
    }

    Sửa lại ví dụ trên bằng cách sửa department thành kiểu weak( vì department có thời gian tồn tại ngắn hơn người).
    Khi đó, sơ đồ relation ship sẽ thành:

    • set gotham = nil -> Departmen k còn strong reference nào trò đến -> được giải phóng khỏi bộ nhớ, khi đó strong reference của Departmen trò đến Person cũng sẽ bị mất.
    • set tuan = nil -> Person không còn strong refernce nào trỏ đến -> ARC giải phóng khỏi bộ nhớ.
      Kết quả thu được:

    Unowned reference:

    • Tương tự như weak refernce, nó sẽ không tạo ra 1 strong reference.
    • Khác weak reference ở chỗ, unowned dùng cho những biến có thời gian tồn tại lâu hơn.

    Note: nếu bạn truy cập đến 1 biến unowned sau khi nó đã được giải phóng khỏi bộ nhớ -> app sẽ crash. Vì vậy hãy cẩn thận khi dùng unowned.
    Sửa ví dụ trên thành như sau và sẽ vẫn thu được kết quả tương tự so với khi dùng weak:

    class Department {
        unowned var owner: Person?
        
        deinit {
            print("Department is being deinitialized")
        }
    }

    Hi vọng bài viết trên đã đem lại cho bạn cái nhìn tổng quan về ARC và cách sử dụng weak/unowned để tránh retain cycle giữa các class.

  • Design Pattern: Singleton trong Swift

    Design Pattern: Singleton trong Swift

    Singleton là một design pattern rất phổ biến trong phát triển phần mềm. Singleton rất đơn giản, phổ biến và dễ sử dụng. Chính vì những ưu điểm đó mà nó được rất nhiều người sử dụng.

    Singleton pattern là gì?

    Singleton pattern là một pattern đảm bảo rằng một (class) chỉ có một thể hiện(instance) duy nhất.

    Trong Swift các bạn có thể đã được sử dụng khá nhiều các singleton khi làm việc với các framework của Apple. Dưới đây là một số ví dụ cho singleton:

    // Shared URL Session
    let sharedURLSession = URLSession.shared
    
    // Default File Manager
    let defaultFileManager = FileManager.default
    
    // Standard User Defaults
    let standardUserDefaults = UserDefaults.standard

    Ưu điểm và nhược điểm của Singleton pattern

    Ưu điểm:

    • Đảm bảo một class chỉ có 1 instance duy nhất được khởi tạo.
    • Ẩn các các hàm khởi tạo của class. (Đảm bảo không thể khởi tạo từ bên ngoài)
    • Có thể truy cập ở bất cứ nơi nào trên project

    Nhược điểm

    • Khó kiểm soát và quản lý
    • Hạn chế số lượng instance của class
    • Khi chỉ cho phép một trường hợp cụ thể của một class

    Nên sử dụng Singleton pattern khi nào?

    Singleton pattern là một pattern rất hữu ích. Đôi khi bạn muốn chắc chắn rằng chỉ có một instance của một class được khởi tạo và ứng dụng của bạn chỉ sử dụng instance đó.

    Ví dụ: Ứng dụng của bạn có tính năng bật tắt nhạc nền khi người dùng mở ứng dụng thì nhạc nền sẽ tự động chạy và nếu người dùng muốn tắt thì phải vào setting để tắt nó. Bạn chắc chắn phải sử dụng duy nhất một instance trong trường hợp này, bởi vì bạn không thể tạo một instance để mở và một instance để tắt vì nó là 2 instance độc lập nên không thực hiện việc cho nhau được. Vì vậy chúng ta sẽ cần đến Singleton pattern trong trường hợp này để giải quyết bài toán gọn gàng và nhẹ nhàng.

    Tuy rằng Singleton pattern khá dễ sử dụng và hữu dụng nhưng bạn cần cân nhắc khi sử dụng nó tránh lạm dụng dẫn đến việc khó kiểm soát code cũng như debug.

    Các bạn nên sử dụng Singleton khi mà bài toán của các bạn yêu cầu phải có một đối tượng để quản lí các nguồn tài nguyên hoặc theo dõi trạng thái của hệ thống mà nó ảnh hưởng đến nhiều nơi trong dự án.

    Sử dụng class thông thường

    class LocationManager{
    //MARK: - Location Permission
        func requestForLocation(){
            //Code Process
            print("Location granted")
        }
        
    }
    // Truy cập vào class
    let location = LocationManager() // khởi tạo class
    location.requestForLocation()    // gọi func

    Đây là trường hợp thông thường của 1 class không sử dụng Singleton pattern để truy cập vào các func trong class. Mỗi lần muốn sử dụng một func trong class chúng ta phải khởi tạo một instance cho nó sau đó mới có thể gọi func đó được. Để giải quyết vấn đề này chúng ta tạo ra một Singleton class với static instance.

    Tạo Singleton class

    class LocationManager {
        
        static let shared = LocationManager()
        
        init(){}
        
        func requestForLocation() {
            //Code Process
            print("Location granted")
        }
        
    }
    // Truy cập func trong class sử dụng Singleton Pattern 
    LocationManager.shared.requestForLocation()
    
    // Bạn vẫn có thể truy cập vào class một cách thông thường
    let location = LocationManager()
    location.requestForLocation()
    

    Cách tốt hơn để tạo Singleton class

    class LocationManager {
        
        static let shared = LocationManager()
        
        var locationGranted: Bool?
        // thay đổi hàm init thành private để chỉ có thể khởi tạo trong class này.
        private init(){}
        
        func requestForLocation() {
            //Code Process  
            locationGranted = true     
            print("Location granted")
        }
        
    }
    // Gọi hàm trong class bằng việc sử dụng đoạn code dưới
    LocationManager.shared.requestForLocation()

    Lúc này chúng ta đã thay đổi access level của hàm khởi tạo từ internal sang private. Vì vậy nếu chúng ta cố tính sử dụng LocationManager() XCode sẽ báo lỗi: ‘LocationManager’ initializer is inaccessible due to ‘private’ protection level

    Sử dụng Singleton class

    Bây giờ, bất kể chỗ nào trong project bạn muốn sử dụng hàm requestForLocation của LocationManager bạn cũng chì cần sử dụng như sau:

    LocationManager.shared.requestForLocation()

    Sử dụng biến toàn cục (Global variables)

    Đây là cách đơn giản nhất để ứng dụng Singleton pattern:

    let sharedNetworkManager = NetworkManager(baseURL: API.baseURL)
    
    class NetworkManager {
    
        // MARK: - Properties
    
        let baseURL: URL
    
        // Initialization
    
        init(baseURL: URL) {
            self.baseURL = baseURL
        }
    }

    Bằng cách khai báo một biến toàn cục, bấn kì object nào cũng có thể truy cập dến biến toàn cục đó. Trong Swift, biên toàn cục được khai báo một cách trì hoãn cho đến khi nó được gọi ra để sử dụng lần đầu tiên. Việc khởi tạo trong swift cũng dựa trên hàm dispatch_one, điều này sẽ có ích khi bạn chỉ muốn khởi tạo object của mình một lần duy nhất.

    Tuy nhiên khi sử dụng cách này bạn sẽ gặp vấn đề là code của bạn sẽ khó hiểu và khó để debug hơn. Và hàm khởi tạo của class NetworkManager thì không thể khai báo là private.

    Lưu ý

    Mình không khuyến khích các bạn sử dụng cách tạo global variable vì các nhược điểm của nó.

    Tổng kết

    Vậy là giờ các bạn đã hiểu cách tạo Singleton class trong dự án của bạn. Nó khá đơn giản, dễ sử dụng và tốn ít thời gian để tạo. Nó có một số ưu điểm và nhược điểm. Nếu bạn sử dụng nhiều Singleton pattern trong dự án của mình thì sẽ khó có thể quản lý vòng đời của Singleton class của bạn. Ngoài ra, nó duy trì một trạng thái chia sẻ toàn cầu có thể thay đổi. Tuy rằng nó có các ưu điểm rõ rệt, nhưng hãy tránh lạm dụng Singleton pattern thay vào đó tốt hơn chúng ta nên sử dụng Dependency injection.

  • Các bước hướng dẫn chi tiết submit ứng dụng lên Google Store

    Các bước hướng dẫn chi tiết submit ứng dụng lên Google Store

    Khi bạn đã có trong tay một sản phẩm tốt và đầy tâm huyết. Vậy làm cách nào để có thể giới thiệu ứng dụng của mình đến với nhiều người dùng? Để mọi người biết đến và có thể sử dụng nó? Để nhận được những góp ý giúp cải thiện hơn, hay cũng là một nguồn thu nhập của bạn, thì bạn sẽ phải làm gì?. Chắc chắn, với một developer phát triển về mobile thì bạn sẽ có những câu hỏi như vậy, phải không?

    Để giải đáp cho câu hỏi đó thì iOS bạn sẽ sử dụng App Store để có sumit ứng dụng của bạn, App Store chính là market chính và có nhiều người iOS biết tới nhất.
    Với Android, đó chính là Google Play Store. Google Play Store cũng là market chính và có nhiều người dùng Android biết tới nhất. Vậy làm cách nào để có thể submit ứng dụng lên Google Store? Đừng lo, bài viết này mình sẽ giới thiệu tới các bạn luôn đây. Let’s start…

    Nội dung chính của bài viết:

    • Bạn cần chuẩn bị gì trước khi submit ứng dụng lên Google Store?
    • Cần tối ưu kích thước APK file
    • Từng bước submit ứng dụng lên Google Store
    • Đăng kí tài khoản Google Developer
    • Tạo ứng dụng mới và điền thông tin mô tả ứng dụng
    • Upload logo và screenshot ứng dụng
    • Tiến hành upload APK lên Store
    • Hoàn thành đánh giá Content rating
    • Đăng kí ứng dụng miễn phí hay trả phí

    Bạn cần chuẩn bị gì trước khi submit ứng dụng lên Google Store?

    Khác với Apple, khi mà App Store có quá trình review rất chặt chẽ. Các ứng dụng khi submit lên App Store đều trải qua quá trình review thủ công. Điều này sẽ đảm bảo ứng dụng trên App Store có chất lượng tốt nhất trước khi tới tay người dùng.

    Google Play Store thì quá trình review ứng dụng thường làm bằng máy. Do vậy, ứng dụng của bạn có nhiều cơ hội được approve hơn rất nhiều.

    Tuy nhiên, để ứng dụng thành công với hàng nghìn, thậm chí hàng triệu lượt tải thì bạn cần bỏ công sức ra tối ưu cũng như chuẩn bị kĩ càng trước khi submit.

    Những điều bắt buộc phải làm khi submit ứng dụng lên Google Store

    • Tạo một Bundle ID cho ứng dụng
    • Tạo một APK or Android App Bundles có sign key
    • Và tất nhiên là phải có một tài khoản Google Developer( chi phí để tạo là 25$)

    Cần tối ưu kích thước APK file

    Nếu ứng dụng của bạn có kích thước lớn thì nên chia ra thành nhiều module. Cách làm giống như các Game hay làm vậy. Các bạn chỉ đưa phần chính của ứng dụng lên Store. Sau khi người dùng tải ứng dụng về thì sẽ tiếp tục tải data.

    Tuy nhiên, mình khuyến khích là kích thước APK càng nhỏ càng tốt vì điều đó tốt cho ASO( App Store Optimization). Google Play Store ưu tiên các ứng dụng nhỏ nhẹ, nhưng chất lượng tốt.

    Lưu ý: Google Play supports compressed app downloads of only 150 MB or less.

    Đăng kí tài khoản Google Developer

    Đầu tiên, bạn cần đăng kí trở thành nhà phát triển ứng dụng ở đây: Google Play Console.

    Bạn đăng nhập bằng tài khoản Google như bình thường. Tick vào ô bên dưới Developer Agreement để chuyển sang màn hình thanh toán.

    Sau khi thanh toán bằng thẻ VISA/ MasterCard xong thì bạn cần điền các thông tin cần thiết cho nhà phát triển như: developer name, email address, website, phone number.

    Cuối cùng là nhấn vào nút COMPLETE REGISTRATION.

    Tạo ứng dụng mới và điền thông tin mô tả ứng dụng

    Như vậy là bạn đã trở thành nhà phát triển ứng dụng rồi đấy. Công việc tiếp theo là tạo ứng dụng mới bằng cách nhấn vào nút CREATE APPLICATION

    Lưu ý: Tiêu đề là phần hiển thị trên Google Play chứ không phải tên ứng dụng khi cài vào điện thoại. Nên bạn có thể khéo léo đưa từ khóa vào tiêu đề để tối ưu ASO.

    Tiếp theo, chúng ta cần điền thông tin mô tả ứng dụng: short description và full description.

    Rồi nhấn nút SAVE DRAFT. khi đã điền xong.

    Upload logo và screenshot ứng dụng

    Phần này chúng ta sẽ cần upload ảnh logo, screenshot của ứng dụng. Lưu ý là logo cần có kích thước là 512x512px.

    Nhấn vào Add high-res icon để tải logo lên.

    Chọn Add feature graphic. Đây là ảnh promo được hiển thị trên đỉnh của trang ứng dụng trên Google play.

    Nói chung, bạn có ảnh hay video nào có thể promo được cho ứng dụng thì upload hết lên đây nhé. Càng nhiều càng tốt…

    Cuối cùng là nhấn SAVE DRAFT. để tiếp tục.

    Phía dưới của màn hình có phần chọn kiểu ứng dụng là : App hay Game. Chọn Category phù hợp với ứng dụng: Tool, Productivity, Entertainment…

    Ở màn này có một mục là Content rating. Phần này mình sẽ hướng dẫn chi tiết ở phía dưới bài viết nhé.

    Điền URL tới file privacy policy. Nếu bạn chưa biết cách viết privacy policy như thế nào thì có thể sử dụng công cụ sau để tạo tự động App Privacy Policy Generator

    Bạn sẽ được chuyển đến màn hình như bên dưới.

    Tiến hành upload APK lên Store

    Bạn chọn MANAGE PRODUCTION để tiếp tục hoàn thành các bước tiếp theo.

    Ngoài ra, bạn cũng có thể lựa chọn chạy alpha hay beta testing trước khi thực sự publish ứng dụng cho tất cả người dùng.

    Ở bài viết này, mình không đề cập đển việc chạy alpha hay beta testing. Chúng ta publish chính luôn.

    Nhấn nút CREATE RELEASE.

    Nếu bạn đã tạo APK có sẵn sign key rồi thì không cần phải làm gì cả, chọn luôn opt out.

    Tiếp tục là chọn browse files để upload APK từ máy tính lên.

    Bạn có thể sửa release name, nhưng mình thì cứ để mặc định là số phiên bản của ứng dụng.

    Điền các thông tin chính cho bản apk này (thường thì người ta hay điền các tính năng mới mà cho lần upgrade ứng dụng) rồi nhấn REVIEW.

    APK cũng đã upload lên. Việc tiếp theo như mình nói ở trên là hoàn thành đánh giá Content rating.

    Hoàn thành đánh giá Content rating

    Phần Content rating này, bạn cứ trả lời thật với những câu hỏi của họ là ổn. Các câu hỏi kiểu như: Ứng dụng có liên quan đến SEX không? Ứng dụng có kích động, phản động hay liên quan đến Phát xít không? … Cứ trả lời thật nhé

    Đăng kí ứng dụng miễn phí hay trả phí

    Phần cuối cùng là Pricing & distribution

    Bạn cần cân nhắc là ứng dụng của bạn sẽ phát hành miễn phí hay là bán cho người dùng.

    Mình chỉ lưu ý là: Một khi đã chọn là ứng dụng miễn phí thì bạn không thể chuyển thành ứng dụng trả phí được nữa. Nhưng ngược lại thì được.

    Nếu bạn chọn là ứng dụng trả phí thì cần phải cài đặt phương thức nhận tiền để Google còn thanh toán cho bạn chứ.

    Tất cả đã xong. Việc của bạn bây giờ là quay trở lại App releases và nhấn nút START ROLLOUT TO PRODUCTION và chờ đợi Google review và appove cho ứng dụng của bạn.

    Theo kinh nghiệm của mình thì thời gian review sẽ tầm khoảng 4 giờ. Trong lúc chờ đợi thì hãy đón đọc các bài tiếp theo cũng mình nhé :))

  • iOS: Apple Notification Center Service

    iOS: Apple Notification Center Service

    Ngày xưa khi sử dụng đồng hồ đeo tay kết nối với điện thoại di động, mỗi khi có tin nhắn hay cuộc gọi đến thì các thiết bị này đều hiển thị được thông tin đó lên màn hình. Lúc đó mình cũng khá băn khoăn tại sao đồng hồ không có sim mà sao nó lại nhận được tin nhắn, cuộc gọi đến. Cho đến gần đây mình có làm một dự án liên quan đến tính năng này mình mới hiểu được tại sao nó lại làm được điều đó. Bài viết này mình sẽ giới thiệu với các bạn về một dịch vụ của Apple có thể làm được điều đó Apple Notification Center Service.

    Giới thiệu

    Mục đích của Apple Notification Center Service (ANCS) là cung cấp cho các phụ kiên Bluetooth (kết nối với thiết bị iOS thông qua Bluetooth low-energy) một cách đơn giản và thuận tiện để truy cập nhiều loại thông báo được tạo trên thiết bị iOS.

    ANCS được thiết kế theo ba nguyên tắc: đơn giản, hiệu quả và khả năng mở rộng. Do dó các phụ kiện khác nhau từ đền LED đơn giản đến phức tạp.

    Sự phụ thuộc(Dependencies)

    ANCS không có phụ thuộc, ngoài bộ tiêu chuẩn phụ của Generic Attribute Profile(GATT). Một phụ kiện sẽ đóng vai trò là GATT Client có thể truy cập và sử dụng các dịch vụ khác do thiết bị iOS cung cấp trong khi sử dụng ANCS.

    Thuật ngữ

    • Apple Notification Center Service được viết tắt là ANCS
    • Notification Provider: nhà xuất bản dịch vụ ANCS ở đây là các thiết bị iOS được viết tắt là NP
    • Notification Consumer: Là những thiết bị ngoại vi được viết tắt là NC
    • Một thông báo hiển thị trên thiết bị iOS trong Notification center se được gọi là iOS Notification
    • Một thông báo được gửi bởi GATT characteristic đưới dạng tin nhắn không đồng bộ sẽ được gọi là GATT Notification.

    Apple Notification Center Service

    APNS là dịch vụ chính có UUID là 7905F431-B5CE-4E99-A40F-4B1E122D00D0.
    Chỉ có duy nhất một instance của ANCS có thể đại diện cho NP. Do tính chất của iOS, ANCS không được đảm bảo luôn hiển thị. Do đó, NC nên tìm kiếm và đăng ký(subcribe) Service Changed characteristic của GATT để theo dõi việc publishing và unpublishing của ANCS bất cứ lúc nào.

    Service characteristics

    Cơ bản thì ANCS có 2 characteristics như sau:

    • Notification Source: UUID 9FBF120D-6301-42D9-8C58-25E699A21DBD (notifiable)
    • Control Point: UUID 69D1D8F3-45E1-49A8-9821-9BBDFDAAD9D9 (writeable with response)
    • Data Source: UUID 22EAC6E9-24D6-4BB5-BE44-B36ACE7C7BFB (notifiable)

    Tất cả các characteristic này yêu cầu phải có authorization để truy cập. Hỗ trợ cho Notification Source characteristic là bắt buộc, trong khi Control Point characteristic làm nhiệm vụ hỗ trợ, còn Data Source characteristic thì là tùy chon (optional).

    Lưu ý:

    Có thể có nhiều hơn 3 characteristics trong ANCS đã kể ở trên. Nó có nghĩa là NC có thể bỏ qua bất kể characteristic nào mà nó không nhận ra.

    Notification Source

    Notification Source characteristic là characteristic mà NC được thông báo:

    • Sự xuất hiện iOS Notification mới trên NP
    • Sự sửa đổi một iOS Notification trên NP
    • Sự xóa bỏ một iOS Notification trên NP

    GATT Notification có thể được gửi ngay khi NC đăng ký Notification Source characteristic. Do đó, NC phải ở trong trạng thái có thể châpps nhận và xử lý đúng các message này trước khi đăng kí vào characteristic này.

    Định dạng của GATT Notification được thể hiện theo hình dưới đây:

    GATT Notification được gửi thông qua Notification Source characteristic nó chứa các thông tin như sau:

    • EventID: Trường này thông báo cho phụ kiện xem iOS Notification đã được thêm, chỉnh sửa hoặc được xóa. Các giá trị được định nghĩa trong EventID Values.
    • EventFlags: Một bitmask có bit được đặt thông báo cho một đặc thù của NC với iOS Notification. Ví dụ: Nếu một iOS Notification được coi là quan trọng thì NC có thể muốn hiển thị giao diện người dùng tích cực hơn để đảm bảo người dùng được cảnh báo chính xác. Các bit được liệt kê cho trường này được định nghĩa trong Event Flags.
    • CategoryID: Một giá trị số cung cấp một danh mục trong đó iOS Notification có thể được phân loại. NP sẽ nỗ lực hết sức để cung cấp danh mục chính xác cho mỗi iOS Notification. Nó được định nghĩa trong CategoryID Values.
    • CategoryCount: Số lượng iOS notification đang hoạt động trong category đã cho. Ví dụ: Nếu hai email chưa đọc đang nằm trong hòm thư đến email của người dùng và một email mới được đẩy lến thiết bị iOS của người dùng thì giá trị của CategoryCount = 3.
    • NotificationUID: 32 bit số định danh duy nhất(UID) cho iOS notification. Giá trị này được sử dụng để điều khiển trong các lệnh được gửi đến Control Point characteristic để tương tác với iOS Notification.

    Thời gian tồn tại của iOS Notification có thể được suy đoán ngầm thông qua chuỗi Notification Source. GATT notifications được tạo ra bởi NP:

    Control Point

    Một NC có thể muốn tương tác với một iOS Notification. Nó có thể muốn lấy thêm thông tin về nó bao gồm nội dung của nó hoắc nó có thể muốn thự hiện hành động trên nó. Việc truy suất các thuộc tính này được thực hiện thông qua Control Point và Data Source characteristic.

    Một NC có thể đưa ra yêu cầu để nhận thêm thông tin về iOS Notification bằng việc viết lệnh cụ thể tới Control Point characteristic. Nếu việc ghi vào Control Point characteristic thành công, NP sẽ phản hồi kịp thời yêu cầu đó thông qua luồng của GATT notification trên Data Source characteristic.

    Một NC có thể thực hiện các hành động được xác đinh trước trên iOS Notification bằng cách viết các lênh cụ thể vào Control Point characteristic.

    Get notification Attributes

    Lệnh lấy Notification Atributes cho phép NC truy xuất các thuộc tính của iOS Notification cụ thể.

    Nội dung của lệnh lấy Notification Attribute như sau:

    • CommandID: nên được gán là 0 (CommandIDGetNotificationAttributes)
    • NotificationUID: 32 bit đại diện cho UID của iOS Notification mà khách hàng muốn biết thông tin.
    • AttributeIDs: Là danh sách các thuộc tính mà NC muốn lấy. Một số thuộc tính có thể cần phải được theo dõi bởi tham số độ dài 16 bit chỉ định số byte tối ta của thuộc tính NC muốn truy xuất.

    Định dạng của phản hồi từ lệnh Get Notification Attributes như sau:

    • CommandID: để là 0(CommandIDGetNotificationAttributes)
    • NotificationUID: 32 bit định nghĩa UID của iOS Notification
    • AttributeList: Danh sách các AttributionID có độ dài 16 bit. Một thuộc tính là một cuỗi có độ dài tính bằng byte được cung cấp tring bộ dữ liệu, Không được NULL. Nếu thuộc tính được yêu cầu là trống(empty) hoặc thiếu cho iOS Notification, độ dài của nó được đặt = 0. Dữ liệu sẽ luôn theo thứ tự các AttributionID của lệnh Get Notification Attributes

    Nếu phản hồi lớn hơn đơn vị truyền tối đâ GATT(MTU) đã đàm phán, nó sẽ được NP chia thành nhiều mảnh. NC phải tổng hợp lại phản hồi bằng cách ghép từng đoạn. Phản hồi hoàn tất khi các bộ dữ liệu hoàn chỉnh cho từng thuộc tính được yêu cầu đã được nhận.

    Get App Attribute

    Lệnh Get App Atrributes cho phép NC truy xuất các thuộc tính của một ứng dụng cụ thể được cài đặt trên NP, định dạng của nó thể hiện như hình dưới:

    Nội dung của lênh Get App Attribute như sau:

    • CommandID: cần gán là 1 (CommandIDGetAppAttributes)
    • AppIdentifier: là chuỗi định danh ứng dụng muốn lấy thông tin. Không được null
    • AttributeIDs: Danh sác các thuộc tính mà NC muốn truy xuất.

    Nội dung phản hồi của lệnh Get App Attributes như sau:

    • CommandID: là 1(CommandIDGetAppAttributes)
    • AppIdentifier: là chuỗi định danh ứng dụng muốn lấy thông tin. Không được null
    • AttributeList: Danh sách AttributeIDs có 16 bit. Một thuộc tính là một cuỗi có độ dài tính bằng byte được cung cấp trong bộ dữ liệu, Không được NULL. Nếu thuộc tính được yêu cầu là trống(empty) hoặc thiếu cho App, độ dài của nó được đặt = 0. Thứ tự dữ liệu sẽ giống với lệnh.

    Cúng giống như lệnh Get Notification Attributes. Nếu phản hồi lớn hơn đơn vị truyền tối đâ GATT(MTU) đã đàm phán, nó sẽ được NP chia thành nhiều mảnh. NC phải tổng hợp lại phản hồi bằng cách ghép từng đoạn. Phản hồi hoàn tất khi các bộ dữ liệu hoàn chỉnh cho từng thuộc tính được yêu cầu đã được nhận.

    Perform Notification Action

    Lệnh thực hiện hành động Notification cho phép NC thực hiện hành động được xác định trước trên một Notification cụ thể của iOS Notification. Lệnh thực hiện Notification Action có các trường sau:

    BytesNameDescription
    1CommandIDlà 2 (CommandIDPerformNotificationAction).
    2-5NotificationUID32-bit đại diện cho UID của iOS Notification
    6ActionIDID của hành động mà bạn muốn thực hiện trên iOS Notification

    Không có dữ liệu nào được tạo ra trên Data Source characteristic khi lệnh này gặp vấn đề, cho dù nó có thành công hay không.

    Notification Actions

    Bắt đầu từ iOS 8.0, NP có thể thông báo cho NC về các hành động tềm năng có liên quan đến iOS Notification. Thay mặt người dùng, NC có thể yêu cầu NP để thực hiện một hành động liên quan tới một iOS Notification cụ thể.

    NC được thông báo về sự tồn tại của các hành động có thể thực hiện trên một iOS Notification bằng việc phát hiện sự hiện diện của các cờ(flag) được đặt trong trường EventFlags của GATT Notification được tạo bởi Notification Source characteristic.

    • EventFlagPositiveAction: là hành động tích cực lên iOS notification
    • EventFlagNegativeAction: là hành động tiêu cực lên iOS notification

    Các hành động thực thế được thực hiên bởi NP thay mặt NC được xác định bơi NP và thay đổi tùy thuộc vào iOS Notification mà chúng được thực hiện trên nó. Ví dụ: Có một cuộc gọi đến, hành động Trả lời thì là hành động tích cực(EventFlagPositiveAction), còn Từ Chối là hành động tiêu cực dựa trên iOS notification đó.

    NC phải giả định hay cố gắng đoán trước hành động chính xác được thực hiện trên iOS Notification, bởi vì những hành động này dựa trên thông tin không có sẵn cho nó, cũng như cá yếu tố khác như phiên bản ANCS do NP triển khai. NP đảm bảo rằng hành động tích cực và tiêu cực có liên quan đến kết quả, mà nó không làm cho người dùng bất ngờ.

    Nếu iOS Notification được hiển thị, hành động tích cực và tiêu cưc có thể hiển thị cho người dùng dựa trên kí tự X hoặc màu sắc.

    NC có thể truy suất các nhãn mô tả ngắn gọn các thành động thực tế được liên kết với một iOS Notification bằng việc truy xuất thuộc tính thông báo mới được giới thiệu trong iOS 8.0

    • NotificationAttributeIDPositiveActionLabel: nhãn(label) được sử dụng để mô tả hành động tích cực có thể được thực hiện trên iOS Notification.
    • NotificationAttributeIDNegativeActionLabel: nhãn(label) được sử dụng để mô tả hành động tiêu cực có thể được thực hiện trên iOS Notification.

    Sessions

    Một ANCS session bắt đầu khi NC đăng ký Notification Source characteristic trên NP và kết thúc khi NC hủy đăng ký khỏi dùng một chẩcteristic hoặc mất kết nối tới NP. Vì ANCS không được thiết kế để trở thành dich vụ đồng bộ hoàn chỉnh, nên nó không giữ lại các trạng thái qua các session(phiên). Do đó, tất cả các Identifiers (như NotificationUID, và AppIdentifier) và tất cả dữ liệu được trao đổi giữa NC và NP chỉ có hiệu lực trong một session cụ thể.

    Khi một session cụ thể kết thúc, NC nên xóa bỏ bất kể identifier và dữ liệu mà nó thu thập và lưu lại trong session đó. Khi một session mới được bắt đầu, NP sẽ cố gắng hết sức để thông báo cho NC về bất kỳ iOS Notification hiện có nào trên hệ thống. NC có thể sử dụng thông tin này để xây dụng mô hình để sử dụng phần còn lại của session.

    Attribute Fetching và Caching

    Apple đặc biệt khuyến nghị NC chủ fetch các thuộc tính khi cần và có thể đáp ứng với hành động của người dùng. Ví dụ nếu một NC chọn hiển thị iOS Notification đang hoạt động trong một danh sách đơn giản và chỉ hiển thị chi tiết cụ thể của iOS Notification khi được người dùng chọn, sau đó việc truy xuất thuộc tính của iOS notification này có thể được kích hoạt lazily.

    Tring một session, Apple khuyến nghị NC xây dựng bộ đệm(cache) của App Attribute cho mỗi app identifier mà nó gặp phải. Việc xây dựng cache cho phép NC tránh truy xuất cùng một App Attribute nhiều lần, vì vậy nó giúp tiết kiệm thời gian và mức tiêu hao pin.

    Error codes

    • Unknown command (0xA0): NP không nhận ra CommandID
    • Invalid command (0xA1): command không đúng định dạng
    • Invalid parameter (0xA2): một trong những tham số không tồn tại trên NP
    • Action failed (0xA2): hành động không được thực hiện

    Nếu NP trả lời với một lỗi, nó sữ không tạo ra bất kỳ GATT Notification nào trên Data Source characteristic cho command(lệnh) tương ứng.

    Example Diagrams

    Hai hình dưới đây cho thấy các ví dụ về tương tác phổ biến giữa NP và NC.

    Hình dưới đây cho thấy chuỗi các command và phản hồi điển hình cần thiết để thiết lập ANCS trên NC:

    Hình dưới đây thì hiển thị các chuỗi command và phản hồi cần thiết để có thêm thông tin về iOS Notification để hiển thị trên NC:

    Tổng kết

    Vậy là bài viết này chúng ta đã đi qua một loạt các thông tin của ANCS như ANCS dùng làm gì, nó có những service characteristic nào, các characteristic có định dạng như nào và rất nhiều thông tin liên quan đến việc triển khai ANCS.

    Bài viết tiếp theo liên quan đến ANCS mình sẽ đi vào ví dụ code cụ thể để các bạn có thể hiểu rõ hơn về cách hoạt động cũng như chức năng của ANCS.