2014年10月7日 星期二

如何讓 Swift 與 Objective-C 共生 - 無痛轉生

Hi There,  Swift 語言推出以來締造了許多的記錄,對於 iOS Developer 來說 Objective-C 是再熟悉不過了,現在有個新的小朋友 Swift 的出現,不知道需要需要再花時間來學習,這篇文章不是來教導怎麼學習 Swift 來開發 iOS 程式,而且先從 Swift 怎麼利用已定義好的 Objective-C 的 Class 來給想學或是正在學的 Developers 一個參考。
首先要介紹的是 Bridging Header 這個檔案。
首先 產生一個 Single View Application 的專案。如下圖
這個就是新的 Xcode 6 產生專案的畫面,比之前 Xcode 5 少了幾個 (少了幾個呢?留給讀者去比較了)。
接著我們要選擇 Swift 這個語言。如下
 命名為 SwiftObjc
然後我們要在專案的 Navigator 中新增一個 Objective-C 的 Class。如下。
記得要選的是 Cocoa Touch Class 而不是 Objective-C File 哦 。
命名為 Car。
記得 Language 是 Objective-C 哦。
接著不一樣的事情發生了。Xcode 會自動跳出一個提醒視窗。如下。

這個視窗的意思是說,Xcode 想幫開發者自動加入 bridging header 的檔案。
現在就先選擇 Yes 。來看看專案有什麼變化?如下
多了三個檔案哦。我們想要的 Car 這個 Class 的 .h, .m ,還有一個所謂的 Bridging Header 。在這的名稱為 SwiftObjC-Bridging-Header.h 。所以跳出視窗問的就是這個 Header,裡面長什麼樣子呢?基本上是空白。
因為我們要在 Swift Class 裡面用 Objective-C 的 Car class 。所以必需要加上如下的。程式碼。
就是 #import "Car.h" 。
也可以自已手動加,但需要改變專案的設定,如下圖
有一個 Objective-C Bridging Header 的設定要寫,記得除了寫 .h 檔名之外,還要加上 Product 名稱哦,通常就是 Project 名稱。
接下來我們就在 ViewController.swift 裡面使用 Car 這個 Class
如下
就可以直接在 viewDidLoad 裡面用 Car 這個 Class。而且用 Swift 語法
Car()
在 Swift 中 Car() 就是和 Objective-C 的
[[Car alloc] init ]
 而且聰明的讀者有沒有發現到,在 Swift 裡面每個 row 的程式碼最後都不需要分號 ; 結尾
接著我們來加一下 method 在 Car 裡面。如下新增

Method 的名稱叫 setPrice:andYear: 這樣的 selector 要怎麼在 Swift 呼叫呢?Swift 也是支援  selector 的觀念,如下使用。
setPrice:andYear: 在 Swift 的語法就是
func setPrice( a:Int, andYear: b:Int)
 這也是 Swift 跳脫了傳統的 C function 的束縛加了一個新的語法叫 external parameter name 。也就是 andYear: ,在 Swift 的 function 裡用第二個參數的 external parameter name 來表示。
接著執行是會 Crash。為什麼?因為 Car.m 並沒有實作 -setPrice:andYear:。所以記得要實作。如下。
順便加上兩個  ivar,各是 price 和 year
這樣一來在 ViewController.swift 就可以執行成功。
對於 Selector 的觀念,我們再來做一個小小的實驗。
先在 ViewController.swift 再加上一些程式碼

我們要利用 NSTimer 來呼叫兩個  function 。分別是
func action(a:NSTimer, second b:Int ) {
        println("action1")}
這個的 selector 寫成 Selector("action:second:")

以及
func action(action a:NSTimer, second b:NSTimer){
        println("action2") }
而這樣的寫法,無法對應到 Objective-C 觀念的 Selector
執行後會發現一直出現 action1 的 log是正常的。

接著我們來看一下存取 ivar。在這個時候如果直接從 Swift 存取 Car 的 ivar 會發生如下錯誤。


放在  .m 的 ivar 應該是無法被看到,如果試著把 ivar 拿到 .h 來如下

結果也是一樣。在 Swift 要正常存取 Objective-C 的 ivar 。最好的方式就是利用 property,將 Car.h 改寫如下


再利用 Swift 來執行如下
就可以成功執行。

最後要來解釋一下 Objective-C 的 class method 要怎麼在 Swift 被使用。
我們要在 Car.h 加上 class method 如下
當然 .m 要對應好

怎麼在 Swift 中使用 +sharedInstance 呢?如下

var myCar = Car.sharedInstance()

