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.readdirfs.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 課程。
咱們下次再見了。

2013年2月25日 星期一

Mobile Game App 開發者交流會


Mobile Game App 開發者交流會

時間:2/25 9:30 ~ 12:00 A.M.
地點:Apple Taiwan


第三次的交流會,重點是放在 Mobile Game 的開發,可以利用 Corona SDK 這個工具很簡單快速地開發 iOS 和 Android 的 App 只要寫一次程式碼可以應用在多個裝置上。

這次很高興地邀請到 Wei Wei 來和我們分享他的人生歷程和開發歷程。從 Corona SDK 的使用方法

到 Hello World
和 Wei Wei 為了這個會議特地打造的 Game App
可見 Wei Wei 對於這個會議的用心並讓我們了解到 Corona SDK 的簡單和強大。
最後 Wei Wei 讓我們了解到 Corona SDK 並不是只能開發 Game 一般的 Application 也是可以辦到的。



雖然 Corona SDK 目前還有一些缺點,比如直接在 Device 上面 Debug 等,但是就 Game 而言,硬體的 Sensor 等等的支援不是這麼的必要。好玩最要緊。
當天的投影片,已放到網路上。http://www.slideshare.net/scentsome/superstar-dj-pdf
想跟著 Wei Wei 一起學習 Game App 的開發嗎?最新的課程 http://www.zencher.com/#game

花絮:
等待中的大家

聽課中的大家

2013年2月21日 星期四

局部畫面是要用 View 來呈現,還是要用 View Controller + View ?

我們先來看一下這個例子,預設的Yahoo 的股票軟體如下

這次討論的主角是在下半部,也就是大家看到的折線圖這個區塊。如下,這篇文章討論的是局部的畫面切換。

這個區塊如果要 Michael 來寫的話,是用 View 去呈現還是要再多寫一個 View Controller 呢?在下決定之前不能只看這個區塊,要再觀察之後的畫面,和使用者互動的過程,怎麼說呢?在這個 App,下方這個區塊,不是只可以呈現折線圖,還可以左右滑動來看其他的資訊,如下。

或者是如下

簡單畫一個Flow char 就是如下


好我們知道整個操作的流程之後,我們來分析一下。比較複雜和比較單純的畫面
就我理解,比較複雜的是如下
理由是,左邊要畫線圖還要和使用者互動,右邊要呈現滿多筆資料也要和使用者互動,這兩個畫面是用不同的邏輯思考去操作的。我們暫且稱,左邊的是 Chart View。右邊的是 List View。
比簡單的就是
這個畫面不用和使用者互動(暫不理會,底下的 Yahoo! 和 Information 按鈕,這兩個按鈕是把整個 Yahoo 股票首頁都換掉)。暫說這個為 Info View
看完整個流程,還是沒說要用 View 還是 View + View Controller 呢?
Michael 的看法是這樣

  • 如果只有一個畫面,也不切換,或是切換的畫面和 Info View 這樣比較簡單沒有互動的話,就用 View 就可以了
  • 如果切換過去的畫面,比較複雜如 List View 和 Chart View,這樣就建議用 View Controller + View。

再來就是說說,在 Storyboard 裡面,可以呈現切換局部的,View Controller 的新元件。叫 Container View 。如下
把這個元件的拉到某一個 View 上面會看到如下的畫面。
自動地產生一個 View Controller 在右邊,代表著這個 View Controller 的 View 就是會被畫在左邊 Container 的區塊。

2013年1月2日 星期三

Git : 動手做看看 - Xcode 裡的 Git

繼上篇我們了解檔案在 Git 管理的資料夾裡面的角色之後,來看一下 Xcode 是如何和 git 互動的。
首先開啟一個 Xcode project 選擇 Single View Application 如下圖。命名為 GitPractice 

選擇下一步,記得不要存在任何 git init 過的資料夾底下,要存在其他的資料夾,然後會有一個 source control 可以勾選。如下圖

