顯示具有 教程 標籤的文章。 顯示所有文章
顯示具有 教程 標籤的文章。 顯示所有文章

2015年11月12日 星期四

[iOS XMPP] 關於XMPP Framework的大小事 - xmpp stream設定

上一篇我們已經提到了XMPP Framework裡面主要分為兩個部分,xmpp core & xmpp extension。而xmpp core裡面最重要的就是xmpp stream,所以今天我們來看看如何在我們的app裡設定xmpp stream吧!
(在設定以下這些東西,通常都是在AppDelegate中進行實作)

1. 首先,設定連線

其實設定連線步驟很簡單,只要設定JID就可以了,範例如下:

xmppStream.myJID = [XMPPJID jidWithString:@"tungtungtsai@gmail.com"];
至於剩下的就交給xmpp stream完成吧!等等..我都還沒跟xmpp stream說我的server在哪啊?沒關係,在這裡,當你輸入了XMPPJID,xmpp stream就會去做SRV查找_xmpp-client._tcp.domain。例如,我在範例中打的domain是gmail.com,google server就很可能回傳像是"talk.google.com"的東西,然後xmpp stream就可以去和這個server建立連線。當然,如果說SRV查找失敗的話,那麼xmpp stream就直接去連JID的domain了。所以你要是已經知道你準備要連過去的xmpp server並沒有xmpp SRV records,你可以直接告訴xmpp stream你的host name,讓他不用花時間去查找了(反正也找不到...),範例如下:
xmppStream.myJID = [XMPPJID jidWithString:@"user@tungtungtsai.com"];
xmppStream.hostName = @"tungtungtsai.com";
你直接指派hostname有個最大好處就是他在development xmpp server也可以work。例如你的伺服器沒有DNS address只有實體IP,或是只能在local network run,那也沒關係,一樣能夠work。範例如下:

xmppStream.myJID = [XMPPJID jidWithString:@"user@dev1.myCompany.com"];
xmppStream.hostName = @"192.168.2.27";
設定了這些之後還有一件事,就是port。我們可以選擇性的看要不要給port,對每個xmpp spec來說,預設xmpp server都是開port 5222,當然如果你要開不同port就要特別設定一下hostPost了。


2. 添加代理(Delegates)

上一篇提到關於XMPPStream有些讓framework更具彈性、延展性、易開發的設計,而其一就是MulticastDelegate。由於xmpp framework需要支援很多的extensions,包含官方的和個人開發者所自己撰寫的plugin,所以傳統的代理模式(Delegate pattern)並不適合。因此,MulticastDelegate就可以讓你在你自己的extension中使用標準的delegate paradigm,並且允許多個classes去接收同一個delegate notification。這樣地好處是你不需要把你所有的xmpp handling code放進單一個class。

這裡簡單提一下:

在iOS開發中,主要有兩個callback systems,delegate和notification。Delegate很直覺也很簡單。使用者把自己設為delegate,然後就可以實作自己需要的delegate method。例如:

[worker setDelegate:self];

- (void)workerDidFinish:(Worker *)sender
{
}

- (void)worker:(Worker *)sender didFinishSubTask:(id)subtask inDuration:(NSTimeInterval)elapsed
{
}

- (BOOL)worker:(Worker *)sender shouldPerformSubTask:(id)subtask
{
}
Notifications也不難,使用者需要個別地註冊每個notification type (寫起來複雜一些),例如:

[[NSNotificationCenter defaultCenter] addObserver:self
                                         selector:@selector(workerDidFinish:)
                                             name:WorkerDidFinishNotification
                                           object:nil];

[[NSNotificationCenter defaultCenter] addObserver:self
                                         selector:@selector(workerDidFinishSubTask:)
                                             name:WorkerDidFinishSubTaskNotification
                                           object:nil];

- (void)workerDidFinish:(NSNotification *)notification
{
    Worker *sender = [notification object];
}

- (void)workerDidFinishSubTask:(NSNotification *)notification
{
    Worker *sender = [notification object];
    id subtask = [[notification userInfo] objectForKey:@"subtask"];
    NSTimeInterval elapsed = [[[notification userInfo] objectForKey"duration"] doubleValue];
}

有時候要從notification中的Dictionary取出參數的話,就要先知道他擁有哪些keys。另外,notification是不允許回傳變數的。因此簡單地對兩個callback機制的優缺點分析如下:

Delegate優點:

1. 易於註冊多個callback
2. 當有多個參數時較易於使用
3. 允許回傳變數

Delegate缺點:

1. 只能夠是單一delegate

Notification優點:

1. 可以多個物件註冊同一個notification

