2014年1月30日 星期四

NSBlockOperation 的 re-run

如果希望在 iOS 的程式中動態指定要被執行的多行程式述句(或以「程式塊」來稱呼),其中的一個選擇是使用 NSBlockOperation。

NSBlockOperation 的運用邏輯很簡單,只要使用類別方法的 blockOperationWithBlock: 回傳該類別的實體後,再等到要被執行時傳送 start 訊息給該實體即可。

不過,NSBlockOperation 的實體是不能被 re-run 的。也就是說,第二次傳送 start 訊息後,定義於該實體內的程式塊並不會再被執行。

由於某個原因,我不想變更設計來適應這個特性。怎麼辦呢?我是這樣做的:

NSBlockOperation *blockOperation = [NSBlockOperation new];
[blockOperation addExecutionBlock: [[self.operation executionBlocks] objectAtIndex: 0]];
[blockOperation start];

其中,self.operation 本身是 NSBlockOperation 的實體;為了能重複執行它,在每次需要執行時,我採用這樣的 wrap 策略讓它的內容可以被重複執行。

分享給大家。

2014年1月28日 星期二

iOS 7 的 iBeacon 接收端

在 iBeacon 接收端,主要有 CLLocationManager、CLLocationManagerDelegate 及 CLBeaconRegion 三個類別執行工作。

CLBeaconRegion 用來表示「和某個應用程式批配的某個範圍」。由於一個範圍內可能有多個不同的接收端要收到不同的訊息,或是同一個接收端要在不同的範圍內接受同一個訊息,所以才需要把範圍和應用程式同時批配。用以識別範圍的方式是使用 NSUUID 物件,而識別應用程式則是以 identifier 作為區分。

region = [[CLBeaconRegion alloc]
                       initWithProximityUUID:@“UUID的值”
                       identifier:@“應用程式的識別字”];
region.notifyOnEntry = YES;  //當進入指定範圍時通知
region.notifyOnExit = YES;   //當離開指定範圍時通知
region.notifyEntryStateOnDisplay = NO;


CLLocationManager 是實際進行偵測的類別,它接受 CLBeaconRegion 的實體,在指定的範圍及應用程式識別的狀況下,接收者來自發送端送來的訊息。

manager = [CLLocationManager new];
[manager startMonitoringForRegion: region];
[manager startRangingBeaconsInRegion: region];


CLLocationManagerDelegate 定義了給 CLLocationManager 物件呼叫的種種 call-back 函式,這樣當 CLLocationManager 偵測到什麼之後,就透過送訊息給這些函式讓應用程式可能針對不同的狀況、資訊進行判斷、執行自身行為。

以下僅取幾個方法為例:

- (void)locationManager:(CLLocationManager *)manager
         didEnterRegion:(CLRegion *)region
{
    //進入範圍時通知
}

- (void)locationManager:(CLLocationManager *)manager
          didExitRegion:(CLRegion *)region
{
    //離開範圍時通知
}

- (void)locationManager:(CLLocationManager *)manager
        didRangeBeacons:(NSArray *)beacons
               inRegion:(CLBeaconRegion *)region
{
    // 在範圍內時會不斷被呼叫。
    // 從 beacons 參數中可以取得 CLBeacon 物件,可以從該中取得鄰近數值 (proximity) 、準確性數值 (accuracy),以及 RSSI 等數值。
}

iOS7 的 iBeacon 簡介

beacon 有「烽火台、燈塔、電臺」等意思,這裡筆者以「廣播電台」做為物喻。而 iBeacon 是由 Apple Inc. 所制定的一種室內系統。

這個系統的作用是什麼呢?我們可以由發送端和接受端兩方面來認識它。
發送端就像廣播電台,向它的四周發送特定頻率的訊息。
接受端就像收音機,若它設定的頻率和電台的頻率一致,它就會對該訊息進行處理。
在接受端接受到的訊息裡,包括它和發送端的距離、信號強度,甚至其他客製化的資訊。
換句話說,接受端可以依其收到的資訊,選擇性/策略性地去執行一些工作。

這個技術的應用範圍可以很廣,目前已經實際上路的,是在美國的 Apple Store。每個人只要帶著安裝 iOS7 的 iDevice,在藍芽開啟的情況下,走進商店後,就可以在 Apple Store App (不是 App Store 哦)上收到一些店家想提供給你的訊息。

對於店家而言,他們也可以得到一些訊息。例如,有一個 iBeacon 接收端經常在某些個發送端的附近。換成商業的說法,就是說,他們能得知自己的哪些商品最吸睛。這樣的技術幫助下,要做購物籃策略也就能更具體地制訂。