這樣就可以了。看起來不難,不過提醒讀者名稱這事對於 Objective-C 和 Swift 都很重要,如果有一個 class method 命名成如下
+(instancetype) car; 

在使用

var myCar = Car.car()
就會造成 compiler 的錯誤,目前可以的話就盡量避免,直接用 Class 名稱第一個字小寫的 class method。
談了這麼多,到底應用在真實的例子是如何呢?
我們就拿 AFNetworking 的 framework 來測試。

這是一個非常有名也很多人用在網路的資料的傳輸上的 open source 的 framework。
在這就不簡介。先把 AFNetworking 的檔案都拉到專案
然後在 Bridging Header File 也要加上 AFNetworking.h 如下
最後補上一段,下載 Image 的程式碼就大功告成了啦
最後回答大家常問麥老師的問題,Swift 是不是非學不可?Swift 是不是會取代 Objective-C ?
Swift 是不是非學不可?
在這一二年內答案不是,但是因為 Swift 的好學好寫,我想再過不久 Swift 的 framework 不會比 Objective-C 少,到時,如果用到只有 Swift 版的 framework 就是非學不可了。
Swift 是不是會取代 Objective-C ?
我個人是覺得再 5 年可能有機會,因為一個語言從出生到非常成熟可能需要 10 的年磨練,依照熱門程度不同,時間會有所調整,Swift 這麼火紅的情況下,可能會使它比較快穩定。我們可以看一個參考,如果 Apple 某個常用應用程式是用 Swift 重新改寫,完全取代 Objective-C 的日子就很近了。

以上一些,很久沒有寫文章的碎碎唸。希望大家有所收獲。請不吝指教。Michael 筆。

2014年3月9日 星期日

使用者導向設計 - User-Centered Design

最近在書架上看到一本很有趣的書
使用者導向設計
乍看之下是給設計師看的書,翻了幾頁之後才了解是對開發者的案件的討論比較多。主要在建議開發者多和使用者互動和討論,多了解使用者心意更能設計出好的產品。
傳統來說,討論電子產品和使用者的關係可以從人機互動介面 Human Computer Interface (HCI) 開始說起,一直延伸到近來非常熱門的 User Experience (UX) 的討論,這幾些和技術不太相關的名詞一直以來不被開發者重視,開發者多半會想把時間花在更好的技術討論上,殊不知如果多了解使用者的行為和心態,更可以創造出好的產品,也許不需要用到非常高深的技術。UCD 的討論就是從這樣的角度出發,試圖說服開發者,從另一個身份和角度來思考產品的設計。
UCD 主要重視 User-Centered Design 和 User Experience 這兩個討論主題,也就是從產生程式前的兩個要研究主題。
讓我們來介紹一下其中一個主題

與使用者合作

這是對於開發者而言,最難克服的一關卡。開發者在開會的時候,一聽到需求往往會先入為主的先思考每個需求會用到的技術是什麼?在平常有多時間開發者也會選擇去研究新的技術,而不會花時間去了解使用者,知道使用者的需求。這也是很正常的事,喜歡寫程式的人往往不喜歡和人打交道,同樣是要花時間為什麼不把時間多花在鑽研新的技術上面來的可能比較有更多的成就感。
UCD 要強調的是產品是給人用的,開發者如果可以多花一些時間在使用性價值或是了解使用者的心理上的話,對於開發一定是正面的幫助。愈了解使用者,開發出來的產品就會愈接近使用者的需求,開發者不妨試試先放下鍵盤,從另一個角度去思考,多和使用者聊天,也許就有意想不到的收獲。
一般我們可以把使用者分成幾個種類
  • 資訊提供狂
    • 這類的使用者提供過多的資訊,又不好意思請他們停止,最好是可以把資訊分類,還有設定優先順序,說真的,過份的時候可以暫時不理他們,不過,有提供想法總比什麼都沒有還好。
  • 控制狂
    • 這樣的使用者想要領導整個專案進行,他們也只是想要得到認同和尊重,往好處想,這類的使用者會主動找出好或不好的地方,有可能成為戰友。
  • 自以為代言者
    • 代言者的特色是提供負面的主意比正面的多,如果影響到團隊的進行,可以試圖請代言者以科學的方式和證據來佐證比主觀和個人的想法來的可以說服大家。
以上簡單介紹 UCD 重要的因素。有機會我們再來和大家討論更多的方法用來設計更符合人性的產品。最後還是提醒開發者,接近使用者,會有不一樣的靈感出現。

2013年8月9日 星期五

Node Pattern - Recursive Loop