Notification缺點:

1. 註冊多個callback較複雜
2. 要從dictionary中拿到參數很麻煩(要知道正確的key)
3. 當需要回傳變數時就不能使用

所以,對於XMPP framework來說哪一個方式適用呢?對XMPP來說,xmpp framework需要能夠廣播給多個listeners。例如當你有個message進來,你需要同時廣播給聊天的視窗、歷史紀錄和推播系統等等。此外,xmpp framework需要易於延展。xmpp要能夠支持很多的XEP,所以要能在broadcast端與listener端易於使用。xmpp framework 需要選擇能支持回傳變數的方式,也需要能夠幫助維持 thread-safety,因為xmpp framework大量地使用平行化與多線程。

講完你就發現好像delegate和notification都不是最好的解決方法,因此xmpp就創了一個GCDMulticastDelegate class。你可以在任何時候把你自己新增/移除為XMPPStream的delegate。

[xmppStream addDelegate:self delegateQueue:dispatch_get_main_queue()];
...
[xmppStream removeDelegate:self];
簡單範例如下:
// Add myself as a delegate, and tell xmppStream to invoke my delegate methods on the main thread
[xmppStream addDelegate:self delegateQueue:dispatch_get_main_queue()];

// Then just implement whatever delegate methods you need like normal
- (void)xmppStream:(XMPPStream *)sender didReceiveMessage:(XMPPMessage *)message
{
   ...
}
就和傳統的代理模式差不多,但是你可以具體地說明thread-specific資訊。而如果你想要讓某些process離開主線程到背景執行也很容易,在iPhone中這種操作方式可以讓你的app維持良好的perfomance,範例如下:
// Handle most stuff on the main thread
[xmppStream addDelegate:self delegateQueue:dispatch_get_main_queue()];

// But do that one slow thing on a background queue so it doesn't slow down the UI anymore
[xmppStream addDelegate:bgProcessor delegateQueue:bgProcessorQueue];


3. 添加模組(Modules)

接下來就是要加入我們自己需要的extensions了,當然你也可以自己寫自己需要的extensions,這裡我們不會探討到底有哪些extensions,僅簡單列出一些,因為這裡最重要的是要學會加入模組。
XMPPReconnect - 當斷線時自動重新連線
XMPPRoster - 提供聯絡人功能
XMPPRoom - 提供聊天室,群聊功能
XMPPPubSub - 提供發布與訂閱功能
現在我們來試著加入XMPPReconnect module到我們的xmpp stream吧!

xmppReconnect = [ [XMPPReconnect alloc] init];

// Optional configuration of xmppReconnect could go here.
// The defaults are fine for our purposes.

[xmppReconnect activate:xmppStream];

// You can also optionally add delegates to the module.

[xmppReconnect addDelegate:self delegateQueue:dispatch_get_main_queue()];

// And that's all that is needed.
// The module will receive any delegate methods it needs automatically
// from the xmpp stream, and will continue to do its thing unless you deactivate it.

4. 建立連線

當我們都加好要用的modules,就可以開始建立連線了!

NSError *error = nil;
if (![xmppStream connect:&error])
{
    NSLog(@"Oops, I probably forgot something: %@", error);
}

若是我們在過程中忘記設定必須的屬性,例如JID,那連線就會回傳NO,並在error message中給予提示。

在連線過程中,client端和server端會建立xmpp handshake(交握),達成協定。如果你的伺服器需要建立安全連線,例如透過SSL/TLS方式,xmpp stream會自動建立安全連線。如果你的伺服器有不正確的X509 certificate,那就必須去實作xmppStream:willSecureWithSettings: 這個方法來改掉預設的安全設定。

5. 認證(Authenticating)

在所有連線handshaking都已經完成後,就會開始呼叫 xmppStreamDidConnect:方法,並開始進行認證程序。程式碼如下:

- (void)xmppStreamDidConnect:(XMPPStream *)sender
{
    [xmppStream authenticateWithPassword:password error:NULL];
}


五個步驟進行完,在實際執行時就能與xmpp stream進行連線操作嘍!


參考連結:https://github.com/robbiehanson/XMPPFramework/wiki/IntroToFramework

2015年11月4日 星期三

[iOS] Action Extension 簡易教學

這幾天在研究iOS裡面的Extension。中文資源好像不太多,所以我把一些最近看到的心得陸續寫一下。

今天我要做的簡單教學,是讓APP可以打開Action Sheet,裡面會有一個Read按鈕,當使用者點了Read,APP就會幫你念出APP中的文字內容。




