Month: March 2020

  • iOS/Swift: NFC là gì? Cách quét thẻ NFC sử dụng CoreNFC

    iOS/Swift: NFC là gì? Cách quét thẻ NFC sử dụng CoreNFC

    Lời mở đầu

    Hôm nay mình sẽ giới thiệu với các bạn về công nghệ NFC. Công nghệ từng được dự đoán có thể thay đổi thế giới :D. Vậy NFC là gì? Nó được sử dụng như nào? Chúng ta sẽ đi vào chi tiết dưới đây.

    NFC là gì?

    NFC là viết tắt của Near-Field communications được hiểu là chuẩn kết nối không dây trong khoảng cách gần. NFC được phát triển dựa trên nguyên lý nhận dạng bằng tín hiệu tần số vô tuyến có tốc độ truyền tải dữ liệu tối đa 424 Kbps. Khoảng cách truyền dữ liệu tối đa của nó là khoảng 4 cm.

    Công dụng của NFC là để truyền dữ liệu ở phạm vi gần một cách nhanh chóng và an toàn. Hiện nay một số nước người ta cũng ứng dụng vào việc thanh toán hàng hóa cũng như đọc thông tin sản phẩm.

    Để sử dụng NFC chúng ta cần có thiết bị đọc (thường là điện thoại di động) và thiết bị đích(có thể là một điện thoại khác, 1 thẻ NFC…)

    NFC rất phổ biến ở các dòng điện thoại chạy hệ điều hành Android. Ngày NFC nổi đình nổi đám Apple vẫn không tin tưởng công nghệ này. Cho đến tận Iphone 7 NFC mới được apple thêm vào thiết bị của họ.

    Cách sử dụng CoreNFC Framework để quét NFC Tag

    Cho đến gần đây thì có vẻ Apple đã chú ý hơn tới công nghệ này. Vì vậy nó chỉ được hỗ trợ từ Iphone 7 trở lên và iOS >= 11.

    CoreNFC có nhiệm vụ phát hiện thẻ NFC và đọc thông điệp có chứa dữ liệu NDEF và lưu dữ liệu vào thẻ có thể ghi dữ liệu.

    Cài đặt môi trường

    Enable Near Field Communication Tag Reading

    Bấm vào Capability > Tìm Near Field Communication Tag Reading và add

    Lưu ý: Near Field communication Tag Reading chỉ hỗ trợ cho các tài khoản trả tiền. Chi tiêt xem tại: https://help.apple.com/developer-account/#/dev21218dfd6

    Thêm CoreNFC Framework

    Lưu ý bạn nên để CoreNFC Frameworkoptional. Nếu không nó sẽ bị crash app khi chạy trên các thiết bị không hỗ trợ.

    Thêm NFC description trong Info.plist

    Việc này nhằm mục đích mô tả cho người dùng biết sơ qua về tính năng này. Và khi upload ứng dụng lên store không bị apple reject.

    Vậy là việc setup môi trường đã xong. Chúng ta sẽ đi vào code thôi.

    Đầu tiên để sử dụng được chúng ta cần Import CoreNFC vào ViewController của bạn.

    import CoreNFC

    Thêm một biến global để lưu thông tin session reader

    var session: NFCNDEFReaderSession?

    Thêm 2 dòng code dưới đây vào viewDidLoad() trong trường hợp này mình đang để mỗi lần vào màn hình này là nó bắt đầu scan luôn. Các bạn có thể để 2 dòng này trong đoạn code mà các bạn mong muốn để chủ động hơn:

    session = NFCNDEFReaderSession(delegate: self, queue: DispatchQueue.main, invalidateAfterFirstRead: false)
    session?.begin()

    Lúc này XCode sẽ báo lỗi ViewController của bạn. Nó yêu cầu bạn phải conform NFCNDEFReaderSessionDelegate protocol.

    class ViewController: UIViewController, NFCNDEFReaderSessionDelegate 

    XCode tiếp tục báo lỗi, nó báo bạn đang thiếu các phương thức bắt buộc của NFCNDEFReaderSessionDelegate vì vậy ta thêm 2 func dưới đây:

    func readerSession(_ session: NFCNDEFReaderSession, didDetectNDEFs messages: [NFCNDEFMessage]) {
        for message in messages {
            for record in message.records {
                if let string = String(data: record.payload, encoding: .ascii) {
                    print(string)
                }
            }
        }
    }
    
    func readerSession(_ session: NFCNDEFReaderSession, didInvalidateWithError error: Error) {
    
    }
    • didDetectNDEFs messages: Hàm này được gọi khi thiết bị của bạn tìm thấy và đọc được thẻ NFC ở gần đó. Từ hàm này chúng ta có thể sử dụng dữ liệu mà thẻ NFC cung cấp.
    • didInvalidateWithError: Hàm này trả về khi có lỗi xảy ra

    Vậy là chúng ta đã hoàn thành việc coding đọc thẻ NFC. Hãy build ứng dụng lên và trải nghiệm 😀

    Lưu ý: Trong quá trình test cũng như trải nghiệm tính năng này mình thấy các thiết bị của Apple có tầm khá ngắn. Và để quét được NFC tag các bạn phải dùng mặt trước của điện thoại. Mặt sau quét rất khó thành công.

    Chúc các bạn thành công!

  • iOS: Giảm thời gian khởi động ứng dụng

    iOS: Giảm thời gian khởi động ứng dụng

    Để tăng trải nghiệm của người dùng, khiến người dùng cảm thấy ứng dụng của chúng ta chạy nhanh hơn thì một trong những việc đó là giảm thời gian khởi động của chúng ta. Để người dùng có thể trải nghiệm tính năng chính một cách nhanh nhất.

    Refer

    Lời mở dầu

    Apple khuyến cáo chúng ta nên lập trình sao cho tổng thời gian khởi động ứng dụng dưới 400ms và bạn phải thực hiện trong vòng chưa đầy 20s nếu không muốn hệ thống tự dừng ứng dụng của bạn. Tuy nhiên thì vì nhiều lý do app chúng ta thường khởi động lâu hơn thế. Bên cạnh việc xử lý những tác vụ tiền khởi động ở trong AppDelegate thì chúng ta cũng cần phải biết cách debug việc khởi động chậm và biết được những gì đã xảy ra trước khi app chạy vào AppDelegate. Dưới đây là một số mẹo từ hội nghị WWDC 2016.

    Pre-main time

    Rất nhiều xảy ra trước khi hệ thống thực hiện chạy hàm main() của ứng dụng và gọi các hàm trong App Delegate như applicationWillFinishLaunching. Trước iOS 10, rất khó để chúng ta tìm hiểu tại sao một ứng dụng lại khởi động chậm vì những lí do khác ngoài code của bạn.

    Hiện tại từ iOS chúng ta có thể làm việc đó dễ dàng hơn băng việc thêm DYLD_PRINT_STATISTICS = 1 vào trong Environment variables của project scheme

    Chạy thử ứng dụng trên iPhone 8 ios 13 ta được kết quả như sau:

    Khi kiểm tra việc khởi động chậm chúng ta nên chọn thiết bị chậm nhất mà ứng dụng hỗ trợ.

    Kết quả cho chúng ta thấy tổng thời gian tính đến thời điểm hệ thông gọi hàm main() của ứng dụng theo sau đó là breakdown của các main steps.

    Các bạn xem clip này để tìm hiểu sâu hơn  WWDC 2016 Session 406 Optimizing App Startup Time

    Dưới đây là một số lưu ý mà mình đã đúc kết từ các tài liệu và clip trên để cải thiện thời gian khởi động ứng dụng:

    dylib loading time

    dynamic loader có nhiệm vụ tìm và đọc các thư viện(dylibs) được ứng dụng sử dụng. Mỗi thư viện có thể có các phụ thuộc(Dependencies). Cơ chế load framework hệ thống của Apple là rất tối ưu, tuy nhên thì khi load những thư viện được nhúng vào ứng dụng của bạn thì lại không như vậy. Để tăng tốc các dylib thì Apple khuyến cáo bạn bên hạn chế sử dụng các thư viện bên ngoài hoặc gộp chúng lại với nhau làm một framework.

    Rebase/binding time

    Là khoảng thời gian ứng dụng rebale và binding các pointer.

    • Ứng dụng chứa nhiều Objective-C class, selector hay category có thể tốn thêm thời gian cho việc khởi động ứng dụng.
    • Để tăng tốc rebase/binding time bạn cần sử dụng ít các pointer fix-ups hơn.
    • Nếu bạn sử dụng C++ thì nên sử dụng ít đi các virtual functions.
    • Trong Swift sử dụng Struct cũng thường nhanh hơn

    ObjC setup time

    Objective-c runtime cần thiết lập một vài tác vụ cho việc đăng ký class, category và phân biệt các selector. Vì vậy bất kể những cải tiến nào bạn thực hiện để rebase/binding time cũng sẽ áp dụng cho thời gian thiết lập này.

    Initializer time

    Là lúc các hàm khởi tạo chạy, nếu bạn sử dụng phương thức Objective-C +load(deprecated) hãy thay thế nó bằng +initialize.

    Các bạn cũng có thể sử dụng Instruments để theo dõi các chỉ số này.

    Sau cùng thệ thống sẽ gọi hàm main() tiếp đến gọi UIApplicationMain() và các phương thức trong AppDelegate.

    Loading framework làm tăng thời gian khởi động

    Để kiểm chứng chúng ta cùng theo dõi thử nghiệm dưới đây:

    Trước khi thêm thư viện:

    Total pre-main time: 408.97 milliseconds (100.0%)
         dylib loading time: 383.84 milliseconds (93.8%)
        rebase/binding time:   7.86 milliseconds (1.9%)
            ObjC setup time:   6.82 milliseconds (1.6%)
           initializer time:  10.36 milliseconds (2.5%)
           slowest intializers :
             libSystem.B.dylib :   2.33 milliseconds (0.5%)

    Sau khi thêm 10 thư viện Swift sử dụng Cocoapods

    Total pre-main time: 682.90 milliseconds (100.0%)
         dylib loading time: 631.17 milliseconds (92.4%)
        rebase/binding time:  17.06 milliseconds (2.4%)
            ObjC setup time:  17.47 milliseconds (2.5%)
           initializer time:  17.09 milliseconds (2.5%)
           slowest intializers :
             libSystem.B.dylib :   6.05 milliseconds (0.8%)

    Thời gian dylib loading time tăng lên từ 380ms đến 631ms. Các bạn có thể trực tiếp thử nghiệm trên chính ứng dụng của mình để thấy sự khác biệt.

    Tổng kết

    Bài viết trên mình đã chia sẻ cho các bạn về một số mẹo làm giảm thời gian khởi động ứng dụng. Giúp tăng trải nghiệm người dùng. Chúc các bạn thành công.

  • Deep dive into Memory Leaks Swift

    Deep dive into Memory Leaks Swift

    Đa số với mỗi lập trình viên đều đã gặp phải những vấn đề về memory leaks.
    Ở bài viết này, mình sẽ đi sâu vào memory Leaks và cách xử lí.
    Bài viết này đòi hỏi sự hiểu biết về weak/strong reference, retain cycle và ARC trong swift.

    Contents:

    • Memory leaks là gì?
    • Xử lí memory leaks bằng weak/unowned
    • Non-escaping closure vs escaping closure
    • Delay Deallocation
    • Optional self vs Unwrapped self
    • Example

    Memory leaks là gì?

    • Memory leaks là 1 phần bộ nhớ bị chiếm vĩnh viễn và không được giải phóng mặc dù không cần dùng đến -> Dẫn đến không thể tái sử dụng phần bộ nhớ này.
    • Thường xảy ra do retain cycle.

    Memory leaks gây ra những gì:

    • memory của ứng dụng tăng cao không cần thiết -> dẫn đến memory warning và có thể crash.
    • Những object bị leaks sẽ không bị hủy bỏ -> object đó sẽ luôn lắng nghe thông báo và sẽ thực hiện phản ứng mỗi khi nhận thông báo -> Dẫn đến sai lệch kết quả, có thể đặc biệt nghiêm trọng nếu ảnh hưởng đến database.

    Memory leaks demo

    Khởi tạo 2 View Controller như sau:

    • VC1 có 1 button để push sang VC2.
    • VC2 có hàm deinit để print ra "VC2 was deallocate from memory" khi VC2 được giải phóng bộ nhớ.

    Build thử, tap vào button để push sang VC2, và tap vào nút back để quay lại VC1. Quan sát màn hình console, có thể thấy không có gì được print ra.

    Ở đây, VC2 không được giải phóng bộ nhớ bởi VC2 giữ 1 strong reference đến closure completion, closure completion cũng giữ 1 strong reference đến VC2 -> Tạo ra 1 retain cycle -> Memory leaks.

    Xử lí memory leaks bằng weak/unowned

    Cách giải quyết mà mọi người thường dùng nhất là sử dụng weak/unowned.
    Vậy sự khác biệt giữa weak và unowned là gì?

    • Giống: Weak và unonwed tạo ra 1 weak reference thay vì 1 strong reference để loại bỏ retain cycle -> Bộ nhớ sẽ dc giải phóng ngay lập tức khi không cần dùng đến.
    • Khác:
    WeakUnowned
    – Có thể nilKhông thể nil

    Tuy nhiên, unowned cũng giống như việc force unwrapping self và cố gắng truy cập đến contents của nó ngay cả sau khi self đã được giải phóng -> dẫn đến crash.

    Crash do truy cập đến self trong khi self đã được xóa khỏi bộ nhớ

    Vì vậy ta thường thấy weak self được sử dụng nhiều hơn. Thay đoạn code viewDidLoad ở VC2 bằng:

    override func viewDidLoad() {
        super.viewDidLoad()
        completion = { [weak self] in
            self?.view.backgroundColor = .red
        }
    }

    Build và chạy thử. Sau khi pop từ VC2 về VC1, màn hình console đã hiện "VC2 was dellocated from memory" -> Memory leaks đã được giải quyết.

    Tuy nhiên câu hỏi ở đây là, liệu có cần sử dụng weak cho mọi closure?

    Non-escaping closure vs escaping closure

    Để trả lời câu hỏi trên, có 1 vài điều trước hết bạn cần phải biết.

    • non-escaping closure (ví dụ higher-order functions như compactMap): Được thực hiện trong 1 phạm vi thân hàm nhất định, được thực hiện ngay lập tức và sau khi thực hiện thì được giải phóng, không được lưu lại.
    • escaping closure: Được lưu lại, có thể truyền đi như 1 biến, và có thể được thực hiện lại vào 1 thời điểm khác trong tương lai.

    Closure sẽ tạo ra 1 strong reference đối với những thứ được đóng gói bên trong closure, trong ví dụ ở đây là self.

    • Đối với non-escaping closure: strong reference này sẽ chỉ tồn tại trong thời gian closure đó dc thực hiện -> Khi closure thực hiện xong, strong reference biến mất -> self không còn strong reference nào trỏ đến nên được giải phóng khỏi bộ nhớ.
    • Đối với escaping closure: Nếu escaping closure này đóng gói 1 self bên trong, thì strong reference này sẽ tồn tại mãi mãi -> tạo ra retain cycle. -> self không được giải phóng.
      Demo 2: Thay đoạn code viewDidLoad của VC2 thành và run thử.
    override func viewDidLoad() {
        super.viewDidLoad()
        let nonEscapingClosure = {
            self.view.backgroundColor = .red
        }
    }
    Tuy closure này đóng gói self bên trong nhưng khi back về VC1, VC2 vẫn được giải phóng khỏi bộ nhớ.

    Ở đây bạn tạo ra 1 non-escaping closure, nó tạo ra 1 strong reference tới self, khi kết thúc thân hàm, strong reference này sẽ biến mất -> Không bị retain cycle. Vì vậy, không cần dùng weak self trong trường hợp này.

    Delay Deallocation

    Gỉa sử có 1 func download image từ internet như sau:

    Ở đây URLSession.shared.dataTask là 1 non-escaping closure cần nhiều thời gian để chạy.

    Non-escaping closure không yêu cầu bạn phải dùng weak self để tránh retain cycle, tuy nhiên với những closure cần nhiều thời gian chạy như trên, thì sẽ tạo ra 1 khoảng thời gian delay trước khi VC2 được deallocate rất lớn -> Nếu ng dùng push và pop vào VC2 liên tục thì vẫn tạo ra tăng memory.

    Note: Cân nhắc việc sử dụng weak self đối với non-escaping closure!

    Optional self vs Unwrapped self

    Dùng weak self sẽ làm cho self thành kiểu optional. Khi đó sẽ thường có 2 kiểu giải quyết:

    • Dùng optional self: self?
    • Dùng unwrapped self

    Vậy 2 cách này có khác gì nhau?

    • Khi unwrapped self, thì sẽ chỉ kiểm tra xem self có tồn tại 1 lần duy nhất ở đầu thân hàm, nếu self khác nil thì sẽ tạo 1 strong reference tồn tại trong thời gian chạy hàm. Khi hàm kết thúc, strong reference biến mất, khi đó VC2 mới được deallocated mặc dù đã pop về VC1. -> Vẫn tạo ra 1 delay deallocated, không có retain cycle.
    • Dùng self? thì sẽ check mỗi lần gọi đến self. Nếu self đã dc giải phóng khỏi bộ nhớ thì trình biên dịch sẽ bỏ qua dòng code đó. -> Không tạo ra delay deallocated, không có retain cycle.

    Examples

    Grand Central Dispatch

    • GCD được khởi tạo và thực hiện ngay lập tức, không được lưu lại nên là non-escaping closure, không cần dùng weak self.
      -> Đó là lí do không cần dùng weak self ở main.async
    DispatchQueue.main.async {
        self.view.backgroundColor = .yellow
    }

    UIView.Animate

    UIView.animate(withDuration: 0, animations: {
        self.view.backgroundColor = .yellow
    }, completion: nil)

    Tương tự như GCD, closure trong UIView.animate cũng là 1 non-escaping closure nên không cần weak self.

    Timer

    let _ = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { (_) in
         self.view.backgroundColor = .yellow
    }

    Timer sẽ tạo ra 1 strong reference và retain cycle nếu thỏa mãn 2 điều kiện:

    • Timer chạy lặp lại liên tục.
    • Timer capture lại self trong block closure

    -> Nếu thoả mãn 2 điều kiện trên thì phải dùng weak self đối với Timer.

  • [AWS] Phát triển ứng dụng Lambda bằng Java

    [AWS] Phát triển ứng dụng Lambda bằng Java

    Như các bạn đã biết hiện nay môi trường thực thi sử dụng trong Lambda phần lớn đang sử dụng Node hay Python. Tuy nhiên trên thực tế đôi khi bạn cần sử dụng một môi trường thực thi khác như Java chẳng hạn. Trên thực tế thì AWS cũng đang hỗ trợ khá nhiều môi trường thực thi khác nhau. Có nhiều lý do dẫn tới việc chúng ta phải sử dụng một môi trường thực thi nào đó tuỳ vào tình hình dự án. Trong bài viết này tôi sẽ hướng dẫn các bạn xây dựng ứng dụng Lamba sử dụng môi trường thực thi là Java.

    Các công cụ cần thiết

    Docker

    Chúng ta cần Docker bởi vì công cụ thực thi SAM CLI sẽ sử dụng docker container để thực thi ứng dụng. Bạn thao khảo đường dẫn sau để cài đặt Docker

    SAM

    Chúng ta sẽ sử dụng SAM vì chúng ta cần một môi trường thực thi có thể chạy trên môi trường cục bộ và có thể debug được. Để cài SAM bạn làm theo hướng dẫn sau:

    brew tap aws/tap
    brew install aws-sam-cli
    

    Chúng ta sử dụng brew để cài SAM nên bạn cần cài brew trước. Nếu chưa cài brew thì bạn có thể thao khảo cách cài brew như sau:

    /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)"
    hieunv@HieuNV ~ % brew --version
    Homebrew 2.2.10
    Homebrew/homebrew-core (git revision f0179; last commit 2020-03-22)
    Homebrew/homebrew-cask (git revision 0a88ae; last commit 2020-03-22)
    

    Để kiểm tra xem bạn đã cài đặt thành công chưa, bạn sử dụng lệnh sau:

    hieunv@HieuNV ~ % sam --version
    SAM CLI, version 0.45.0
    

    Trên Windows thì bạn thao khảo đường dẫn này

    Oracle JDK

    Chúng ta sẽ sử dụng môi trường thực thi Java nên việc cài đặt Oracle JDK là đương nhiên đúng không. Các bạn tham khảo cách cài đặt Oracle JDK tại đây nhé.

    Maven

    SAM sẽ sử dụng maven để build nên chúng ta cần cài đặt thêm maven. Để cài đặt Maven các bạn sử dụng lệnh sau:

    brew install --ignore-dependencies maven
    

    Các bạn chú ý, chúng ta cần sử dụng --ignore-dependencies để bỏ qua việc cài đặt Open JDK nhé. Mặc định maven sẽ sử dụng Open JDK. Tuy nhiên chúng ta đã cài đặt Oracle JDK rồi nên không cần cài Open JDK nữa.

    Tài liệu tham khảo:

    Tạo project bằng SAM

    • Tạo một project mới
    hieunv@HieuNV hieunv % sam init -r java11
    Which template source would you like to use?
    	1 - AWS Quick Start Templates
    	2 - Custom Template Location
    Choice: 1
    
    Which dependency manager would you like to use?
    	1 - maven
    	2 - gradle
    Dependency manager: 1
    
    Project name [sam-app]:
    
    Cloning app templates from https://github.com/awslabs/aws-sam-cli-app-templates.git
    
    AWS quick start application templates:
    	1 - Hello World Example: Maven
    	2 - EventBridge Hello World: Maven
    	3 - EventBridge App from scratch (100+ Event Schemas): Maven
    Template selection: 1
    
    -----------------------
    Generating application:
    -----------------------
    Name: sam-app
    Runtime: java11
    Dependency Manager: maven
    Application Template: hello-world
    Output Directory: .
    
    Next steps can be found in the README file at ./sam-app/README.md
    
    • Trước khi thực thi bạn cần build project trước
    hieunv@HieuNV hieunv % cd sam-app
    hieunv@HieuNV sam-app % sam build
    Building resource 'HelloWorldFunction'
    /usr/local/bin/mvn is using a JVM with major version 13 which is newer than 11 that is supported by AWS Lambda. The compiled function code may not run in AWS Lambda unless the project has been configured to be compatible with Java 11 using 'maven.compiler.target' in Maven.
    Running JavaMavenWorkflow:CopySource
    Running JavaMavenWorkflow:MavenBuild
    Running JavaMavenWorkflow:MavenCopyDependency
    Running JavaMavenWorkflow:MavenCopyArtifacts
    
    Build Succeeded
    
    Built Artifacts  : .aws-sam/build
    Built Template   : .aws-sam/build/template.yaml
    
    Commands you can use next
    =========================
    [*] Invoke Function: sam local invoke
    [*] Deploy: sam deploy --guided
    
    • Khởi động ứng dụng (trước khi khởi động bạn cần đảm bảo rằng Docker đang hoạt động)
    hieunv@HieuNV sam-app % sam local start-api
    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-22 22:07:45  * Running on http://127.0.0.1:3000/ (Press CTRL+C to quit)
    

    Chúng ta thử truy cập vào http://127.0.0.1:3000/hello bằng Postman. Nếu các bạn chưa chạy lần nào thì sẽ phải chờ hơi lâu một chút để Docker tải image cần thiết.

    start-api

    Trong bài viết này tôi đã hướng dẫn các bạn cách viết một API bằng Lambda sử dụng môi trường thực thi Java. Hy vọng bài viết sẽ giúp ích cho dự án của các bạn.

  • iOS/Swift: Một số kỹ thuật truyền dữ liệu phổ biến trong swift

    iOS/Swift: Một số kỹ thuật truyền dữ liệu phổ biến trong swift

    Lời mở đầu

    Trong Swift, chúng ta có khá nhiều cách để truyền dữ liệu qua lại giữa các đối tượng. Bài viết này mình muốn chia sẻ với các bạn về một số kỹ thuật phổ biến và ưu nhược điểm của nó. Bài viết này mình sẽ đề cập đến 3 cách phổ biến đó là Delegation, Closure và NotificationCenter Observation

    Bài toán

    Màn hình của mình cần làm có một UITableView, trong UITableView này chứa các UITableViewCell các cell thì có chứa 2 UIButton Dark và Light. Việc của mình phải làm là mỗi khi người dùng tương tác với button ở trong cell thì UIViewController sẽ bắt được sự kiện và hiển thị lên màn hình thông tin cell và hành động mà người dùng vừa tương tác.

    Các bạn tải về project bắt đầu này để tiện hơn trong quá trình thực hành nhé:

    Delegation

    Delegation là một design pattern cho phép đối tượng gửi thông điệp(data, message …) đến đối tượng khác khi có một sự kiện xảy ra. Ví dụ ta có 2 đối tượng A và B, trên B thực hiện hành động gửi thông điệp sang A để A thực hiện hành động dựa trên kết quả hành động trên B.

    Do chúng bài toán của chúng ta cần bắt hành động của người dùng khi họ action lên các button trên cell vì thế trong trường hợp này trong UITableViewCell chúng ta cần tạo protocol cho MyTableViewCell.swift cụ thể như sau:

    protocol ActionOnCell: class {
        func didTapDark(indexPath: IndexPath?)
        func didTapLight(indexPath: IndexPath?)
    }

    Ở đây mình tạo ra protocol định nghĩa các action trên cell. Khi người dùng bấm vào button Dark nó sẽ call protocol này tương tự với Button Light.

    Tiếp đến chúng ta tạo thêm 2 variables để lưu delegate và indexPath:

    var indexPath: IndexPath?
    weak var delegate: ActionOnCell?
    • indexPath để lưu lại cell mà ngươi dùng tương tác.
    • delegate để biết đối tượng nào đang conform protocol của MyTableViewCell

    Đối với delegate chúng ta cần khai báo weak để tránh tham chiếu strong dẫn đến Retain cycle, không giải phóng được các object này vì nó đang tham chiếu tới nhau.

    Tiếp đến chúng ta cần gọi các protocol tương ứng khi người dùng tương tác lên các button trên cell:

        @IBAction func dark(_ sender: Any) {
            if let delegate = delegate {
                delegate.didTapDark(indexPath: indexPath)
            }
        }
    
        @IBAction func light(_ sender: Any) {
            if let delegate = delegate {
                delegate.didTapLight(indexPath: indexPath)
            }
        }

    Vậy là chúng ta đã setup protocol cho MyTableViewCell.swift giờ tất cả những UIViewController nào sử dụng cell này đều có thể conform protocol của nó để bắt được event khi người dùng tương tác lên các button cell.

    Giờ chúng ta quay lại file ViewController.swift kéo xuống hàm cellForRowAt indexPath của tableview và update như sau:

        func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
            let cell = tableView.dequeueReusableCell(withIdentifier: "\(MyTableViewCell.self)", for: indexPath) as! MyTableViewCell
            cell.indexPath = indexPath
            cell.delegate = self
            return cell
        }

    Chúng ta gán lại giá trị indexPath cho các cell. Và set delegate cho Cell là self(viewController hiện tại)
    Lúc này XCode sẽ báo lỗi Cannot assign value of type ‘ViewController’ to type ‘ActionOnCell?’ vì chúng ta chưa conform protocol ActionOnCell trên ViewController.

    Vì vậy chúng ta sẽ tạo 1 extension của ViewController và conform ActionOnCell

    extension ViewController: ActionOnCell {
        func didTapDark(indexPath: IndexPath?) {
            showDark(indexPath: indexPath)
        }
    
        func didTapLight(indexPath: IndexPath?) {
            showLight(indexPath: indexPath)
        }
    }

    Khi người dùng tap vào button trên cell nó sẽ call protocol tương ứng và trả dữ liệu về cho viewController vì vậy bạn có thể sử dụng dữ liệu mà bạn muốn để update dữ liệu lên màn hình.

    Các bạn Build project sẽ nhận được kết quả như sau:

    NotificationCenter observation

    Là một cơ chế gửi thông báo cho phép truyền phát thông tin đến các đối tượng đã đăng ký quan sát notification đó.

    Chúng ta sẽ dựa trên 2 cơ chế post và observer của NotificationCenter để gửi và nhận dữ liệu giữa các đối tượng. Đối với notification thì chúng ta có thể sử dụng gửi thông tin từ một đối tượng đến nhiều đối tượng khác đã đăng ký theo dõi notification đó.

    Cụ thể trong bài toán này chúng ta sẽ làm như sau:
    Tạo 2 notification Name đinh nghĩa việc người dùng bấm vào Button Dark và Light:

    extension Notification.Name {
        static let didTapDarkNotification = NSNotification.Name(rawValue: "didTapDarkNotification")
        static let didTapLightNotification = NSNotification.Name(rawValue: "didTapLightNotification")
    }

    Mở file MyTableViewCell.swift thực hiện việc phát(post) thông báo tại 2 event của 2 button trên cell cụ thể như dưới đây:

        @IBAction func dark(_ sender: Any) {
            NotificationCenter.default.post(name: .didTapDarkNotification, object: indexPath)
        }
    
        @IBAction func light(_ sender: Any) {
            NotificationCenter.default.post(name: .didTapLightNotification, object: indexPath)
        }

    Nhiệm vụ của 2 hàm này là để khi nào người dùng bấm vào nut Dark hoặc Light thì NotificationCenter sẽ gửi đi một notification, những đối tượng nào đang lắng nghe các notification name đó sẽ nhận được thông điệp.

    Vì vậy chúng ta cần add observer cho ViewController.swift để nó nhận được thông tin khi người dùng tương tác lên các button trên cell.
    Chúng ta sẽ làm như sau:

        override func viewWillAppear(_ animated: Bool) {
            super.viewWillAppear(animated)
            NotificationCenter.default.addObserver(self, selector: #selector(didTapDark(noti:)), name: .didTapDarkNotification, object: nil)
            NotificationCenter.default.addObserver(self, selector: #selector(didTapLight(noti:)), name: .didTapLightNotification, object: nil)
        }
    
        override func viewWillDisappear(_ animated: Bool) {
            super.viewWillDisappear(animated)
            NotificationCenter.default.removeObserver(self)
        }

    Chúng ta cần đăng ký theo dõi notification ở viewWillApper và Remove theo dõi notification khi viewWillDisappear vì nếu chúng ta không remove view controller sẽ luôn nhận được thông báo khi mà nó còn trong view hierarchy dẫn đến những lỗi không mong muốn.

    // MARK: NotificationCenter handle
    extension ViewController {
        @objc func didTapDark(noti: Notification) {
            showDark(indexPath: noti.object as? IndexPath)
        }
    
        @objc func didTapLight(noti: Notification) {
            showLight(indexPath: noti.object as? IndexPath)
        }
    }

    Giờ build ứng dụng và các bạn sẽ nhận được kết quả tương tự như phần delegation

    Closure

    Nếu các bạn muốn tìm hiểu rõ hơn về Closure hãy đọc tài liệu tham khảo của Apple: https://docs.swift.org/swift-book/LanguageGuide/Closures.html

    Ý tưởng ở đây chúng ta sẽ tạo 1 closure callback có 2 tham số là action type và dữ liệu truyền lại là index path.

    Mở file MyTableViewCell.swift và thêm 1 enum định nghĩa các kiểu action trên cell

    enum ActionType {
        case dark
        case light
    }

    Tạo closure cho MyTableViewCell

    var didTapButton: ((ActionType, IndexPath?) -> Void)?

    Gọi closure khi người dùng bấm vào các nút trên cell

        @IBAction func dark(_ sender: Any) {
            if let didTapButton = didTapButton {
                didTapButton(.dark, indexPath)
            }
        }
    
        @IBAction func light(_ sender: Any) {
            if let didTapButton = didTapButton {
                didTapButton(.light, indexPath)
            }
        }

    Tiếp đến để nhận được event khi người dùng bấm vào các button chúng ta cần gán giá trị cho closure ở nơi mà bạn muốn. Cụ thể trong trường hợp này là ViewController.swift

        func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
            let cell = tableView.dequeueReusableCell(withIdentifier: "\(MyTableViewCell.self)", for: indexPath) as! MyTableViewCell
            cell.indexPath = indexPath
            cell.didTapButton = {[weak self] (action, index) in
                guard let self = self else {return}
                if action == .dark {
                    self.showDark(indexPath: index)
                } else {
                    self.showLight(indexPath: index)
                }
            }
            return cell
        }

    Giờ run ứng dụng chúng ta sẽ được kết quả như 2 cách trên.

    Đánh giá

    • Delegation: là cách dùng rất phổ biến và đễ sử dụng nhât khi truyền dữ liệu giữa các đối tượng. Nó dựa vào các protocol để truyền dữ liệu qua lại giữa các đối tượng. Thường dùng trong các trường hợp truyền dữ liệu dạng 1 – 1. Tuy nhiên nếu bạn muốn sử dụng delegate dạng 1 – n hãy tham khảo bài viết này https://magz.techover.io/2019/08/09/swift-design-patterns-multicast-delegate/
    • NotificationCenter: Là dạng truyền nhận dữ liệu 1 – n hoặc 1 – n. Và khi một thông báo được đẩy đi tất cả những đối tượng đã đăng ký nhận thông báo đó sẽ nhận được thông báo. Khi bạn dùng cần phải kiểm soát chặt các notification. Trong mỗi trường hợp khác nhau chúng ta lại có cách đăng ký notification và remove nó ở các chỗ khác nhau.
      VD:
      – Đối với các trường hợp bình thường chúng ta sẽ phải thêm notification ở ViewWillAppear và remove nó ở viewWillDisappear để đảm bảo lần viewWillAppear tiếp theo nó không đăng ký lại dẫn đến bị lặp nhiều lần.
      – Chúng ta không thể để ở viewDidLoad vì nếu remove ở ViewWillDisappear thì khi pop lại sẽ mất notification, nếu không remove thì khi nó vẫn còn trong view hierarchy chưa bị giải phóng nó sẽ vẫn nhận được notification mặc dù chungs ta không mong muốn điều đó.
      Trong tất cả thì cách này là cách rắc rối nhất. Bạn chỉ nên dùng khi muốn thực hiện dạng truyền dữ liệu 1-n
    • Closure: Là cách dùng khá phổ biến hiện tại, vì nó là cách viết khá gọn gàng vì vậy tiết kiệm được thời gian code. Tuy nhiên cách viết của nó khá khó hiểu với người mới. Các bạn cũng có thể nhận thấy sự phổ biến của nó thông qua các hàm completion, callback mà Apple đã sử dụng rất nhiều trong các API của họ.

    Tổng kết

    Mình hi vọng bài viết giúp cho các bạn có thể lựa chọn các cách truyền dữ liệu phù hợp với bài toán của mình. Chúc các bạn thành công!

    Nếu bài viết có vấn đề gì các bạn hãy comment xuống dưới để mình update nhé!

  • [AWS] Serverless và SAM, bạn chọn dùng cái nào?

    [AWS] Serverless và SAM, bạn chọn dùng cái nào?

    Mình đã viết khá nhiều bài sử dụng Serverless, tại sao mình lại viết bài này. Thực ra mình cũng mới bắt đầu làm AWS Lambda được một thời gian ngắn. Dự án đầu tiên mình làm Lambda thì đã các bạn đi được đã chọn Serverless để phát triển. Dự án thứ hai mình làm với AWS Lambda thì khách hàng đưa cho mình bộ mã nguồn đã được cấu hình sử dụng Serverless. Mọi thứ đều có vẻ ổn cho đến một ngày mình quyết định thử debug Lambda bằng Visual Studio Code. Mọi thứ trở nên phức tạp và mình tìm thấy SAM, dường như nó đã giải quyết vấn đề của mình nên mình quyết định viết bài này để cho các bạn nếu mới đến với thế giới AWS thì có thể dễ dàng lựa chọn thứ mình cần.

    Chọn Serverless hay SAM?

    Dự án đầu tiên mình dùng Serverless và viết bằng JavaScript, mọi thứ đều ổn vì mình chỉ dùng Serverless có kết hợp với Serverless Offline để chạy các hàm Lambda API trên máy tính cá nhân được. Việc debug cũng không gặp trở ngại gì do Serverless Offline có hỗ trợ debug. Thế nhưng đến dự án tiếp theo, ngôn ngữ được chọn làm môi trường thực thi là Python và mình thực sự gặp khó khăn. Mình vẫn có thể chạy được các hàm Lambda trên máy tính cá nhân nhưng không thể debug đươc. Và thế là mình bắt đầu tìm hiểu để giải quyết vấn đề này. Rồi mình tìm thấy SAM và mọi thứ dường như được giải quyết.

    Ngôn ngữ nào được hỗ trợ?

    • Serverless Offline hỗ trợ những ngôn ngữ sau:
      • Python
      • Ruby
      • Node
    • SAM hỗ trợ nhưng ngôn ngữ sau:
      • Python
      • Ruby
      • Node
      • Java
      • .NET Core

    Được hỗ trợ như thế nào?

    • Serverless Offline là plugin được cá nhân phát triển. Nó không phải gói được hỗ trợ chính thức từ AWS.

    • SAM được hỗ trợ chính thức từ AWS.

    Hỗ trợ debug như thế nào?

    • Serverless Offline chỉ hỗ trợ debug với Node.

    • SAM thì có vẻ như đã hỗ trợ tất cả các trình thực thi ở trên. Mình đã thử debug với Java thì thấy vẫn OK.

    Môi trường thực thi

    • Serverless chạy trực triếp trên máy host.
    • SAM thì sử dụng container trong docker để thực thi.

    Các bạn có thể tham khảo hướng dẫn sử dụng Serverless ở các tài liệu sau nhé:

    Kết luận

    SAM dường như có những lợi thế hơn hẳn so với Serverless. Nếu bạn quyết định phát triển bằng Node thì bạn sẽ không gặp nhiều khó khăn khi dùng Serverless hay SAM. Nếu bạn chọn một môi trường thực thi khác như Python hay Ruby hay bất kỳ môi trường nào khác thì lựa chọn SAM sẽ là quyết định sáng suốt hơn đấy. Mình sẽ hướng dẫn các bạn sử dụng SAM trong loạt bài viết về SAM sau nhé.

  • Basic Test in Swift (P1)

    Basic Test in Swift (P1)

    Trong lập trình, để có thể tự kiểm tra app để hoạt động chính xác hay chưa, thì ta phải test hết toàn bộ các case xảy ra. Tuy nhiên, nếu làm bằng 1 cách test thủ công thì rất tốn thời gian.
    Để có thể test tự động, thì có thể dùng Unit test. Vậy ở bài viết này, mình sẽ giới thiệu về cách sử dụng Unit test trong lập trình iOS.

    Nội dung bài viết

    • Unit test là gì?
    • Giới thiệu về unit test trong iOS
    • Useful Test
    • Khởi tạo Test Target
    • Run the test
    • Functional test Demo

    Unit test là gì?

    • Là phương pháp dùng để kiểm tra tính đúng đắn của một đơn vị source code. Một Unit (đơn vị) source code là phần nhỏ nhất có thể test được của chương trình, thường là một phương thức trong một lớp hoặc một tập hợp các phương thức thực hiện một mục đích thiết yếu.
    • Bạn viết các test case để Xcode tiến hành test các test case đó.

    Giới thiệu về Unit test trong iOS

    Xcode cung cấp 3 kiểu test chính:

    • Functional test: tập trung vào test các func.
    • Performance tests: tập trung vào đo lương thời gian app của bạn thực thi xong các task trên các loại thiết bị khác nhau.
    • User Interface tests (UI Tests): tập trung vào những tác vụ của người dùng trên UI.

    Note:

    • Functional test & performance test: Là những đoạn test mà bạn tự viết để test sự hoạt động của các func trong app.
    • UI Test: Ghi lại những thứ mà bạn tương tác trên UI của app.

    Useful Test

    1 test case được coi là useful khi:

    • Test case phải có khả năng fail: Nếu test đó không thể fail, thì việc test sẽ rất vô giá trị, bạn nên xốa nó đi.
    • Test case phải có khả năng success: Nếu test đó không thể success, thì việc test sẽ rất vô giá trị, tương tự ở trên, bạn nên xốa nó đi.
    • Test case phải được refactor và được viết đơn giản

    Khởi tạo 1 Test Target

    Để viết được test, trước hết cần tạo 1 Unit test target để viết test:

    Điền tên cho class rồi chọn Next.

    1. Xcode imports sẵn cho bạn frameworks XCTest và class được tạo là subclass của XCTestCase.
    2. func setUp(): dùng để thiết lập lại trạng thái trước mỗi lần test.
    3. func tearDown(): dùng để thực hiện dọn dẹp sau khi mỗi lần test kết thúc.
    4. measure: dùng để đo thời gian thực hiện xong việc test -> giúp test performance.

    Trình tự mỗi khi thực hiện test 1 test case như sau:

    Run the Tests:

    Có 3 cách để run test:

    • Product ▸ Test or Command-U: Cách này sẽ run tất cả test classes trong project.
    • Click vào button mũi tên ở Test navigator.
    • Click chọn nút hình kim cương để run 1 test case cụ thể.

    Basic Functional Test Demo

    Ở ví dụ này, mình sẽ tiến hành việc test xem dữ liệu học sinh ở file json đã chính xác chưa.

    Tạo 1 class Person:

    class Person: Decodable {
        let name: String
        let age: Int
        
        init(name: String, age: Int) {
            self.name = name
            self.age = age
        }
    }
    

    Tạo 1 class Service chứa các logic thực hiện việc lấy dữ liệu:

    class Service {
        func getListStudent() -> [Person] {
            var students: [Person] = []
            guard let url = Bundle.main.url(forResource: "config", withExtension: ".json") else {
                return students
            }
            
            
            do {
                let data = try Data(contentsOf: url)
                let results = try JSONDecoder().decode([Person].self, from: data)
                students += results
            } catch {
                print("Can get data, error: \(error)")
            }
            return students
        }
    }

    Tiến hành việc viết test thông tin cho sinh viên đầu tiên:

    1. @tesable import + tên project: Cho phép unit tests truy cập đến toàn bộ các dữ liệu kiểu internal của project.
    2. Khai báo 1 biến kiểu service.
    3. Khởi tạo sut mỗi lần bắt đầu thực hiện test 1 test case.
    4. Set sut = nil mỗi khi kết thúc việc test 1 test case.
    5. Viết func test để test thông tin học sinh 1.
    6. Viết các hàm để test.

    Note: Khi viết func để test thì phải tên func phải luôn bắt đầu với từ test để Xcode có thể biết đó là 1 test.

    Thực hiện tiến hành test func, nhận được kết quả dữ liệu cần kiểm tra đã chính xác:

    Để đảm bảo đây là 1 Userful test, thực hiện sửa đổi name của sinh viên 1 trong file json thành "Cuong1" và tiến hành test lại func, nhận được kết quả sau:

    Việc test thất bại, đồng thời XCode cũng thông báo ra kết quả để biết sai ở đâu.

    Ở phần tiếp theo, mình sẽ nói về performance test, UI Test và 1 vài thứ hay ho mà Xcode support cho việc test.

  • iOS/Testflight: Cách sử dụng iOS Beta testing

    iOS/Testflight: Cách sử dụng iOS Beta testing

    Lời mở đầu

    Ở bài viết trước mình đã hướng dẫn cho các bạn cách upload ứng dụng lên store. Nhưng ứng dụng nào trước khi đẩy lên store chúng ta cũng phải có giai đoạn kiểm thử. Điều đó đảm bảo ứng dụng của bạn phát sinh ít lỗi trong quá trình người dùng sử dụng cũng như đảm bảo ứng dụng chạy đúng theo yêu cầu. Vì vậy hôm nay mình sẽ chia sẻ với các bạn tất tần tật về Testflight ứng dụng được Apple phát hành với nhiệm vụ giúp tester dễ dàng tiếp cận bản build hơn.

    Testflight là gì?

    Testflight là một ứng dụng được Apple cung cấp để cho Developer của họ upload ứng dụng lên nhằm mục đích kiểm thử ứng dụng. Ứng dụng này gồm 2 phần: Tính năng quản lí cho developer nằm trong App store connect và ứng dụng Testflight là ứng dụng cho tester.

    Làm thế nào để upload bản build lên Testflight?

    Nếu các bạn chưa biết cách upload bản build của mình lên Testflight hãy đọc bài viết trước của mình nhé: iOS/App store connect: Các bước đẩy ứng dụng của bạn lên App store

    Định nghĩa các loại tester trên Testflight

    • Internal tester: Đây là những App store connect user được phân quyền Admin, App manager, legal, developer … có thể quyền truy cập vào quản lý ứng dụng của bạn. Thường thì đó là những người trong đội phát triển ứng dụng hoặc khách hàng. Số lượng tối đa cho internal tester là 25. Với mỗi lần bạn submit bản build lên App store connect hoàn thành, tất cả các internal tester sẽ nhận được thông báo về phiên bản mới nhất.
    • External tester: Đây là những người dùng không nằm trọng đội phát triển ứng dụng nhưng họn muốn trải nghiệm, kiểm thử ứng dụng của bạn. Các extenal tester không có quyền truy cập vào App store connect. Nhưng họ vẫn có thể tải và cài đặt phiên bản ứng dụng mà bạn muốn họ test. Hiện nay Apple đang giới hạn tối đa external tester là 10,000. Để external tester có thể cài đặt và kiểm thử ứng dụng của bạn thì bạn cần Submit bản build mà bạn muốn cho bên apple review gần giống như việc submit bản build lên app store. Khi bản build đó được approve thì tất cả các External tester sẽ nhận được thông báo và có thể tải và cài đặt ứng dụng dựa trên bản build đó.

    Internal testers

    Để thêm một internal tester bạn cần truy cập vào App store connect đăng nhập và truy cập vào mục Users and Assess

    Bấm vào dấu + để thêm mới một user:

    Điền đầy đủ thông tin và bấm invite để gửi email mời họ.

    Lưu ý: Mục email là tài khoản apple(apple id) vì vậy những người muốn vào nhóm internal tester phải có tài khoản apple. Khi bạn add user này vào đồng nghĩa họ cũng sẽ có quyền truy cập vào App store connect để quản lý ứng dụng của bạn. Vì vậy hãy chú ý mục phân quyền(Role) cho user mà bạn muốn add.

    App store connect sẽ gửi một thư mời tới user mà bạn vừa thêm. User này cần truy cập vào hòm thư của họ để mở thư mời và chọn Active your account.

    Khi đó bạn sẽ được điều hướng sang trang của apple và bạn đăng nhập vào để active tài khoản của bạn. Hoàn thành bước này là bạn đã add tài khoản đó vào App store connect.

    Nhưng để các tài khoản này có thể tải và cài đặt những bản build trên Testflight các bạn cần mời họ vào nhóm App store connect users(Internal tester). Vì vậy chúng ta sẽ chuyển qua mục My app

    Một danh sách các ứng dụng của bạn được hiện ra. Hãy chọn ứng dụng mà bạn đang muốn quản lý. Chuyển sang tab Testflight > App store connect user > bấm dấu + để thêm user vào App store connect user.

    Lúc này một danh sách các user hiện ra. Bạn hãy chọn User lúc trước bạn đã invite ở bước trên và bấm Add để Testflight gửi thư mời cho tài khoản đó.

    Thư mời sẽ nằm trọng hòm thư của người dùng mà bạn vừa gửi. Bấm vào View In Testflight và hoàn thành các bước đơn giản để hoàn tất việc add Internal tester. Thư mời có định dạng như sau:

    Vậy là hoàn tất việc add Internal tester. Từ giờ mỗi khi có bản build mới được upload lên TestFlight là người dùng này sẽ nhận được cả mail và thông báo.

    External testers

    Như đã giới thiệu ở trên, external tester là những user không thể truy cập vào App store connect của bạn nên chúng ta cần cung cấp thông tin bản kiểm thử cũng như cần chọn bản build cho apple review.

    Cập nhật thông tin kiểm thử

    Bạn mở mục Test information và điền đầy đủ các thông tin cho bản kiểm thử này. Nó là bắt buộc nên bạn cần phải điền đầy đủ thông tin cho nó. Nhớ bấm Save để lưu lại thông tin nhé.

    Hãy điền đầy đủ thông tin cho mục Test information

    Tạo nhóm External tester

    Chọn mục Add Group để thêm mới nhóm External testers:

    Đặt tên nhóm > Bấm Create để tạo nhóm tester mới.

    Khi tạo thành công sẽ hiển thị một giao diện như dưới

    Do nhóm vừa tạo nên chưa có tester nào. Để thêm tester chúng ta bấm vào dấu +

    • Add new testers: Đây là cách thêm thủ công, sử dụng khi ban muốn thêm số lượng ít tester
    • Add existing testers: Thêm các tester đã từng test ứng dụng rồi
    • Import from CSV: Sử dụng file CSV để thêm tester, sử dụng khi bạn muốn thêm số lượng lớn các tester

    Ở bài này mình sẽ chỉ nói đến Add New Testers. Bạn điền thông tin của tester và bấm add để invite họ vào group.

    Lúc này 1 thư mời sẽ được gửi tới hòm thư của email đó họ chỉ cần accept là được.

    Chọn bản build dành cho external tester

    Chuyển sang mục Builds và bấm vào dấu + để chọn bản build bạn muốn external tester kiểm thử.

    Một danh sách các bản build được hiển thị, chọn bản mà bạn muốn test. Và bấm Add

    Lúc này bạn đã Add thành công, giờ chờ để apple review.

    Lúc này trạng thái của bản build đang là waiting for review có nghĩa là ứng dụng của bạn đang trong hàng đợi để nhân viên của apple review. Để được approve bản build của bạn phải không vi phạm điều luật apple đã đưa ra trong App Store Review Guideline. Việc review sẽ tốn một khoảng thời gian lớn tùy thuộc vào ứng dụng của bạn và thời gian bạn submit. Tuy nhiên một khi Apple đã approve cho version của bạn thì những bản build tiếp theo sẽ không phải review cho đến khi bạn thay đổi version.

    Sau khi apple họ Approve bản build của bạn, một thông báo sẽ được gửi cho tất cả các tester trong nhóm. Để họ có thể tài và cài ứng dụng lên thiết bị của họ.

    Vậy là chúng ta đã hoàn thành việc tạo nhóm external tester và thêm bản build cho nhóm external tester.

    Tester cần làm gì?

    Tải ứng dụng TestFlight trên App store

    Khi có bản mới thông báo sẽ báo về mail các bạn chỉ cần bấm vào để sử dụng. Khi đó bạn sẽ được chuyển qua ứng dụng TestFlight và có thể Tải cũng như cài đặt ứng dụng.

    Tổng kết

    Bài viết này mình đã chia sẻ với các bạn về cách thêm internal tester, external tester để hỗ trợ việc tester kiểm thử ứng dụng. Hi vọng nó giúp các bạn phần nào bớt bỡ ngỡ khi tiếp cận TestFilght.

    Ngoài ra còn một số cách đẩy bản build cho tester kiểm thử khác không phải của apple cũng khá được ưa chuộng.
    Các bạn có thể tham khảo bài viết: https://magz.techover.io/2019/12/17/cach-upload-1-app-ios-len-deploygate/

  • [React] Kết hợp các mẫu lập trình để sử dụng hiệu quả Runtime Type Checking

    [React] Kết hợp các mẫu lập trình để sử dụng hiệu quả Runtime Type Checking

    Như các bạn đã biết, Static Type Checking giúp chúng ta kiểm soát kiểu dữ liệu tốt hơn khi viết mã nguồn. Các bạn có thể sử dụng Flow hoặc TypeScript đều được. Tuy nhiên khi dữ liệu được liên kết với API chẳng hạn, khi đó bạn không thể kiểm soát được giá trị được truyền vào cho biến nào đó. Do đó chúng ta vẫn cần thêm một bước nữa để hạn chế các lỗi tiềm ẩn bằng Runtime Type Checking. Trong bài viết này tôi sẽ hướng dẫn các bạn sử dụng Runtime Type Checking như thế nào cho hiệu quả.

    Runtime Type Checking là gì?

    Runtime Type Checking là quá trình xác minh lại kiểu dữ liệu của giá trị truyền vào cho biến có đúng với kiểu dữ liệu mong muốn hay không. Trong ứng dụng React khi bạn khai báo propTypes cho component nào đó chính là lúc bạn đang định nghĩa kiểu dữ liệu mong muốn. Tại thời điểm thực thi các định nghĩa này sẽ được kiểm tra giúp bạn kiểm soát việc binding dữ liệu từ API vào component có tương thích với nhau hay không.

    Định nghĩa propTypes

    React cung cấp gói prop-types giúp bạn định nghĩa các thuộc tính cần cho Runtime Type Checking. Trước đây thì gói này nằm trong React, hiện tại thì nó đã tách ra thành một gói riêng rồi. Các bạn khai báo propTypes như sau:

    import React from 'react';
    import PropTypes from 'prop-types';
    
    export function Octocat(props) {
      const { login, avatar_url, name } = props;
      return (
        <ul>
          <li>{login}</li>
          <li>{name}</li>
          <li>
            <img src={avatar_url} alt={login} />
          </li>
        </ul>
      );
    }
    
    Octocat.propTypes = {
      login: PropTypes.string,
      avatar_url: PropTypes.string,
      name: PropTypes.string
    };
    

    Trong quá trình thực thi nếu dữ liệu được truyền vào component không đúng với định nghĩa propTypes bạn sẽ nhận được một thông báo tương tự như sau:

    prop-types

    Kết hợp với Spread Attributes

    Bạn có để ý rằng khi chúng ta khai báo giá trị truyền vào trong props thì khi sử dụng component này bạn phải khai báo như sau:

    <Octocat login="abc" avatar_url="def" name="xyz" />
    

    Nếu số lượng thuộc tính trong component này tăng lên thì việc viết mã nguồn sẽ khá là vất vả. Rất may là trong JSX cung cấp cho bạn tính năng Spead Attributes và bạn có thể viết code như sau:

    const data = {login: "abc" avatar_url: "def" name: "xyz"};
    <Octocat {...data} />
    

    Toán tử ... được gọi là toán tử spead.

    Kết hợp với Destructuring Props

    Các bạn có thấy rằng khi khai báo Octocat component tôi đã truyền props như làm tham số của một hàm và sau đó mới gán lại vào các biến. Với Destructuring Props bạn có thể viết mã nguồn đơn giản hơn như sau:

    import React from 'react';
    import PropTypes from 'prop-types';
    
    export function Octocat({ login, avatar_url, name }) {
      return (
        <ul>
          <li>{login}</li>
          <li>{name}</li>
          <li>
            <img src={avatar_url} alt={login} />
          </li>
        </ul>
      );
    }
    
    Octocat.propTypes = {
      login: PropTypes.string,
      avatar_url: PropTypes.string,
      name: PropTypes.string
    };
    

    Tài liệu tham khảo:

    Cám ơn các bạn đã theo dõi bài viết. Hy vọng bài viết sẽ giúp các bạn viết mã nguồn tốt hơn.

  • [AWS] Sử dụng API Gateway Lambda Authorizers với JWT như thế nào?

    [AWS] Sử dụng API Gateway Lambda Authorizers với JWT như thế nào?

    Một trọng những vấn đề quan trọng trong các dự án đó là điều khiển quyền truy cập. Với các ứng dụng xây dựng trên nền tảng AWS việc điều khiển truy cập cũng phức tạp hơn. Trong bài viết này tôi sẽ hướng dẫn các bạn cách tôi đã làm để điểu khiển truy cập với các API sử dụng API Gateway Lambda Authorizers.

    Luồng xác thực Lambda Authorizer

    Luồng xác thực của Lambda Authorizer được minh hoạ trong hình sau:

    auhorizer

    Các bược xác thực như sau:

    1. Máy khách gửi yêu cầu lên API Gateway API có kèm theo Bearer Token.
    2. API Gateway kiểm tra cấu hình authorizer đã được cấu hình tương ứng với hàm Lambda. Nếu nó tồn tại thì Lambda Authoirizer sẽ được gọi.
    3. Lambda Authorizer sẽ thực hiện xác thực bằng Bearer Token đã được gửi lên.
    4. Nếu việc gọi Lambda Authrorizer thực hiện thành công, hàm Lambda sẽ trả về thông tin chứa chính sách IAM và thông tin người dùng.
    5. API Gateway sử dụng thông tin trả về từ Lambda Authorizer để kiểm tra quyền truy cập:
    • Trường hợp nhận được thông tin từ chối truy cập thì API Gateway sẽ trả về mã 403 và từ chối truy cập tới API từ máy khách.
    • Trường hợp nhận được thôn tin cho phép truy cập thì phương thức sẽ được thực thi.

    Định nghĩa Lambda Authorizer

    • Khai báo authorizer trong serverless.yml:
    functions:
      authorizer:
        handler: src.api.authorizer.lambda_handler
        cors: true
    
    • Định nghĩa hàm Lambda Authorizer:
    import jwt
    
    
    def lambda_handler(event, context):
        try:
            token = event.get("authorizationToken").split(" ")[1] # lấy thông tin token trong Authorization header
            claims = jwt.decode(token, "secret", algorithms=["HS256"]) # decode xem token có hợp lệ không
            return {
                "principalId": claims["uid"], # lấy thông tin user đề gán vào IAM
                "policyDocument": {
                    "Version": "2012-10-17",
                    "Statement": [
                        {
                            "Action": "execute-api:Invoke",
                            "Effect": "Allow", # cho phép nếu token hợp lệ
                            "Resource": event["methodArn"],
                        }
                    ],
                },
            }
        except:
            return {
                "principalId": "denied",
                "policyDocument": {
                    "Version": "2012-10-17",
                    "Statement": [
                        {
                            "Action": "execute-api:Invoke",
                            "Effect": "Deny", # từ chối nếu token không hợp lệ
                            "Resource": event["methodArn"],
                        }
                    ],
                },
            }
    

    Định nghĩa hàm Lambda cần điều khiển quyề truy cập

    • Khai báo hàm Lambda trong serverless.yml
    functions:
      test:
        handler: src.api.test.lambda_handler
        events:
          - http:
              method: get
              path: api/test
              cors: true
              authorizer: authorizer # khai báo Lambda Authorizer
    
    • Định nghĩa hàm Lambda:
    import json
    
    
    def lambda_handler(event, context):
        headers = {"Access-Control-Allow-Origin": "*", "Accept": "application/json"}
        body = {"status": "success"}
        return {
            "statusCode": 200,
            "headers": headers,
            "body": json.dumps(body),
        }
    

    Test thử Lambda với Authorizer

    • Trường hợp không truyền token trên Authorizer Header, API Gateway sẽ trả về 403

    No Auth

    • Trường hợp có truyền token trên Authorization Header, API Gateway sẽ cho phép phương thức được thực thi

    Token được tạo như sau

    (zpn) hieunv@HieuNV lambda % python
    Python 3.7.7 (default, Mar 10 2020, 15:43:33)
    [Clang 11.0.0 (clang-1100.0.33.17)] on darwin
    Type "help", "copyright", "credits" or "license" for more information.
    >>> import jwt
    >>> jwt.encode({'uid': 'hieunv'}, "secret", algorithm='HS256')
    b'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1aWQiOiJoaWV1bnYifQ.xuZSlS_3lw6NvGvw_fQ2qXGBWiv2HpXTFtYtO85lQac'
    

    Truyền token lên Authorizer Header

    Bearer Auth

    Tài liệu tham khảo:

    Cám ơn các bạn đã theo dõi bài viết. Hy vọng bài viết có thể giúp các bạn cài đặt việc điều khiển truy cập dễ dàng hơn với các ứng dụng trên nền tảng AWS.