Month: September 2020

  • CoreData with MultiThreading

    CoreData with MultiThreading

    Nội dung

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

    Tóm tắt về CoreData:

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

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

    MultiThreading with CoreData:

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

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

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

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

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

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

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

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

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

    Debug Concurrency:

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

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

    Ví dụ:

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

    Kết luận:

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

    Hướng dẫn tạo plugin cho dự án Cordova/Ionic

    Table of contents

    • Tại sao cần tạo plugin cho Cordova
    • Tạo plugin bằng plugman
    • Hoàn thiện plugin

    Tại sao cần tạo plugin cho Cordova

    Về cơ bản, thì Cordova là framework phát triển các app iOS/Android (là chính) sử dụng html/js/css làm UI, và các bộ plugin làm cầu nối để call xuống source native của platform (iOS/Android)

    Cordova bao gồm:

    • Bộ html/js/css làm UI.
    • Native webview engine làm bộ render hiển thị UI
    • Cordova framework chịu trách nhiệm cầu nối giữa function call js và funtion native.
    • Source code native làm plugin cùng các config và các pulic js method.
    Cordova project struct

    Bình thường đối với người làm Cordova thì chủ yếu họ sẽ focus vào tầng UI bằng html/js/css. Việc sử dụng các chức năng native của platform thì sẽ sử dụng các plugin được cung cấp sẵn. Vậy nên về cơ bản, một lập trình viên làm Cordova chỉ cần làm được html/js/css là đủ.

    Tuy nhiên trong một số trường hợp, các plugin có sẵn không đảm bảo giải quyết được vấn đề bài toán, lúc này việc phát triển riêng một plugin thực hiện được logic của project và support được các platform là điều cần phải làm.

    Trong một vài trường hợp khác, có thể dự án đã có sẵn source native, tuy nhiên cần chuyển sang Cordova để support multi platform và tận dụng source code native có sẵn.

    -> Đo đó, hiểu biết về cách tạo một plugin để giải quyết nhu câu bài toán sẽ nảy sinh. Bài viết này sẽ tập trung vào việc

    • Làm thế nào để tạo plugin
    • Luồng xử lý từ js xuống native source của plugin như nào
    • Install plugin vào Cordova project
    • Build và test plugin trên iOS và Androd

    Tạo plugin bằng plugman

    Tạo Cordova project

    Để tạo Cordova plugin sample thì trước tiên cần có một Cordova project để test việc add plugin và kiểm tra hoạt động của plugin trên từng platform.

    • Install Cordova CLI: sudo npm install -g cordova
    • Create Cordova project: cordova create SamplePlugin com.nhathm.samplePlugin SamplePlugin

    Install plugman và tạo plugin template

    plugman là command line tool để tạo Apache Cordova plugin. Install bằng command: npm install -g plugman

    Create plugin:

    • Command: plugman create –name pluginName –plugin_id pluginID –plugin_version version
    • Ví dụ: plugman create –name GSTPlugin –plugin_id cordova-plugin-gstplugin –plugin_version 0.0.1

    Thêm platform mà plugin sẽ hỗ trợ:

    • plugman platform add –platform_name android
    • plugman platform add –platform_name ios

    Sau khi cài đặt xong thì thư mục plugin sẽ có struct như dưới.

    .
    └── GSTPlugin
    ├── plugin.xml
    ├── src
    │ ├── android
    │ │ └── GSTPlugin.java
    │ └── ios
    │ └── GSTPlugin.m
    └── www
    └── GSTPlugin.js

    Ở đây, plugin.xml là file config cho plugin, bao gồm các thông tin như tên của plugin, các file assets, resources. Define js-module như file js của plugin, define namespace của plugin, define các plugin phụ thuộc của plugin đang phát triển…

    Hoàn thiện plugin

    Cùng view file plugin.xml của plugin mới tạo:

    <?xml version='1.0' encoding='utf-8'?>
    <plugin id="cordova-plugin-gstplugin" version="0.0.1"
    xmlns="http://apache.org/cordova/ns/plugins/1.0"
    xmlns:android="http://schemas.android.com/apk/res/android">
    <name>GSTPlugin</name>
    <js-module name="GSTPlugin" src="www/GSTPlugin.js">
    <clobbers target="cordova.plugins.GSTPlugin" />
    </js-module>
    <platform name="android">
    <config-file parent="/*" target="res/xml/config.xml">
    <feature name="GSTPlugin">
    <param name="android-package" value="cordova-plugin-gstplugin.GSTPlugin" />
    </feature>
    </config-file>
    <config-file parent="/*" target="AndroidManifest.xml" />
    <source-file src="src/android/GSTPlugin.java" target-dir="src/cordova-plugin-gstplugin/GSTPlugin" />
    </platform>
    <platform name="ios">
    <config-file parent="/*" target="config.xml">
    <feature name="GSTPlugin">
    <param name="ios-package" value="GSTPlugin" />
    </feature>
    </config-file>
    <source-file src="src/ios/GSTPlugin.m" />
    </platform>
    </plugin>

    Trong file này có một vài điểm cần hiểu như dưới:

    • <clobbers target="cordova.plugins.GSTPlugin" /> đây là namespace phần js của plugin. Từ file js, call xuống method native của plugin thì sẽ sử dụng cordova.plugins.GSTPlugin.sampleMethod
    • <param name="android-package" value="cordova-plugin-gstplugin.GSTPlugin" /> đây là config package name của Android, cần đổi sang tên đúng => <param name="android-package" value="com.gst.gstplugin.GSTPlugin" />. Như vậy Cordova sẽ tạo ra file GSTPlugin.java trong thư mục com/gst/gstplugin.

    Trong sample này, chúng ta sẽ sử dụng Swift làm ngôn ngữ code Native logic cho iOS platform chứ không dùng Objective-C, do đó phần platform iOS cần update.

    • Trong thẻ <platform name="ios> thêm tag <dependency id="cordova-plugin-add-swift-support" version="2.0.2"/>. Đây là plugin support việc import các file source Swift vào source Objective-C. Mà bản chất Cordova sẽ generate ra source Objective-C cho platform iOS.
    • Vì sử dụng Swift nên ta thay thế <source-file src="src/ios/GSTPlugin.m" /> bằng <source-file src="src/ios/GSTPlugin.swift" />. Và đổi tên file GSTPlugin.m sang GSTPlugin.swift

    Tiếp theo, chỉnh sửa các file js và native tương ứng cho plugin.

    File GSTPlugin.js

    • File này export các public method của plugin. Update file như dưới
    var exec = require('cordova/exec');
    
    exports.helloNative = function (arg0, success, error) {
        exec(success, error, 'GSTPlugin', 'helloNative', [arg0]);
    };
    • File này sẽ export method helloNative ra js và call method helloNative của native platform tương ứng.

    File GSTPlugin.swift

    • File này chứa logic và implementation cho iOS platform. Chỉnh sửa file như dưới
    @objc(GSTPlugin) class GSTPlugin : CDVPlugin {
        @objc(helloNative:)
        func helloNative(command: CDVInvokedUrlCommand) {
            // If plugin result nil, then we should let app crash
            var pluginResult: CDVPluginResult!
    
            if let message = command.arguments.first as? String {
                let returnMessage = "GSTPlugin hello \(message) from iOS"
                pluginResult = CDVPluginResult(status: CDVCommandStatus_OK, messageAs: returnMessage)
            } else {
                pluginResult = CDVPluginResult (status: CDVCommandStatus_ERROR, messageAs: "Expected one non-empty string argument.")
            }
    
            commandDelegate.send(pluginResult, callbackId: command.callbackId)
        }
    }

    File GSTPlugin.java

    package com.gst.gstplugin;
    
    import org.apache.cordova.CordovaPlugin;
    import org.apache.cordova.CallbackContext;
    
    import org.json.JSONArray;
    import org.json.JSONException;
    import org.json.JSONObject;
    
    import android.util.Log;
    
    public class GSTPlugin extends CordovaPlugin {
    
        @Override
        public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
            if (action.equals("helloNative")) {
                String message = args.getString(0);
                this.helloNative(message, callbackContext);
                return true;
            }
            return false;
        }
    
        private void helloNative(String message, CallbackContext callbackContext) {
            if (message != null && message.length() > 0) {
                callbackContext.success("GSTPlugin hello " + message + " from Android");
            } else {
                callbackContext.error("Expected one non-empty string argument.");
            }
        }
    }

    Tại thư mục của plugin, chạy command plugman createpackagejson . và điền các câu trả lời, phần nào không có thì enter để bỏ qua

    Switch sang thư mục chứa project Cordova SamplePlugin đã tạo, run command cordova plugin add --path-to-plugin.

    -> Ví dụ cordova plugin add /Users/nhathm/Desktop/Cordova-plugin_sample/GSTPlugin/GSTPlugin

    Bây giờ, plugin đã được add vào project Cordova. Tiếp theo sẽ chỉnh sửa source của index.html và index.js để test hoạt động của project.

    File index.html

    <div class="app">
          <h1>Apache Cordova</h1>
          <div id="deviceready" class="blink">
              <p class="event listening">Connecting to Device</p>
              <p class="event received">Device is Ready</p>
          </div>
    
          <button id="testPlugin">Test Plugin</button><br/>
    </div>

    File index.js

    • Add vào onDeviceReady()
    document.getElementById("testPlugin").addEventListener("click", gstPlugin_test);

    Thêm function:

    function gstPlugin_test() {
        cordova.plugins.GSTPlugin.helloNative("NhatHM",
            function (result) {
                alert(result);
            },
            function (error) {
                alert("Error " + error);
            }
        )
    }

    Sau khi chỉnh sửa hoàn chỉnh, build source cho platform iOS và Android

    • cordova platform add ios
    • cordova platform add android

    Build project native đã được generate ra và kiểm tra kết quả

    cordova plugin sample

    Đối với việc tạo UI cho project Cordova, chúng ta nên dùng các framework support như Ionic: https://ionicframework.com/

    Note: các dự án hybrid rất hạn chế về mặt performance, do đó nên cân nhắc khi bắt đầu dự án mới bằng hybrid