其實Extension最常被使用的地方是:雖然你沒有開啟APP,但你可以讓使用者可以使用某些APP功能或內容,所以通常使用Extension的時候都是因為有些特殊工作需要執行。Extension的種類很多,今天我要介紹的是Action Extension,這個Extension主要的目的是讓使用者查看或操作你APP裡的內容。在實作Extension特別需要注意的一個觀念是:Extension並不是一個APP唷!

好了,我們開始吧!


Step 1. 打開Xcode 新增一個Project,使用Single View Application並取一個自己喜歡的Project名字,我這裡取的是:“ReadExtPractice”





Step 2. 從Target的地方新增一個Target,並選擇Action Extension,一樣自己取一個你喜歡的名字吧!這裡我是叫做"Read"。然後在Activate處先選Cancel吧。






Step 3. 現在就開始來動工做一下簡單的UI吧!這裡我們以iPhone作為例子,就暫且不考慮那些iphone尺寸不同問題拉。先到ReadActionExt裡面Main.storyboard裡點選View Controller,並把 Simulated Metrics裡面的size設定成 iPhone 4.7-inch,就假設他是iPhone6吧。然後加入一個TextView與一個Button,並且把TextView的內文設定為自己想要的內容,如果不知道寫什麼,就跟我寫一樣吧!我的是“Hello, my name is Tung-tung Tsai. Welcome to Use Action Extension. Have a nice day.”並將按鈕的Title換成"Action"。如果不知道元件的size如何設定比較好,可以參考我這裡是設定textview{0, 28, 375, 200},button{112, 250, 150, 40}。






Step 4. 接下來開始寫點code拉。先到ViewController.h裡加入textView這個property並命名為"textView"並加入按鈕的Action命名為"ActionPressed",然後到ViewController.m裡面找到- (IBAction)ActionPressed:(id)sender 這個函數,並在裡面加上這段code,讓使用者點擊按鈕後出現一個Activity Controller。

- (IBAction)ActionPressed:(id)sender {
    UIActivityViewController *activityVC = [[UIActivityViewController alloc] initWithActivityItems:@[self.textView.text] applicationActivities:nil];
    [self presentViewController:activityVC animated:YES completion:nil];
}





Step 5.  都沒問題後就要開始來處理Action Extension的部分拉!一樣先處理UI,我們到Read裡面打開MainInterface.storyboard,這裡原本會有一個imageView不過我們不需要他,就直接將他Delete吧,然後加上一個TextView,並將Navigation Bar的標題改成"Read Ext"。

然後進入ActionViewController.h裡加入一個property命名為"TextView"連結到剛剛你加到storyboard裡的TextView吧!





Step 6.  打開ActionViewController.m,先在上面@import AVFoundation後,將- (void)viewDidLoad 的code先拿掉並重新寫上:

    NSExtensionItem *item = self.extensionContext.inputItems[0];
    NSItemProvider *itemProvider = item.attachments[0];
    
    if ([itemProvider hasItemConformingToTypeIdentifier:(NSString *)kUTTypePlainText]) {

        [itemProvider loadItemForTypeIdentifier:(NSString *)kUTTypePlainText options:nil completionHandler:^(NSString* item, NSError * error) {
            if (item) {
                [[NSOperationQueue mainQueue] addOperationWithBlock:^{
                    [self.TextView setText:item];
                    
                    //Set Synthesizer and utterence
                    AVSpeechSynthesizer *synthesizer = [[AVSpeechSynthesizer alloc]init];
                    AVSpeechUtterance *utterance = [AVSpeechUtterance speechUtteranceWithString:self.TextView.text];
                    [utterance setRate:0.5];
                    [synthesizer speakUtterance:utterance];
                }];
            }
        }];
        
    }



Step 7. 完成上述程式碼就代表他會在Extension被呼叫時讀取原本在TextView裡面的文字並且朗讀出來。但還沒結束,還需要在Read裡面的Info.plist設定一些參數才行。先進入Info.plist,然後打開NSExtension -> NSExtensionAttributes -> NSExtensionActivationRule,將NSExtensionActivationRule的type改為Dictionary,並加入一個新的項目NSExtensionActivationSupportsText,把這個type設為Boolean,並把Value設為YES。



Step 8. 終於完成了,最後到Asset.xcassets裡面把120x120的圖示放進去,並在App icon source裡面設定Icon來源為AppIcon就完成了。







Step 9. 實際執行看看吧!!應該會出現下面的樣子,如果我點選Action,再點選Read就會出現一個新頁面然後開始讀文字內容給你聽嘍!!!!











完成了! 希望這個教學可以幫助你更認識Action Extension嘍!如果有需要這份代碼,可以從Github上面查看。