Để làm đa ngôn ngữ cho APNs rất đơn giản, đầu tiên chúng ta cần những thứ sau đây: 1. File Localizable.strings define các localization 2. Đăng ký Push Notification cho ứng dụng Ở đây mình mặc định hai công việc trên đã được hoàn thành, và tập trung vào việc xử lý đa ngôn ngữ cho APNs.
Thông thường, một Notification đơn giản sẽ có nội dung như sau:
// Notification payload
{
"aps":{
"alert":{
"title":"Đây là tiêu đề thông báo",
"body":"Còn đây chắc là nội dung thông báo"
}
}
}
Nội dung notification hiển thị trên device
Như ví dụ trên, giá trị tương ứng với key title và body sẽ được hiển thị trực tiếp vào tiêu đề và nội dung của notification như ảnh. Để áp dụng đa ngôn ngữ, ta sẽ thay thế title và body bằng hai key khác là title-loc-key và loc-key, tiếp đó ta truyền giá trị của hai trường trên bằng key của localization đã define trong file Localizable.strings:
Như vậy là ta xong với các Notification có nội dung cố định. Đối với các Notification có sử dụng thêm tham số ví dụ như “Bạn có 10 thông báo mới” hay “NhatHM đã ném tiền vào mặt bạn”, chúng ta sử dụng thêm hai key title-loc-args và loc-args, và setting như sau:
// Localizable.strings content
"notification_title_args" = "Bạn có thêm %@ thông báo mới"; "notification_content_agrs" = "Nội dung thông báo với giá trị %@";
Nội dung notification hiển thị trên device
Để đề phòng trường hợp ứng dụng chưa định nghĩa các key trong file Localizable.strings, ta chỉ cần cung cấp thêm hai key title và body và truyền vào giá trị mặc định cho Notification. Hai giá trị này sẽ được hiển thị khi ứng dụng client không tìm thấy strings được trả về trong Notification payload. Nên để đẹp trai thì Notification sẽ như thế này:
// Notification payload
{
"aps":{
"alert":{
"title":"Đây là tiêu đề thông báo",
"body":"Còn đây chắc là nội dung thông báo",
"title-loc-key":"notification_title_args",
"title-loc-args":["10"],
"loc-key":"notification_content_agrs",
"loc-args":["tinh thần"]
}
}
}
Như vậy là đủ để quẩy đa ngôn ngữ cho Notification rồi đấy 😀
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(_:) và 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.
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.
Ở đâ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…
<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)
}
}
1 đối tượng kiểu EAAcessory đại diện cho 1 thiết bị ngoại vi đang kết nối với app thông qua Lightning connector hoặc Bluetooth.
EAAcessory gồm các thuộc tính chứa các thông tin quan trọng về thiết bị ngoại vi: isConnected, name, manufacturer, serialNumber, protocols mà thiết bị ngoại vi dùng, firmware version, …
Ngoài ra, cũng có thể lấy macAddress của thiết bị ngoại vi:
let macAddress = accessory.value(forKey: PrinterConstant.MACAddress) as? String
Từ những thông tin đó, bạn có thể mở 1 session tới thiết bị ngoại vi để trao đổi dữ liệu.
Config Project:
Không phải cứ kết nối Bluetooth là app sẽ scan thấy hoặc lấy được thông tin của thiết bị ngoại vi đó.
Để app có thể giao tiếp với thiết bị ngoại vi, thì app cần khai báo các protocols mà thiết bị ngoại vi đó hỗ trợ vào Info.plist.
Đọc thông tin accessory:
Sau khi config project và kết nốt với thiết bị ngoại vi thông qua Bluetooth, có thể bắt đầu đọc thông tin của thiết bị ngọai vi:
There is no firm guideline on how many bytes to write at one time. Although it may be possible to write all the data to the stream in one event, this depends on external factors, such as the behavior of the kernel and device and socket characteristics. The best approach is to use some reasonable buffer size, such as 512 bytes, one kilobyte, or a page size (four kilobytes) – Apple
Nhận data:
Khi thiết bị ngoại vi gửi dữ liệu đến app của bạn, StreamDelegate sẽ trigger event hasSpaceAvailable, khi đó app sẽ đọc data thông qua inputStream:
Có thể bạn đã từng gặp lỗi constraint bị breaking với kiểu log như thế này:
Thường thì Xcode sẽ tự loại bỏ 1 constraint để view không bị conflict nữa. Điều này sẽ dẫn đến UI hiển thị trên màn hình đúng hoặc không, tùy thuộc vào việc Xcode loại bỏ constraint nào.
Tuy nhiên, kể cả trong trường hợp UI hiển thị đúng, thì chúng ta vẫn nên đi fix cái lỗi này.
Đối với những màn hình phức tạp, nhiều subview, việc biết view nào đang bị lỗi constraint là khó có thể phán đoán được.
Vì vậy ở bài viết này, hãy cùng nhau đi đọc đống log kia để biết view nào đang bị conflict constraint 🙂
Tìm xem view nào bị lỗi bằng cách đọc địa chỉ
Để í đến dòng log sau:
Đoạn log này hướng dẫn rằng code sẽ recover bằng cách loại bỏ constraint 0x60000336d7c0 của view có địa chỉ 0x7fccafe06a10.
Giờ thì bắt đầu đi tìm view có địa chỉ 0x7fccafe06a10:
Chọn Show Debug Navigator (Command + 7).
Chọn View Memory Graph Hierachy
Nhập địa chỉ của View cần tìm vào phần Filter.
Chọn vào View có địa chỉ cần tìm, Xcode sẽ hiển thị kết quả như sau:
Click chuột phải vào View, chọn Quick Look để xem đó là View nào:
Với Quick Look, Xcode sẽ hiển thị View đang bị conflict constraint:
Đổi màu background của View bằng lldb:
Nếu trong trường hợp có quá nhiều View giống nhau, và bạn vẫn chưa thể xác định đó là view nào?
-> Dùng lldb để đổi màu background view đó để có thể xác định dễ hơn.
Đầu tiên, pause chương trình lại và thực hiện lệnh sau ở cửa sổ lldb:
Bluetooth, một công nghệ không dây năng lượng thấp, tầm ngắn ra đời từ những ngày đầu của điện thoại di động, được sử dụng rộng rãi và có mặt trong hàng tỉ thiết bị và vật dụng hàng ngày. Bluetooth tạo ra một thế giới không cần dùng dây và là hình thức đầu tiên kết nối các thiết bị khác nhau, sản sinh ra những tính năng và hành vi tiêu dùng mới.
Bluetooth low energy (BLE)
Bluetooth Low Energy là chuẩn kết nối không dây hướng tới các ứng dụng tiết kiệm năng lượng (giải thích: việc truyền không dây thường tốn nhiều năng lượng, do đó, người ta cố gắng nghiên cứu những công nghệ, kỹ thuật sao cho việc truyền nhận dữ liệu không dây ít tiêu tốn năng lượng nhất có thể và thế là BLE ra đời).
Bluetooth version
Trong quá trình phát triển của Bluetooth, Bluetooth SIG đã công bố nhiều version và vẫn đang tiếp tục phát triển các version mới
Bluetooth 1.x: Phiên bản Bluetooth đầu tiên, gần như không còn được sử dụng. Có tốc độ lý thuyết là 1Mbps – tốc độ này được giọi là Basic rate (BR). Capability : BR
Bluetooth 2.x: Phiên bản nâng cấp của version 1.x cho tốc độ truyền tải lý thuyết cao hơn – 3 Mbps – tốc độ này được gọi là Enhanced data rate (EDR). Lưu ý: EDR là optional, thiết bị hỗ trợ Bluetooth 2.x vẫn có thể kết nối với tốc độ BR. Capability: BR + EDR
Bluetooth 3.x: Hỗ trợ việc thay đổi lower layer. Các ứng dụng có lower layer thiết kế theo chuẩn của Bluetooth có thể chuyển qua sử dụng của chuẩn của giao thức 802.1 với tốc độ truyền tải dữ liệu cao hơn. Bluetooth 3.x có thể đạt tốc độ truyền tải lý thuyết lên đến 23Mbps – tốc độ này gọi là High speed (HS) và là một option của thiết bị hỗ trợ Bluetooth 3.x. Capability: BR + EDR + HS
Bluetooth 4.x: Bổ sung thêm chuẩn giao tiếp Bluetooth Low Energy. Với ưu điểm là thu thập dữ liệu từ các thiết bị có tần số gửi data thấp, mỗi lần gửi một lượng data nhỏ (ví dụ: heart rate sensor, temperature sensor, humid sensor, …). Capability: BR + EDR + HS + LE
Bluetooth 5.x: Go Faster. Go Further. Highlight Improvement: Range, Speed, bandwidth. Khoảng cách truyền dữ liệu lên đến 120m so với 30m của version 4.x. Tốc độ truyền tối đa của Bluetooth 5 low energy theo lý thuyết là 2Mbps, gấp đôi Bluetooth Low Energy 4.2 mà không làm tăng power consumption.
Capability trong trường hợp này là khả năng hỗ trợ của version Bluetooth. Ví dụ như thiết bị mình mua về, nhà sản xuất kêu nó hỗ trợ Bluetooth 2.0 thì bạn không thể nào mong nó có khả năng kết nối với thiết bị hỗ trợ Bluetooth 3.0 với tốc độ HS. Tuy nhiên hai thiết bị này vẫn có thể kết nối với nhau với tốc độ EDRNote: Refer wiki dưới đây biết version bluetooth mà iOS device đang sử dụng:
https://en.wikipedia.org/wiki/List_of_iOS_devices
Các kiểu thiết bị Bluetooth
• Thiết bị chỉ hỗ trợ Bluetooth Smart (Bluetooth Low Energy) không thể kết nối trực tiếp tới thiết bị chỉ hỗ trợ Bluetooth Classic (Bluetooth ít tiết kiệm năng lương)
• Thiết bị hỗ trợ Bluetooth Smart Ready có thể kết nối trực tiếp với cả thiết bị hỗ trợ Bluetooth Low Energy và Bluetooth Classic. Ví dụ như iphone sẽ là 1 thiết bị Bluetooth smart ready và nó có thể kết nối với tai nghe không dây thông qua chuẩn Bluetooth Classic và cũng có thể kết nối với smart watch thông qua chuẩn Bluetooth Low Energy.
Beacons là các máy phát không dây nhỏ, sử dụng công nghệ Bluetooth Low Energy để gửi tín hiệu đến các thiết bị thông minh khác gần đó. Đây là một trong những phát triển mới nhất trong công nghệ vị trí và tiếp thị gần (Proximity Marketing). Nói một cách đơn giản, chúng kết nối và truyền thông tin đến các thiết bị thông minh giúp cho việc tìm kiếm và tương tác dựa trên vị trí trở nên dễ dàng và chính xác hơn.
Ưu thế lớn nhất của BLE là tiết kiệm năng lượng, cho phép beacons truyền thông tin liên tục lên đến 2-3 năm chỉ với một viên pin nhỏ. Khoảng cách truyền BLE cũng lên đến 100m như Classic Bluetooth.
BLE có hai chế độ trao đổi thông tin:
• Advertising: chỉ truyền một chiều
• Connecting: trao đổi hai chiều
Beacons chỉ sử dụng chế độ truyền advertising (chỉ gửi thông tin một chiều). Beacons theo một chu kỳ sẽ phát thông tin quảng bá để các thiết bị khác như smartphone nhận. Beacons có thể phát với chu kỳ từ 20ms đến 10s, chu kỳ càng dài thì thời lượng pin càng lâu.
Thiết bị Beacon rất đơn giản. Mỗi thiết bị chứa CPU, radio và pin, và nó hoạt động bằng cách liên tục phát ra một mã định danh (ID) . Mã nhận dạng này được chọn bởi thiết bị của bạn, thường là điện thoại di động và đánh dấu một vị trí quan trọng trong môi trường của bạn. Mã định danh là số ID duy nhất mà điện thoại thông minh của bạn nhận ra là duy nhất cho Beacon. Sau khi kết nối, Beacon sẽ thực hiện bất kỳ chức năng nào nó đã được lập trình bao gồm : quảng cáo, điều hướng, theo dõi…
Chúng ta có thể xem xét ví dụ điển hình về quảng cáo trong Mall:
Bước 1: Người dùng tìm kiếm ở Google về “Black Shoes” Bước 2: Quảng cáo tìm kiếm Google của bạn xuất hiện. Bước 3: Người dùng nhấp vào quảng cáo tìm kiếm, duyệt sản phẩm, sau đó đóng điện thoại của họ. Bước 4: Người dùng này quyết định họ muốn thử giày trước khi mua, vì vậy họ bước vào cửa hàng của bạn. Bước 5: Khi họ vào cửa hàng, điện thoại của họ nhận một số nhận dạng ID từ Beacon của cửa hàng của bạn. Bước 6: Beacon nhận ra rằng điện thoại này giống với điện thoại đã nhấp vào quảng cáo tìm kiếm của bạn và liên kết dữ liệu này với tài khoản Google Ads của bạn dưới dạng chuyến thăm cửa hàng trên mạng.
Bằng cách ghi nhật ký lượt truy cập cửa hàng thực tế từ quảng cáo tìm kiếm của bạn, công nghệ này sẽ giúp bạn hiểu được tác động và hiệu quả của quảng cáo tìm kiếm của bạn. Nếu bạn thấy họ đang thu hút rất nhiều lượt truy cập vào cửa hàng của bạn, bạn có thể muốn đầu tư nhiều hơn vào tìm kiếm.
Beacons gửi dữ liệu gì?
Một gói tin để các thiết bị đọc được phải tuân theo các chuẩn đã được định trước, trước tiên là gói dữ liệu advertising. Một gói tin advertising có độ dài lên đến 47 bytes.
IBeacon Là giao thức BLE được Apple đưa ra 12/2013, đây là một bộ giao thức chính thức đầu tiên về BLE, đa số mọi beacons đều hỗ trợ. Giao thức này được hỗ trợ chỉ trên iOS, nhưng hiện nay có thể tìm rất nhiều hàm API hỗ trợ tìm kiếm các iBeacon trên Android. Cần có một ứng dụng để tìm kiếm beacons và thực hiện các thao tác với chúng.
iBeacon hỗ trợ hai kiểu tương tác, giám sát (monitoring) và vùng phủ
(ranging). Với chế độ giám sát ứng dụng sẽ cảnh báo ngay cả khi ứng
dụng đã tắt. Khác chế độ giám sát, chế độ cự ly chỉ hoạt động khi ứng
dụng đang chạy.
Với iBeacon thì các beacon sẽ phát ra dữ liệu gì?, chúng phát ba
thông tin UUID, Major, và Minor. Sẽ không bao giờ có hai beacon cùng
UUID, Major, và Minor.
Eddystone Là giao thức BLE do Google công bố 7/2015, được hỗ trợ chính thức trên cả hai nền tảng iOS và Android. Là một giao thức mở và hỗ trợ nhiều gói tin khác nhau. Chỉ hỗ trợ một kiểu tương tác cơ bản, gần giống với vùng phủ của iBeacon.
Các gói tin của Eddystone gồm có:
Eddystone-UID: gần giống gói tin của iBeacon, gồm các thông tin
Namespace (chức năng giống UUID của iBeacon) và Instance (chức năng
giống Major và Minor của iBeacon)
Eddystone-URL: gửi thông tin một đường dẫn site . Với gói tin này
trên điện thoại sẽ mở site và ko cần cái ứng dụng.
Eddystone-TLM: là gói tin gửi các thông tin của beacons như điện áp
pin, nhiệt độ, số gói tin đã gửi, và thời gian bật beacons. Gói tin
này sẽ gửi với chu kỳ dài hơn hai gói trên.
Ứng dụng của Beacon
Công nghệ Beacon còn rất nhiều mở rộng trong tương lai, nên việc sử dụng nó vẫn đang được phát hiện. Hiện tại, trọng tâm chính vẫn là trong lĩnh vực điều hướng và và quảng cáo. Đây là nơi bạn thấy các công ty đầu tư mạnh vào thời điểm này.
Core Bluetooth là một framework cung cấp các lớp cần thiết cho ứng dụng iOS và MacOS có thể giao tiếp với các thiết bị có hỗ trợ công nghệ không dây bluetooth được trang bị các chuẩn low energy (LE) và Basic Rate / Enhanced Data Rate (BR/EDR) .
Central, Peripheral
Có hai đối tượng chính trong quá trình giao tiếp Bluetooth: central và peripheral.
Một peripheral thường là một đối tượng có chứa dữ liệu, và các thiết
bị khác sẽ cần sử dụng dữ liệu đó.
Một central thường là một đối tượng mà sử dụng thông tin được cung
cấp bởi peripheral để thực hiện một nhiệm vụ nào đó.
Ví dụ: Một chiếc máy đo nhịp tim (peripheral) chứa những thông tin hữu ích mà các ứng dụng trên điện thoại và máy tính (central) cần để hiển thị nhịp tim của người sử dụng theo một cách dễ hình dung trên màn hình.
Central phát hiện và kết nối tới các Peripheral đang advertising
• Peripheral phát tán một vài dữ liệu của chúng ở dạng advertising packet. Một advertising packet là như một gói dữ liệu nhỏ chứa những thông tin mà peripheral phải cung cấp để nhận dạng, ví dụ như tên của peripheral và chức năng chính của nó. Ví dụ, một máy điều hòa nhiệt độ phải advertise rằng chúng cung cấp nhiệt độ hiện tại của căn phòng. Trong công nghệ Bluetooth, advertising là cách chính để peripheral thể hiện sự có mặt của nó.
• Một central, mặt khác, sẽ quét và lắng nghe bất cứ thiết bị peripheral nào đang advertising thông tin mà nó muốn, sau đó có thể yêu cầu quyền truy cập tới thiết bị đó.
Dữ liệu của một Peripheral được cấu trúc như thế nào ?
Mục đích của việc kết nối tới peripheral là dò xét và tương tác với với dữ liệu mà nó cung cấp, và trước khi làm vậy, sẽ tốt hơn nếu bạn hiểu dữ liệu của peripheral được cấu trúc ra sao.
• Peripheral có thể bao gồm một hoặc nhiều service hoặc cung cấp thông tin hữu ích về cường độ tín hiệu kết nối. Một service là một tập dữ liệu và hành vi đi kèm để thực hiện một chức năng hay một tính năng của thiết bị. Ví dụ, một service của máy đo nhịp tim là đưa ra dữ liệu nhịp tim từ bộ cảm biến.
• Service được tạo nên từ các characteristic hoặc từ việc sử dụng các service khác. Một characteristic cung cấp những thông tin chi tiết hơn về một service của peripheral. Ví dụ, service nhịp tim vừa mô tả ở trên có thể bao gồm một characteristic mô tả vị trí hướng cơ thể của bộ cảm biến và một characteristic khác trao đổi dữ liệu tính toán nhịp tim. Hình dưới đây cung cấp một cấu trúc có thể có về service và characteristic của một máy đo nhịp tim.
Central dò xét và tương tác với dữ liệu của Peripheral
• Sau khi central thiết lập kết nối thành công tới peripheral, nó có thể thấy được toàn bộ service và characteristic mà peripheral cung cấp (dữ liệu lúc advertising có thể chỉ là một phần của những service hiện có).
• Một central có thể tương tác với service của peripheral bằng việc đọc và ghi các characteristic của service đó. Ví dụ, ứng dụng của bạn có thể yêu cầu thông tin về nhiệt độ phòng từ một máy điều hòa nhiệt độ hoặc cung cấp cho máy điều hòa nhiệt độ một giá trị để thiết lập nhiệt độ phòng.
Khi thiết bị của bạn đóng vai trò là Central
Phần lớn các hoạt động khi xử lý với Bluetooth của bạn nằm ở phía central
Ở phía central, một thiết bị local central được thể hiện bởi một đối tượng CBCentralManager. Đối tượng này được sử dụng để quản lý việc phát hiện và kết nối tới thiết bị remote peripheral (thể hiện bởi đối tượng CBPeripheral), bao gồm việc quét, phát hiện, và kết nối tới peripheral đang advertising.
Thiết bị central sẽ có nhiệm vụ tìm và kết nối tới các peripheral, sau đó sẽ đọc và tương tác với dữ liệu của peripheral đó.
Khởi tạo central manager object
Tìm kiếm và kết nối tới một peripheral nào đó đang phát sóng.
Đọc dữ liệu trên peripheral sau khi kết nối thành công.
Gửi các request đọc và ghi một characteristic nào đó của các dịch vụ trong peripheral.
Đăng ký nhận thông báo khi một characteristic cập nhật.
Máy Mac sử dụng MacOS 10.9 trở lên, thiết bị iOS sử dụng iOS 6.0 trở lên có chứa các tính năng để tương tác như một peripheral, cung cấp dữ liệu cho các thiết bị khác, bao gồm các máy Mac, iPhone và iPad khác. Khi thiết lập thiết bị của bạn để thực thi trong vai trò peripheral, bạn đang thực hiện những hoạt động ở phía peripheral trong việc giao tiếp giữa các thiết bị Bluetooth.
Ở phía peripheral, một thiết bị local peripheral được thể hiện bởi đối tượng CBPeripheralManager. Đối tượng này được sử dụng để quản lý các service công khai bên trong cơ sở dữ liệu về service và characteristic của thiết bị local peripheral, và có nhiệm vụ advertising các service này tới các thiết bị remote central (thể hiện bởi đối tượng CBCentral). Đối tượng peripheral manager còn được sử dụng để trả lời các yêu cầu đọc và ghi từ remote central.
Thiết bị peripheral có nhiệm vụ cung cấp, phát tán các service của nó, và trả lời các request từ central.
Khởi tạo một peripheral manager object.
Thiết lập các service và characteristic trên thiết bị.
Publish các service và characteristic tới database của thiết bị.
Phát tán các service.
Trả lời các yêu cầu đọc và ghi từ các central được kết nối.
Gửi các giá trị của characteristic cho các central đã đăng ký (subscribe).
Qua bài viết trên đây hi vọng mọi ngừời sẽ nắm được những thông tin cơ bản của bluetooth cũng như các ứng dụng của nó tới đời sống, ngoài ra các bạn hãy code demo về corebluetooth như guide của apple mình đã refer link ở trên nhé.
Traits là observables nhưng với 1 phạm vi hành vi hẹp hơn so với các Observables thông thường.
Lợi ích của Traits:
Vì phạm vi hẹp hơn, nên khi sử dụng Traits thì sẽ làm cho người đọc clear hơn về í nghĩa của task.
Traits luôn được observe và subcribe trên Main.
Các loại Traits:
Trait trong RxSwift gồm 3 loại, đó là: Single, Completable
Single:
Single sẽ chỉ emit ra duy nhất 1 event, và event đó phải thuộc 1 trong 2 kiểu .success(value) hoặc .error.
.success(value) về bản chất là sự kết hợp của .next và .complete event của Observable.
Sau khi emit event, single sẽ tự terminate.
Demo
enum DevideError: Error {
case divideByZero
}
func divide(number1: Int, number2: Int) -> Single<Int> {
return Single<Int>.create(subscribe: { observer in
if number2 == 0 {
observer(.error(DevideError.divideByZero))
} else {
let result = number1 / number2
observer(.success(result))
}
return Disposables.create()
})
}
divide(number1: 10, number2: 2)
.subscribe { (event) in
switch event {
case .success(let result):
print(result)
case .error( let error):
print(error)
}
}
Vì number2 khác 0, nên khi thực hiện hàm divide 10 cho 2 thì sẽ trả về 1 Single và nó sẽ emit ra .success(5).
Completable:
Completable sẽ chỉ emit ra duy nhất 1 event, và event đó phải thuộc loại .completed hoặc .error.
Sau khi emit ra event thì sẽ tự động terminate.
func divide(number1: Int, number2: Int) -> Completable {
return Completable.create(subscribe: { observer in
if number2 == 0 {
observer(.error(DevideError.divideByZero))
} else {
let result = number1 / number2
observer(.completed)
}
return Disposables.create()
})
}
Maybe:
Maybe là sự kết hợp của Single và Completable. Nó có thể emit .success(value), .completed hoặc .error event.
Vẫn như 2 loại traits trên, Maybe sẽ chỉ emit ra 1 event duy nhất.
Sau khi emit ra event thì sẽ tự động terminate.
Maybe cho phép bạn emit ra 3 loại event khác nhau.
func divide(number1: Int, number2: Int) -> Maybe<Int> {
return Maybe<Int>.create(subscribe: { observer in
if number2 == 0 {
observer(.error(DevideError.divideByZero))
} else {
let result = number1 / number2
observer(.success(result))
}
return Disposables.create()
})
}
Convert Traits thành Observable và ngược lại
Có thể convert 1 traits thành Observable đơn giản như sau:
single.asObservable()
Việc convert từ Observable thành Traits cũng đơn giản như vậy:
observable.asSingle()
Tuy nhiên Traits thì chỉ emit ra 1 event, còn Observable của bạn thì có thể sẽ emit ra nhiều event. Vì vậy nếu convert 1 Observable emit ra nhiều event sang Traits thì traits đó sẽ emit ra error. Ví dụ như sau:
let observable = Observable<String>.of("abc", "def")
observable.asSingle().subscribe({ event in
switch event {
case .success(let value):
print(value)
case .error(let error):
print(error)
}
})
Lỗi được emit ra.
Khi nào thì sử dụng Single, Completable & Maybe:
Single:
Single có tác dụng cho những tiến trình chỉ chạy 1 lần và sẽ thành công với 1 value, hoặc sẽ bị error.
Ví dụ như download File, load data, …
Completable:
Dùng cho những task mà bạn chỉ quan tâm xem nó thành công hay thất bại.
Ví dụ: Write file…
Maybe:
Dùng cho những task có thể success hoặc fail, và tùy lúc sẽ emit ra 1 value khi success.
Observable là phần quan trọng nhất của RxSwift. có thể coi nó như 1 sequence, có tác dụng phát ra (emit) các event, và các object khác có thể lắng nghe những event đó.
Các event có thể đính kèm các giá trị với các kiểu dữ liệu quen thuộc như Int, String, … hay các kiểu dữ liệu mà bạn tự tạo ra.
Hình trên là ví dụ cho các event 1, 2 ,3 lần lượt được emit ra theo thời gian.
Các loại Event:
Các event được emit ra thuộc 1 trong 3 loại:
next: Khi 1 Observable emit 1 element, thì nó được coi là 1 next event. Next event có thể đính kèm giá trị, và Observable có thể emit nhiều next event trong 1 life cycle của nó.
completed: Khi Observable hoàn thành việc emit event của mình, thì nó sẽ emit ra event completed và tự động terminate.
error: Khi Observable gặp lỗi trong quá trình emit event, thì nó sẽ emit ra event error và tự động terminate.
Note:
Tóm gọn lại, Observable có thể phát ra nhiều next event trong 1 life cycle; ngược lại thì Observable chỉ có thể phát ra 1 event complete hoặc error, sau đó sẽ tự hủy và không phát ra bất cứ event nào nữa.
Khởi tạo Observable:
of:
let observable = Observable.of(1,2,3)
let observable2 = Observable.of([4,5,6])
Khởi tạo bằng func of cùng với các giá trị khởi tạo.
Cho mỗi next event, Observable sẽ lần lượt emit ra các element được cung cấp.
Các giá trị cung cấp phải cùng kiểu dữ liệu: Cùng Int, String, …
Sau khi phát ra hết các element, observable sẽ emit ra completed event và terminate.
from:
let observable = Observable.from([1,2,3])
Khi khởi tạo 1 Observable bằng func from, thì giá trị được khởi tạo mặc định phải thuộc kiểu Array.
Cho mỗi event next, Observable sẽ lần lượt emit ra các giá trị trong mảng.
Sau khi emit hết các phần tử trong mảng, observable sẽ emit ra event completed và terminate.
Ngoài of và from, thì còn các cách khởi tạo khác như just, never, empty.
create:
Với các cách ở trên, thì observable sẽ tự động emit các event được cung cấp sẵn 1 cách lần lượt và tự emit 1 complete sau khi hoàn thành.
Bằng cách sử dụng hàm create để tạo 1 Observable, bạn có thể custom Observable theo cách riêng của ban để có thể emit ra completed hoặc error bất cứ khi nào bạn muốn
let observable = Observable<String>.create({ observer in
observer.onNext("Global")
observer.onNext("Smart")
observer.onCompleted()
observer.onNext("Technology")
return Disposables.create()
})
Kết quả được in ra: (Sau khi emit ra event complete thì observable sẽ bị terminate, do đó nó sẽ không emit ra "Technology" nữa.
Subcribe:
Chỉ copy đoạn code ở trên và run thì bạn sẽ không thấy có gì được print ra màn hình cả. Tại sao vậy?
1 Observable chỉ có thể emit ra event khi nó được subcribe.
Đoạn code ở trên, ta chưa subcribe Observable đó.Vậy làm thế nào để subcribe 1 Observable?
subcribe event
Để subcribe tới 1 Observable thì vô cùng đơn giản với hàm subcribe:
let observable = Observable.of(1,2,3)
observable.subscribe { (value) in
print(value)
}
Để í phần Summary của hàm subcribe này: Đó là hàm có tác dụng subcribes 1 event. Vì vậy, các giá trị được print ra sẽ có cả kiểu của event được emit ra (next/complete/error).
Kết quả print ra trên màn hình:
Vậy nếu bạn muốn chỉ print ra giá trị của value mà không cần print ra kiểu event?
subcribe element
Cách 1: Mỗi event sẽ có thuộc tính element để lấy ra giá trị đính kèm với event đó.
observable.subscribe { (value) in
if let element = value.element {
print(element)
}
}
Cách 2: Sử dụng hàm subcribe:
observable.subscribe(onNext: { (element) in
print(element)
})
Theo như summary, hàm subcribe này sẽ subcribe đến element, vì vậy nó sẽ chỉ in ra giá trị của element.
Cả 2 cách đều cho 1 kết quả như sau:
Ngoài ra , với hàm trên, bạn có thể thực hiện các hành động khi observable emit ra event completed, error, … như sau:
let observable = Observable<String>.create({ observer in
observer.onNext("Global")
observer.onNext("Smart")
observer.onCompleted()
observer.onNext("Technology")
return Disposables.create()
}).subscribe(onNext: { (element) in
print(element)
}, onCompleted: {
print("Completed")
})
Kết luận:
Observable là 1 thứ đơn giản nhưng vô cùng quan trọng trong RxSwift.
Tham khảo: RxSwift – Reactive Programming with Swift v1.1
Link tham khảo: https://help.apple.com/developer-account/ Code Signing là một thứ bắt buộc để ta có thể cài đặt ứng dụng vào devices thật, hoặc để upload lên AppStore Connect. Có hai cách để cài đặt code signing, “Automatically manage signing” hoặc “Manually manage signing”. Bài viết này sẽ hướng dẫn cài đặt manual code signing.
Để cài đặt app, bạn cần có :
Signing certificate (Personal Information Exchange, .p12)
Provisioning profile (.mobileprovision)
Signing certificate là chứng chỉ giúp xác định danh tính để cài app
Provision profile (development hoặc distribution) chứa những thông tin về appID, các devices mà app có thể cài đặt, thông tin certificate để signing app. Lưu ý rằng nếu ứng dụng có chứa các extensions, bạn cần thêm các provision profile tương ứng
Mỗi dự án sẽ có những certificate và provision profile riêng. Như ảnh dưới, ta có 2 certificate, cho môi trường dev và distribute, ta cũng có các provision dev và distribute tương ứng, kèm theo đó là những provision profile của các extension của app.
Sau đây là hướng dẫn tạo manually signing app:
B1. Tạo certificate cho app
1. Ở Certificates, Identifiers & Profiles, chọn Certificates
2. Chọn nút (+)
3. Chọn loại certificates mà mình muốn và chọn nút “Tiếp tục”
4. Tạo certificate signing request
4.1. Mở app Keychain Access ở máy
4.2. Chọn Keychain Access > Certificate Assistant > Request a Certificate from a Certificate Authority.
4.3. Điền thông tin như email, name, bổ trống CA Email Address
4.4. Chọn “Save to disk” và chọn tiếp tục
5. Chọn file đuôi .certSigningRequest đã tạo ở b4
6. Chọn “Tiếp tục” và “Tải về” máy. (File certificate sẽ có đuôi .cer)
B2. Đăng ký AppID
AppID sẽ định danh app của bạn trong provisioning profile. Có 2 loại AppID: explicit AppID (sử dụng riêng từng app) và wildcard AppID (sử dụng chung 1 số app). Wildcard AppID sẽ chỉ enable được một số Capabilities, nếu muốn sử dụng những Capabilities khác, bạn phải tạo explicit AppID
Các bước tạo AppID:
Trong Certificates, Identifiers & Profiles, chọn “Identifiers”, rồi chọn (+)
Chọn AppIDs
Điền name, descriptions, chọn các loại Capabilities mà app sẽ dùng
Nếu chọn Explicit App ID, bạn phải điền giống bundleID của app trong Xcode
Nếu chọn Wildcard App ID, bạn phải điền bundle ID với hậu tố (VD: com.domainname.*)
B3. Đăng ký devices
Đăng ký một device:
Trong Certificates, Identifiers & Profiles, chọn Devices, rồi chọn (+)
Chọn platform, điền device name, device ID (UDID)
Chọn tiếp tục, chọn “Register” để hoàn tất đăng ký
Đăng ký nhiều device:
Bạn có thể dùng app “Configurator 2” trên MacAppStore hoặc tạo file .txt chứa thông tin (mỗi dòng chứa deviceID, device name, platform name cách nhau bởi tab-delimited)
import Foundation
protocol ICategoryService {
func getCategoryById(id: Int) -> Category?
}
class CategoryService: ICategoryService {
func getCategoryById(id: Int) -> Category? {
let categories = [Category(id: 3, name: "Movies"),
Category(id: 4, name: "Books"),
Category(id: 5, name: "Computer")]
let category = categories.filter({
$0.id == id
}).first
return category
}
}
ViewModel Layer
import Foundation
import RxSwift
class HomeViewModel {
var category: Category
var service: ICategoryService
var displayName: String {
return transformName(category.name)
}
// 1
var valueSubject: PublishSubject<String>
init(category: Category, service: ICategoryService) {
self.category = category
self.service = service
valueSubject = PublishSubject()
}
func transformName(_ name: String) -> String {
let newName = name.enumerated().map { (index, character) -> String in
if index % 2 == 0 {
return character.uppercased()
} else {
return character.lowercased()
}
}
return newName.joined()
}
// 2
func getCategory(id: Int) {
if let newCategory = service.getCategoryById(id: id) {
self.category = newCategory
valueSubject.onNext(transformName(category.name))
}
}
}
Khởi tạo 1 subject kiểu Publish Subject để phát ra các event khi giá trị của category’s name được thay đổi.
ViewModel sẽ thông qua Model Layer để truy xuất đến database. Sau khi lấy được data cần thiết, thì valueSubject sẽ phát ra 1 event có giá trị là name đã được transform của category mới.
Tap button để yêu cầu viewModel lấy ra 1 category có id = 3.
Tác dụng của MVVM:
Tương tự như MVP, lợi ích đầu tiên của MVVM là tách biệt phần logic ra khỏi View. Từ đó dẫn đến dễ viết unit test, dễ maintain, …
Dễ dàng reuse các ViewModel.
Bằng việc tương tác với View thông qua cơ chế data binding, vì vậy không cần tạo thêm nhiều protocol, class…
MVVM vs MVP:
Trong MVVM, vì ViewModel tương tác với View bằng data binding, vì vậy ViewModel không có 1 reference nào đến View. Từ đó ViewModel sẽ dễ dàng được reuse, dễ dàng viết test hơn so với MVP.
Presenter và View là liên kết chặt với quan hệ 1:1 trong MVP Quan hệ giữa ViewModel và View trong MVVM
Trong MVVM, 1 View có thể có nhiều ViewModel.
MVVM sẽ không cần phải tạo nhiều protocol như MVP.
Việc sử dụng cơ chế data binding cũng dẫn đến 1 hệ quả là MVVM sẽ khó để debug hơn nhiều so với việc sử dụng MVP.
Nếu sử dụng RxSwift kết hợp với MVVM, thì sẽ phải đòi hỏi team của bạn đều phải biết RxSwift ở mức ổn.
Kết luận:
Mục tiêu chính của MVVM là tách biệt logic khỏi View. Tuy nhiên, những phần logic như truy vấn database, networking, … thì chưa được xử lí. Những logic như vậy có thể được đặt ở presenter, hoặc tạo 1 class riêng ở Model tùy theo bản thân bạn. Tuy nhiên, nên tách biệt các phần logic Database, networking, … ra các class riêng để tuân thủ nguyên tắc Single Responsibility Principle của SOLID.
Theo nguyên tắc MVVM thì ViewModel không import UIKit để tách biệt logic và View.