今天要討論的主要是在 Asynchronous function 和 Loop 之間的關係。用過 Node.js 的朋友們,應該都知道 Node.js 的特色的 Nonblocking I/O 或者另一個說法是 Node.js 的寫法裡面充斥著許多的 callback function。這也是 Node.js 的優點之一,因為這個 Nonblocking 的特色可以讓 Node.js 只有一個 Thread 而遇到多個 I/O 存取的情況下還可以保持存取效能非常的高。
但是這樣的 Nonblocking 的特色會讓大部分的開發者,已經習慣 blocking 寫法的開發者,在開發 Node.js 應用的時候常常會遇到不知所措的錯誤發生。這篇文章就是要來討論其中一個很常見的錯的寫法。錯誤的寫法是在非常常用的 for-loop 上。
先把問題述敘一下:
利用 fs 這個 Module 來呈現,某個資料夾裡面所有的子資料夾的名稱。
我們會用到 fs 裡的 fs.readdir 和 fs.stat。假設我們產生一個 function 如下
function listDir (rootDir, callBack){
}
當我們在使用這個 listDir 的時候只要給開頭的 dirName 就會在 Console 呈現出,這個 dirName 裡面所有的子資料夾。於是我們準備一下測試用的資料夾。如下圖(藍色的為資料夾)
假設我們把上方提到的 fanit_pattern_for.js 這個檔案裡,同一個資料夾(NodePattern)裡有兩個空的子資料夾,各別是 folder1 和 folder2。如此一來寫程式前的準備動作就做好了。
程式方便同樣寫在 anti_pattern_for.js 裡面就要有使用 listDir 的程式碼。如下
listDir(".",function  (err, dirs) {
if(!err){
console.log("got folders : "+dirs);
}else{
console.log("error occurs : "+ err);
}
});

使用的時候就把第一個 rootDir 的名稱 "." ,這個 js 檔所存在的目錄當成根目錄,所以我們預期在 Console 的結果就是 
got folders : folder1,folder2

工作準備好,我們要來動手寫 listDir 的程式了。首先來看一下很常見的版本。但是是錯的 XD。

var fs = require("fs");

function listDir (dirName, callBack) {
    fs.readdir(dirName, function  (err, files) {
 if(err){
     callBack(err);
  return;
 }
 var dirs = [];
 for (var i = files.length - 1; i >= 0; i--) {
  fs.stat(dirName+"/"+files[i], function  (err, stats) {
   if(stats.isDirectory()){
    dirs.push(files[i]);
   }
  });
 };
 callBack(null, dirs);
    });
}
上面的程式碼。如果用
listDir(".",function  (err, dirs) {
if(!err){
console.log("got folders : "+dirs);
}else{
console.log("error occurs : "+ err);
}
});
來呼叫的話,會得到一個出乎我們意料的結果。
got folders
沒有任何的資料夾?怎麼會是這樣的結果?我們來檢視一下程式碼,看 for-loop 這個地方
for (var i = files.length - 1; i >= 0; i--) {
    fs.stat(dirName+"/"+files[i], function (err, stats) {
       if(stats.isDirectory()){
         dirs.push(files[i]);
       }
    });
};

看起來的複雜但可以簡單成如下
for (var i = files.length - 1; i >= 0; i--) {
   fs.stat(....);
};

就是做 fs.stat 很多次。也沒什麼奇怪,奇怪的是在 fs.stat 本身是 Nonblocking 的機制。整個 for-loop 會認為 fs.stat(); 已經結束,但是 fs.stat(); 裡面的 callback funciton。
fs.stat(dirName+"/"+files[i], function (err, stats) {
    if(stats.isDirectory()){
       dirs.push(files[i]);
    }
});
第二個參數就是 callback function 都還沒被呼叫,for-loop 就結束,然後就呼叫
callBack(null, dirs); 
因為整個執行的速度很快 dirs 就沒有值被寫入。就得到空的值。
對於習慣 blocking 寫法的開發者而言,這麼常用的寫法,竟然會在 Nonblocking 裡出錯,對於剛接觸 Node.js 的開發者應該相當的困擾。
那要怎麼解決呢?
讀者介紹一個常用的解決方法,不能用 for-loop 而改用遞迴的方式。如下先來看一種遞回方法來達到和 for-loop 一樣的效果。
基本原型長得像這樣。
function iterator (index) {
if(index < array.length){
asynchronous function  () {
// do something
iterator(index+1);
});
}else{
callBack();
}
} ;
iterator(0);    // 執行 iterator
更簡單的寫法可以寫成
(function iterator (index) {
if(index < array.length){
asynchronous function  () {
// do something
iterator(index+1);
});
}else{
callBack();
}
} )(0);
這樣的寫法,可以想成是 for-loop 的遞迴寫法。我們以一個 array 有三個元素來舉例,可以展開如下。
function iterator (0) {
function iterator (1) {
               function iterator (2) {
                }
        }
}
// 執行下一行程式
這樣的寫法除了可個保證是依照 index 一個一個執行之外,最後一個 index ( 在這邊是 2) 執行完之後,整個程式才可以執行下一行。我們把原本的 for-loop 版本有問題的地方改寫成如下
(function iterator (index) {
if(index < files.length){
fs.stat(rootDir+"/"+files[index], function  (err, stats) {
if(stats.isDirectory()){
dirs.push(files[index]);
}
iterator(index+1);
});
}else{
callBack(null, dirs);
}
})(0);

