Universal Link & URL Scheme

Universal Link & URL Scheme

如果你希望在 App A 按一個按鈕,立即切換至 App B
或是用手機瀏覽網頁時,按一個連結,自動打開 App

這個時候,需要使用的技術有二個,一個叫 URL Scheme, 一個叫 Universal Link,見下圖

這二種技術略有不同,分析如下:

程式範例

iOS URL Scheme (Objective C)
[application openURL:[NSURL URLWithString:@"iotecapp1://page/12"] options:@{}
completionHandler:^(BOOL success) {}];
Universal Link (HTML)
<a href="https://www.iotec.tw/page/12">Open IOTEC App </a>

設定方式

iOS URL Scheme

Xcode > TARGETS > your_app > Info > URL Types 下增加 URL Scheme 為 iotecapp1

Universal Link
  1. Xcode > TARGETS >your_app > Capabilities > Associated Domains 增加 applinks:www.iotec.tw
  2. www.iotec.tw 要支援 HTTPS
  3. 編寫 apple-app-site-association (詳見官方文件)
    註一: 文件類型需為 json且無副檔名
    註二:如果要支援 iOS 8, 還需要 sign 這個檔案,不過現在 iOS 8 (含)以下市佔率不足3%,還是算了吧~
  4. 將 apple-app-site-association 放至 https://www.iotec.tw/ 下 或是
    https://www.iotec.tw/.well-known/下
  5. 到 Apple Developer webpage 去將 App 的 Associated Domain 功能打開

可否接收完整 URL(也就是說,可否傳遞參數?)

iOS URL Scheme &Universal Link

二者皆可。可以取得完整的 URL,把後面的參數部份取出即可。

例: iotecapp1://page/12 後面的 page 與 12 等等即可傳遞參數

觸發方法 (iOS Objective C, AppDelegate.m)

URL Scheme
- (BOOL)application:(UIApplication *) application  openURL:(NSURL *)url
      options: (NSDictionary <UIApplicationOpenURLOptionsKey, id>*)options;
Universal Link
-(BOOL) application:(UIApplication *)application
          continueUserActivity:(NSUserActivity *)userActivity
          restorationHandler:(void (^)(NSArray * _Nullable))restorationHandler;

從網頁觸發時,如果沒有裝 App, 可否導向指定網頁?

URL Scheme

Partially YES
可以用 Timeout 方式處理,但不是 100% 成功

Universal Link

可以無縫接軌

可否從 APP 觸發?

URL Scheme

沒有問題,還可以判斷另一個 APP 是否有安裝


    [application openURL:URL options:@{} completionHandler:^(BOOL success) {
        if (success) {
            NSLog(@"Opened URL Scheme %@",scheme);
        }else{
            NSLog(@"Opened URL Scheme %@ FAILED",scheme);
        }
    }];
Universal Link

有些情況下可能有問題

用 UIWebview 或是用 SFSafariViewController 開啟 Universal Link
在官方文件上似乎是不建議的作法,
有文章提到 UIWebView 裡必需要在切換 domain 時才會觸發 Universal
Link。實際上會發生什麼事還需要有用過的人交流一下。

安全性?

URL Scheme

因為任何 APP 都可以註冊自己處理 iotecapp1的
prefix 的 URL Scheme,這會有潛在性的安全問題。
Ex: 如果您的官網透過 URL Scheme 來開啟您的 APP,使用的 URL Scheme prefix 為
“yellow://…”, 而很不巧的剛好有個限制級 APP 也使用 yellow 作為 URL Scheme prefix,
那麼就會有機率開錯了 App…
雖然手機上為何會裝該 APP 使用者本身也要負一點責任…. 不過這邊的行為與設計原意就不一致了

Universal Link

相對安全很多

參考官方文件
Support Universal Links

Communicating with Other Apps Using Custom URLs

彈跳式視窗的用法

在 iOS 裡彈跳式視窗主要分為由下方昇起的 UIActionSheet 以及Dialog 類型的 UIAlertView

iOS8起 UIAlertView 已 deprecated, 改統一使用 UIAlertController

用法範例如下

 

Simple alert example:
UIAlertController * alert=   [UIAlertController alertControllerWithTitle:@"Password required" message:@"Please enter password" preferredStyle:UIAlertControllerStyleAlert];
     