應用還可以延伸的方向太多了,大家一同發揮想像力吧。

從今以後,請使用 Storyboard 開發、實作 Unit test 。

從 Xcode 5 開始,在新建 project 時,以常用的 Single View Application 來說,Storyboard 以及 Unit Test 已經不是可以選擇的選項了。

也就是說,新建立的 project 將與生俱來地使用 Storyboard 以及配置好 Tests Target 了。我想這個改變是某種決心。

使用 Storyboard 而非止於 xib,或是使用 CoreData 而非直接操作 SQLite DB,在 iPhone 5s 已經裝載 64位元處理器的現在,追求更好的開發邏輯及工法將愈來愈被強調。倒不是技術方就不需要考量效能問題,但是「產品」的生命週期應該長過資訊裝置的軟硬體,不然以成本、品牌角度來看,都不利於健康的商業活動。為了提供更好的軟硬體架構以支撐未來的需求及改變,我們需要整體來看技術的選擇及精進。

更正確而言,其實技術的發展,有時是為了把關注點更正確的轉移。像 CoreData 就不是用來取代 SQLite 的,因為它不是 DB 系統本身,它關注的是怎麼運用能更直覺被詮釋的商業邏輯、提供更多相關機制來維護該邏輯眼下的執行、未來的變遷…等等的正確性。

就像想吃泡麵時沒有筷子,把筆擦乾淨還是可以將就的;但是現在有筷子,而且還是專門為泡麵麵條設計的筷子給我們用,為什麼不考慮呢?可能有人會說:「可是筆還可以寫字,筷子不行呀!」(可能有人覺得這個反問很…欠智慧,不過不知道為什麼,在很多時候,人都有這種想要什麼都有的心態和選擇。不然就不會看到有人拿著平板/接近平板大小的行動裝置、貼著耳朵、歪著脖子、拉著嘴巴、瞪著斜眼在講電話了…。) 關於這個反問,我選擇「微笑以對,不想解釋」。

Storyboard 明顯是強調 UX flow 的可視化,使其易於解釋、審視、變更,這些都關乎產品在使用需求上的直接品質;Unit Test 則是關注於強調程式邏輯的正確性、與需求的應對性。

這幾點被強調的文章還太少,至少不常見於與 iOS 程式設計的書/文章,所以筆著覺得還是要故意拿出來分享比較好。

iOS 7 中 MapKit 相關的類別及擴充



上圖是筆者大略整理的類別圖。由於並沒有完全遵照 UML 語法,所以這裡的圖僅利於理解物件工作原理哦。

1. 首先,撰寫程式碼時,我們會使用到 MapKit.framework 這個 library,請記得先加入並 import。

2. 上圖中,我們從 MKMapView 著眼:可以透過它的 userLocation 取得 MKUserLocation 實體,再透過其 location 取得 CLLocation 實體,然後其再往圖的右方,直至取得 CLLocationCoordinate2D 的實體。這條引用路徑頗長,目的說穿了就是取得使用者所在地的經緯度。

3. 接下來,我們要用這個經緯度要做的事情是:計算兩個地方之間的導航路徑。

首先,兩個地方,一者為出發地 (source) ,另一者就是目的地 (destination) 囉。用於表示這兩個地點的類別是 MKMapItem。

MKMapItem 用於表示地圖上的某個東西,要建立它的實體,需要在初始化時傳入 MKPlacemark 的實體。MKPlacemark 則用於封裝座標(就是 CLLocationCoordinate2D 物件囉)及國家代碼。

也就是說,我們可以透過 MKMapView 取得使用者位置的座標,然後建立 MKPlacemark 物件,再然後就可以建立 MKMapItem 物件了。

MKMapItem 除了透過 MKPlacemark 物件裝載了座標位置外,還提供了 name、phoneNumber、url …等 property,所以再明顯不過:它用於封裝了在地圖上某點其上的相關資訊:叫什麼名字、聯絡的方式、提供的資訊…等等。

要取得兩個地點之間的路徑,就是要取得 MKDirections;MKDirections 物件的取得則是透過 MKDirectionsRequest 。MKDirectionsRequest 要做的事是:裝載了來源地(source)、目的地(destination)以及交通方式(transportType,包括走路、騎車或任何種類)…等資訊後,作為 MKDirections 的初始化參數傳入。

MKDirections 這個類別取名就是複數,很明顯地它包括多個路徑,這個路徑是以 MKRoute 這個類別來表示。傳送 calculateDirectionsWithCompletionHandler 訊息給 MKDirections 的實體後,透過 block 語法會回傳 MKDirectionsResponse 的實體,該實體有一個 routes 的 property 可以存取;明顯示 routes 是一個集合(正確來說是 NSArray),每個元素是一個 MKRoute。