接下來就會出現 Xcode 編輯的主畫面。如下 
看起來沒什麼特別的,我們用 Terminal 來檢視一下這個資料夾。
用 Terminal 移到這個 Xcode Project 的根目錄。
然後輸入
ls -al
會看到如下
total 16
drwxr-xr-x   6 chronoer  staff   204  1  2 19:20 .
drwx------+ 78 chronoer  staff  2652  1  2 19:18 ..
-rw-r--r--@  1 chronoer  staff  6148  1  2 19:20 .DS_Store
drwxr-xr-x  13 chronoer  staff   442  1  2 19:20 .git
drwxr-xr-x  13 chronoer  staff   442  1  2 19:17 GitPractice
drwxr-xr-x   5 chronoer  staff   170  1  2 19:17 GitPractice.xcodeproj
Xcode 自動對這個資料夾做了 git init 的動作了。我們再用
git log
來看看 Xcode 做了什麼事,會得到
commit 823888353b2d6d19426b56658b90db86f2f2ab8a
Author: chronoer <scentsome@gmail.com>
Date:   Wed Jan 2 19:17:17 2013 +0800
    Initial Commit 
Xcode 己經幫我們把 Single View Application 的檔案都加入給 git 管理並 commit 目前狀態為歷史的節點了。
接下來我們要看一下git 的狀態用
git status
會得到如下的結果

# On branch master
# Untracked files:
#   (use "git add <file>..." to include in what will be committed)
#
# GitPractice.xcodeproj/project.xcworkspace/
# GitPractice.xcodeproj/xcuserdata/
nothing added to commit but untracked files present (use "git add" to track)
原來 Xcode 預設沒有把這兩個資料夾加入 git 管理。那到底我們需不需要這兩個資料夾裡面的東西加入呢?當然是不需要,這些都是 Xcode 的衍生物和源始碼沒有太大的關係。好,那就這樣放著不管嗎? untracked files,如果之後有新增的 file 是想要加入 git 管理,又忘了加入,這樣就會和這兩個資料夾被列在 git status 的結果,一旦檔案多了,就會把真的想要加入和不想加入的搞混了,好像不怎麼好。git 有沒有方式可以設定,不想要 git 管理又不要出現在 untracked files 這邊的方法?有。要新增一個檔案名為 .gitignore。
所以我們新增 .gitignore 和 .git 同一個目錄下。內容如下

*.xcworkspace/
xcuserdata/
對應到上方紅色的兩個目錄。接著我們要把新增的 .gitignore 加入 git 管理然後 commit 做一個歷史記錄如下輸入
git add .gitignore
然後 commit
git commit .gitignore -m "add ignore file"
這樣一來,再做一次
git status
就會看到
# On branch master
nothing to commit (working directory clean)
這樣 git 穩定的狀態
除了這兩個資料夾,還有沒有要忽略的?有的。
那是哪些?其實我也是從 github 裡找到比較有名的幾個 project 來參考的。如下列出來給大家參考

.DS_Store
.localized
# Build directory
build/
# XCode user specific files
xcuserdata/
*.xcworkspace/
*.mode1v3
*.mode2v3
*.perspectivev3
*.pbxuser
output
可以當成一個 Xcode project 用 Git 要忽略的範本了。請加到 .gitignore 。然後再一次 git add ,git commit。
加了 ignore 之後,我們來看Xcode 如何處理 modified file 。首先在 AppDelegate.m 的 application:didFinishLaunchingWithOptions: 新增程式碼。如下

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    // Override point for customization after application launch.
    NSLog(@"first line");
    return YES;
}
我們新增一行 code 在上面綠色之處。存好檔之後會在 Xcode 左邊 project navigator 欄看到在 AppDelegate.m 旁邊有一個小 m 的符號。如下圖。
代表這個 AppDelegate.m 有被修改過。此時,我們在 Terminal 輸入以下指令
git status
會看到

# On branch master
# Changes not staged for commit:
#   (use "git add <file>..." to update what will be committed)
#   (use "git checkout -- <file>..." to discard changes in working directory)
#
# modified:   GitPractice/AppDelegate.m
#
no changes added to commit (use "git add" and/or "git commit -a")
和之前用 Terminal 改變 file 的一樣情況。AppDelegate.m 變成了 unstaged file。所以我們要怎麼做?git add or 直接 git commit 是吧?那是在 Terminal 的情況下,如果用了 Xcode 這種比較方便的 IDE 都會整合起來,比如在現在的情況下,只要在 AppDelegate.m 按下右鍵然後選 source control -> commit selected files 如下圖
然後就會跳出另一個視窗讓我們輸入此次歷史節點的註解。如下圖。
主要分幾個部分,最左邊是有修改過的檔案,中間是目前修改過的程式碼,右邊是之前最後一次的 commit 記錄。還可以往下拉看看有什麼其他的地方不一樣的。藍色的框框是小紅槓,標示著目前和最近 commit 兩個檔案不同之處,底下的紅色框框,就是寫註解的地方。如果按下右下 commit 1 file 這個按鈕,就會看到 Xcode 裡 AppDelegate.m 這個旁的小m 不見了,代表是己經 commit 的穩定狀態。如下圖
再把鏡頭轉到 Terminal,目前為止我們利用 Xcode 很方便的工具做了 git commit 的動作,用 Terminal 檢視一下,輸入
git log
出現