完整的程式如下。

var fs = require("fs");

function listDir (rootDir, callBack) {
  fs.readdir(rootDir, function  (err, files) {
    if(err){
 callBack(err);
   return;
    }
    var dirs = []; 
    (function iterator (index) {
 if(index < files.length){
   fs.stat(rootDir+"/"+files[index], function  (err, stats) {
     if (err) {
  callBack(err);
  return;
     };
     if(stats.isDirectory()){
  dirs.push(files[index]);
     }
     iterator(index+1);
   });
 }else{
   callBack(null, dirs);
 }
    })(0);
  });
}


listDir(".",function  (err, dirs) {
  if(!err){
 console.log("got folders : "+dirs);
  }else{
 console.log("error occurs : "+ err);
  }
});

就可以看到我們所預期的結果。
got folders : folder1,folder2 
以上程式碼都放在 GitHub。

2013年7月12日 星期五

程式設計初學者 - 書籍推薦

想踏進 iOS App 開發的世界,有沒有什麼好的書可以介紹的?無論是對於沒接觸過程式開發的朋友們?我推薦的是這本。可以在這裡買到
有寫過 iOS App 的朋友也許會問為什麼是 C ?而不是 Objective-C ?雖然 Objective-C 是寫 iOS 的程式語言,但有以下幾點原因,使得初學者不得不從 C 語言開始學起。
  1. 物件的觀念利用到了 C 語言的指標。
  2. 數值的運算 ( 比如,加減) 還是用到 C 語言的型別。
  3. iOS 底層的資料也是用 C 語言的型別儲存,比如通訊錄資料,照片,錄影檔。
  4. Objective-C 本身就是包裝好的 C 語言。
  5. C 語言比較單純
你可以這樣想像,Objective-C 這個語言是一個大集合,裡面包含了 C 語言這個小集合,而專門用來編譯 Objective-C 語言的 IDE Xcode 可是可以編譯 C, C++, Objective-C 三種語言。
回到書本上的內容,對於想學 iOS App 設計的朋友們,前兩章是必看的內容,第一章就教導了基本的程式語言的概念還有型別,if-else, for-loop, 等幾個重要的流程控制。第二章介紹了指標這個很重要的觀念,前文提到除了和物件有關之外,iOS App 的記憶體管理也是需要這個觀念。前兩章一定要看熟。如果還有一點時間可以看到第四章看完,讀懂,差不多就具備學習 iOS App 開發的前置工作了。
書本上,有介紹了用終端機,搭配任意的文字編輯器來編譯程式,對於有 Mac 的朋友們,接下來介紹如何用 Xcode 來編譯前四章的程式內容。
看到書裡面的內容對初學者來說,一開始是相當的苦惱,如 Chapter 1 第 2 頁的圖


什麼是 Source 又如何 Compile 之後的 Output 又如何應用和呈現?
這時借助 IDE 的功能是最方便的,對初學者來說可以免去許多的麻煩。對於有 Mac 的讀者,首先要先確定自己的作業系統有 Mac App Store,在左上角蘋果圖形

按下之後會出現 App Store 字樣

接著可以在視窗裡 Search "Xcode"
看到 Xcode 然後點選看到詳細內容如下圖
上圖 Installed 的按鈕是因為筆者己經安裝,如果未安裝則是 Free 字樣。Xcode 是免費的可以直接安裝。安裝中的時候,從 Mac App Store 下載的 App 都會被放在 LaunchPad。如下圖
接著下載完成之後,可以利用 Mac 的快速鍵 Ctrl + Space 打開右上的 Spotlight 來開啟 Xcode。如下步驟。
選擇開啟 Xcode 之後應該會如下畫面
因為我們要利用 Xcode 來練習書本中的例子,需要開一個專案或叫 Project。在如下的地方
或者在 Menu bar 上面也可以開啟新的專案。如下畫面。
兩個方法選一種,都可以看到下面畫面。
拉近一點看,我們看中間可以選擇的地方。
我需要簡單的 OS X Application,然後選擇 Command Line Tool 。接著按下右下角的 Next 看到如下畫面。