最後,透過 MKMapView 物件的 addOverlay 方法就可以把 MKRoute 物件的 ployLine property 加到地圖上囉。

請大家參見上圖,一步步跟著走,大概就不難理解其工作原理了。


iOS 7 提供 AVSpeechSynthesizer 支援 TTS 功能

iOS 7 開始,終於有開放的內建 TTS 功能了。

相關的類別是在 AVFoundation framework 裡的 AVSpeechSynthesizer 、AVSpeechUtterance,以及 AVSpeechSynthesisVoice。
使用的方式如下:

1. 加入 AVFoundation.framework 這個 library,並於 client 類別中 import <AVFoundation/AVFoundation.h> 。

2. 建立 AVSpeechSynthesizer 實體。要讓它"說話",可以傳送 speakUtterance 訊息給它,這個方法接受一個 AVSpeechUtterance 實體作為參數。

3. AVSpeechUtterance 用於封裝語言種類 (AVSpeechSynthesisVoice 的實體)、要朗讀的文字內容,以及朗讀的延遲時間…等。


以下來思考一下這些類別的封裝原則。

1. AVSpeechSynthesisVoice:這個類別明顯地關注於語言種類本身。
以 voiceWithLanguage: 這個類別方法建立實體,其接受一個字串參數,該參數用於指定語言種類,若傳進 nil 則使用系統語系。至於支援的範圍請見 「BCP-47 language tag」。

2. AVSpeechUtterance:這個類別將 AVSpeechSynthesisVoice 實體以及要進行朗讀的文字內容綁定,並且封裝了朗讀速度、延遲、音量…等元素。感覺上很像一個 player。

3. AVSpeechSynthesizer:這個類別是最後負責把聲音發出來的類別。
感覺上,怎麼不是由AVSpeechUtterance 進行發聲呢?原因是需要顧及進行朗讀時,播放中可能得視情形進行中斷(停止或暫停),這時需要一個明定的規則來進行調派。就像交通警察一樣的角色。AVSpeechSynthesizer 就是這個作用。相關的方法有:stopSpeakingAtBoundary: 、pauseSpeakingAtBoundary:、continueSpeaking。

其中,stopSpeakingAtBoundary/pauseSpeakingAtBoundary 接受一個 AVSpeechBoundary 列舉,該列舉的值包括:AVSpeechBoundaryImmediate 及 AVSpeechBoundaryWord。

舉例來說,若在傳送 stopSpeakingAtBoundary 訊息時夾帶 AVSpeechBoundaryImmediate 這個列舉作為參數的話,當有別的聲音事件進來時,AVSpeechSynthesizer 就會立即停止自己所列管的 AVSpeechUtterance 實體的播放。

由於網路上不乏範例,而且自己就著原理寫一次才能學到東西,所以這裡就不加上範例程式囉。




2014年1月27日 星期一

iOS 7 的 Dynamic Type

Mac OS 在很久以前就是多國語言的了。
(筆者只知道10年前自己開始用 Mac 時,它就是多國語言的了。)

此外,Mac OS 也早就有為了視障、聽障者的使用方便而搭載的相關輔助功能。
這種設計觀、關懷性,是 Apple 的產品的特色、DNA。

iOS 當然也一直以同樣的方向努力著。

iOS 7 的新功能之一,Dynamic Type,讓使用者能調整「動態字級」的方式放大/縮小 App 中的文字大小。(設定方式:「設定」->「一般」->「輔助使用」->「較大文字」)

對於開發者而言,若您的 App 要能反應使用者的 Dynamic Type 設定,需要:

1. 加入對 UIContentSizeCategoryDidChangeNotification 這個通知的觀察
2. 從 handler method (也就是 selector 的參數對應的 method)的輸入參數 (NSNotification 的實體) 中取出 UIContentSizeCategoryNewValueKey 及 UIContentSizeCategoryTextLegibilityEnabledKey 及其相對應的值。
3. 依上述值調整元件的 font property。(例如,使用 UIFont 的 preferredFontForTextStyle)

Xcode 的 Autolayout 有很酷的 Constraints

Xcode 提供的 Autolayout 對於應用型 App 的版面配置帶來不小的方便。
這個說法可能認同的人一半一半。

事實上,筆者以前的選擇也比較偏向以載入不同 xib 物件的方式呈現介面,而不是使用 autolayout。一來是 UX 的設計文件中不一定都很明確地把位置尺寸全列出來,再者有時得視種種需求改變配置方式的話,使用 autolayout 都很…麻煩。(有實作過的人應該知道)