[self presentViewController:alert animated:YES completion:nil];
Alert with ok button example
UIAlertController * alert=   [UIAlertController
                                alertControllerWithTitle:@"Name required"
                                message:@"Please input name"
                                preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction* okAction = [UIAlertAction
                       actionWithTitle:@"OK"
                       style:UIAlertActionStyleDefault
                       handler:^(UIAlertAction * action)
                       {
                           [alert dismissViewControllerAnimated:NO completion:nil];
                           // do something
                       }];
[alert addAction:okAction];

[self presentViewController:alert animated:YES completion:nil];
ActionSheet example
UIAlertController * view=   [UIAlertController
                             alertControllerWithTitle:@"Select Sex"
                             message:@""
                             preferredStyle:UIAlertControllerStyleActionSheet];

UIAlertAction* male = [UIAlertAction
                       actionWithTitle:@"Male"
                       style:UIAlertActionStyleDefault
                       handler:^(UIAlertAction * action)
                       {
                           [view dismissViewControllerAnimated:NO completion:nil];
                           // do something
                       }];
UIAlertAction* female = [UIAlertAction
                         actionWithTitle:@"Roaster"
                         style:UIAlertActionStyleDefault
                         handler:^(UIAlertAction * action)
                         {
                             [view dismissViewControllerAnimated:NO completion:nil];
                             // do something
                         }];

UIAlertAction* other = [UIAlertAction
                        actionWithTitle:@"Individual"
                        style:UIAlertActionStyleDefault
                        handler:^(UIAlertAction * action)
                        {
                            [view dismissViewControllerAnimated:NO completion:nil];
                            // do something
                        }];


UIAlertAction* cancel = [UIAlertAction
                         actionWithTitle:@"Cancel"
                         style:UIAlertActionStyleDefault
                         handler:^(UIAlertAction * action)
                         {
                             [view dismissViewControllerAnimated:YES completion:nil];
                         }];

[view addAction:male];
[view addAction:female];
[view addAction:other];
[view addAction:cancel];
[self presentViewController:view animated:YES completion:nil];

Core Data 使用教學(一)

對於熟悉 SQL 語法的開發者而言,使用 SQLite 作為 App 端的資料儲存方案是很方便實用的選擇。然而並非每位開發者皆熟悉 SQL,而且 SQLite 並不提供物件化的存取方式,而且使用 C 函式的方式操作,不小心就會讓程式碼變得太複雜,對此 Apple 提供了 Core Data Framework:它使用物件化的操作方式,不需要具備底層資料庫知識又可以達成同樣的功能。

Core Data 元件

Core Data 由幾個元件進行運作:Managed Object(實際操作的物件), Managed Object Context(抽象的操作空間), Persistent Store Coordinator, Managed Object Model(像是Database Schema 資料關聯藍圖), Persistent Object Store(如實體檔案),關連圖如下:

Core Data Architecture
Core Data Architecture

要使用 Core Data 的話就需要先對每個元件進行了解。

Managed Objects

Managed objects 是你的程式裡實際上使用的資料物件,你所產生的 Core Data 物件都會繼承自 NSManagedObject,它就像是資料庫裡的一個 Row 或是 Record。因此,要新增資料就要先新增 Managed object,而要查詢目前的資料列表,回傳的就是 Managed object 列表。而所有的 Managed objects 由 Managed object context 保存管理。

Managed Object Context

使用 Core Data 的 App 並不會直接存取實際的儲存檔案,而是透過 Managed Object Context 來存取 Managed Objects 而運作。Managed Object Context 處理 Managed objects 物件與 Persistent Object Store 之間的狀態,以及各 Managed objects 之間的關聯。所有的變動像是 Managed objects 的新增刪除修改等等,會暫存在 Managed Object Context 裡,直到收到 Managed Object Context 要儲存的命令才真的寫入 Persistent Object Store。

Managed Object Model

Managed objects 的物件定義,以及物件之間關聯就是定義在 Managed Object Model 裡。它的作用有點近似於 Database Schema ,是物件關聯的藍圖。每個物件會定義屬性(Attribute),以及關聯(Relationships),fetched property,fetch request。

Attribute

可定義屬性的名稱,與資料型別。由於這些名稱與型別在轉換為 NSManagedObject subclass 時會立即變為該物件的屬性,因此名稱最好是以小寫開頭。

Relationship

與關聯式資料庫裡的 relation 相同,用來定義Managed Object 之間的關聯性。有分為 one-to-one,one-to-many,many-to-many 三種。

Fetch property

這是另一種定義 Managed Object 關聯性的另一種方法。它允許一個物件單向的存取另一個 Managed Object。而 Relationship 則為雙向的關係。

Fetch request

預先定義好的查詢條件,讓使用端方便的取用。比如說可以先定義好查詢:今天沒有報到的學生人數。

Persistent Store Coordinator

Persistent Store Coordinator用來處理多個 persistent object store (如實體檔案)的存取。iOS 開發者並不會直接使用到這個服務,雖然還是需要創建這個服務。大部份情況下我們只需要一個 persistent object store 即可,即始使用複數個persistent object store,經由 Persistent Store Coordinator 的管理,上層的 App 也不需要知道與處理,就當作單一一個 Object Store 的方式來進行作業。

Persistent Object Store

Persistent Object Store 是 Core Data 實際存取實體裝置的地方,Core Data 支援的儲存裝置有三種硬碟類型以及一種記憶體類型。三種硬碟類型分別為有:SQLite, XML, 以及 binary 檔案。預設是使用 SQLite 檔案,實際上在使用 Core Data 開發的 App 並不會感覺實際上是使用什麼儲存方式。因為無論使用那種儲存方式,Core Data 操作的命令與程式碼都是相同的。

建立Entity Description

在使用 Core Data 的第一步,就是要建立 Entity Description。建立好之後這些 Entity Description 即可快速轉換為 Objective-C Class。在建立一個新的 Project 時,選擇使用 Core Data 將會自動產生一個<Project name>.xcdatamodeld的檔案,點選該檔案之後在最下方點選 Add Entity 即可增加一個新的 Entity。

core_data_new_project

core_data_new_entity

預設的名稱就叫作 Entity,可以用滑鼠雙點來改名字。接著在右方的 Attribute 下方點擊 + 號來新增Attribute。由於這裡的 Attribute 名稱會對映到之後自動產生的 Class attribute,所以建議應該用小寫開頭,比如說要用 name 而不是用 Name。而型別也會直接複製過去。
接著,我們也可以在這裡建立relationship,比如說我們建立了 User 與 Company 二個 Entity,接著分別在 User 與 Company 的 Relationships下方按+號來新增 relationship,互相設為 Inverse。設好之後可以切換 Editor Style 來觀察關聯圖。

core_data_relationship

取用  Managed Object Context

由於許多 Core Data  的操作都會需要 Managed Object Context,所以接著我們需要知道如何取用。不過作法其實很簡單,因為在建立一個有 Core Data 的 Project時,程式就已經自動幫我們在 AppDelegate 裡建好程式碼了,因此只要呼叫以下的程式碼即可取得 Managed Object Context

AppDelegate *appDelegate=(AppDelegate *)[[UIApplication sharedApplication] delegate];
NSManagedObjectContext *managedObjectContext=[appDelegate managedObjectContext];

 

另:如果你的 Project 已經開發了一段時間才臨時要增加 Core Data 的使用,最簡單的作法就是直接建一個新的使用  Core  Data 的 Project 然後將這些程式碼複製過去即可。

取用 Entity Description

在操作 Core Data 時使用的 FetchRequest 都會需要 Entity Description 作為參數,要取用 Entity Description 的程式碼如下:

NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:@"Entity名稱" inManagedObjectContext: managedObjectContext];
[fetchRequest setEntity:entity];

 