幾個設定說明一下

  • Product Name - 這個專案的名稱,沒有一定要寫什麼但是一定要寫。也就是這個專案資料夾的名稱
  • Organization Name - 組織名稱,也不是什麼重要的資料,如果沒有公司就任意寫
  • Company Identifier - 這個 ID 會影響到上架 App 到 App Store 販賣的時候 Apple 會檢查,目前是沒有要上架,就用任意的英文加上 . 來填入資料
  • Type - 這裡有幾個可以可選,目前是練習 C 語言所以選 C,如果是要練習 Objective-C 語言,就選 Foundation
  • Use Automatic Reference Counting - 是和記憶體管理有關,目前也不是重點。就選打勾就好了。
接著按 Next 按鈕。會看到如下畫面。

選擇一個目錄,儲存這個專案,也是一整個資料夾,就是 Product Name。接著會看到如下畫面。
這些都是專案的設定值,目前也用不到。接下來是我們要寫程式的主角,就是在左邊,很多個檔案有一個名稱叫 main.c 。如下畫面
拉近一點看,左邊是如下圖面
中間主要編輯畫面如下
這些程式碼是不是很像有看過?在哪呢?就在書中

還記得嗎?一開始書中提到 Source -> Compile -> Output 的 Source 
如果我們把 Xcode 的 main.c 的內容改成和 rocks.c 的內容一樣如下



int main(int argc, const char * argv[])
{
    puts("C Rocks");
    return 0;
}
上方是程式碼文字的部分
接著就是 Compile 的部分了。對於 Xcode 來說就是按下左上角的有點類似 Play 的符號。
這個就是 Compile 了。那 Output 呢?按下 Run 之後在 Xcode 下方應該會有一個視窗突然出現。
拉進一點看。如下
在 puts 裡面的內容被呈現在最下方的視窗裡。這個就是 Output 結果。
之後在書中的程式碼,都可以被放憂 main function 裡。也就是在

int main(int argc, const char * argv[])
{
和
return;
中間 比如接下來 Chapter 1 在第 3 頁的

int card_count = 11; 
if (card_count > 10)
    puts("The deck is hot. Increase bet.");
讀者將會在 Output 的地方看到
其他的練習就交給讀者了。只要有一個專案就可以練習書上所有的程式碼。這樣才可以理解書上說了什麼。
以上是用 Mac 的 Xcode 來建立一個方便初學者練習書上程式碼的方法,當然其他的平台也有其好用的 IDE 。比如跨平台的  Code::Block 等等。當然如果要用 Objective-C 開發 iOS App ,最推薦的,還是 Xcode。
最後提醒大家,想要學 iOS App 程式設計,前 2 個章節一定要看完哦。
有問題可以到粉絲團問問題。
祝各位初學者,學習快樂。

2013年6月4日 星期二

在 Apple 舉辦的開發者交流會 - Node.js

過了好久才把照片放上來 orz..
這次的主題是 Node.js 。
主要的原因是廣大的學員們除了開發 iOS App 之外多多少少都會想要接觸 Sever 端的程式。
或者要和 Server 端的開發者合作開發一個雲端和手持式整合的服務。
這時候了解 Server 在幹嘛也是一件可以幫助快速整合的方法。

主題:Server Side Application for iOS Developer - Node.js
時間:2013 5/2
地點:Taiwan Apple 大型會議室

首來先看一下大家等待的照片

接下來由小弟我為大家來個 Opening

Apple 人員來解說一下 Apple 在台灣提供相當多的服務,而不是只有賣東西而己,對於企業,和教育市場也投入很多的心思在裡面。企業內部有問題可以直接找他們。

接下來就是主題啦。Fin 上場了啦。
Fin 在 Web Front End 混了許久,因為 Node.js 是用 Javascript 來開發,對於前端工程師來說是很熟悉的一件事。對於最新的前端技術:HTML5、CSS3、Responsive Design、Node.JS等議題有深入的研究。在一年前就投入 Node.js 相關技術的開發工作。

Fin 很愛法國鬥牛犬


好啦,大家很高興的回家啦。
如果要再見到 Fin,可以參考他開的 Node.js 課程。
咱們下次再見了。