不過若我們知道 Constraints 物件本身也可以拉線成為 IBOutlet 、進而能用程式方式操控其值的話,將可能為它的靈活性感到著迷。

經驗上,一定要很重視 Constraint IBOutlet 的命名,否則程式碼的可讀性會下降。我的話,覺得利用單一個命名良好的 method 來封裝它的動作都是絕對值得的。

Xcode 7 裡的鍵盤退出新機制

以往當 App 內使用了提供使用者輸入文字的元件時,如:UITextField,我們就得去考慮鍵盤在什麼情形下應該退出螢幕,讓使用者能繼續和螢幕上的其他元件進行互動。

一般來說,不外乎:

.製訂鍵盤的 Return Key 類型為 Done,讓使用者按下右下角的 Done 鈕來退鍵盤。
.讓使用者能輕擊畫面上其他的元件來退鍵盤。

前者需要實作 Delegate 方法來取得鍵盤操作的 call back 時機;後者需要註冊其他的 View 上的手勢操作或實作 View 的子類別來針對 touch 事件函式攔截時機。不論是何種時機到來,都是使用呼叫「輸入元件」(本例為 UITextField) 的 resignFirstResponder 方法,完工。

iOS 7 裡,Apple 官方提供的 App 支援另一個退鍵盤的操作方式:只要把鍵盤外的 view 往下一撥,鍵盤就會往下退出了。這個操作方式,非常符合 Apple 一直追求一根姆指就能流暢操作 App 的精神。

這個操作方式也開放給開發者利用了,而且非常容易實作。
只要在 InterfaceBuilder / Storyboard 編輯區,選取要被往下撥的 View。這個 View 必須得是 UIScrollView及其子類。然後就可以在右邊的 Attributes inspector 中找到 Scroll View 的 Keyboard 設定,選擇其值為「Dismiss interactively」即可。

另一個選擇是使用 DAKeyboardControl 這個 MIT License 的 framework。

iOS 7 的視差效果

iOS 7 的背景圖片增加了動態效果。
拿到眼前晃呀晃的,會感覺到背景像是3D的。

這個效果可是有被公開出來讓開發者使用的哦!

1. 建立 UIInterpolatingMotionEffect 的實體。
2. 在該實體上設定偏差值。
3. 使用 UIView 的 addMotionEffect: 加入該實體。

這裡不花篇幅轉貼程式碼,請大家自己 Google 囉。

Xcode 5 的圖像管理

從 Xcode 5 新建出來的專案裡,有個 Images.xcassets 檔案。
從副檔名來看,就知道是個資產 (assets) 管理用的檔。

依 iOS 的規範,我們最好為非 Retina 及 Retina 螢幕提供不同解析度的圖像。
假設其主檔名是 cute_cat ,那麼就得提供 cute_cat.png 及 cute_cat@2x.png 兩種圖。

Images.xcassets 這個檔案的機制很容易用:選取它,然後編輯區會分為左窄右寬的兩個 view,左邊的 view 是一個垂直的清單。

開啟 Finder 、同時選取 cut_cat.png 及 cute_cat@2x.png,然後拖進左邊的 view。完成後會看到多了「cute_cat」這個項,然後右邊的 view 就會對應好不同螢幕時用什麼圖的設定。

非常方便,趕快試試吧。

對了,為什麼這邊的例子中,圖片的名字是取 cute_cat 而不是 cuteCat 呢?
這個軟性規範的道理很簡單:不是所有 OS 採用的 FS 都區分大小寫的;還有,網路協定也是。所以,資源類型的檔案,使用底線分隔是比較好的方式。

Xcode 5 有更好的封裝引導

有 API / Framework 設計經驗的人應該都了解一個原則:

公開的部分愈少愈好。

以 Java 來說, interface 中宣告的 methods 就象徵 API/Framework 提供者的承諾;在 C / Objective-C 來說,header 檔中的內容也同樣兼負這項責任。

換句話說,不需要提供給 API/Framework 終端使用者知道的資訊,應該封裝在別處:使用者以一般方式使用 API/Framework 時看不到的地方。

從 Xcode 4 開始,InterfaceBuilder 和 code editor 結合成了一個視窗,當程式員試圖連結 IBOutlet 或 IBAction 時,只要切換 editor 至 assistant editor,就可以看到視窗被一分為二個 view,然後透過拉線完成。這個方式實在太酷了。

這裡想討論的,是再更細的事。