新增 Managed Object

在準備好 Managed Object Context 與 Entity Description 之後,就可以開始新增 Managed Object 了。新增程式碼如下:

NSManagedObject *newUser = [NSEntityDescription
insertNewObjectForEntityForName:@"User"
inManagedObjectContext:managedObjectContext];

[newUser setValue:@“Orange” forKey:@“name”]; // 設定屬性

NSError *error;
[managedObjectContext save:&error];  // 要呼叫 save 才會儲存

可以觀察到 NSManagedObject 的屬性存取方式是像 Dictionary 的 key-value 設定法。

取得 Managed Object

透過NSFetchRequest可以對 Core Data 取得所需要的 Managed Object 列表。下面的程式碼將取得全部的 User 物件:

NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:@"User" inManagedObjectContext: managedObjectContext];
[fetchRequest setEntity:entity];

NSError *error = nil;
NSArray *fetchedObjects = [ managedObjectContext executeFetchRequest:fetchRequest error:&error];
if (fetchedObjects == nil) {
NSLog(@"Fetch error, something's wrong. %@",error);
}
return fetchedObjects;

若要對物件作篩選,則只要加上NSPredicate條件即可:

NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:@"User" inManagedObjectContext: managedObjectContext];
[fetchRequest setEntity:entity];

NSPredicate *predicate = [NSPredicate predicateWithFormat:@"name=%@", name];
[fetchRequest setPredicate:predicate];

NSError *error = nil;
NSArray *fetchedObjects = [ managedObjectContext executeFetchRequest:fetchRequest error:&error];
if (fetchedObjects == nil) {
NSLog(@"Fetch error, something's wrong. %@",error);
}
return fetchedObjects;

NSPredicate 所接受的語法有很多,簡單的=語法若不夠用的話,可以好好研究一下官方的網頁:Predicate Programming Guide
不過還是要再提醒一下新手工程師:簡單的方式可以達成任務就一定要用簡單的方式,複雜的程式碼往往會增加程式撰寫時間,除錯時間,以及後續修改維護的工程師時間。

To be continued…

基本的 NSManagedObject 的 key-value 的取用方法很實際,而且若使用在迴圈裡時會很方便。但是也不無缺點:若給的 attribute String 打錯的話,無法在 compile 時就發現錯誤,而必需要到執行時間才能發覺。下一篇將介紹我個人很喜歡的 NSManagedObject subclass 的存取方式…