commit 47454d43e92257d81a3a26ed1e8c69a5b4cbafbcAuthor: chronoer <scentsome@gmail.com>
Date:   Wed Jan 2 21:53:58 2013 +0800
    add first line
commit 00544060eb268857bda27c13f392feaeb3d9ed09
Author: chronoer <scentsome@gmail.com>
Date:   Wed Jan 2 21:12:51 2013 +0800
    add ignore file
commit 823888353b2d6d19426b56658b90db86f2f2ab8a
Author: chronoer <scentsome@gmail.com>
Date:   Wed Jan 2 19:17:17 2013 +0800
    Initial Commit
最上方就是最新的,也就是剛剛用 Xcode commit 的結果。
如果修改了很多個檔案才 commit ,比如我們在 AppDelegate.m 和 ViewController.m 都加一些程式碼如下,我們看到有兩個小m。


commit 的時候可以選擇多個檔案之後,再用剛剛的做法,或是在 menu 上面 File -> Source Control -> Commit 也可以一次把所有有修改的檔案列出來。如下圖
然後會出現寫註解的畫面,最左邊也會列出有多少個 File 需要被 commit 如下圖。


可以勾選,也可以一一檢查改變的地方。Commit 之後我們用 Terminal 來檢查狀態如下輸入。
git log
會看到

commit e5f66ee471d2944f6faabfaac8256e748c5df3b9
Author: chronoer <scentsome@gmail.com>
Date:   Wed Jan 2 22:07:06 2013 +0800
    add code in appDelegate.m and ViewController.m
commit 47454d43e92257d81a3a26ed1e8c69a5b4cbafbc
Author: chronoer <scentsome@gmail.com>
Date:   Wed Jan 2 21:53:58 2013 +0800
    add first line
commit 00544060eb268857bda27c13f392feaeb3d9ed09
Author: chronoer <scentsome@gmail.com>
Date:   Wed Jan 2 21:12:51 2013 +0800
    add ignore file
commit 823888353b2d6d19426b56658b90db86f2f2ab8a
Author: chronoer <scentsome@gmail.com>
Date:   Wed Jan 2 19:17:17 2013 +0800
    Initial Commit
然後再輸入
git status
會看到

# On branch master
nothing to commit (working directory clean)
這樣又回到常態了。
在編輯程式碼的時候隨時都可以和之前 commit 過的版本做個比較,要用到 Xcode 右上的按鈕,如下圖
比如我們選定 AppDelegate.m 然後按下上面的按鈕就會看到如下畫面

中間編輯區有兩大塊,左邊就是目前的狀態,右邊就是最近 commit 的狀態,因為剛剛 commit 完沒有修改所以兩邊看起來一樣,而且還可以比較目前的和之前 commit 的狀態,可以按下上圖紅色地方,會長出一條黑黑的有很多間格。如下

然後可以變動圖中右邊的白色三角形,上圖紅色處,就可以看到之前 commit 版本和現在不一樣,移動到如下圖
如紅色所框左邊的 line 17 是和之前不一樣的地方。
還記得我們曾用 Terminal 利用
git checkout <commit id> <file name>
回到之前 commit 的版本吧?目前 Xcode 4 還沒支援直接 checkout 到舊的版本,不過可以從上圖看到某個版本的一些資訊包含 commit id ,然後再回到 Terminal 來 checkout 到舊的版本。
如下圖,mouse 移到中間白色橫槓,就會變大,然後出現資訊。
這就是 Xcode 的 Git 對檔案的基本操作,目前為止我們把所有檔案版存在 .git 檔裡,之後會再討論怎麼和 Server 溝通。我們下次見。