當 editor 上呈現 xib 的 layout view時,開啟 assistant editor ,會發現右邊的 view 應該會自動對應到 xib 上目前正被選取的元件的 Custom Class。在 Xcode 4 ,我們會看到該類別的 .h 檔展示在右邊的 view。

為什麼是 .h (header) 檔,而不是 .m 呢?

以 IBOutlet 來說,它會被宣告成是 property。以 property 的定義來說,它的確是有提供外部元件與所屬物件互動的功能。所以,開啟 assistant editor 時把 .h 檔秀出來,似乎合道理。

不過,不是所有的 IBOutlet 都應該和外部元件互動;
而且,若要連結的是 IBAction,而 IBAction 與外部元件的互動機會可能更少,那麼宣告在 .h 曝露出去真的好嗎?

在 Xcode 5 ,切換到 assistant editor 時,預設打開的是 .m 而不是 .h了。這時程式員可以把 IBOutlet 或 IBAction 的宣告放在匿名的 category 宣告中。這是更好的資訊隱藏、更好的封裝。

我想 Xcode 的發展實在是很有想法的…。



寫好程式碼


引個言…

傻瓜都能寫出電腦能理解的程式。優秀的工程師寫出的是人類能讀懂的程式。
(Any fool can write code that a computer can understand. Good programmers write code that humans can understand.  )

--Martin Fowler

如果您是團隊的技術領導者、架構師,記得蒐集、研磨一份 coding guideline,讓您的團隊在有所拘束下,養出好的寫碼習慣。

幫點小忙,把一些現有資源列出來:

Coding Guidelines for Cocoa
Google Objective-C Style Guide
NYTimes Objective-C Style Guide
Code Conventions for the Java TM Programming Language
Code Style Guidelines for Android Contributors

真的,寫一份有規矩的程式碼,會讓大家的日子都好過很多!


一個人寫的爛軟體將會給另一個人帶來一份全職工作。(Jessica Gaston)
One man’s crappy software is another man’s full time job. (Jessica Gaston)


Xcode 中斷點的可攜性

在程式碼裡,經常可以看到:工程師為了驗證想法、隨時監控系統…等原因,而在程式碼之間嵌入列印 log 的敘述。例如:

log.info("name變數的值為:" + name);  // Java
NSLog(@"name變數的值為:%@", name);  // Objective-C

這個作法有什麼問題嗎?簡單講,對於閱讀程式碼的人,多少會造成閱讀感的不流暢;其次, log 的列印會影響程式的執行速度、增加不必要的大量歷史記錄。(除非執行環境有被妥善的區分)

至於習於使用 preprocessor 的程式風格,例如以 #if .. #endif 來區分執環境…這種程式碼就更…醜。個人覺得要激怒程式碼未來的維護者(也可能是自己),這個風格的效果應該不錯。

Xcode 的中斷點機制能以更好的方式來滿足上面的需求。

我們可以在建置中斷點後,在其上方以右鍵/雙指點擊的方式呼叫功能選單,並選擇「Edit Breakpoint ...」後,我們可以透過:

1. 將 Action 選擇為「Log Message」
2. 以上面的例子為例,就在 Action 下方文字列輸入:

name變數的值為:@name@

3. 選擇「Log Message to console」
4. 選擇 Options 為「Automatically continue after evaluating」

完成以上步驟後,在執行過程中就會將 log 資訊列印到 console ,效果和加上列印 log 敘述是一樣的,而且不會有因為加上該敘述造成程式碼的閱讀負壓。


故事只講完開場,這篇文章要關注的重點是接下來的內容:

在最基本的要求下,我們會把程式碼在 iteration / refactoring 的過程中,增量式地保存到 repository 裡。(目前 Xcode 預設使用 Git )

那麼,我們在上文中,對於中斷點設置的詳細內容,會跟著 commit 到 Git 裡,然後當下一次不管誰、在哪一台主機裡將程式碼 clone/pull 時,他得到的程式碼中,還會含帶該中斷點的設定嗎?

答案是:如果使用 Xcode 提供的 commit 功能選單,來進行程式碼保存的操作時,將不會把中斷點設置資訊一併存入 Git。更具體地說,在專案的

專案名稱.xcodeproj /xcuserdata/使用者名稱.xcuserdatad/xcdebugger/ 目錄裡

.xcbkptlist 檔,並沒有被保存至 Git 的原因。

解決方式:

自行輸入 git add 及 git commit  。這樣 xcbkptlist 檔才會被存到 Git,而中斷點的設定才具被「可攜性」哦!

對了,Xcode 的中斷點設置,其功能還不只如此!要讓它講話、唱歌都行。
這個就讓讀者自行 play 一下囉 ~