tag:blogger.com,1999:blog-34697544612801756712024-03-05T03:36:06.671-08:00Developer's Note麥克http://www.blogger.com/profile/01716179223350987115noreply@blogger.comBlogger63125tag:blogger.com,1999:blog-3469754461280175671.post-58831495568291154482016-02-24T21:11:00.002-08:002016-02-24T23:24:19.885-08:00Swift Vapor Framework on Heroku大家早,之前<a href="http://iosdevelopersnote.blogspot.tw/2016/02/swift-server-side-app-swift-package.html">文章</a>得到大家的喜愛,雖然沒有超過 300 人回覆,但有超過 300 人看過,筆者為了感謝大家的支持,想介紹如何把 Vapor 的專案放到 <a href="https://id.heroku.com/login">Heroku</a> 這個有名的平台上面。讀者應該也是面臨後 Parse 時代,正在尋找替代方案,筆者個人覺得 Heroku 是個不錯的選擇,也撐超過 5 年呢,應該還蠻可靠的。<br />
首先就是先註冊 <a href="https://id.heroku.com/login">Heroku</a> 帳號,登入之後,有個 heroku 的工具程式要下載,在<a href="https://devcenter.heroku.com/articles/getting-started-with-nodejs#set-up">這邊</a>下載。下載安裝完在 Terminal 執行 heroku login 就可以登入使用 heroku 的資源,大部分的設定都可以在本機,用 Heroku 的工具程式就可以完成,所以這個程式很重要。<br />
還記得上一篇的 Vapor 專案嗎?如下<br />
swiftServer/<br />
├── Package.swift<br />
├── Resources<br />
│ └── index.html<br />
└── Sources<br />
└── main.swift<br />
<br />
因爲我們要部署到 Heroku 所以需要一個 Procfile<br />
新增如下<br />
<br />
swiftServer/<br />
├── <b>Procfile </b><br />
├── Package.swift<br />
├── Resources<br />
│ └── index.html<br />
└── Sources<br />
└── main.swift<br />
<br />
Procfile 內容如下<br />
<br />
web: SwiftServer --port=$PORT<br />
<br />
簡單的一行,存檔就行了<br />
接著我們要把這個專案部署到 Heroku<br />
詳細的內容可以參考 <a href="https://devcenter.heroku.com/articles/git">Heroku 的教學</a><br />
<br />
接著我們先把 Vapor 專案用 Git 來管理<br />
在 Terminal 輸入<br />
git init<br />
得到<br />
Initialized empty Git repository in /Users/michael/Documents/project/heroku/<某資料夾>/.git/<br />
<br />
再輸入<br />
git add .<br />
目的是加入檔案到 git repo<br />
最後要 commit 輸入<br />
git commit -m "first on heroku"<br />
得到<br />
<br />
6 files changed, 42 insertions(+)<br />
create mode 100644 .DS_Store<br />
create mode 100644 Package.swift<br />
create mode 160000 Packages/vapor-0.2.3<br />
create mode 100644 Procfile<br />
create mode 100644 Resources/index.html<br />
create mode 100644 Sources/main.swift<br />
<br />
<br />
<br />
然後輸入 heroku create,藉以產生一個 heroku的專案,會看到如下結果。<br />
https://stark-journey-93380.herokuapp.com/ | https://git.heroku.com/stark-journey-93380.git其中 stark-journey-93380 是 heroku 隨機產生的專案名稱,之後還可以改。<br />
回到 Heroku 網頁看一下,會看到如下圖片<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj1lFTtish29WwQr4gRp7enX87jjlXHDgRYVf2TvcCIOV14gRe-Z0mdqQyeyXpPt04OFC4d4DJpZ-oW03GTC_JPAkSQVgAGj6n8oGDKWWOsMABMjxsb7gMNmGFJj7VSbbUV1PKPsD1QBVPs/s1600/heroku_app.png" imageanchor="1"><img border="0" height="176" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj1lFTtish29WwQr4gRp7enX87jjlXHDgRYVf2TvcCIOV14gRe-Z0mdqQyeyXpPt04OFC4d4DJpZ-oW03GTC_JPAkSQVgAGj6n8oGDKWWOsMABMjxsb7gMNmGFJj7VSbbUV1PKPsD1QBVPs/s640/heroku_app.png" width="640" /></a><br />
<br />
有看到一個同樣名稱的 Heroku App在上面。<br />
然後為了可以讓 Heroku 認得是 Swift 的專案<br />
要利用這個 heroku 的 <a href="https://github.com/kylef/heroku-buildpack-swift">buildpack</a><br />
在 Terminal 輸入<br />
<pre>heroku buildpacks:set https://github.com/kylef/heroku-buildpack-swift.git --app <專案名稱> </pre>
最後的專案名稱就是 heroku 一開始產生給我們的,在這個例子是 stark-journey-93380 <br />
得到如下回覆<br />
<br />
Buildpack set. Next release on stark-journey-93380 will use https://github.com/kylef/heroku-buildpack-swift.git.<br />
Run git push heroku master to create a new release using this buildpack.<br />
<br />
因為 Heroku 有提供 git 機制,所以我們要把這個 local 的 git 和 remote heroku 的 git 結合<br />
利用 git remote -v 來看一下遠端有什麼可以結合的 git repositoty<br />
會看到如下<br />
heroku https://git.heroku.com/stark-journey-93380.git (fetch)<br />
heroku https://git.heroku.com/stark-journey-93380.git (push)<br />
<br />
這個 local 的 Vapor 專案就是要和 stark-journey-93380結合<br />
輸入<br />
<pre><span class="function">heroku git:remote -a </span>stark-journey-93380</pre>
會看到<br />
set git remote heroku to https://git.heroku.com/stark-journey-93380.git<br />
代表結合成功了<br />
最後一個步驟就是把程式碼 push 到 Heroku 上面,輸入<br />
git push heroku master<br />
會得到如下回覆<br />
Counting objects: 10, done.<br />
Delta compression using up to 4 threads.<br />
Compressing objects: 100% (6/6), done.<br />
Writing objects: 100% (10/10), 1.22 KiB | 0 bytes/s, done.<br />
Total 10 (delta 0), reused 0 (delta 0)<br />
remote: Compressing source files... done.<br />
remote: Building source:<br />
remote: <br />
remote: -----> Fetching set buildpack https://github.com/kylef/heroku-buildpack-swift.git... done<br />
remote: -----> Swift app detected<br />
remote: Cloning into 'swiftenv'...<br />
remote: -----> Installing DEVELOPMENT-SNAPSHOT-2016-02-08-a<br />
remote: Downloading https://swift.org/builds/development/ubuntu1404/swift-DEVELOPMENT-SNAPSHOT-2016-02-08-a/swift-DEVELOPMENT-SNAPSHOT-2016-02-08-a-ubuntu14.04.tar.gz<br />
remote: DEVELOPMENT-SNAPSHOT-2016-02-08-a has been installed.<br />
remote: -----> Installing clang-3.7.0<br />
remote: -----> Building Package<br />
remote: Cloning https://github.com/tannernelson/vapor.git<br />
remote: Using version 0.2.4 of package vapor<br />
remote: Compiling Swift Module 'Vapor' (33 sources)<br />
remote: Linking Library: .build/release/Vapor.a<br />
remote: Compiling Swift Module 'SwiftServer' (1 sources)<br />
remote: Linking Executable: .build/release/SwiftServer<br />
remote: -----> Copying dynamic libraries<br />
remote: -----> Copying binaries to 'bin'<br />
remote: <br />
remote: -----> Discovering process types<br />
remote: Procfile declares types -> web<br />
remote: <br />
remote: -----> Compressing...<br />
remote: Done: 7.8M<br />
remote: -----> Launching...<br />
remote: Released v3<br />
remote: https://stark-journey-93380.herokuapp.com/ deployed to Heroku<br />
remote: <br />
remote: Verifying deploy... done.<br />
To https://git.heroku.com/stark-journey-93380.git<br />
* [new branch] master -> master<br />
<br />
有點長<br />
因為我們有寫 Heroku buildpack,所以除了把 code 放到 heroku 之外,heroku 還會利用 Swift Package Manager 去下載 compiler 再來 compiler 我們的 Vapor 專案<br />
在 Terminal 可以輸入<br />
heroku open<br />
就可以用預設 Browser 打開 Heroku App 會看到<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg7k9g6zZhiMcyNLoR1TyL10ps1x3fq2zpmUyjh7CYdihk_0yiXHD30PUeZ1sgV1RbKRhOqhY297s6QrAKS2JNqbQmAMyTh5ioS6EVrxO82tnPLkG3kA22QNzNZ85DohAtlzXvZcSaMx4Sg/s1600/%25E8%259E%25A2%25E5%25B9%2595%25E5%25BF%25AB%25E7%2585%25A7+2016-02-25+%25E4%25B8%258B%25E5%258D%25883.15.11.png" imageanchor="1"><img border="0" height="116" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg7k9g6zZhiMcyNLoR1TyL10ps1x3fq2zpmUyjh7CYdihk_0yiXHD30PUeZ1sgV1RbKRhOqhY297s6QrAKS2JNqbQmAMyTh5ioS6EVrxO82tnPLkG3kA22QNzNZ85DohAtlzXvZcSaMx4Sg/s640/%25E8%259E%25A2%25E5%25B9%2595%25E5%25BF%25AB%25E7%2585%25A7+2016-02-25+%25E4%25B8%258B%25E5%258D%25883.15.11.png" width="640" /></a><br />
<br />
為什麼事 Page not found 是因為我們沒有定義 / 要做什麼事<br />
所以我們更改路徑爲<br />
https://stark-journey-93380.herokuapp.com/welcome<br />
就會看到<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg724Qc0sS5Wvwlvuw2RGR4RXNAfp5buM7MF8ydS_rgLmy5Jj7HJ5mf4VGj1DepWdKTMN7mCee91WSpnLu2R8Tv6Wbc1x3vIjgTKuXZDbg2rfXqwWg7_fQ7wEyjx2MOu1-svl28plzjMpDC/s1600/%25E8%259E%25A2%25E5%25B9%2595%25E5%25BF%25AB%25E7%2585%25A7+2016-02-25+%25E4%25B8%258B%25E5%258D%25883.17.17.png" imageanchor="1"><img border="0" height="72" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg724Qc0sS5Wvwlvuw2RGR4RXNAfp5buM7MF8ydS_rgLmy5Jj7HJ5mf4VGj1DepWdKTMN7mCee91WSpnLu2R8Tv6Wbc1x3vIjgTKuXZDbg2rfXqwWg7_fQ7wEyjx2MOu1-svl28plzjMpDC/s640/%25E8%259E%25A2%25E5%25B9%2595%25E5%25BF%25AB%25E7%2585%25A7+2016-02-25+%25E4%25B8%258B%25E5%258D%25883.17.17.png" width="640" /></a><br />
再測試 https://stark-journey-93380.herokuapp.com/view<br />
就會看到<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgJ4JNqyuc6UIjpre0YJcgJBkRGZo1prfhB9NKLCQ3pVxkybUzHHzPRsjwbp9RqjwG6M7PbMuZXatOlbUoQb2a5VpkCkTHF9R28NvvZul4-tCyWidTUsLVo73CD5a8BgGrt3doWwxyfIOg1/s1600/%25E8%259E%25A2%25E5%25B9%2595%25E5%25BF%25AB%25E7%2585%25A7+2016-02-25+%25E4%25B8%258B%25E5%258D%25883.17.29.png" imageanchor="1"><img border="0" height="96" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgJ4JNqyuc6UIjpre0YJcgJBkRGZo1prfhB9NKLCQ3pVxkybUzHHzPRsjwbp9RqjwG6M7PbMuZXatOlbUoQb2a5VpkCkTHF9R28NvvZul4-tCyWidTUsLVo73CD5a8BgGrt3doWwxyfIOg1/s640/%25E8%259E%25A2%25E5%25B9%2595%25E5%25BF%25AB%25E7%2585%25A7+2016-02-25+%25E4%25B8%258B%25E5%258D%25883.17.29.png" width="640" /></a><br />
如此,部署完成。<br />
之後如果程式有什麼修改<br />
就用<br />
1. git commit -a<br />
2. git push heroku master<br />
<br />
這兩個指令就可以了<br />
如果要更改 Heroku app 名稱要用<br />
<pre><span class="function">heroku apps:rename <新的名稱></span></pre>
不過要注意名稱不能被其他 Heroku 使用者使用,會更改不成功,多注意回覆就好了。<br />
<br />
<br />麥克http://www.blogger.com/profile/01716179223350987115noreply@blogger.com0tag:blogger.com,1999:blog-3469754461280175671.post-3378634044278423642016-02-23T23:12:00.000-08:002016-02-23T23:12:34.251-08:00用 Swift 開發 Server Side App 並利用 Swift Package Manager - Vapor Framework首先和各位看官說聲 2016 新年快樂。希望大家在新的一年立訂新的目標。這篇文章要如何用 <a href="https://github.com/qutheory/vapor">Vapor</a> 這個 Framework 來開發 Server Side App。Vapor 使用 Swift 來架構而成,個人覺得使用起來比起 <a href="https://github.com/necolt/swifton">Swifton</a> 或是 <a href="https://github.com/IBM-Swift/Kitura">Kitura</a> 還有 <a href="http://perfect.org/">Perfect</a> 來的簡潔。有興趣的可以去多多比較。<br />
而 Vapor 剛好利用 Swift Package Manager 的方式來管理套件,有別於比較常見的 CocoaPods 和 Carthage 來管理,Vapor 使用 Apple 提出的 Swift Package Manager 方式來管理。<br />
要提醒讀者,Vapor 更新還蠻頻繁的所以 Google 到的寫法可能是舊的,請看好 Vapor 的版本,這篇用的是 0.2.3 。筆者會持續更新。<br />
<h4>
安裝 Swift 3.0 Dev </h4>
<br />
準備工具:Swift 3.0 Dev 從<a href="https://swift.org/download/">這邊</a>下載,目前 Swift 2.2 Release 還沒支援 Swift Package Manager 所以要下載 Latest Development Snapshot 。<br />
作業環境: OS X El Capitan 10.11.2<br />
下載完會看到一個 swift-DEVELOPMENT-SNAPSHOT-2016-02-08-a-osx.pkg<br />
點兩下安裝。如果沒有看到如下安裝畫面<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj5-7CHov2ct7TQ7ZVlSc-nYDkURct8qtFAmR3NMP-A2CgMMADWPnmxRL26zXknqqDudxkyquIqXj-uun9uZBFoj3a5h73ixat_wdbHUxntmSrlg9lfSMRs3T4CpKULSnBXbMj7Q8ajqP3K/s1600/%25E8%259E%25A2%25E5%25B9%2595%25E5%25BF%25AB%25E7%2585%25A7+2016-02-23+%25E4%25B8%258B%25E5%258D%25885.49.25.png" imageanchor="1"><img border="0" height="480" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj5-7CHov2ct7TQ7ZVlSc-nYDkURct8qtFAmR3NMP-A2CgMMADWPnmxRL26zXknqqDudxkyquIqXj-uun9uZBFoj3a5h73ixat_wdbHUxntmSrlg9lfSMRs3T4CpKULSnBXbMj7Q8ajqP3K/s640/%25E8%259E%25A2%25E5%25B9%2595%25E5%25BF%25AB%25E7%2585%25A7+2016-02-23+%25E4%25B8%258B%25E5%258D%25885.49.25.png" width="640" /> </a><br />
這是安全的考量,Mac 認為這個程式不是認證的開發者???<br />
要手動打開,系統偏好設定->安全性與隱私<br />
安裝好之後會在<br />
/Library/Developer/Toolchains/<br />
看到這幾個資料夾<br />
swift-DEVELOPMENT-SNAPSHOT-2016-02-08-a.xctoolchain/<br />
swift-latest.xctoolchain/<br />
之後就用 swift-latest.xctoolchain/ 就可以了<br />
試一下 Swift 版本<br />
打開 Terminal 輸入以下指令<br />
/Library/Developer/Toolchains/swift-latest.xctoolchain/usr/bin/swift --version<br />
看結果是不是以下<br />
Apple Swift version 3.0-dev (LLVM a7663bb722, Clang 4ca3c7fa28, Swift 1c2f40e246)<br />
Target: x86_64-apple-macosx10.9<br />
如果是 3.0-dev 就沒有問題了。<br />
為了省輸入時間可以在.profile 檔加上<br />
export PATH=/Library/Developer/Toolchains/swift-latest.xctoolchain/usr/bin:$PATH<br />
開啟 Terminal 就執行 .profile 這樣就直接用 swift 就好了。<br />
<h4>
建立 Vapor 專案</h4>
建立一個資料夾名為 swiftServer,其架構如下<br />
swiftServer/<br />
├── Package.swift<br />
├── Resources<br />
│ └── index.html<br />
└── Sources<br />
└── main.swift<br />
<br />
Package 內容如下<br />
<pre> <code class="objective-c">
import PackageDescription
let package = Package(
name: "SwiftServer",
dependencies: [
.Package(url: "https://github.com/tannernelson/vapor.git", majorVersion: 0)
]
)
</code></pre>
稍微解釋一下,Package.swift 的語法也是 Swift 一開始要 import PackageDescription 接著就可以用 Package 這個 class 有興趣的讀者可以<a href="https://github.com/apple/swift-package-manager/blob/a25ba1c21a449cae4b683a4d463fc021c2f97576/Sources/PackageDescription/Package.swift">在這</a>看到 Package 的全貌。majorVersion 爲 0 因為現在 Vapor 的版本是 0.2.1 majorVersion 爲 0 依照 source code 會找 0..<1 的版本<br />
<br />
main.swift 內容如下<br />
<pre> <code class="swift">
import Vapor
let app = Application()
app.get("welcome") { request in
return "Hello Vapor"
}
app.start(port:8080)</code> </pre>
Vapor 裡面有 Application 這個 class 也就 Server 一直執行的 Daemon 。然後綁定在 port 8080,<br />
接著我們利用 app.get 來定義 HTTP GET Method 的 Path。目在這個例子新增 /welcome 這個 Path。<br />
是時候在本機來執行這個 Vapor server 。<br />
在 Package.swift 同一個目錄下,在 Terminal 輸入以下指令。<br />
swift build<br />
<br />
成功之後會看到<br />
<br />
Cloning https://github.com/tannernelson/vapor.git<br />
Using version 0.2.3 of package vapor<br />
Compiling Swift Module 'Vapor' (32 sources)<br />
/Users/michael/Documents/project/swiftServer/Packages/vapor-0.2.3/Sources/Application+Route.swift:108:13: warning: variable 'handler' was never mutated; consider changing to 'let' constant<br />
var handler = { request in<br />
~~~ ^<br />
let<br />
Linking Library: .build/debug/Vapor.a<br />
Compiling Swift Module 'SwiftServer' (1 sources)<br />
Linking Executable: .build/debug/SwiftServer<br />
<br />
看到目前的 Vapor 版本為 0.2.3<br />
然後這理有個 Warning 是 Swift compiler 常見的 Warning ,建議把 var 改成 let 。雖然我們有 source code 但是還是不建議自己改,就給 Vapor 維護者在一個版本改。最後一行是 Linking Executable: .build/debug/SwiftServer 就是代表 compiler 完的執行檔。<br />
只要在 Terminal 直接執行<br />
.build/debug/SwiftServer<br />
會看到<br />
Server has started on port 8080<br />
<br />
然後打開本機的 Browser 在 URL 的地方輸入<br />
http://localhost:8080/welcome<br />
<br />
就會看到如下<br />
<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhTvmjHXQkH7QWeAv9uSJ2CCjIGSBagmOiDwk0UPUVysEL8j4hr7nilCjdai_mKydWEnHuotJa9M9wVJCq6b6VULxtNZXxgGGMERjLZj2QR1gY3YnB9cC4aa-LPDwl-UTPEjUiQ1kinL2fK/s1600/%25E8%259E%25A2%25E5%25B9%2595%25E5%25BF%25AB%25E7%2585%25A7+2016-02-24+%25E4%25B8%258B%25E5%258D%25882.35.52.png" imageanchor="1"><img border="0" height="242" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhTvmjHXQkH7QWeAv9uSJ2CCjIGSBagmOiDwk0UPUVysEL8j4hr7nilCjdai_mKydWEnHuotJa9M9wVJCq6b6VULxtNZXxgGGMERjLZj2QR1gY3YnB9cC4aa-LPDwl-UTPEjUiQ1kinL2fK/s640/%25E8%259E%25A2%25E5%25B9%2595%25E5%25BF%25AB%25E7%2585%25A7+2016-02-24+%25E4%25B8%258B%25E5%258D%25882.35.52.png" width="640" /></a><br />
<br />
幾件事提醒<br />
<br />
1. 如果有修改 code 要重新利用 swift build 來 compile 之前,請先用 rm .build/debug/SwiftServer 把舊的執行檔刪除。<br />
2. 程式碼的 app.start(port:8080) 請務必是最後一行。<br />
3. 也可以輸出 JSON 比如直接回傳 [ "name":"Michael"],留給讀者自行嘗試。 <br />
<br />
<h4>
HTML 結合 </h4>
輸出的結果有時候需要是 html ,Vapor 也可以輸出已經編寫好的 html 檔案,但必須放在 Resources 這個資料裡面,在看一次專案結構圖。<br />
swiftServer/<br />
├── Package.swift<br />
├── <b>Resources</b><br />
│ └── index.html<br />
└── Sources<br />
└── main.swift<br />
<br />
在這 index.html 的內容為
<br />
<pre><code class="html">
<html>
<body>
<h1>Vapor Server</h1>
This is tutorial for building Vapor Server.
</body>
</html>
</code></pre>
輸入好之後在 main.swift 新增一個 Path<br />
修改完如下
<br />
<pre> <code class="swift">
import Vapor
let app = Application()
app.get("welcome") { request in
return "Hello Vapor"
}
app.get("view") { request in
var view:View? = nil
do {
try view = View(path: "index.html")
}catch {
print("can not find html : error")
}
if let okView = view {
return okView
}
return "Error"
}
app.start(port:8080)</code> </pre>
我們新增了 /view 這個 path。
要注意的是 View 這個 class 的 init?(path:) 會丟出 error,所以要用 do-try-catch 來承接。<br />
最後把 unwrapped 的 okView 回傳就好了。<br />
先把 Server 停掉,ctrl+C <br />
記得<br />
1. rm .build/debug/SwiftServer<br />
2. swift build<br />
<br />
再重新執行 <br />
.build/debug/SwiftServer<br />
<br />
打開 Browser 輸入,http://localhost:8080/view<br />
會看到<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjfBgNf7Ej8wtk3APBgSnYLWSqG0ORBELxuFK34X8cF4rCU1HQDTDC9_BjeIH5Ahj96vFLT41RVbBCGkt1A_dsCzbb0A7hP5Jowx0POwr2TCYJVItubq-a2HGEP-WQasPEarlE2kt82tabN/s1600/%25E8%259E%25A2%25E5%25B9%2595%25E5%25BF%25AB%25E7%2585%25A7+2016-02-24+%25E4%25B8%258B%25E5%258D%25883.03.51.png" imageanchor="1"><img border="0" height="285" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjfBgNf7Ej8wtk3APBgSnYLWSqG0ORBELxuFK34X8cF4rCU1HQDTDC9_BjeIH5Ahj96vFLT41RVbBCGkt1A_dsCzbb0A7hP5Jowx0POwr2TCYJVItubq-a2HGEP-WQasPEarlE2kt82tabN/s640/%25E8%259E%25A2%25E5%25B9%2595%25E5%25BF%25AB%25E7%2585%25A7+2016-02-24+%25E4%25B8%258B%25E5%258D%25883.03.51.png" width="640" /></a><br />
<br />
如果這篇很多人回應我們再來分享一篇,如何結合 Database。很多人是多少呢?超過 300 人吧?試試人氣。<br />
<br />
<br />麥克http://www.blogger.com/profile/01716179223350987115noreply@blogger.com4tag:blogger.com,1999:blog-3469754461280175671.post-59928820340706713392015-10-12T06:18:00.000-07:002015-10-12T06:18:59.501-07:00螢幕的轉向<div class="p1">
<span class="s1"><span style="font-size: large;"><b>螢幕的轉向</b></span></span></div>
<div class="p2">
<span class="s1"></span><br /></div>
<div class="p3">
<span class="s1">要設定app支援的轉向,有兩個要素。分別是”裝置”與”View Controller”。兩者設定的交集,才是真正最後能呈現的轉向。</span></div>
<div class="p3">
<br /></div>
<div class="p3">
<b>裝置支援的方向</b></div>
<div class="p3">
<span class="s1">要設定裝置所支援的方向,只要在專案Device Orientation勾選要支援的方向。 </span></div>
<div class="p2">
<span class="s1"></span><br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgij0oGZC_AUo6yrmDjv4q-zDKeNVsKl7uZHG74B_E6s9LMVjwjGxpOWqnZdv0aJ0q-akWd1jf8karjY7yPDoeC27lw9i6EhZKJfcVkoAE0L7CvTI0kWCeyBNDdIJJngT7bkWS7KvhjBgkr/s1600/deviceOrientation.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="180" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgij0oGZC_AUo6yrmDjv4q-zDKeNVsKl7uZHG74B_E6s9LMVjwjGxpOWqnZdv0aJ0q-akWd1jf8karjY7yPDoeC27lw9i6EhZKJfcVkoAE0L7CvTI0kWCeyBNDdIJJngT7bkWS7KvhjBgkr/s400/deviceOrientation.png" width="400" /></a></div>
<div class="p2">
<br />
<span class="s1"></span></div>
<div class="p2">
<br /></div>
<div class="p3">
<span class="s1">其所修改後,其實是對應到Info.plist中,Supported interface orientations的設定。 </span></div>
<div class="p3">
<span class="s1"><br /></span></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh692-hHMjdReMtS4dijyjCowTnXOiafZbu-fZe1AMeHcljtzyo3B9OFKLhLvrc4Ec1dCsuivWgICVD9cIgdyjmYjI9daVhFO6-xMuhDa5-QF9Pt-ZlhAHN72dKaVafCxN0td5qXZCP4GU6/s1600/infoPList.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="63" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh692-hHMjdReMtS4dijyjCowTnXOiafZbu-fZe1AMeHcljtzyo3B9OFKLhLvrc4Ec1dCsuivWgICVD9cIgdyjmYjI9daVhFO6-xMuhDa5-QF9Pt-ZlhAHN72dKaVafCxN0td5qXZCP4GU6/s400/infoPList.png" width="400" /></a></div>
<div class="p3">
<span class="s1"><br /></span></div>
<div class="p2">
<span class="s1"></span><br /></div>
<div class="p2">
<span class="s1"></span><br /></div>
<div class="p4">
<span class="s1"><b>View Controller 支援的方向</b></span></div>
<div class="p3">
<span class="s1">View Controller以呈現類型,大致上有三種。分別是UINavigationController, UITabBarController與的UIViewController。</span></div>
<div class="p2">
<span class="s1"></span><br /></div>
<div class="p3">
</div>
<ul>
<li>UINavigationController</li>
</ul>
<br />
<blockquote class="tr_bq">
<span class="s1">若以UINavigationController管理多個UIViewController,則以UINavigationController統一管理支援的方向。</span></blockquote>
<div class="p3">
</div>
<ul>
<li>UITabBarController</li>
</ul>
<br />
<blockquote class="tr_bq">
<span class="s1">若以UITabBarController管理多個UIViewController,則以UITabBarController統一管理支援的方向。</span></blockquote>
<div class="p3">
</div>
<ul>
<li>UIViewController</li>
</ul>
<br />
<blockquote class="tr_bq">
<span class="s1">單獨的UIViewController則自己處理支援的方向。若前面UINavigationController或UITabBarController以presentViewController方式呈獻UIViewController,則此UIViewController不受UINavigationController或UITabBarController所管理。</span></blockquote>
<div class="p2">
<span class="s1"></span><br /></div>
<div class="p3">
<span class="s1">從iOS 7到目前的iOS9有兩個method要去實作,分別表示</span></div>
<div class="p3">
</div>
<ul>
<li>shouldAutorotate() -> Bool :表示是否支援轉向</li>
<li>supportedInterfaceOrientations() -> UIInterfaceOrientationMask :列出所支援的轉向</li>
</ul>
<br />
<div class="p2">
<span class="s1"></span><br /></div>
<div class="p4">
<span class="s1"><b>說明</b></span></div>
<div class="p3">
<span class="s1">若裝置支援的方向勾選Portrait, Landscape Left, Landscape Right。用如下支援轉向的method寫在不同地方做說明。</span></div>
<div class="p2">
<span class="s1"></span><br /></div>
<div class="p3">
<span class="s1"><span style="font-family: Courier New, Courier, monospace;"> override func shouldAutorotate() -> Bool {</span></span></div>
<div class="p3">
<span class="s1"><span style="font-family: Courier New, Courier, monospace;"> return true</span></span></div>
<div class="p3">
<span class="s1"><span style="font-family: Courier New, Courier, monospace;"> }</span></span></div>
<div class="p2">
<span style="font-family: Courier New, Courier, monospace;"><span class="s1"></span><br /></span></div>
<div class="p3">
<span class="s1"><span style="font-family: Courier New, Courier, monospace;"> override func supportedInterfaceOrientations() -> UIInterfaceOrientationMask {</span></span></div>
<div class="p3">
<span class="s1"><span style="font-family: Courier New, Courier, monospace;"> return [.Portrait]</span></span></div>
<div class="p3">
<span class="s1"><span style="font-family: Courier New, Courier, monospace;"> }</span></span></div>
<div class="p2">
<span class="s1"></span><br /></div>
<div class="p3">
</div>
<ul>
<li>使用UINavigationController,但支援轉向的method寫在其中的一個UIViewController中。進行轉向的結果如下。可以看出已被轉向。表示method限制只支援Portrait的設定無作用。</li>
</ul>
<div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg4dhYo5YroBN04w9ul-2T0mw0saCRCT4M0KM1UcReHCD314OcqrMsuCJcaPGp-jiRGDqclwWjXvsx-35sXeVm4RiSzkHcsSJloNFxJc19umZRbikQmlo5S3L1wSq-VZF7wNqVdsvrIlYvt/s1600/%25E8%259E%25A2%25E5%25B9%2595%25E5%25BF%25AB%25E7%2585%25A7+2015-10-11+%25E4%25B8%258B%25E5%258D%258811.02.09.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em; text-align: center;"><img border="0" height="226" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg4dhYo5YroBN04w9ul-2T0mw0saCRCT4M0KM1UcReHCD314OcqrMsuCJcaPGp-jiRGDqclwWjXvsx-35sXeVm4RiSzkHcsSJloNFxJc19umZRbikQmlo5S3L1wSq-VZF7wNqVdsvrIlYvt/s400/%25E8%259E%25A2%25E5%25B9%2595%25E5%25BF%25AB%25E7%2585%25A7+2015-10-11+%25E4%25B8%258B%25E5%258D%258811.02.09.png" width="400" /></a></div>
<div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<br />
<div class="p3">
</div>
<ul>
<li>使用UINavigationController,但新增繼承UINavigationController的Class,將支援轉向的method寫在裡面。並使用這個新增的class管理Navigation。可以看得出來,轉向被限制在Portrait有起作用。</li>
</ul>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjHRD2mIK6e0_rMp7UsFjzh17H2hEL_2f6awWlCc5tDyAoIP_TZQU4x13A2fyCwfVMdblgALYjB32ZaqKNZFceWOYH93-OY3s4d9mJklG6oB2ZXUQHNMFQ0ZmsoCd8VdJzuRw_otE9NEBeq/s1600/%25E8%259E%25A2%25E5%25B9%2595%25E5%25BF%25AB%25E7%2585%25A7+2015-10-11+%25E4%25B8%258B%25E5%258D%258810.59.34.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="225" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjHRD2mIK6e0_rMp7UsFjzh17H2hEL_2f6awWlCc5tDyAoIP_TZQU4x13A2fyCwfVMdblgALYjB32ZaqKNZFceWOYH93-OY3s4d9mJklG6oB2ZXUQHNMFQ0ZmsoCd8VdJzuRw_otE9NEBeq/s400/%25E8%259E%25A2%25E5%25B9%2595%25E5%25BF%25AB%25E7%2585%25A7+2015-10-11+%25E4%25B8%258B%25E5%258D%258810.59.34.png" width="400" /></a></div>
<div class="p2">
<span class="s1"></span><br /></div>
<div class="p2">
有興趣可以動手對UITabBarController做一樣的嘗試。可以發現,原來UINavigationController與UITabBarController除了管理UIViewController之外,還負責轉向的支援。</div>
<div class="p2">
<br /></div>
<div class="p2">
<span class="s1"></span><br /></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div class="p2">
<span class="s1"></span><br /></div>
<div class="p2">
<span class="s1"></span><br /></div>
<div class="p2">
<span class="s1"></span><br /></div>
<div class="p2">
<span class="s1"></span><br /></div>
<div class="p2">
<span class="s1"></span><br /></div>
<div class="p2">
<span class="s1"></span><br /></div>
<div class="p2">
<span class="s1"></span><br /></div>
<div class="p2">
<span class="s1"></span><br /></div>
<div class="p2">
<span class="s1"></span><br /></div>
<div class="p2">
<span class="s1"></span><br /></div>
<br />
<div class="p2">
<span class="s1"></span><br /></div>
</div>
</div>
Deanhttp://www.blogger.com/profile/07226049448566927643noreply@blogger.com0tag:blogger.com,1999:blog-3469754461280175671.post-54092456363705901592015-09-17T09:55:00.002-07:002015-10-05T10:35:34.356-07:00從 Swift 1.x 到 Swift 2.0從 Swift 1.x 到 Swift 2.0 轉換時候,有什麼要注意的呢?<br />
<br />
一般 Apple 的做法是新的 IDE 才會支援新的語法或是平台,所以要測試就拿新的 Xcode 7 來測試一下<br />
不過在測試之前要先來討論一下,用 Xcode 6 開發 和用 Xcode 7 開發差別在哪?<br />
對於開發者而言,最重要的事情就是 Xcode 可以支援的平台 OS 是幾版到幾版?<br />
以 Apple 的做法來說,非常舊版本的 Xcode。以這次 iOS 9 的更新來說我用 Xcode 6 就無法把 App 部署到 iOS 9 的 Device 上面,所以來比較一下可以部署的平台<br />
用 Xcode 7 打開一個專案如下可以看到<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjJvTh-ccPSXJsL8L4ZXsJKoON7-Bd99KxMGikjlOxBvgdEYTTh81E26xFJdUfF1s1Bkk0_Cs0sH-vBy8erDyDFFH8FrotRmg-MhAHzyw_FnP1cqJM21yuV6C55nP_G1JTgoKMhPH__5HQP/s1600/%25E8%259E%25A2%25E5%25B9%2595%25E5%25BF%25AB%25E7%2585%25A7+2015-09-18+%25E4%25B8%258A%25E5%258D%258811.05.13.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="390" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjJvTh-ccPSXJsL8L4ZXsJKoON7-Bd99KxMGikjlOxBvgdEYTTh81E26xFJdUfF1s1Bkk0_Cs0sH-vBy8erDyDFFH8FrotRmg-MhAHzyw_FnP1cqJM21yuV6C55nP_G1JTgoKMhPH__5HQP/s640/%25E8%259E%25A2%25E5%25B9%2595%25E5%25BF%25AB%25E7%2585%25A7+2015-09-18+%25E4%25B8%258A%25E5%258D%258811.05.13.png" width="640" /></a></div>
<br />
圖片無法 Copy 到下方的版本,Xcode 7 可以支援的就是<br />
iOS 6.0 - 9.1<br />
另外再來看一下 Xcode 6.4<br />
同樣的地方<br />
會看到是<br />
iOS 6.0 - 8.4<br />
換句話說,如果要寫 App 支援 iOS 6.0 之前的版本, Xcode 6.4也不能部署<br />
要開發 iOS 9 請用 Xcode 7<br />
<br />
<br />
<br />
<br />
接著回到正題,拿一個現成的 Project 來轉看看
這個 App 操作方式如下<br />
https://www.youtube.com/watch?v=gIyhpRXP8so<br />
先用 Xcode 7 打開
<br />
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgTkZrK8oVwWA1cHvoWKYgaMcgvwJve1rolpsnJDWxGybaOhXkJ_M7J1RMQRWST9YyK45Y9ruaCqoQGMs7rF5cfQFLYQueec_gs65Tsk0Hw2Eybq1jzJabVfX6vPfgDklENXZkjiN7E_KHu/s1600/%25E8%259E%25A2%25E5%25B9%2595%25E5%25BF%25AB%25E7%2585%25A7+2015-09-17+%25E4%25B8%258B%25E5%258D%258811.54.06.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="388" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgTkZrK8oVwWA1cHvoWKYgaMcgvwJve1rolpsnJDWxGybaOhXkJ_M7J1RMQRWST9YyK45Y9ruaCqoQGMs7rF5cfQFLYQueec_gs65Tsk0Hw2Eybq1jzJabVfX6vPfgDklENXZkjiN7E_KHu/s640/%25E8%259E%25A2%25E5%25B9%2595%25E5%25BF%25AB%25E7%2585%25A7+2015-09-17+%25E4%25B8%258B%25E5%258D%258811.54.06.png" width="640" /> </a></div>
<div class="separator" style="clear: both; text-align: left;">
就會看到轉換成最新 Swift 語言的提示</div>
<div class="separator" style="clear: both; text-align: left;">
接著按下一步</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgW6cRreWzFqkcQxDnY-61NoZZWQVfaPpC-1EW7ICJWOq1y4h7i72W8Zk7BO28a5onkMajeiA-c7_IFKi6dDbgG0LUi0Yh7tRpdRILFmLLMJWFL5Tw0JACbJiwfG76xry8C1_LMEtNDR8As/s1600/%25E8%259E%25A2%25E5%25B9%2595%25E5%25BF%25AB%25E7%2585%25A7+2015-09-17+%25E4%25B8%258B%25E5%258D%258811.54.13.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="388" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgW6cRreWzFqkcQxDnY-61NoZZWQVfaPpC-1EW7ICJWOq1y4h7i72W8Zk7BO28a5onkMajeiA-c7_IFKi6dDbgG0LUi0Yh7tRpdRILFmLLMJWFL5Tw0JACbJiwfG76xry8C1_LMEtNDR8As/s640/%25E8%259E%25A2%25E5%25B9%2595%25E5%25BF%25AB%25E7%2585%25A7+2015-09-17+%25E4%25B8%258B%25E5%258D%258811.54.13.png" width="640" /></a></div>
<div class="separator" style="clear: both; text-align: left;">
接著選擇要檢視的 Target</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjo4KMhZx_LG8J897feAU243TT9l8ynXM1FAeWHvilBPXn6j4Yovg80BcDRoUeg6lK83TMsrbiUF_qh-7Fs0e-ijkOPr8-sxJxZcAByB3Ur23x8eSeSVL1UYZQhpTz0yew2puMU3a73JbHV/s1600/%25E8%259E%25A2%25E5%25B9%2595%25E5%25BF%25AB%25E7%2585%25A7+2015-09-17+%25E4%25B8%258B%25E5%258D%258811.54.17.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="390" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjo4KMhZx_LG8J897feAU243TT9l8ynXM1FAeWHvilBPXn6j4Yovg80BcDRoUeg6lK83TMsrbiUF_qh-7Fs0e-ijkOPr8-sxJxZcAByB3Ur23x8eSeSVL1UYZQhpTz0yew2puMU3a73JbHV/s640/%25E8%259E%25A2%25E5%25B9%2595%25E5%25BF%25AB%25E7%2585%25A7+2015-09-17+%25E4%25B8%258B%25E5%258D%258811.54.17.png" width="640" /></a></div>
<div class="separator" style="clear: both; text-align: left;">
PS. 如果沒有自動出現的話可以手動啟動這個 Convert 機制如下圖</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhQhOtNKk6Ljoh23PCdDgX0zS1rlwAGrrFWW22CLbIJUFX-NN-XxRBBkRaEmyMIPNuPUiFyGWOadhba1KBcbmgEDbQsKXPc3WmO4HfEFzB06i8EOlRVwnJsZmyIt8fQ6nV8K3gvyuuwxQ29/s1600/%25E8%259E%25A2%25E5%25B9%2595%25E5%25BF%25AB%25E7%2585%25A7+2015-09-19+%25E4%25B8%258A%25E5%258D%25881.21.24.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="390" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhQhOtNKk6Ljoh23PCdDgX0zS1rlwAGrrFWW22CLbIJUFX-NN-XxRBBkRaEmyMIPNuPUiFyGWOadhba1KBcbmgEDbQsKXPc3WmO4HfEFzB06i8EOlRVwnJsZmyIt8fQ6nV8K3gvyuuwxQ29/s640/%25E8%259E%25A2%25E5%25B9%2595%25E5%25BF%25AB%25E7%2585%25A7+2015-09-19+%25E4%25B8%258A%25E5%258D%25881.21.24.png" width="640" /></a></div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
接著就會出現比較的畫面,我們一一來看一下</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEji1Br25KBgfRqmpKPAh-7JrYPbKvp-mVo6edEsD4mo6XWV0JHHA8PH7FXJOiSWZZm_Zz-LffxKIgn6hfK9N1Pz8rMmHZL6iQwwxEmQgkX-HR_TPZITqrs3HW1jsrMtyCWxP5TQlHuMWoZZ/s1600/%25E8%259E%25A2%25E5%25B9%2595%25E5%25BF%25AB%25E7%2585%25A7+2015-09-18+%25E4%25B8%258A%25E5%258D%258812.19.09.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="336" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEji1Br25KBgfRqmpKPAh-7JrYPbKvp-mVo6edEsD4mo6XWV0JHHA8PH7FXJOiSWZZm_Zz-LffxKIgn6hfK9N1Pz8rMmHZL6iQwwxEmQgkX-HR_TPZITqrs3HW1jsrMtyCWxP5TQlHuMWoZZ/s640/%25E8%259E%25A2%25E5%25B9%2595%25E5%25BF%25AB%25E7%2585%25A7+2015-09-18+%25E4%25B8%258A%25E5%258D%258812.19.09.png" width="640" /></a></div>
<div class="separator" style="clear: both; text-align: left;">
第一個我們看到的不一樣</div>
<div class="separator" style="clear: both; text-align: left;">
<b>Swift 2.0 之後,都用 print() 來取代之前的 print() 和 println()</b></div>
<div class="separator" style="clear: both; text-align: left;">
<b>差別在 2.0 的 </b></div>
<blockquote class="tr_bq">
<div class="separator" style="clear: both; text-align: left;">
<span style="color: blue;"><b>print() </b></span></div>
</blockquote>
<div class="separator" style="clear: both; text-align: left;">
<b>代表 1.x 的 </b></div>
<blockquote class="tr_bq">
<div class="separator" style="clear: both; text-align: left;">
<b>println()</b></div>
</blockquote>
<div class="separator" style="clear: both; text-align: left;">
<b>而 1.0 的 </b></div>
<blockquote class="tr_bq">
<div class="separator" style="clear: both; text-align: left;">
<b>print() </b></div>
</blockquote>
<div class="separator" style="clear: both; text-align: left;">
<b>在 2.0 要寫成 </b></div>
<blockquote class="tr_bq">
<div class="separator" style="clear: both; text-align: left;">
<b>print("Hello", terminator: "")</b></div>
</blockquote>
<div class="separator" style="clear: both; text-align: left;">
<b>或者你也可以想成 2.0 的 </b></div>
<blockquote class="tr_bq">
<div class="separator" style="clear: both; text-align: left;">
<b>print() </b></div>
</blockquote>
<div class="separator" style="clear: both; text-align: left;">
<b>其實是</b></div>
<blockquote class="tr_bq">
<div class="separator" style="clear: both; text-align: left;">
<span style="color: blue;"><b> </b><b>print("Hello", terminator: "\n") </b></span></div>
</blockquote>
<div class="separator" style="clear: both; text-align: left;">
<b>的簡寫</b></div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
接著看下一個</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEieXP1i0seTSn50biKw4xcht0zxn1RNavnBj25W4tmyvUU477j-SNQOPhTGpxpzts3Kwi1yWtcLJgDCKLWoKDkJlB72TTkwnxQNCmoAu4Oi1SqSNG9RAC9Wpm0QwSjayd3ShXTGi9cka48w/s1600/%25E8%259E%25A2%25E5%25B9%2595%25E5%25BF%25AB%25E7%2585%25A7+2015-09-18+%25E4%25B8%258A%25E5%258D%258812.28.17.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="352" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEieXP1i0seTSn50biKw4xcht0zxn1RNavnBj25W4tmyvUU477j-SNQOPhTGpxpzts3Kwi1yWtcLJgDCKLWoKDkJlB72TTkwnxQNCmoAu4Oi1SqSNG9RAC9Wpm0QwSjayd3ShXTGi9cka48w/s640/%25E8%259E%25A2%25E5%25B9%2595%25E5%25BF%25AB%25E7%2585%25A7+2015-09-18+%25E4%25B8%258A%25E5%258D%258812.28.17.png" width="640" /></a></div>
<div class="separator" style="clear: both; text-align: left;">
<b> </b></div>
<div class="separator" style="clear: both; text-align: left;">
這點是如果 var some = "Hello",對於 some 這個變數,如果之後的程式碼沒有用到,Xcode 就會強烈建議把 var 改成 let ,也就是會自動把 </div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<blockquote class="tr_bq">
var some = "Hello"</blockquote>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
轉成</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<blockquote class="tr_bq">
<div class="separator" style="clear: both; text-align: left;">
<span style="color: blue;">let</span> some = "Hello"</div>
</blockquote>
<br />
<div class="separator" style="clear: both; text-align: left;">
接著看下一個</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhwluULKtPp28Gl1aFR83MKZiKeYJfKNof7dnR1w8noOH-UEyR1SLbGxbsMaOF_whMMfiQQ8s_ugOEgCP-Iqobp7b5DA5GatZWlu8D_Ph74-3R-cpYaVGCdfvOI41vKo2c94Iix5d9ryHBP/s1600/%25E8%259E%25A2%25E5%25B9%2595%25E5%25BF%25AB%25E7%2585%25A7+2015-09-18+%25E4%25B8%258A%25E5%258D%258812.32.32.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="352" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhwluULKtPp28Gl1aFR83MKZiKeYJfKNof7dnR1w8noOH-UEyR1SLbGxbsMaOF_whMMfiQQ8s_ugOEgCP-Iqobp7b5DA5GatZWlu8D_Ph74-3R-cpYaVGCdfvOI41vKo2c94Iix5d9ryHBP/s640/%25E8%259E%25A2%25E5%25B9%2595%25E5%25BF%25AB%25E7%2585%25A7+2015-09-18+%25E4%25B8%258A%25E5%258D%258812.32.32.png" width="640" /></a></div>
<div class="separator" style="clear: both; text-align: left;">
這個原因是</div>
<div class="separator" style="clear: both; text-align: left;">
在 1.x 的時候,有一個 </div>
<blockquote class="tr_bq">
<div class="separator" style="clear: both; text-align: left;">
var name:String = "Michael" </div>
</blockquote>
name 的型別是 String<br />
如果我們想要用 for-in 走訪這個 string 把大寫的字找出來<br />
在 1.x 會這樣寫<br />
<br />
<blockquote class="tr_bq">
for c:Character in <b>name</b> {<br />
var str = "\(c)"<br />
if str == str.uppercaseString {<br />
print("\(str) is uppercase ")<br />
}<br />
}</blockquote>
<br />
直接把 name 好像當成集合用, <br />
而在 2.0 會改成<br />
<blockquote class="tr_bq">
for c:Character in <span style="color: blue;"><b>name.characters</b></span> {<br />
var str = "\(c)"<br />
if str == str.uppercaseString {<br />
print("\(str) is uppercase ")<br />
}<br />
} </blockquote>
這樣比較合理一點,用 characters 來回傳一個由 Character 所組成的 Array 用 for-in來 iterate 所有的元素<br />
<br />
<br />
有學員提供這個問題<br />
在 1.x 的時候這樣寫<br />
<br />
<blockquote class="tr_bq">
var urlStr = "http://www.apple.com"<br />
<br />
var iphoneURL = <span style="color: blue;">urlStr.stringByAppendingPathComponent("iphone")</span></blockquote>
<br />
stringByAppendingPathComponent 本來是 NSString 常用的 method<br />
Swift 的 String 也是可以用 <br />
但是這樣 urlStr 裡面是 http:// 開頭的,也就是意圖要處理一個 URL<br />
此時用 Xcode 7 打開,Convert 到新的 Syntax 就會出現下圖<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhZGdgZ0NHxWBZhg6YfqgH6DAXEvMmfAkcumcwzZnaZ5SP_D1dMdtakD6s1hy8mr6DANr64TN7l0VUbeOCKAgqTYgFXSuNzLiCAe4b0Q_SU8nPmbfTEqnQU-B1otAv1EZaTCfrZ_ZHYlbmw/s1600/%25E8%259E%25A2%25E5%25B9%2595%25E5%25BF%25AB%25E7%2585%25A7+2015-09-19+%25E4%25B8%258A%25E5%258D%258812.55.58.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="78" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhZGdgZ0NHxWBZhg6YfqgH6DAXEvMmfAkcumcwzZnaZ5SP_D1dMdtakD6s1hy8mr6DANr64TN7l0VUbeOCKAgqTYgFXSuNzLiCAe4b0Q_SU8nPmbfTEqnQU-B1otAv1EZaTCfrZ_ZHYlbmw/s640/%25E8%259E%25A2%25E5%25B9%2595%25E5%25BF%25AB%25E7%2585%25A7+2015-09-19+%25E4%25B8%258A%25E5%258D%258812.55.58.png" width="640" /></a></div>
<br />
也就是目前 Xcode 7 建議先轉成 NSString 再做 stringByAppendingPathComponent<br />
這樣一來可以正常的執行<br />
如果不聽,在 Xcode 7 就會直接給錯誤如下圖<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjconxRPXquZvLD8_Y8UCnBnp3w1gDpnRUvSE6DQ1llhQVIkGU-fYc2YOP6Q32zQHFp2sj5mHJ-N4XJ5eYFSp5PasChcBl51MkVjbQXcvna6DPO2kjdU3Uh8KkikqldQI71AaQwd2Jidg1S/s1600/%25E8%259E%25A2%25E5%25B9%2595%25E5%25BF%25AB%25E7%2585%25A7+2015-09-19+%25E4%25B8%258A%25E5%258D%258812.59.10.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="70" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjconxRPXquZvLD8_Y8UCnBnp3w1gDpnRUvSE6DQ1llhQVIkGU-fYc2YOP6Q32zQHFp2sj5mHJ-N4XJ5eYFSp5PasChcBl51MkVjbQXcvna6DPO2kjdU3Uh8KkikqldQI71AaQwd2Jidg1S/s640/%25E8%259E%25A2%25E5%25B9%2595%25E5%25BF%25AB%25E7%2585%25A7+2015-09-19+%25E4%25B8%258A%25E5%258D%258812.59.10.png" width="640" /></a></div>
它就直接出現錯誤,stringByAppendingPathComponent 不可以在 String 用了,建議用 NSURL 來處理 path component 的問題<br />
<br />
<br />
<br />
String 的 toInt() 被拿掉了如下圖<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgIYz314L_n367KEXcR6MG6BekGKbxNc4vON1TodjwXV0OrFohFHVjiq4lC0XpccNO5vgHvgQhr3f5fVspMkrDDFq5G0OXS0BWS6jHSM_QLtiBSxy3__vzvG-DCVnm13aoInqeHBiNW1Z1z/s1600/%25E8%259E%25A2%25E5%25B9%2595%25E5%25BF%25AB%25E7%2585%25A7+2015-10-05+%25E4%25B8%258A%25E5%258D%258810.30.47.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="34" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgIYz314L_n367KEXcR6MG6BekGKbxNc4vON1TodjwXV0OrFohFHVjiq4lC0XpccNO5vgHvgQhr3f5fVspMkrDDFq5G0OXS0BWS6jHSM_QLtiBSxy3__vzvG-DCVnm13aoInqeHBiNW1Z1z/s640/%25E8%259E%25A2%25E5%25B9%2595%25E5%25BF%25AB%25E7%2585%25A7+2015-10-05+%25E4%25B8%258A%25E5%258D%258810.30.47.png" width="640" /></a></div>
在 2.0 如果要把數字集合而成的 String 轉成 Int 要就要用 Int 的 constructor<br />
<blockquote class="tr_bq">
Int("12345")</blockquote>
<br />
<br />
<span style="color: #38761d;">/* by MusiCabbage */</span><br />
好康道相報!! SequenceType 本來一些<strike>奇怪的</strike>方法,變得更直覺了!!<br />
<br />
例如說,本來我們想知道Array中有沒有某個Object,<br />
必需這樣寫…<br />
<br />
var array = ["a", "b", "c", "d", "e"]<br />
<br />
<div class="p1">
<span class="s1"><span style="color: #cc0000;">contains(array, "a")</span></span></div>
<br />
在 swift 2.0 的世界裡,我們只需要這樣寫…<br />
<br />
<span style="color: blue;">array.contains("a")</span><br />
<br />
類似的做法,CollectionType 裡面那些<strike>奇怪的</strike>方法,也一起跟進了!!<br />
以前如果想要找到某個Object在Array中的Index,<br />
是這樣寫…<br />
<br />
<span style="color: #cc0000;">var index = find(array, "c")</span><br />
<span style="color: #cc0000;"><br /></span>
在 swift 2.0,完全換了個方法,但更容易懂了…<br />
<br />
<span style="color: blue;">array.<span class="s2">indexOf</span><span class="s1">("c")</span></span><br />
<span class="s1"><br /></span>
<span class="s1"><br /></span>
<br />
<br />
<br />
持續更新中<br />
<br />麥克http://www.blogger.com/profile/01716179223350987115noreply@blogger.com1tag:blogger.com,1999:blog-3469754461280175671.post-49418286007178660832014-12-07T22:03:00.002-08:002014-12-07T22:12:06.729-08:00Swift - Struct 與 Class 的差異性Hi, There。今天這個篇文章要來介紹 Swift 中 struct 和 class 有什麼不一樣的地方?首先要先和大家提到一個觀念,Value Type 和 Reference Type 其中 struct 是 Value Type 而 class 是 Reference Type 所以這篇文章呈現的 struct 的行為也可以套用到所有的 value type 物件,相同地 class 的行為也可以套用到 reference type 的物件上。<br />
<br />
我們來建立一個自已的 struct 名稱為 SRectangle 。程式碼如下呈現。<br />
<pre><code class="swift">
struct SRectangle {
var width = 200
}
</code></pre>
這個 struct 有一個 property 名為 width 。<br />
再來建立一個 class 名為 CRectangle 也有一個叫做 width 的 property 程式碼如下
<br />
<pre><code class="swift">
class CRectangle {
var width = 200
}
</code></pre>
準備好了,我們先來看第一個差異點
<br />
<h3>
1. 建構物件時</h3>
<h4>
struct
</h4>
<pre><code class="swift">
var sRect = SRectangle()
// 或者
sRect = SRectangle(width:300)
sRect.width // 結果就是 300
</code></pre>
<hr />
<h4>
class
</h4>
<pre><code class="swift">
var cRet = CRectangle()
// class 不能直接用 CRectangle(width:300) 必需要定義一個 constructor
cRect.width // 為 200
</code></pre>
<hr/>
主要的差別就是 class 在產生物件時不能很自然把 property 放在 constructor 的參數裡
<h3>2. 指定給另一個變數的行為不同 </h3>
<h4>struct</h4>
<pre><code class="swift">
var sRect = SRectangle()
// 或者
var sRect2 = sRect
sRect2.width // 目前值是 200,因為 sRect 直接 copy 一份完整記憶體給 sRect2
sRect2.width = 500
sRect.width // sRect.width 值不受 sRect2 影響還是 200
</code></pre>
<hr/>
<h4>class</h4>
<pre><code class="swift">
var cRect = CRectangle()
// 或者
var cRect2 = cRect
cRect2.width // 目前值是 200,因為 sRect 直接 copy 一份完整記憶體給 sRect2
cRect2.width = 500
cRect.width // cRect.width 也改變成了 500
</code></pre>
<hr/>
以上是 value type 和 reference type 最大的不同,在每次做 assignment 的時候,value type 都會複製一份完整相同的內容給另一個變數,而 class 則是把記憶體的位置給變數。所以 reference type 大多會保有一份記憶體而重覆利用。
<h3>
3. 關於 immutable 變數 </h3>
在 Swift 這個語言的特色之一就是可變動內容的變數和不可變動內容的變數用 var 和 let 來區別。如果一開始用 let 來修飾變數就會造成 compiler 的錯誤。如下
<pre><code class="swift">
var name = "Michael"
name.write(" Pan") // 這樣是正常
</code></pre>
<pre><code class="swift">
let name = "Michael"
name.write(" Pan") // 會造成 compile 錯誤
</code></pre>
<h4>struct</h4> 大至也遵循這個規則如下
<pre><code class="swift">
let sRect = SRectangle()
sRect.width = 500 // 會造成錯誤
</code></pre>
<hr />
<h4>
class
</h4>
<pre><code class="swift">
let cRect = CRectangle()
cRect.width = 500 // 預設可以直接更改雖然是 let 修飾的 cRect
</code></pre>
<hr/>
let 這個效果無法延申至 class 。不過要提醒大家在 Swift 常用的 String, Array, Dictionary 都是 struct 所以 let 是會有效果的
<h3>4. mutating function </h3>
我們先要為 struct 和 class 新增一個 method 名為 changeWidth()
而且我們在不改變原始的程式碼為前提下,新增一個 method。如下
<h4> struct </h4>
<pre><code class="swift">
struct SRectangle {
var width = 200
}
extension SRectangle {
mutating func changeWidth(width:Int){
self.width = width
}
}
</code></pre>
<hr />
<h4> class </h4>
<pre><code class="swift">
class CRectangle {
var width = 0
}
extension CRectangle {
func changeWidth(width:Int){
self.width = width
}
}
</code></pre>
<hr />
這裡用了 extension 新增 class 裡的 method 而不需要改到原本的程式碼。
以。struct 和 class 的差別是 struct 的 function 要去改變 property 的值的時候需要加上 mutating 字樣,而 class 不用這樣做
<h3>5. Struct 和 Class 裡的 function 共同特色 - Implicit External Parameter Name </h3>
function 在定義時,有一種特別的寫法叫做 external parameter name 舉例來說
一般寫法
<pre><code class="swift">
func setSize( width:Int, height:Int) {
println("width \(width), height \(height)")
}
setSize(50, 100) // 直接丟入參數
</code></pre>
External Parameter Name
<pre><code class="swift">
func setSize(width width:Int, height height:Int) {
println("width \(width), height \(height)")
}
setSize(width:50, height:100) // 必需在參數值前加上 external parameter name
</code></pre>
另一個簡寫
<pre><code class="swift">
func setSize(#width:Int, #height:Int) {
println("width \(width), height \(height)")
}
setSize(width:50, height:100) // 當 External name 和 internal name 一樣的時候可以在 internal name 前面加上 # 就可以少寫 external name
</code></pre>
這樣的行為對於一般 function 並沒有強制一定要寫 external name 不過在 struct 和 class 裡是強制使用的。如下
<h4> struct </h4>
<pre><code class="swift">
struct SRectangle {
var width = 200
}
extension SRectangle {
mutating func changeWidth(width:Int){
self.width = width
}
func setName(name:String, width:Int) {
println("name \(name), width is \(width)")
}
}
var sRect = SRectangle()
sRect.setName("Michael", width: 500)
</code></pre>
<hr />
<h4> class </h4>
<pre><code class="swift">
class CRectangle {
var width = 0
}
extension CRectangle {
func changeWidth(width:Int){
self.width = width
}
func setName(name:String, width:Int) {
println("name \(name), width is \(width)")
}
}
var cRect = CRectangle()
cRect.setName("Michael", width: 500)
</code></pre>
<hr />
無論 struct 或是 class 裡的 function 第二個參數開始都強制加入 external parameter name。在定義的時候不用特別寫,就會有了。
<h3>6. 繼承 </h3>
物件導向語言,令人覺得強大的能力莫過於繼承,而 struct 沒有 繼承的功能,只有 class 有。如下
<h4> class </h4>
<pre><code class="swift">
class CRectangle {
var width = 0
}
extension CRectangle {
func changeWidth(width:Int){
self.width = width
}
}
class CSquare : CRectangle { // :CRectangle 代表繼承的語法
// CSquare 這個 class 看起來什麼內容都沒有
}
var cSquare = CSquare()
cSquare.changeWidth(300) // 可以直接使用父類別的 function
cSquare.width // 也可以直接使用從父類別繼承下來的 property
</code></pre>
<hr/>
以上是簡單常用到的行為裡 struct 和 class 常令使用者搞混的情況,希望對大家有幫助。
歡迎提供更多建議。麥克http://www.blogger.com/profile/01716179223350987115noreply@blogger.com5tag:blogger.com,1999:blog-3469754461280175671.post-92014960206440146392014-10-27T02:24:00.000-07:002014-10-27T02:24:01.373-07:00關於 Swift 繼承的兩三事談到繼承就要用先寫一下 Swift 這個語言的 class 的寫法:
<br />
<pre><code class="swift">
class Rectangle { // 非正確寫法
var width:Double
var height:Double
}
</code></pre>
這個就是 swift class 的定義方式,名稱為 Rectangle。有兩個 property 各是 width 和 height。
但是這樣寫並不算完成,因為 Swift 要求在產生這個 class 的實體(instance) 的時候所有的 property 都要有明確的值。所以我們來加上初始值
<br />
<pre><code class="swift">
class Rectangle {
var width:Double = 200.0
var height:Double = 300.0
}
</code></pre>
這樣簡單的 class 就準備完成,然後我們需要來產生實體如下
<br />
<pre><code class="swift">
var rect = Rectangle()
</code></pre>
這樣就可以用 rect 來使用實體,然後改變 property 的內容如下
<br />
<pre><code class="swift">
var rect = Rectangle()
rect.width = 50.0
</code></pre>
Rectangle() 做了什麼事?讀者一定很好奇。其實對熟悉 Objective-C 的開發者來說,他就是 alloc - init 這兩個動作的簡化。
在 Swift 裡 Rectangle() 就會對應到 class 裡面的 init()。比如我們改寫一下 Rectangle 成
<br />
<pre><code class="swift">
class Rectangle {
var width:Double
var height:Double
init(){
width = 200.0
height = 300.0
}
}
</code></pre>
多了一個 init() 而且 var width 和 var height 之後並沒有給值,這樣可以嗎?
這樣是可行的,因為在真正使用實體前,init() 會最先被呼叫,所以在 init() 裡面把 propety 值都設定好,就可以保證之後存取 property 都不怕沒有值。
讀者可以直接在 playground 上面試試,init() 這個第一個被執行的 method 也有一個別名叫做建構子 - constructor。
然後我們來新增一個 method 名為 area 如下
<br />
<pre><code class="swift">
class Rectangle {
var width:Double
var height:Double
init(){
width = 200.0
height = 300.0
}
func area() -> Double {
return width * height
}
}
var rect = Rectangle()
rect.area() // 會得到 60000
</code></pre>
以上,就完成一個很基本的 Class 的定義。接下來才是繼承的問題 XD
所以我們來產生另一個 class 名為 Square,繼承 Rectangle 如下
<br />
<pre><code class="swift">
class Square : Rectangle {
}
var s = Square()
s.height = 20.0
s.width = 20.0
s.area()
</code></pre>
Square 裡面空空如也,只有一個 : Rectangle 的宣告,這樣一來 Square 就擁有 Rectangle 所有內容,包含 width 和 height 的 property 還有 area() 這個 method 自然也可以直接使用
這個就是繼承的好處
接下來我們來把 Square 做一些修改,比較符合我們的期待。
<br />
<pre><code class="swift">
class Square : Rectangle { // 非正確寫法
var side:Double
}
</code></pre>
新增一個 side 的 variable 這樣一來要記得在 init() 指定給 side 值。
但是因為 Square 繼承 Rectangle ,已經有一個 init() 所以在 Square 的 init() 前面要加上 override (覆寫) 字樣。如下
<br />
<pre><code class="swift">
class Square : Rectangle {
var side:Double
override init(){
side = 50
}
}</code></pre>
當子類別(指 Square) 要重寫一個和父類別(指 Rectangle)一樣的 method 的時候就要加上 override 的字樣,這樣執行的時候就是採用子類別的 method 程式碼而不是父類別的程式碼。
這樣一來就可以執行如下程式碼
<br />
<pre><code class="swift">
class Square : Rectangle {
var side:Double
override init(){
side = 50
// 最後會執行 super.init()
}
}
var s = Square()
s.area() // 得到 60000
</code></pre>
有沒有發現一個奇怪的地方?按照推理 Square() 會去執行 Square 的 init(),裡面只有 side =50 ,s.area() 是去執行 Rectangle 裡面的 area() 程式碼,因為在 Square 並沒有 override area()。但 init() 裡面沒有對於 width 和 height 有給定值,s.area() 竟然會有值出現。由此我們可以推論 override init() 最後會去執行父類別的 init(),也就把 Square 從 Rectangle 繼承來的 width 和 height 都給了值
但是還是怪怪的,Square 的 area() 竟然不是和 side 有關而是 width 和 height ,所以我們更新 Square 如下
<br />
<pre><code class="swift">
class Square : Rectangle {
var side:Double
override init(){
side = 50
}
override func area() -> Double {
return side * side
}
}
</code></pre>
所以我們覆寫了 area(),讓它和 side 有關。這樣如下的程式碼在 Square 的用法上就更合理
<br />
<pre><code class="swift">
class Square : Rectangle {
var side:Double
override init(){
side = 50
}
override func area() -> Double {
return side * side
}
}
var s = Square()
s.side = 30.0
s.area() // 會得到 900.0
</code></pre>
So far, So good
看起來很不錯,但還是有一個小小的缺點。也就是當我們用 Square 的 side 的時候,也可以用 width 和 height,這兩個從 Rectangle 得到的變數,但這三個就是不同的變數,這樣一來觀念上很奇怪。比較好的方式是希望使用 Square 的 width 的時候也就是和 side 有連帶關係。拿 width 來說,如果設定 width 的值同時就是設定 side 的值,取得 width 的值也就是取得 side 的值這樣會比較合理。於是我們要來用到 Swift 語言裡很特別的功能,就是 override property。如下
<br />
<pre><code class="swift">
class Square : Rectangle {
var side:Double
override init(){
side = 50
}
override func area() -> Double {
return side * side
}
override var width:Double{
get{
return side
}
set{
side = newValue
}
}
}
</code></pre>
覆寫 property var width:Double 的方法如下
<br />
<pre><code class="swift">
override var width:Double{
get{
return side
}
set{
side = newValue
}
}
</code></pre>
前面也要加上一個 override 然後要用 {} 包起來,裡面有 get 和 set 關鍵字。
get 很好理解就回傳 side 的值,set 的區塊裡面要用 newValue 這個保留字代表要設定給 width 的新值,然後指定給 side
相同地 height 也照辦如下
<br />
<pre><code class="swift">
override var height:Double{
get{
return side
}
set{
side = newValue
}
}
</code></pre>
這樣一來不論是存取 width 或是 height 都是存取 side。
很理想,來測試一下
<br />
<pre><code class="swift">
var s = Square()
s.width = 50.0
s.area() // 得到 2500.0
</code></pre>
做個這篇的小結<br />
<br />
<ol>
<li>簡單 class 定義方式, 建構子 init() 使用時機 </li>
<li>繼承的寫法,init() 在子類別和父類別的使用時機</li>
<li>覆寫 method 和 property 的方法 </li>
</ol>
謝謝各位,大家下次見麥克http://www.blogger.com/profile/01716179223350987115noreply@blogger.com3tag:blogger.com,1999:blog-3469754461280175671.post-75642428484319336132014-10-07T00:46:00.005-07:002014-10-07T00:46:34.367-07:00如何讓 Swift 與 Objective-C 共生 - 無痛轉生Hi There, Swift 語言推出以來締造了許多的記錄,對於 iOS Developer 來說 Objective-C 是再熟悉不過了,現在有個新的小朋友 Swift 的出現,不知道需要需要再花時間來學習,這篇文章不是來教導怎麼學習 Swift 來開發 iOS 程式,而且先從 Swift 怎麼利用已定義好的 Objective-C 的 Class 來給想學或是正在學的 Developers 一個參考。<br />
首先要介紹的是 Bridging Header 這個檔案。<br />
首先 產生一個 Single View Application 的專案。如下圖<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhGwUbgpfSnVyofsmko9s2dVNY-YazdBwHlvWTgZvR-MvV14Ta1pwIvYVyfTeVkAKLWipz2G5smWCg5ZBbzKrLlZ3KqRJViLHaXTbgkZBXOQkEbuH4MKQolnIUfSp81I70fWvC489IqPp3b/s1600/pasted-image-114.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhGwUbgpfSnVyofsmko9s2dVNY-YazdBwHlvWTgZvR-MvV14Ta1pwIvYVyfTeVkAKLWipz2G5smWCg5ZBbzKrLlZ3KqRJViLHaXTbgkZBXOQkEbuH4MKQolnIUfSp81I70fWvC489IqPp3b/s1600/pasted-image-114.png" height="377" width="640" /></a></div>
這個就是新的 Xcode 6 產生專案的畫面,比之前 Xcode 5 少了幾個 (少了幾個呢?留給讀者去比較了)。<br />
接著我們要選擇 Swift 這個語言。如下<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgfTWB9iA-OSf9P2jirng8dgXP0iGcGzzK6rQM6BB1WccSi8rrHTSHZafhxj-4uUEXM4KoslrRFsFS8IUorLj4zCffl4yWbDEQ2rZQjNkvP3lWfu6kv8NXWBp09XBtzI-L1JRw85tCqJt2f/s1600/pasted-image-135.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgfTWB9iA-OSf9P2jirng8dgXP0iGcGzzK6rQM6BB1WccSi8rrHTSHZafhxj-4uUEXM4KoslrRFsFS8IUorLj4zCffl4yWbDEQ2rZQjNkvP3lWfu6kv8NXWBp09XBtzI-L1JRw85tCqJt2f/s1600/pasted-image-135.png" height="378" width="640" /></a></div>
命名為 SwiftObjc<br />
然後我們要在專案的 Navigator 中新增一個 Objective-C 的 Class。如下。<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgzEOd8tN4z294y2K904pJPN_e2BWeXLMkK6PoTWH0TViqLcZCciV-VrXtPMazJRsvc66UKOrGpwpSm1b1OewyIG95kX4bX0yanjv9QIwg67WWeDiu4fV0euqeBP_cG09O0M_D-Gldri-Tp/s1600/pasted-image-158.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgzEOd8tN4z294y2K904pJPN_e2BWeXLMkK6PoTWH0TViqLcZCciV-VrXtPMazJRsvc66UKOrGpwpSm1b1OewyIG95kX4bX0yanjv9QIwg67WWeDiu4fV0euqeBP_cG09O0M_D-Gldri-Tp/s1600/pasted-image-158.png" height="378" width="640" /></a></div>
記得要選的是 Cocoa Touch Class 而不是 Objective-C File 哦 。<br />
命名為 Car。<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi4HhiSIU9idWTvlvTAYD3dG1DKstDplrjtlu8_MBHLnvx4unCtAunzM2NCR9Ud4B6RQd6d3sSru8XQRlWOOWImS0l0mFA1riCPo2U888IvTBdT5N9D4pQmkU1DNCQnRHllYZ9pgXgNFNnD/s1600/pasted-image-175.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi4HhiSIU9idWTvlvTAYD3dG1DKstDplrjtlu8_MBHLnvx4unCtAunzM2NCR9Ud4B6RQd6d3sSru8XQRlWOOWImS0l0mFA1riCPo2U888IvTBdT5N9D4pQmkU1DNCQnRHllYZ9pgXgNFNnD/s1600/pasted-image-175.png" height="378" width="640" /></a></div>
記得 Language 是 Objective-C 哦。<br />
接著不一樣的事情發生了。Xcode 會自動跳出一個提醒視窗。如下。<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh3MKOSwhumQkOIFas99bYNIP46Te9IqkXzIFX54AFh9SZjVccvy0hte8g8tBU48RJ5jESGYG3r3zs0p84LjsazdSN37ndbMMa39sMFsKzApknYnti49va1srdHZarJk2RrzXcLVfnX6R-N/s1600/pasted-image-476.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh3MKOSwhumQkOIFas99bYNIP46Te9IqkXzIFX54AFh9SZjVccvy0hte8g8tBU48RJ5jESGYG3r3zs0p84LjsazdSN37ndbMMa39sMFsKzApknYnti49va1srdHZarJk2RrzXcLVfnX6R-N/s1600/pasted-image-476.png" height="252" width="640" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
這個視窗的意思是說,Xcode 想幫開發者自動加入 bridging header 的檔案。<br />
現在就先選擇 Yes 。來看看專案有什麼變化?如下<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjwG8uqUgEmaIWH17W_sFnUimiusik0zINP5KTjbvJfbTqR6PwgcsH7eRBGenwkspiN6uVLxi9L4WQA56x4R6bpi7MMyUfKPJrCpQveuNX371xfGmzxEvG4Z5eHF2bvoihNXFXzp6Zsr1lE/s1600/pasted-image-198.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjwG8uqUgEmaIWH17W_sFnUimiusik0zINP5KTjbvJfbTqR6PwgcsH7eRBGenwkspiN6uVLxi9L4WQA56x4R6bpi7MMyUfKPJrCpQveuNX371xfGmzxEvG4Z5eHF2bvoihNXFXzp6Zsr1lE/s1600/pasted-image-198.png" height="640" width="637" /></a></div>
多了三個檔案哦。我們想要的 Car 這個 Class 的 .h, .m ,還有一個所謂的 Bridging Header 。在這的名稱為 SwiftObjC-Bridging-Header.h 。所以跳出視窗問的就是這個 Header,裡面長什麼樣子呢?基本上是空白。<br />
因為我們要在 Swift Class 裡面用 Objective-C 的 Car class 。所以必需要加上如下的。程式碼。<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEizR2V_yeQuEgeqiEayTsgzHBeAW_ZNEGUDsXI58s_YcWnQjGhaq1fNIHd1GoF6cjHQ-IRIQk2FqCKzv_Neebpan2cbHP1MCcC3zaH44eF5qm6jBcqMvG52uYFrVsOpyUKNo1E70ELSKCQB/s1600/pasted-image-226.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEizR2V_yeQuEgeqiEayTsgzHBeAW_ZNEGUDsXI58s_YcWnQjGhaq1fNIHd1GoF6cjHQ-IRIQk2FqCKzv_Neebpan2cbHP1MCcC3zaH44eF5qm6jBcqMvG52uYFrVsOpyUKNo1E70ELSKCQB/s1600/pasted-image-226.png" height="234" width="640" /></a></div>
就是 #import "Car.h" 。<br />
也可以自已手動加,但需要改變專案的設定,如下圖<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhw1GeSz8rwH1seuVuzHZSipr2tkw3MG71sxi_p33OabkH32FeGN81TKExC3xVBix38ZD8iWzU2_Y3hUO7D71yRnMDRZrweDgqGbr-4zTegs1Kt3UyUUnzmuoUrQwE12SUzTAfak9DfdvH6/s1600/pasted-image-468.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhw1GeSz8rwH1seuVuzHZSipr2tkw3MG71sxi_p33OabkH32FeGN81TKExC3xVBix38ZD8iWzU2_Y3hUO7D71yRnMDRZrweDgqGbr-4zTegs1Kt3UyUUnzmuoUrQwE12SUzTAfak9DfdvH6/s1600/pasted-image-468.png" height="164" width="640" /></a></div>
有一個 Objective-C Bridging Header 的設定要寫,記得除了寫 .h 檔名之外,還要加上 Product 名稱哦,通常就是 Project 名稱。<br />
接下來我們就在 ViewController.swift 裡面使用 Car 這個 Class<br />
如下<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhsAqoTxyYKLsPUnoRGwMY46ippvmxyRx4NGIIwzIkjkNcF3__GwsJsl9rWkmEy44e3rXA_NI7bT8Wl0PAveRbtUmi7TLDJPkpK03bVaMN76eWK6nAxZF3flePjqFFpfE5WnPKlKi39FEGu/s1600/pasted-image-248.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhsAqoTxyYKLsPUnoRGwMY46ippvmxyRx4NGIIwzIkjkNcF3__GwsJsl9rWkmEy44e3rXA_NI7bT8Wl0PAveRbtUmi7TLDJPkpK03bVaMN76eWK6nAxZF3flePjqFFpfE5WnPKlKi39FEGu/s1600/pasted-image-248.png" height="274" width="640" /></a></div>
就可以直接在 viewDidLoad 裡面用 Car 這個 Class。而且用 Swift 語法<br />
<blockquote class="tr_bq">
Car()</blockquote>
在 Swift 中 Car() 就是和 Objective-C 的<br />
<blockquote class="tr_bq">
[[Car alloc] init ]</blockquote>
而且聰明的讀者有沒有發現到,在 Swift 裡面每個 row 的程式碼最後都不需要分號 ; 結尾<br />
接著我們來加一下 method 在 Car 裡面。如下新增<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiFFnx8iQRO8QzAiboJWsP_KqTAmjb3CDqQPozJ9ruHQl8_pxibOXlo6Ca5G7it8o5vw5qcpMhHeJZbjFQv9FG44uQJYdvo9Lc8IrACVBn2SgNiU_LPV1SKT88JN0TTKtgUdpHEJvsLuSf-/s1600/pasted-image-266.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiFFnx8iQRO8QzAiboJWsP_KqTAmjb3CDqQPozJ9ruHQl8_pxibOXlo6Ca5G7it8o5vw5qcpMhHeJZbjFQv9FG44uQJYdvo9Lc8IrACVBn2SgNiU_LPV1SKT88JN0TTKtgUdpHEJvsLuSf-/s1600/pasted-image-266.png" height="80" width="640" /></a></div>
<br />
Method 的名稱叫 setPrice:andYear: 這樣的 selector 要怎麼在 Swift 呼叫呢?Swift 也是支援 selector 的觀念,如下使用。<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhWQJzb8Aw2414mvLktd7GzhpWCjk5NdKZv4iVBU40InpIW1W9-3mm5Rpq6DMXc_2kJv54hC0NVTWIMj1qSAAFulcs2cCLDyR9DqX4aJ5Lwu2CKpooH-0WFv1YasGHuYiGQHjLgS7hKsOVN/s1600/pasted-image-270.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhWQJzb8Aw2414mvLktd7GzhpWCjk5NdKZv4iVBU40InpIW1W9-3mm5Rpq6DMXc_2kJv54hC0NVTWIMj1qSAAFulcs2cCLDyR9DqX4aJ5Lwu2CKpooH-0WFv1YasGHuYiGQHjLgS7hKsOVN/s1600/pasted-image-270.png" height="172" width="640" /></a></div>
setPrice:andYear: 在 Swift 的語法就是<br />
<blockquote class="tr_bq">
func <b>setPrice</b>( a:Int, <b>andYear:</b> b:Int)</blockquote>
這也是 Swift 跳脫了傳統的 C function 的束縛加了一個新的語法叫 external parameter name 。也就是 andYear: ,在 Swift 的 function 裡用第二個參數的 external parameter name 來表示。<br />
接著執行是會 Crash。為什麼?因為 Car.m 並沒有實作 -setPrice:andYear:。所以記得要實作。如下。<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgCbF9Au_kw3v34wENYRN2mWvt5UJwk0PoWo692aWU9QiGuLiimgTp31fUBkCOzHrRktVfZHjrWxl5CTwAB_2lxGK1wefCvuoPYmOblyXeFJ8mVprEdoFDK7CXQh5ivd8i2zJ96TBps2cO4/s1600/pasted-image-295.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgCbF9Au_kw3v34wENYRN2mWvt5UJwk0PoWo692aWU9QiGuLiimgTp31fUBkCOzHrRktVfZHjrWxl5CTwAB_2lxGK1wefCvuoPYmOblyXeFJ8mVprEdoFDK7CXQh5ivd8i2zJ96TBps2cO4/s1600/pasted-image-295.png" height="244" width="640" /></a></div>
順便加上兩個 ivar,各是 price 和 year<br />
這樣一來在 ViewController.swift 就可以執行成功。<br />
對於 Selector 的觀念,我們再來做一個小小的實驗。<br />
先在 ViewController.swift 再加上一些程式碼<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjo6BLWpaKjrQfpW4Uqh4HISeOPDvGk1z475Jroe1C1WHWsQKu_ptn9AJBZ0zfpSJZ0FHdfJe2YUpRBE1b-oGtV3vFehjYGy8rwq6V0QqlhN5w-9Y_yAQKn-Z6bshs0LeINg0J3foELCFoD/s1600/%E8%9E%A2%E5%B9%95%E5%BF%AB%E7%85%A7+2014-10-07+%E4%B8%8B%E5%8D%883.01.03.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjo6BLWpaKjrQfpW4Uqh4HISeOPDvGk1z475Jroe1C1WHWsQKu_ptn9AJBZ0zfpSJZ0FHdfJe2YUpRBE1b-oGtV3vFehjYGy8rwq6V0QqlhN5w-9Y_yAQKn-Z6bshs0LeINg0J3foELCFoD/s1600/%E8%9E%A2%E5%B9%95%E5%BF%AB%E7%85%A7+2014-10-07+%E4%B8%8B%E5%8D%883.01.03.png" height="288" width="640" /></a></div>
<br />
我們要利用 NSTimer 來呼叫兩個 function 。分別是<br />
<blockquote class="tr_bq">
<span class="s1">func</span> action<span class="s2">(a</span>:<span class="s2">NSTimer,</span> second b:<span class="s3">Int </span>) {<br />
<span class="s4"> </span>println(<span class="s5">"action1"</span><span class="s4">)</span>}</blockquote>
這個的 selector 寫成 Selector("action:second:")<br />
<br />
<div class="p1">
以及</div>
<blockquote class="tr_bq">
<span class="s1">func</span> action(action<span class="s2"> a</span>:<span class="s2">NSTimer,</span> second<span class="s2"> b</span>:<span class="s2">NSTimer)</span>{<br />
<span class="s3"> </span>println(<span class="s4">"action2"</span><span class="s3">)</span> }</blockquote>
而這樣的寫法,無法對應到 Objective-C 觀念的 Selector<br />
執行後會發現一直出現 action1 的 log是正常的。<br />
<br />
接著我們來看一下存取 ivar。在這個時候如果直接從 Swift 存取 Car 的 ivar 會發生如下錯誤。<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiJ2HM891m0G8zbgPH7u3NsriyiltQNr7Rax3ERs1Y6wumykexKbaRbtLOAtHJ61sPEA0javl53kMXrvoGD2_BHTiNMqDcIERJ7yVzzHb6_wEXG0jWF0ohBPzVdZbU9KBxh1E2OZ9lFhaRy/s1600/pasted-image-321.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiJ2HM891m0G8zbgPH7u3NsriyiltQNr7Rax3ERs1Y6wumykexKbaRbtLOAtHJ61sPEA0javl53kMXrvoGD2_BHTiNMqDcIERJ7yVzzHb6_wEXG0jWF0ohBPzVdZbU9KBxh1E2OZ9lFhaRy/s1600/pasted-image-321.png" height="170" width="640" /></a></div>
<br />
放在 .m 的 ivar 應該是無法被看到,如果試著把 ivar 拿到 .h 來如下<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjH3LofoyjMuw88rLlHbDoGZ66vXE3CHxAv2wqwBB-mW35DWGhL8IYR5JNg8L6HkkI8qj8sytzEFG5nv_Fit_q3Kb4ztWNk88KN57-0PlVspCiYjta7TNW0elmCGG7IQ3MkniubZyoUpKWT/s1600/pasted-image-329.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjH3LofoyjMuw88rLlHbDoGZ66vXE3CHxAv2wqwBB-mW35DWGhL8IYR5JNg8L6HkkI8qj8sytzEFG5nv_Fit_q3Kb4ztWNk88KN57-0PlVspCiYjta7TNW0elmCGG7IQ3MkniubZyoUpKWT/s1600/pasted-image-329.png" height="172" width="640" /></a></div>
<br />
結果也是一樣。在 Swift 要正常存取 Objective-C 的 ivar 。最好的方式就是利用 property,將 Car.h 改寫如下<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiWzJG9xSKiJKGp2ZaG3QvQuK80SZlSq4Y40CayI2RsXw7fRcYR3N-LEUfxH_rlVkbIKP2AWYg5puP-RmhgdsnhLtjTqgHI0UR9rvF8MXehHR3dBLkLDiMQcALU661etsZvS9FxRuI3ql8K/s1600/pasted-image-341.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiWzJG9xSKiJKGp2ZaG3QvQuK80SZlSq4Y40CayI2RsXw7fRcYR3N-LEUfxH_rlVkbIKP2AWYg5puP-RmhgdsnhLtjTqgHI0UR9rvF8MXehHR3dBLkLDiMQcALU661etsZvS9FxRuI3ql8K/s1600/pasted-image-341.png" height="204" width="640" /></a></div>
<br />
<br />
再利用 Swift 來執行如下<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgTJAqMOcxbqq8JXcLTwVjo7TAiggLozwopXg1xMPCEuSiSV0n0EABfMyaqSJcJ5oDb0qg7qepBkzMUArhj9Zg_o6PgeVmJQwj_yK9H4o9SSIQp_7jd_Ppmm68HowWPay9HMJ3CT4XYXGOo/s1600/pasted-image-378.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgTJAqMOcxbqq8JXcLTwVjo7TAiggLozwopXg1xMPCEuSiSV0n0EABfMyaqSJcJ5oDb0qg7qepBkzMUArhj9Zg_o6PgeVmJQwj_yK9H4o9SSIQp_7jd_Ppmm68HowWPay9HMJ3CT4XYXGOo/s1600/pasted-image-378.png" height="243" width="640" /></a></div>
就可以成功執行。<br />
<br />
最後要來解釋一下 Objective-C 的 class method 要怎麼在 Swift 被使用。<br />
我們要在 Car.h 加上 class method 如下<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhQuIXvz0d9cuxqcx7RCnUs9y_A70oMYu5UQnzCLKll9cwtV3EXTQV9PoUrBD6AGk05kdJZC9OpIw8beKBk2i92tOujhuJlF6d4UJOkRHDKwFn52CuHp4Z9kDNy7bcxk5RHVDJu4cJArAHa/s1600/%E8%9E%A2%E5%B9%95%E5%BF%AB%E7%85%A7+2014-10-07+%E4%B8%8B%E5%8D%883.25.36.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhQuIXvz0d9cuxqcx7RCnUs9y_A70oMYu5UQnzCLKll9cwtV3EXTQV9PoUrBD6AGk05kdJZC9OpIw8beKBk2i92tOujhuJlF6d4UJOkRHDKwFn52CuHp4Z9kDNy7bcxk5RHVDJu4cJArAHa/s1600/%E8%9E%A2%E5%B9%95%E5%BF%AB%E7%85%A7+2014-10-07+%E4%B8%8B%E5%8D%883.25.36.png" height="176" width="640" /></a></div>
當然 .m 要對應好<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiULBeukVeOxYX_oRDzNNiYQiizIDPQHdpWJ-AvmoWDgqfV73yBmyXAoaK3mA9DnotvWs-954jidhpDDJ5FE0hH5BxAsagt6RA14WXmKQ5jgzOFNSBmChR66s_Lt0PcYr2TY507X6wbCQqH/s1600/%E8%9E%A2%E5%B9%95%E5%BF%AB%E7%85%A7+2014-10-07+%E4%B8%8B%E5%8D%883.26.52.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiULBeukVeOxYX_oRDzNNiYQiizIDPQHdpWJ-AvmoWDgqfV73yBmyXAoaK3mA9DnotvWs-954jidhpDDJ5FE0hH5BxAsagt6RA14WXmKQ5jgzOFNSBmChR66s_Lt0PcYr2TY507X6wbCQqH/s1600/%E8%9E%A2%E5%B9%95%E5%BF%AB%E7%85%A7+2014-10-07+%E4%B8%8B%E5%8D%883.26.52.png" height="266" width="640" /></a></div>
<br />
怎麼在 Swift 中使用 +sharedInstance 呢?如下<br />
<br />
<blockquote class="tr_bq">
<span class="s1">var myCar = Car.sharedInstance()</span></blockquote>
<br />
這樣就可以了。看起來不難,不過提醒讀者名稱這事對於 Objective-C 和 Swift 都很重要,如果有一個 class method 命名成如下<br />
<blockquote class="tr_bq">
<span class="s1">+(</span>instancetype<span class="s1">) car;</span> </blockquote>
<br />
在使用<br />
<br />
<blockquote class="tr_bq">
var myCar = Car.car()</blockquote>
就會造成 compiler 的錯誤,目前可以的話就盡量避免,直接用 Class 名稱第一個字小寫的 class method。<br />
談了這麼多,到底應用在真實的例子是如何呢?<br />
我們就拿 AFNetworking 的 framework 來測試。<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiekmCrR4SiqwwEr3SEAuQZqhnoTNUr7Kg1Ch-TCLCqDNVySCdzAl6kCh_qxFyEHoed09Rp-J7h-aX7ubslVugiNPIU6pHMWkoup4IOr7wcQbZIABS-75v4i56ffxvoToAykqhoQ5lraGN9/s1600/pasted-image-407.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiekmCrR4SiqwwEr3SEAuQZqhnoTNUr7Kg1Ch-TCLCqDNVySCdzAl6kCh_qxFyEHoed09Rp-J7h-aX7ubslVugiNPIU6pHMWkoup4IOr7wcQbZIABS-75v4i56ffxvoToAykqhoQ5lraGN9/s1600/pasted-image-407.png" height="396" width="640" /></a></div>
<br />
這是一個非常有名也很多人用在網路的資料的傳輸上的 open source 的 framework。<br />
在這就不簡介。先把 AFNetworking 的檔案都拉到專案<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiuUqlYscMW9isOah9MvxVxL5MY2kHvv79FMXb1hvNCIzyMRqUu14kuwC1-quQZT8wvCXYl7c_HqMHAPgaq8K6nlRNHT94rwGUtrapr-KV498nGbxd8F5F7Vz6fecdofVlx290L8CJmtxG9/s1600/pasted-image-420.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiuUqlYscMW9isOah9MvxVxL5MY2kHvv79FMXb1hvNCIzyMRqUu14kuwC1-quQZT8wvCXYl7c_HqMHAPgaq8K6nlRNHT94rwGUtrapr-KV498nGbxd8F5F7Vz6fecdofVlx290L8CJmtxG9/s1600/pasted-image-420.png" height="640" width="300" /></a></div>
然後在 Bridging Header File 也要加上 AFNetworking.h 如下<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhzj_PzgUMPLwez9bnllYdYnhXQmkrGn2xhUWxrFZriN1r_7v2xeBdlZL9sO2Ztwfzr1CzYcfFi2dTyLwfdRTT_Oqdf-99lb7rmneM-cqEDuz654lrLNutfz-dcAdUfOmZvFQZxDIfbV3yT/s1600/pasted-image-442.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhzj_PzgUMPLwez9bnllYdYnhXQmkrGn2xhUWxrFZriN1r_7v2xeBdlZL9sO2Ztwfzr1CzYcfFi2dTyLwfdRTT_Oqdf-99lb7rmneM-cqEDuz654lrLNutfz-dcAdUfOmZvFQZxDIfbV3yT/s1600/pasted-image-442.png" height="286" width="640" /></a></div>
最後補上一段,下載 Image 的程式碼就大功告成了啦<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhIuaw6IsSEkw8w_wTRKs1X6NJpsAjratCfYAZorVuzuiq8RytFgXIt7F36ZYFOUwZaIpBLy9EtwA_JPeGm7KJHnRubL6i1VPP40FCehbym6kv5NBE50rOj2MZvIAntblbsuYz09wHN3biy/s1600/pasted-image-457.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhIuaw6IsSEkw8w_wTRKs1X6NJpsAjratCfYAZorVuzuiq8RytFgXIt7F36ZYFOUwZaIpBLy9EtwA_JPeGm7KJHnRubL6i1VPP40FCehbym6kv5NBE50rOj2MZvIAntblbsuYz09wHN3biy/s1600/pasted-image-457.png" height="236" width="640" /></a></div>
最後回答大家常問麥老師的問題,Swift 是不是非學不可?Swift 是不是會取代 Objective-C ?<br />
Swift 是不是非學不可?<br />
<blockquote class="tr_bq">
在這一二年內答案不是,但是因為 Swift 的好學好寫,我想再過不久 Swift 的 framework 不會比 Objective-C 少,到時,如果用到只有 Swift 版的 framework 就是非學不可了。</blockquote>
Swift 是不是會取代 Objective-C ?<br />
<blockquote class="tr_bq">
我個人是覺得再 5 年可能有機會,因為一個語言從出生到非常成熟可能需要 10 的年磨練,依照熱門程度不同,時間會有所調整,Swift 這麼火紅的情況下,可能會使它比較快穩定。我們可以看一個參考,如果 Apple 某個常用應用程式是用 Swift 重新改寫,完全取代 Objective-C 的日子就很近了。</blockquote>
<br />
以上一些,很久沒有寫文章的碎碎唸。希望大家有所收獲。請不吝指教。Michael 筆。<br />
<br />
<div class="p1">
</div>
麥克http://www.blogger.com/profile/01716179223350987115noreply@blogger.com0tag:blogger.com,1999:blog-3469754461280175671.post-6278100283262140152014-03-09T08:41:00.000-07:002014-03-09T08:42:04.366-07:00使用者導向設計 - User-Centered Design最近在書架上看到一本很有趣的書<br />
<a href="http://shop.oreilly.com/product/0636920028741.do">使用者導向設計</a><br />
乍看之下是給設計師看的書,翻了幾頁之後才了解是對開發者的案件的討論比較多。主要在建議開發者多和使用者互動和討論,多了解使用者心意更能設計出好的產品。<br />
傳統來說,討論電子產品和使用者的關係可以從人機互動介面 Human Computer Interface (HCI) 開始說起,一直延伸到近來非常熱門的 User Experience (UX) 的討論,這幾些和技術不太相關的名詞一直以來不被開發者重視,開發者多半會想把時間花在更好的技術討論上,殊不知如果多了解使用者的行為和心態,更可以創造出好的產品,也許不需要用到非常高深的技術。UCD 的討論就是從這樣的角度出發,試圖說服開發者,從另一個身份和角度來思考產品的設計。<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiSppvirZtFvCmRdkyWlKAEzCqXHzimlezkl5S7u08RDvEigXEBt2XhpVmsTB9MfKcnNO2PSRkNfDNGnKg7IUCL6c_O87fGwNB9kcaY_Mlrg3C0xM_qUptcgV_JHEK_EWIhHdJ_tx8GqHDI/s1600/flow.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiSppvirZtFvCmRdkyWlKAEzCqXHzimlezkl5S7u08RDvEigXEBt2XhpVmsTB9MfKcnNO2PSRkNfDNGnKg7IUCL6c_O87fGwNB9kcaY_Mlrg3C0xM_qUptcgV_JHEK_EWIhHdJ_tx8GqHDI/s1600/flow.png" height="86" width="640" /></a></div>
UCD 主要重視 User-Centered Design 和 User Experience 這兩個討論主題,也就是從產生程式前的兩個要研究主題。<br />
讓我們來介紹一下其中一個主題<br />
<h4>
與使用者合作</h4>
<div>
這是對於開發者而言,最難克服的一關卡。開發者在開會的時候,一聽到需求往往會先入為主的先思考每個需求會用到的技術是什麼?在平常有多時間開發者也會選擇去研究新的技術,而不會花時間去了解使用者,知道使用者的需求。這也是很正常的事,喜歡寫程式的人往往不喜歡和人打交道,同樣是要花時間為什麼不把時間多花在鑽研新的技術上面來的可能比較有更多的成就感。</div>
<div>
UCD 要強調的是產品是給人用的,開發者如果可以多花一些時間在使用性價值或是了解使用者的心理上的話,對於開發一定是正面的幫助。愈了解使用者,開發出來的產品就會愈接近使用者的需求,開發者不妨試試先放下鍵盤,從另一個角度去思考,多和使用者聊天,也許就有意想不到的收獲。</div>
<div>
一般我們可以把使用者分成幾個種類</div>
<div>
<ul>
<li>資訊提供狂</li>
<ul>
<li>這類的使用者提供過多的資訊,又不好意思請他們停止,最好是可以把資訊分類,還有設定優先順序,說真的,過份的時候可以暫時不理他們,不過,有提供想法總比什麼都沒有還好。</li>
</ul>
<li>控制狂</li>
<ul>
<li>這樣的使用者想要領導整個專案進行,他們也只是想要得到認同和尊重,往好處想,這類的使用者會主動找出好或不好的地方,有可能成為戰友。</li>
</ul>
<li>自以為代言者</li>
<ul>
<li>代言者的特色是提供負面的主意比正面的多,如果影響到團隊的進行,可以試圖請代言者以科學的方式和證據來佐證比主觀和個人的想法來的可以說服大家。</li>
</ul>
</ul>
<div>
以上簡單介紹 UCD 重要的因素。有機會我們再來和大家討論更多的方法用來設計更符合人性的產品。最後還是提醒開發者,接近使用者,會有不一樣的靈感出現。</div>
</div>
麥克http://www.blogger.com/profile/01716179223350987115noreply@blogger.com0tag:blogger.com,1999:blog-3469754461280175671.post-86951314702857291682013-08-09T01:02:00.000-07:002013-08-09T01:02:38.578-07:00Node Pattern - Recursive Loop<div class="tr_bq">
今天要討論的主要是在 Asynchronous function 和 Loop 之間的關係。用過 Node.js 的朋友們,應該都知道 Node.js 的特色的 Nonblocking I/O 或者另一個說法是 Node.js 的寫法裡面充斥著許多的 callback function。這也是 Node.js 的優點之一,因為這個 Nonblocking 的特色可以讓 Node.js 只有一個 Thread 而遇到多個 I/O 存取的情況下還可以保持存取效能非常的高。</div>
但是這樣的 Nonblocking 的特色會讓大部分的開發者,已經習慣 blocking 寫法的開發者,在開發 Node.js 應用的時候常常會遇到不知所措的錯誤發生。這篇文章就是要來討論其中一個很常見的錯的寫法。錯誤的寫法是在非常常用的 for-loop 上。<br />
先把問題述敘一下:<br />
<blockquote class="tr_bq">
利用 fs 這個 Module 來呈現,某個資料夾裡面所有的子資料夾的名稱。</blockquote>
我們會用到 fs 裡的 <a href="http://nodejs.org/api/fs.html#fs_fs_readdir_path_callback">fs.readdir</a> 和 <a href="http://nodejs.org/api/fs.html#fs_fs_stat_path_callback">fs.stat</a>。假設我們產生一個 function 如下<br />
<blockquote class="tr_bq">
function listDir (rootDir, callBack){<br />
}</blockquote>
當我們在使用這個 listDir 的時候只要給開頭的 dirName 就會在 Console 呈現出,這個 dirName 裡面所有的子資料夾。於是我們準備一下測試用的資料夾。如下圖(藍色的為資料夾)<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjYOGD-6jYL5upgyiNjit7oSFZRqIkglH5VJvsGGoFUEfA7UaTYQegjzytyjhWReDL1_2FXcrUWaUgRa-b2R6U4RVfdQbgxPtATwtdbXUwkaVeYVoYWMEerf3HCn56drxkxya5TNQ8hPPFO/s1600/folder.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="175" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjYOGD-6jYL5upgyiNjit7oSFZRqIkglH5VJvsGGoFUEfA7UaTYQegjzytyjhWReDL1_2FXcrUWaUgRa-b2R6U4RVfdQbgxPtATwtdbXUwkaVeYVoYWMEerf3HCn56drxkxya5TNQ8hPPFO/s320/folder.png" width="320" /></a></div>
<div class="separator" style="clear: both; text-align: left;">
假設我們把上方提到的 fanit_pattern_for.js 這個檔案裡,同一個資料夾(NodePattern)裡有兩個空的子資料夾,各別是 folder1 和 folder2。如此一來寫程式前的準備動作就做好了。</div>
<div class="separator" style="clear: both; text-align: left;">
程式方便同樣寫在 anti_pattern_for.js 裡面就要有使用 listDir 的程式碼。如下</div>
<blockquote class="tr_bq">
listDir(".",function (err, dirs) {<br />
<span class="Apple-tab-span" style="white-space: pre;"> </span>if(!err){<br />
<span class="Apple-tab-span" style="white-space: pre;"> </span>console.log("got folders : "+dirs);<br />
<span class="Apple-tab-span" style="white-space: pre;"> </span>}else{<br />
<span class="Apple-tab-span" style="white-space: pre;"> </span>console.log("error occurs : "+ err);<br />
<span class="Apple-tab-span" style="white-space: pre;"> </span>}<br />
});</blockquote>
<div class="separator" style="clear: both;">
<br /></div>
<div class="separator" style="clear: both;">
使用的時候就把第一個 rootDir 的名稱 "." ,這個 js 檔所存在的目錄當成根目錄,所以我們預期在 Console 的結果就是 </div>
<blockquote class="tr_bq">
got folders : folder1,folder2</blockquote>
<br />
<div class="separator" style="clear: both;">
工作準備好,我們要來動手寫 listDir 的程式了。首先來看一下很常見的版本。但是是錯的 XD。</div>
<pre><code class="javascript">
var fs = require("fs");
function listDir (dirName, callBack) {
fs.readdir(dirName, function (err, files) {
if(err){
callBack(err);
return;
}
var dirs = [];
<span style="color: #0b5394;"><b>for (var i = files.length - 1; i >= 0; i--) {
fs.stat(dirName+"/"+files[i], function (err, stats) {
if(stats.isDirectory()){
dirs.push(files[i]);
}
});
};</b></span>
callBack(null, dirs);
});
}
</code></pre>
上面的程式碼。如果用<br />
<blockquote class="tr_bq">
listDir(".",function (err, dirs) {<br />
<span class="Apple-tab-span" style="white-space: pre;"> </span>if(!err){<br />
<span class="Apple-tab-span" style="white-space: pre;"> </span>console.log("got folders : "+dirs);<br />
<span class="Apple-tab-span" style="white-space: pre;"> </span>}else{<br />
<span class="Apple-tab-span" style="white-space: pre;"> </span>console.log("error occurs : "+ err);<br />
<span class="Apple-tab-span" style="white-space: pre;"> </span>}<br />
});</blockquote>
來呼叫的話,會得到一個出乎我們意料的結果。<br />
<blockquote class="tr_bq">
got folders</blockquote>
沒有任何的資料夾?怎麼會是這樣的結果?我們來檢視一下程式碼,看 for-loop 這個地方<br />
<blockquote class="tr_bq">
for (var i = files.length - 1; i >= 0; i--) {<br /> fs.stat(dirName+"/"+files[i], function (err, stats) {<br /> if(stats.isDirectory()){<br /> dirs.push(files[i]);<br /> }<br /> });<br />}; </blockquote>
<div>
<br />
看起來的複雜但可以簡單成如下<br />
<blockquote class="tr_bq">
for (var i = files.length - 1; i >= 0; i--) {<br /> fs.stat(....);<br />};</blockquote>
</div>
<div>
<br />就是做 fs.stat 很多次。也沒什麼奇怪,奇怪的是在 fs.stat 本身是 Nonblocking 的機制。整個 for-loop 會認為 fs.stat(); 已經結束,但是 fs.stat(); 裡面的 callback funciton。<br />
<blockquote class="tr_bq">
fs.stat(dirName+"/"+files[i], function (err, stats) {<br /> if(stats.isDirectory()){<br /> dirs.push(files[i]);<br /> }<br />});</blockquote>
</div>
<div>
第二個參數就是 callback function 都還沒被呼叫,for-loop 就結束,然後就呼叫<br />
<blockquote class="tr_bq">
callBack(null, dirs); </blockquote>
<div>
因為整個執行的速度很快 dirs 就沒有值被寫入。就得到空的值。</div>
<div>
對於習慣 blocking 寫法的開發者而言,這麼常用的寫法,竟然會在 Nonblocking 裡出錯,對於剛接觸 Node.js 的開發者應該相當的困擾。</div>
<div>
那要怎麼解決呢?</div>
<div>
讀者介紹一個常用的解決方法,不能用 for-loop 而改用遞迴的方式。如下先來看一種遞回方法來達到和 for-loop 一樣的效果。</div>
<div>
基本原型長得像這樣。</div>
<div>
<blockquote class="tr_bq">
function <b><span style="color: #0b5394;">iterator</span></b> (index) {<br />
<span class="Apple-tab-span" style="white-space: pre;"> </span>if(index < <b><span style="color: #741b47;">array</span></b>.length){<br />
<span class="Apple-tab-span" style="white-space: pre;"> </span><b>asynchronous function</b> () {<br />
<span class="Apple-tab-span" style="white-space: pre;"> </span>// do something<br />
<span class="Apple-tab-span" style="white-space: pre;"> </span><b><span style="color: #0b5394;">iterator</span></b>(index+1);<br />
<span class="Apple-tab-span" style="white-space: pre;"> </span>});<br />
<span class="Apple-tab-span" style="white-space: pre;"> </span>}else{<br />
<span class="Apple-tab-span" style="white-space: pre;"> </span>callBack();<br />
<span class="Apple-tab-span" style="white-space: pre;"> </span>}<br />
} ;</blockquote>
<blockquote class="tr_bq">
<b><span style="color: #0b5394;">iterator</span></b>(0); // 執行 iterator</blockquote>
</div>
<div>
更簡單的寫法可以寫成</div>
<blockquote class="tr_bq">
<b><span style="color: #cc0000;">(</span></b>function <b><span style="color: #0b5394;">iterator</span></b> (index) {<br />
<span class="Apple-tab-span" style="white-space: pre;"> </span>if(index < <b><span style="color: #741b47;">array</span></b>.length){<br />
<span class="Apple-tab-span" style="white-space: pre;"> </span><b>asynchronous function</b> () {<br />
<span class="Apple-tab-span" style="white-space: pre;"> </span>// do something<br />
<span class="Apple-tab-span" style="white-space: pre;"> </span><b><span style="color: #0b5394;">iterator</span></b>(index+1);<br />
<span class="Apple-tab-span" style="white-space: pre;"> </span>});<br />
<span class="Apple-tab-span" style="white-space: pre;"> </span>}else{<br />
<span class="Apple-tab-span" style="white-space: pre;"> </span>callBack();<br />
<span class="Apple-tab-span" style="white-space: pre;"> </span>}<br />
} <b><span style="color: #cc0000;">)(0)</span></b>;</blockquote>
<div>
這樣的寫法,可以想成是 for-loop 的遞迴寫法。我們以一個 array 有三個元素來舉例,可以展開如下。</div>
<blockquote class="tr_bq">
function <b><span style="color: #0b5394;">iterator</span></b> (0) {<br />
<span class="Apple-tab-span" style="white-space: pre;"> </span>function <b><span style="color: #0b5394;">iterator</span></b> (1) {<br />
function <b><span style="color: #0b5394;">iterator</span></b> (2) {<br />
}<br />
}<br />
}<br />
// 執行下一行程式</blockquote>
<div>
這樣的寫法除了可個保證是依照 index 一個一個執行之外,最後一個 index ( 在這邊是 2) 執行完之後,整個程式才可以執行下一行。我們把原本的 for-loop 版本有問題的地方改寫成如下</div>
<div>
<blockquote class="tr_bq">
<b><span style="color: #0b5394;">(</span></b>function <b><span style="color: #cc0000;">iterator</span></b> (index) {<br />
<span class="Apple-tab-span" style="white-space: pre;"> </span>if(index < files.length){<br />
<span class="Apple-tab-span" style="white-space: pre;"> </span>fs.stat(rootDir+"/"+files[index], function (err, stats) {<br />
<span class="Apple-tab-span" style="white-space: pre;"> </span>if(stats.isDirectory()){<br />
<span class="Apple-tab-span" style="white-space: pre;"> </span>dirs.push(files[index]);<br />
<span class="Apple-tab-span" style="white-space: pre;"> </span>}<br />
<span class="Apple-tab-span" style="white-space: pre;"> </span><b><span style="color: #cc0000;">iterator</span></b>(index+1);<br />
<span class="Apple-tab-span" style="white-space: pre;"> </span>});<br />
<span class="Apple-tab-span" style="white-space: pre;"> </span>}else{<br />
<span class="Apple-tab-span" style="white-space: pre;"> </span>callBack(null, dirs);<br />
<span class="Apple-tab-span" style="white-space: pre;"> </span>}<br />
}<b><span style="color: #0b5394;">)(0)</span></b>;</blockquote>
<div>
<br /></div>
完整的程式如下。</div>
<pre><code class="js">
var fs = require("fs");
function listDir (rootDir, callBack) {
fs.readdir(rootDir, function (err, files) {
if(err){
callBack(err);
return;
}
var dirs = [];
<b><span style="color: #0b5394;">(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);</span></b>
});
}
listDir(".",function (err, dirs) {
if(!err){
console.log("got folders : "+dirs);
}else{
console.log("error occurs : "+ err);
}
});
</code></pre>
就可以看到我們所預期的結果。
<br />
<blockquote class="tr_bq">
got folders : folder1,folder2 </blockquote>
以上程式碼都放在 <a href="https://github.com/Scentsome/Developer-s-Note">GitHub</a>。</div>
麥克http://www.blogger.com/profile/01716179223350987115noreply@blogger.com1tag:blogger.com,1999:blog-3469754461280175671.post-51467584866117357502013-07-12T22:57:00.002-07:002013-07-12T23:02:10.016-07:00程式設計初學者 - 書籍推薦想踏進 iOS App 開發的世界,有沒有什麼好的書可以介紹的?無論是對於沒接觸過程式開發的朋友們?我推薦的是這本。可以在<a href="http://www.books.com.tw/exep/prod/booksfile.php?item=0010564017">這裡</a>買到<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="http://im1.book.com.tw/exep/lib/image.php?image=http://addons.books.com.tw/G/001/7/0010564017.jpg&width=200&height=280&quality=80" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="640" src="http://im1.book.com.tw/exep/lib/image.php?image=http://addons.books.com.tw/G/001/7/0010564017.jpg&width=200&height=280&quality=80" width="457" /></a></div>
<div class="separator" style="clear: both; text-align: left;">
有寫過 iOS App 的朋友也許會問為什麼是 C ?而不是 Objective-C ?雖然 Objective-C 是寫 iOS 的程式語言,但有以下幾點原因,使得初學者不得不從 C 語言開始學起。</div>
<div class="separator" style="clear: both; text-align: left;">
</div>
<ol>
<li>物件的觀念利用到了 C 語言的指標。</li>
<li>數值的運算 ( 比如,加減) 還是用到 C 語言的型別。</li>
<li>iOS 底層的資料也是用 C 語言的型別儲存,比如通訊錄資料,照片,錄影檔。</li>
<li>Objective-C 本身就是包裝好的 C 語言。</li>
<li>C 語言比較單純</li>
</ol>
<div>
你可以這樣想像,Objective-C 這個語言是一個大集合,裡面包含了 C 語言這個小集合,而專門用來編譯 Objective-C 語言的 IDE Xcode 可是可以編譯 C, C++, Objective-C 三種語言。</div>
<div>
回到書本上的內容,對於想學 iOS App 設計的朋友們,前兩章是必看的內容,第一章就教導了基本的程式語言的概念還有型別,if-else, for-loop, 等幾個重要的流程控制。第二章介紹了指標這個很重要的觀念,前文提到除了和物件有關之外,iOS App 的記憶體管理也是需要這個觀念。前兩章一定要看熟。如果還有一點時間可以看到第四章看完,讀懂,差不多就具備學習 iOS App 開發的前置工作了。</div>
<div>
書本上,有介紹了用終端機,搭配任意的文字編輯器來編譯程式,對於有 Mac 的朋友們,接下來介紹如何用 Xcode 來編譯前四章的程式內容。</div>
<div>
看到書裡面的內容對初學者來說,一開始是相當的苦惱,如 Chapter 1 第 2 頁的圖</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiekJIAkTMHK244lSWF5DKbEqMKLwZ5-O4W9xqBYobzJmTFmCJ_GBvDTBjAVQc9geK54RkO-7uq_n1RTaiBCUuuHk-zXvm1uXt0J_0qjTBfmVJ_zthVXveo94PXn8wYtjX14vgo7dcxs2lL/s1600/Screen+Shot+2013-07-13+at+%E4%B8%8B%E5%8D%8812.12.36.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="342" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiekJIAkTMHK244lSWF5DKbEqMKLwZ5-O4W9xqBYobzJmTFmCJ_GBvDTBjAVQc9geK54RkO-7uq_n1RTaiBCUuuHk-zXvm1uXt0J_0qjTBfmVJ_zthVXveo94PXn8wYtjX14vgo7dcxs2lL/s640/Screen+Shot+2013-07-13+at+%E4%B8%8B%E5%8D%8812.12.36.png" width="640" /></a></div>
<div>
<br /></div>
<br />
什麼是 Source 又如何 Compile 之後的 Output 又如何應用和呈現?<br />
這時借助 IDE 的功能是最方便的,對初學者來說可以免去許多的麻煩。對於有 Mac 的讀者,首先要先確定自己的作業系統有 Mac App Store,在左上角蘋果圖形<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhLQkqWeWHLaIeqMe70WpcalPsgS20a6_SSVpC3W3DRxkDRoPDFmuIwDYPoyFa4DD9M-iv4nYeyZ9bWDd-l8viEbhyphenhyphen1g4zlqOIZj0h8531nqha4x62VgvMn0s9btph4dWS7Xe33OITCdX2Y/s1600/Screen+Shot+2013-07-13+at+%E4%B8%8B%E5%8D%8812.39.42.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhLQkqWeWHLaIeqMe70WpcalPsgS20a6_SSVpC3W3DRxkDRoPDFmuIwDYPoyFa4DD9M-iv4nYeyZ9bWDd-l8viEbhyphenhyphen1g4zlqOIZj0h8531nqha4x62VgvMn0s9btph4dWS7Xe33OITCdX2Y/s1600/Screen+Shot+2013-07-13+at+%E4%B8%8B%E5%8D%8812.39.42.png" /></a></div>
<br />
按下之後會出現 App Store 字樣<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgdOiD2yHALVomvpjGALT1M1Dhq_LcZd9PluH6u0tSOVe-bfELibqPO9KuQl7nKma8XThDYVAbpcd4qBIb6hCpmKtQPNiWw41C1yc9WNOP-xo0QLL3pyV8jO80n4RcEuJ9TOT6Ab1fAQ8r8/s1600/Screen+Shot+2013-07-13+at+%E4%B8%8B%E5%8D%8812.40.38.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgdOiD2yHALVomvpjGALT1M1Dhq_LcZd9PluH6u0tSOVe-bfELibqPO9KuQl7nKma8XThDYVAbpcd4qBIb6hCpmKtQPNiWw41C1yc9WNOP-xo0QLL3pyV8jO80n4RcEuJ9TOT6Ab1fAQ8r8/s320/Screen+Shot+2013-07-13+at+%E4%B8%8B%E5%8D%8812.40.38.png" width="260" /></a></div>
<br />
接著可以在視窗裡 Search "Xcode"<br />
<div class="separator" style="clear: both; text-align: center;">
</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjij2JpfjOt6pFf3CYVHxuBl2mtWUIBbKmsAy5mV9FX1BXFQI6dDhuy2K-mCjWYwVBy_6V8S6TF17D7iT7VeNMxy31l_00AjFphDhZTvpwRL8gpveH0eD4Xw2ADKjrOr4Vq0I3yiB5SbKZX/s1600/Screen+Shot+2013-07-13+at+%E4%B8%8B%E5%8D%8812.41.28.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="554" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjij2JpfjOt6pFf3CYVHxuBl2mtWUIBbKmsAy5mV9FX1BXFQI6dDhuy2K-mCjWYwVBy_6V8S6TF17D7iT7VeNMxy31l_00AjFphDhZTvpwRL8gpveH0eD4Xw2ADKjrOr4Vq0I3yiB5SbKZX/s640/Screen+Shot+2013-07-13+at+%E4%B8%8B%E5%8D%8812.41.28.png" width="640" /></a></div>
看到 Xcode 然後點選看到詳細內容如下圖<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj40Hh6jIgKjtL6N2aFQuxYRaXgqE-3GXvHoADNkDyXGwsQMULBK6RuNxmYfiNT3XgYRISecsTt9S1lvuAT4ytyKcBTGJfhdDMGIZvOMhrKAwy4_I83HwJPPiSLPZl3wymsg8q1vtvdGWdb/s1600/Screen+Shot+2013-07-13+at+%E4%B8%8B%E5%8D%8812.43.37.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="554" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj40Hh6jIgKjtL6N2aFQuxYRaXgqE-3GXvHoADNkDyXGwsQMULBK6RuNxmYfiNT3XgYRISecsTt9S1lvuAT4ytyKcBTGJfhdDMGIZvOMhrKAwy4_I83HwJPPiSLPZl3wymsg8q1vtvdGWdb/s640/Screen+Shot+2013-07-13+at+%E4%B8%8B%E5%8D%8812.43.37.png" width="640" /></a></div>
上圖 Installed 的按鈕是因為筆者己經安裝,如果未安裝則是 Free 字樣。Xcode 是免費的可以直接安裝。安裝中的時候,從 Mac App Store 下載的 App 都會被放在 LaunchPad。如下圖<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgB12fYL1Vhh3GQRcmuV2cpgVAgG-7lX7ip1SvyFD19rHVwQPnz_CFme2il4PevMl3nZPPQd8hUQeubBt0K8bBJcIaPlWENTlg8F-HNG-U68zXkGLeT_L231sfohOmIP0F6joRj9yPYNuAI/s1600/Screen+Shot+2013-07-13+at+%E4%B8%8B%E5%8D%8812.46.34.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="78" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgB12fYL1Vhh3GQRcmuV2cpgVAgG-7lX7ip1SvyFD19rHVwQPnz_CFme2il4PevMl3nZPPQd8hUQeubBt0K8bBJcIaPlWENTlg8F-HNG-U68zXkGLeT_L231sfohOmIP0F6joRj9yPYNuAI/s640/Screen+Shot+2013-07-13+at+%E4%B8%8B%E5%8D%8812.46.34.png" width="640" /></a></div>
接著下載完成之後,可以利用 Mac 的快速鍵 Ctrl + Space 打開右上的 Spotlight 來開啟 Xcode。如下步驟。<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjl-4WRSOYPVEMI9DnK6Ds0VAtE1V4skbOPxwbJjABxTUnQ7HE-v22WVVY-2nVFruUcrg6r-_MMHCLnL3_As5ayBtNscyFrsLH6tbVjpGetEo7CyXu2HZf0CVMNFhUfiLdh7YIG02qKqfsj/s1600/Screen+Shot+2013-07-13+at+%E4%B8%8B%E5%8D%8812.49.34.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="182" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjl-4WRSOYPVEMI9DnK6Ds0VAtE1V4skbOPxwbJjABxTUnQ7HE-v22WVVY-2nVFruUcrg6r-_MMHCLnL3_As5ayBtNscyFrsLH6tbVjpGetEo7CyXu2HZf0CVMNFhUfiLdh7YIG02qKqfsj/s640/Screen+Shot+2013-07-13+at+%E4%B8%8B%E5%8D%8812.49.34.png" width="640" /></a></div>
選擇開啟 Xcode 之後應該會如下畫面<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg3y_BaEy6yQoIoo_ae84y1mkQh6vFkRir5PZuWL-KowvwKlC1JqcIVEbgAcOw2G9RgcaWGgyrUnM075MDWSaEQr2MOeOMLa1N1xLffMgOqEbbuKCw0Da6uUuPRYtOtCnQLU9RzQC1tqlUF/s1600/Screen+Shot+2013-07-13+at+%E4%B8%8B%E5%8D%8812.51.17.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="442" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg3y_BaEy6yQoIoo_ae84y1mkQh6vFkRir5PZuWL-KowvwKlC1JqcIVEbgAcOw2G9RgcaWGgyrUnM075MDWSaEQr2MOeOMLa1N1xLffMgOqEbbuKCw0Da6uUuPRYtOtCnQLU9RzQC1tqlUF/s640/Screen+Shot+2013-07-13+at+%E4%B8%8B%E5%8D%8812.51.17.png" width="640" /></a></div>
因為我們要利用 Xcode 來練習書本中的例子,需要開一個專案或叫 Project。在如下的地方<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhVVUJQjG8be_FI15DYqUe_ywlhGzz0c303dRl5GCpKhqWkTkJzfoo7sDX-0j6mE5D5vDrFHHgrXAcfzxLqxagZdhP2pyz7CAyZ-1ZvMKXGpOTegY9GC8t8G0cA7pPrPxPLahDrWQzIZmn4/s1600/Screen+Shot+2013-07-13+at+%E4%B8%8B%E5%8D%8812.51.17.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="442" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhVVUJQjG8be_FI15DYqUe_ywlhGzz0c303dRl5GCpKhqWkTkJzfoo7sDX-0j6mE5D5vDrFHHgrXAcfzxLqxagZdhP2pyz7CAyZ-1ZvMKXGpOTegY9GC8t8G0cA7pPrPxPLahDrWQzIZmn4/s640/Screen+Shot+2013-07-13+at+%E4%B8%8B%E5%8D%8812.51.17.png" width="640" /></a></div>
或者在 Menu bar 上面也可以開啟新的專案。如下畫面。<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgd107GouNFyMxPUQDZMJZl-HXeSPDlsdYEA3nt_4DCS4HRqWRdjQaHcm1XpDu0JFqcJPzjXlMqJzfzjyayBMS4RmaRJl1Q6MD6BLaf4px13iDmPEO2a609f_Xl1Rek2hkEDIGNHfsEaEOj/s1600/openXcode.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="640" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgd107GouNFyMxPUQDZMJZl-HXeSPDlsdYEA3nt_4DCS4HRqWRdjQaHcm1XpDu0JFqcJPzjXlMqJzfzjyayBMS4RmaRJl1Q6MD6BLaf4px13iDmPEO2a609f_Xl1Rek2hkEDIGNHfsEaEOj/s640/openXcode.png" width="624" /></a></div>
兩個方法選一種,都可以看到下面畫面。<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhyfe23fOfR08eoY-1uZnpBV3erRX2PCMhJ8dLHCU5dRZfu3PhzZ7UI61eL9WBrHOuaHme2pvYOKXbOZBsDvK05sRtUA5fD-ObI2ADKbMm_A4PDXen-LwqDQG3M0ACeQzCGD26nQQAIo5rq/s1600/Screen+Shot+2013-07-13+at+%E4%B8%8B%E5%8D%8812.55.54.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="416" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhyfe23fOfR08eoY-1uZnpBV3erRX2PCMhJ8dLHCU5dRZfu3PhzZ7UI61eL9WBrHOuaHme2pvYOKXbOZBsDvK05sRtUA5fD-ObI2ADKbMm_A4PDXen-LwqDQG3M0ACeQzCGD26nQQAIo5rq/s640/Screen+Shot+2013-07-13+at+%E4%B8%8B%E5%8D%8812.55.54.png" width="640" /></a></div>
拉近一點看,我們看中間可以選擇的地方。<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgZzmNLEK4LOCsIpk7jJBjeIRc5wQ1dURuzkH_6YCAhadK5RaCN3BmwxMdT3p3aHUdPMotU3wDM-zd42ygQRY-yRJI07oNjtmYt5y4J5EkKyzsX_TduoFJ8wN99hm7AEMFw03MxkYoR199H/s1600/Screen+Shot+2013-07-13+at+%E4%B8%8B%E5%8D%8812.56.40.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="430" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgZzmNLEK4LOCsIpk7jJBjeIRc5wQ1dURuzkH_6YCAhadK5RaCN3BmwxMdT3p3aHUdPMotU3wDM-zd42ygQRY-yRJI07oNjtmYt5y4J5EkKyzsX_TduoFJ8wN99hm7AEMFw03MxkYoR199H/s640/Screen+Shot+2013-07-13+at+%E4%B8%8B%E5%8D%8812.56.40.png" width="640" /></a></div>
我需要簡單的 OS X Application,然後選擇 Command Line Tool 。接著按下右下角的 Next 看到如下畫面。<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj3a8x3nYHklPjWX7JeQwAi1xfAwQSjNg9oH3IwdIyCOOU7sJZe2ZhFTq0TCzwotk3wDGSZLiNBAQ_jNXvmE6D6tkOvOgBw-15RMhBXD7R2g49KmnKM4KFRgfBs0-aGGznqS2PtB-i0-Gqn/s1600/Screen+Shot+2013-07-13+at+%E4%B8%8B%E5%8D%8812.59.41.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="430" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj3a8x3nYHklPjWX7JeQwAi1xfAwQSjNg9oH3IwdIyCOOU7sJZe2ZhFTq0TCzwotk3wDGSZLiNBAQ_jNXvmE6D6tkOvOgBw-15RMhBXD7R2g49KmnKM4KFRgfBs0-aGGznqS2PtB-i0-Gqn/s640/Screen+Shot+2013-07-13+at+%E4%B8%8B%E5%8D%8812.59.41.png" width="640" /></a></div>
<br />
幾個設定說明一下<br />
<br />
<ul>
<li>Product Name - 這個專案的名稱,沒有一定要寫什麼但是一定要寫。也就是這個專案資料夾的名稱</li>
<li>Organization Name - 組織名稱,也不是什麼重要的資料,如果沒有公司就任意寫</li>
<li>Company Identifier - 這個 ID 會影響到上架 App 到 App Store 販賣的時候 Apple 會檢查,目前是沒有要上架,就用任意的英文加上 . 來填入資料</li>
<li>Type - 這裡有幾個可以可選,目前是練習 C 語言所以選 C,如果是要練習 Objective-C 語言,就選 Foundation</li>
<li>Use Automatic Reference Counting - 是和記憶體管理有關,目前也不是重點。就選打勾就好了。</li>
</ul>
<div>
接著按 Next 按鈕。會看到如下畫面。</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgjQ6csuGbVZ2Yxlj6hOVnOQERJlJSGHuXGC62jxsp-glIXboe54tMa4XTUUYVJb9lxlyeMnaRyJID-7cU4YfHbjReSJkPIWcOQlBjyKDYgIRuoebFRFkaYLalrOLZlF0shXhPw3ZI5LTdO/s1600/Screen+Shot+2013-07-13+at+%E4%B8%8B%E5%8D%881.06.27.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="540" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgjQ6csuGbVZ2Yxlj6hOVnOQERJlJSGHuXGC62jxsp-glIXboe54tMa4XTUUYVJb9lxlyeMnaRyJID-7cU4YfHbjReSJkPIWcOQlBjyKDYgIRuoebFRFkaYLalrOLZlF0shXhPw3ZI5LTdO/s640/Screen+Shot+2013-07-13+at+%E4%B8%8B%E5%8D%881.06.27.png" width="640" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div>
選擇一個目錄,儲存這個專案,也是一整個資料夾,就是 Product Name。接著會看到如下畫面。</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjDAMoOYnArK2WZ5SjoNyNL9I4OCTiZVOmgAzKBJfKtqjJN73XUECdEfGBYzXVg6LXYg8x7wbeT1BQUOUME_r7hnP8eR9fNN-MCZ2qaP3LetN0hHlFHmSdzDqbevy6mEaELGn1rhbTsV2JH/s1600/Screen+Shot+2013-07-13+at+%E4%B8%8B%E5%8D%881.08.47.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="416" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjDAMoOYnArK2WZ5SjoNyNL9I4OCTiZVOmgAzKBJfKtqjJN73XUECdEfGBYzXVg6LXYg8x7wbeT1BQUOUME_r7hnP8eR9fNN-MCZ2qaP3LetN0hHlFHmSdzDqbevy6mEaELGn1rhbTsV2JH/s640/Screen+Shot+2013-07-13+at+%E4%B8%8B%E5%8D%881.08.47.png" width="640" /></a></div>
<div>
這些都是專案的設定值,目前也用不到。接下來是我們要寫程式的主角,就是在左邊,很多個檔案有一個名稱叫 main.c 。如下畫面</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgjPnlXHGgqkWKRwy4duR9Raf9DhbMcvcnvjMPLfqqy8KONqPG_gnSDuTbKu8jh0RmSLZrssaE3fzT1JcZlCGA6zQj4sABqZIxbG0rbz2QjKzBP_4NUnMOMZYHiumRhxqnRQE5IUHAUpbAP/s1600/Screen+Shot+2013-07-13+at+%E4%B8%8B%E5%8D%881.16.27.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="416" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgjPnlXHGgqkWKRwy4duR9Raf9DhbMcvcnvjMPLfqqy8KONqPG_gnSDuTbKu8jh0RmSLZrssaE3fzT1JcZlCGA6zQj4sABqZIxbG0rbz2QjKzBP_4NUnMOMZYHiumRhxqnRQE5IUHAUpbAP/s640/Screen+Shot+2013-07-13+at+%E4%B8%8B%E5%8D%881.16.27.png" width="640" /></a></div>
<div>
拉近一點看,左邊是如下圖面</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiKq9NPUnri4XTgYSCi5lg8z1enhFZiM7pS5q3bw6iJBmE8bZvgCenTNNzQoQJtrzEHehEblwvpc7hK64kBKtoMXfzrK2ce6vgtc7ml70FEWBJ9eAcwItdUp50E2jpGjdlgNCaEz9ovOc6S/s1600/Screen+Shot+2013-07-13+at+%E4%B8%8B%E5%8D%881.18.06.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="245" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiKq9NPUnri4XTgYSCi5lg8z1enhFZiM7pS5q3bw6iJBmE8bZvgCenTNNzQoQJtrzEHehEblwvpc7hK64kBKtoMXfzrK2ce6vgtc7ml70FEWBJ9eAcwItdUp50E2jpGjdlgNCaEz9ovOc6S/s320/Screen+Shot+2013-07-13+at+%E4%B8%8B%E5%8D%881.18.06.png" width="320" /></a></div>
<div>
中間主要編輯畫面如下</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg-GY8KV0m4AxeFhsmbzusQAO9yLREZ2YeN9pYtmCtrhgCUyg2Tp2NdHdB5Ko3zEONzZVcJFOKS8zcgOQq4e1E7wwHYT8Td3v7q1_Tz6WBcfIiQuZ0dGKykfVuNTe-79XDsTP77EBR-UPWS/s1600/Screen+Shot+2013-07-13+at+%E4%B8%8B%E5%8D%881.18.55.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="278" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg-GY8KV0m4AxeFhsmbzusQAO9yLREZ2YeN9pYtmCtrhgCUyg2Tp2NdHdB5Ko3zEONzZVcJFOKS8zcgOQq4e1E7wwHYT8Td3v7q1_Tz6WBcfIiQuZ0dGKykfVuNTe-79XDsTP77EBR-UPWS/s640/Screen+Shot+2013-07-13+at+%E4%B8%8B%E5%8D%881.18.55.png" width="640" /></a></div>
<div>
這些程式碼是不是很像有看過?在哪呢?就在書中</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg6bEEDd-MtscAwx6QNvESpdmlWzpF6KOjXersEAKGviZTIuonwBV3qnmF_B7tOkPXqssrh6gpAux2pzMso1vXkRarE1Do7lCIt8OIa6zhDXcM3AVbtUw6DdlaRlqSCMVoWIb9Sskin2IgH/s1600/Screen+Shot+2013-07-13+at+%E4%B8%8B%E5%8D%881.29.42.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg6bEEDd-MtscAwx6QNvESpdmlWzpF6KOjXersEAKGviZTIuonwBV3qnmF_B7tOkPXqssrh6gpAux2pzMso1vXkRarE1Do7lCIt8OIa6zhDXcM3AVbtUw6DdlaRlqSCMVoWIb9Sskin2IgH/s320/Screen+Shot+2013-07-13+at+%E4%B8%8B%E5%8D%881.29.42.png" width="226" /></a></div>
<div>
<br /></div>
<div>
還記得嗎?一開始書中提到 Source -> Compile -> Output 的 Source </div>
<div>
如果我們把 Xcode 的 main.c 的內容改成和 rocks.c 的內容一樣如下<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhAgIp8cOaokOOQHvAODuuO3VXqKrEqq78kUZYqiX1iLkv6Ay5z4qCl044tGQqZJT_lA5sgWolZBgDAivs8VDM3bvxtdh_3M8zvg2c9D_HgLwJmQ__VppQcZPYxHAImynCJSIPjpe5_qBEO/s1600/Screen+Shot+2013-07-13+at+%E4%B8%8B%E5%8D%881.34.01.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="208" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhAgIp8cOaokOOQHvAODuuO3VXqKrEqq78kUZYqiX1iLkv6Ay5z4qCl044tGQqZJT_lA5sgWolZBgDAivs8VDM3bvxtdh_3M8zvg2c9D_HgLwJmQ__VppQcZPYxHAImynCJSIPjpe5_qBEO/s640/Screen+Shot+2013-07-13+at+%E4%B8%8B%E5%8D%881.34.01.png" width="640" /></a></div>
<br /></div>
<pre><code class="c">
int main(int argc, const char * argv[])
{
puts("C Rocks");
return 0;
}
</code></pre>
上方是程式碼文字的部分
<br />
接著就是 Compile 的部分了。對於 Xcode 來說就是按下左上角的有點類似 Play 的符號。<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj4nKFsdeQKFh7JKqpAoNmpJE5tBS3j1HA6d_hjvMBFTFIUeQg_dNLR_-CnHeBXxgnh03eNhPq54b4igyWSEhBn9adDC5Ypw8XUo95LnihYM_vNPm-3EektFlLgzErCt60_PTVdKbzBx3a0/s1600/Screen+Shot+2013-07-13+at+%E4%B8%8B%E5%8D%881.36.48.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="294" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj4nKFsdeQKFh7JKqpAoNmpJE5tBS3j1HA6d_hjvMBFTFIUeQg_dNLR_-CnHeBXxgnh03eNhPq54b4igyWSEhBn9adDC5Ypw8XUo95LnihYM_vNPm-3EektFlLgzErCt60_PTVdKbzBx3a0/s640/Screen+Shot+2013-07-13+at+%E4%B8%8B%E5%8D%881.36.48.png" width="640" /></a></div>
這個就是 Compile 了。那 Output 呢?按下 Run 之後在 Xcode 下方應該會有一個視窗突然出現。<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhjsX0wvGFeaSqxf7eroSsXBLCpC64wYqA6tZB7qyDg54OhM8Oh4tIdfHfjhESg_sVbJcMVeZDDw6qWmYvWss-4-yqy-F8-zjr54Ix8JQ8ZzF3B1ssy9WCkqdi-AgNiIH0x96uPws5BHiCf/s1600/Screen+Shot+2013-07-13+at+%E4%B8%8B%E5%8D%881.40.55.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="416" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhjsX0wvGFeaSqxf7eroSsXBLCpC64wYqA6tZB7qyDg54OhM8Oh4tIdfHfjhESg_sVbJcMVeZDDw6qWmYvWss-4-yqy-F8-zjr54Ix8JQ8ZzF3B1ssy9WCkqdi-AgNiIH0x96uPws5BHiCf/s640/Screen+Shot+2013-07-13+at+%E4%B8%8B%E5%8D%881.40.55.png" width="640" /></a></div>
拉進一點看。如下<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhSizg_uRZpVjGreSuw5GuK2mCq0L7OIgCseCyobZUKn-xKyyEl4X5Lwb0jP-zIhNv3PMOoYSQj5X3ZM4cEghXSS6D1ncEjtWOF7s2hClJJmDOaEpLYpqu0AmYfr0ycA6QQDDmxLOLN4Hcr/s1600/Screen+Shot+2013-07-13+at+%E4%B8%8B%E5%8D%881.41.45.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="102" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhSizg_uRZpVjGreSuw5GuK2mCq0L7OIgCseCyobZUKn-xKyyEl4X5Lwb0jP-zIhNv3PMOoYSQj5X3ZM4cEghXSS6D1ncEjtWOF7s2hClJJmDOaEpLYpqu0AmYfr0ycA6QQDDmxLOLN4Hcr/s640/Screen+Shot+2013-07-13+at+%E4%B8%8B%E5%8D%881.41.45.png" width="640" /></a></div>
在 puts 裡面的內容被呈現在最下方的視窗裡。這個就是 Output 結果。<br />
之後在書中的程式碼,都可以被放憂 main function 裡。也就是在<br />
<pre><code class="c">
int main(int argc, const char * argv[])
{
</code></pre>
和<br />
<pre><code class="c">return;
</code></pre>
中間
比如接下來 Chapter 1 在第 3 頁的
<br />
<pre><code class="c">
int card_count = 11;
if (card_count > 10)
 puts("The deck is hot. Increase bet.");
</code></pre>
讀者將會在 Output 的地方看到<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjhcbaD3ofSNHWP_ruP6oBA7D3-JpjdBlKESwAFdHBDCYFEGu1C8xhU4riqwqD9v59Vw3ZwoTDuCxhsxmF8gzzki27JAAIrOpTS74bCsq6WHXcJSWADSHWxxEx2k-uS48na1-3qZQ882NsA/s1600/Screen+Shot+2013-07-13+at+%E4%B8%8B%E5%8D%881.49.37.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="104" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjhcbaD3ofSNHWP_ruP6oBA7D3-JpjdBlKESwAFdHBDCYFEGu1C8xhU4riqwqD9v59Vw3ZwoTDuCxhsxmF8gzzki27JAAIrOpTS74bCsq6WHXcJSWADSHWxxEx2k-uS48na1-3qZQ882NsA/s640/Screen+Shot+2013-07-13+at+%E4%B8%8B%E5%8D%881.49.37.png" width="640" /></a></div>
其他的練習就交給讀者了。只要有一個專案就可以練習書上所有的程式碼。這樣才可以理解書上說了什麼。<br />
以上是用 Mac 的 Xcode 來建立一個方便初學者練習書上程式碼的方法,當然其他的平台也有其好用的 IDE 。比如跨平台的 <a href="http://www.codeblocks.org/">Code::Block</a> 等等。當然如果要用 Objective-C 開發 iOS App ,最推薦的,還是 Xcode。<br />
最後提醒大家,想要學 iOS App 程式設計,前 2 個章節一定要看完哦。<br />
有問題可以到<a href="http://on.fb.me/WHSgjt">粉絲團</a>問問題。<br />
祝各位初學者,學習快樂。麥克http://www.blogger.com/profile/01716179223350987115noreply@blogger.com0tag:blogger.com,1999:blog-3469754461280175671.post-33631753060577420132013-06-04T20:55:00.003-07:002013-06-04T20:56:01.185-07:00在 Apple 舉辦的開發者交流會 - Node.js過了好久才把照片放上來 orz..<br />
這次的主題是 Node.js 。<br />
主要的原因是廣大的學員們除了開發 iOS App 之外多多少少都會想要接觸 Sever 端的程式。<br />
或者要和 Server 端的開發者合作開發一個雲端和手持式整合的服務。<br />
這時候了解 Server 在幹嘛也是一件可以幫助快速整合的方法。<br />
<br />
主題:Server Side Application for iOS Developer - Node.js<br />
時間:2013 5/2<br />
地點:Taiwan Apple 大型會議室<br />
<br />
首來先看一下大家等待的照片<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh-agOEs4eaK37-FAgcLhiDvvFScNmBHa_E4gxuMNK0Hhb_zUvrJIL9EHWNmVHOrfXiElSlg_VwRBZH-Q238QL-WEHiTCF22M31vybenTbiUWGvBCymvQjWY_4VgUEtU9EgA5sxaLrU83uf/s1600/362723814650.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="480" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh-agOEs4eaK37-FAgcLhiDvvFScNmBHa_E4gxuMNK0Hhb_zUvrJIL9EHWNmVHOrfXiElSlg_VwRBZH-Q238QL-WEHiTCF22M31vybenTbiUWGvBCymvQjWY_4VgUEtU9EgA5sxaLrU83uf/s640/362723814650.jpg" width="640" /></a></div>
<br />
接下來由小弟我為大家來個 Opening<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEid3rpxs8Of0zDd_WJ2oRU1bwsf_qFT8lhu5a966IBuZHM0EsqURd0XRmWa521GE9GqbiZHZgOvvJ7ZL9iTwXITOp7wey5_8VR56a7ngdzjPLiaSJtRoQXFMJ1fk8O7QNShcNSZV6NCxT0n/s1600/362721538070.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="480" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEid3rpxs8Of0zDd_WJ2oRU1bwsf_qFT8lhu5a966IBuZHM0EsqURd0XRmWa521GE9GqbiZHZgOvvJ7ZL9iTwXITOp7wey5_8VR56a7ngdzjPLiaSJtRoQXFMJ1fk8O7QNShcNSZV6NCxT0n/s640/362721538070.jpg" width="640" /></a></div>
<br />
Apple 人員來解說一下 Apple 在台灣提供相當多的服務,而不是只有賣東西而己,對於企業,和教育市場也投入很多的心思在裡面。企業內部有問題可以直接找他們。<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiCevHavxbAS4CR68Acu2yOA9h8lLfTXqxfv-yaH9XRmx-Zk_TUQ9p22Tib76Vc52Zcht6rw40JsNbAiGAFW-z9Ka4qrnvC0pwllYvdJdZDgjShMmUSxbyM_OmGnBsV-CCAqxuBVWcm7r8a/s1600/362724840606.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="480" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiCevHavxbAS4CR68Acu2yOA9h8lLfTXqxfv-yaH9XRmx-Zk_TUQ9p22Tib76Vc52Zcht6rw40JsNbAiGAFW-z9Ka4qrnvC0pwllYvdJdZDgjShMmUSxbyM_OmGnBsV-CCAqxuBVWcm7r8a/s640/362724840606.jpg" width="640" /></a></div>
<br />
接下來就是主題啦。<a href="http://rettamkrad.blogspot.tw/">Fin</a> 上場了啦。<br />
Fin 在 Web Front End 混了許久,因為 Node.js 是用 Javascript 來開發,對於前端工程師來說是很熟悉的一件事。對於最新的前端技術:HTML5、CSS3、Responsive Design、Node.JS等議題有深入的研究。在一年前就投入 Node.js 相關技術的開發工作。<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjBnB0njTW9oMIL90Y-9JHTsOeNTk1d1Cfp8BfZ9OW52gzQb_srEgXFRpRB0_zUcUq-JagecqM3vfCE0OHMxBB4cvl6BEXuuAbF5dxTqP4lBbIwIAVTLeUI6XR_yRxY79tFj-c17rT9eDsN/s1600/362725284515.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="480" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjBnB0njTW9oMIL90Y-9JHTsOeNTk1d1Cfp8BfZ9OW52gzQb_srEgXFRpRB0_zUcUq-JagecqM3vfCE0OHMxBB4cvl6BEXuuAbF5dxTqP4lBbIwIAVTLeUI6XR_yRxY79tFj-c17rT9eDsN/s640/362725284515.jpg" width="640" /></a></div>
<div>
<br /></div>
<div>
Fin 很愛法國鬥牛犬</div>
<div>
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiFQPSIwfl9-rXg-cgVujyIAIITorkgAKR6AS0_j59HMT3mFGRrQ0UX0uTGo7X9Qea9Auk6Lo1WUI7YQ7jjaDONV060hol7WfGC93ZLqP_Edk8lEr6neI-UgQ6iaKKZKJQgEwUOXrIDCmmo/s1600/362725858782.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="480" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiFQPSIwfl9-rXg-cgVujyIAIITorkgAKR6AS0_j59HMT3mFGRrQ0UX0uTGo7X9Qea9Auk6Lo1WUI7YQ7jjaDONV060hol7WfGC93ZLqP_Edk8lEr6neI-UgQ6iaKKZKJQgEwUOXrIDCmmo/s640/362725858782.jpg" width="640" /></a></div>
<div>
<br />
好啦,大家很高興的回家啦。<br />
如果要再見到 Fin,可以參考他開的 Node.js 課程。<br />
咱們下次再見了。</div>
麥克http://www.blogger.com/profile/01716179223350987115noreply@blogger.com1tag:blogger.com,1999:blog-3469754461280175671.post-2167094284293453242013-02-25T18:21:00.003-08:002013-02-25T18:33:13.689-08:00Mobile Game App 開發者交流會<br />
<h2>
<span class="s1"><b>Mobile Game App 開發者交流會</b></span></h2>
<div class="p1">
<span class="s1">
</span></div>
<div class="p1">
<span class="s1"><b>時間:2/25 9:30 ~ 12:00 A.M.</b></span></div>
<div class="p1">
<span class="s1"><b>地點:Apple Taiwan</b></span></div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhH2sSB4cYR1CcYPE7pqpn3AHYV12lkHeYNJIPmC2-Ks00NuwkvTXFi3H5pL7loHyL8ZYBKfxplOvPZwh17kOgjm_jqW7EX5VypOV2X2s5GtAU0b_kBAUAx3A8KV1XQjertu419Z-lr_LT9/s1600/IMG_0249.JPG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="480" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhH2sSB4cYR1CcYPE7pqpn3AHYV12lkHeYNJIPmC2-Ks00NuwkvTXFi3H5pL7loHyL8ZYBKfxplOvPZwh17kOgjm_jqW7EX5VypOV2X2s5GtAU0b_kBAUAx3A8KV1XQjertu419Z-lr_LT9/s640/IMG_0249.JPG" width="640" /></a></div>
<br />
<div class="p1">
<span class="s1">第三次的交流會,重點是放在 Mobile Game 的開發,可以利用 Corona SDK 這個工具很簡單快速地開發 iOS 和 Android 的 App 只要寫一次程式碼可以應用在多個裝置上。</span></div>
<div class="p2">
<span class="s1"></span><br /></div>
<div class="p1">
<span class="s1">這次很高興地邀請到 <a href="http://www.facebook.com/djthomaswei"><span class="s2">Wei Wei</span></a> 來和我們分享他的人生歷程和開發歷程。從 Corona SDK 的使用方法</span></div>
<div class="separator" style="clear: both; text-align: center;">
</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjWlO6j8Dxp-M2e2U3YuCiKvb6Ht-hEw7N_F8OkdaB8Iaa6bS-FDVd4oOmqAyCLz_pJUHiKG26jecWEAXH8z1AHVKPSoyfiKMnmzZ44yrhkYWizmDdpWMgqyIoDKqal9Y4XF3y5dSxwIQz8/s1600/IMG_0251.JPG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="480" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjWlO6j8Dxp-M2e2U3YuCiKvb6Ht-hEw7N_F8OkdaB8Iaa6bS-FDVd4oOmqAyCLz_pJUHiKG26jecWEAXH8z1AHVKPSoyfiKMnmzZ44yrhkYWizmDdpWMgqyIoDKqal9Y4XF3y5dSxwIQz8/s640/IMG_0251.JPG" width="640" /></a></div>
<div class="p1">
<span class="s1"><br /></span></div>
<div class="p1">
<span class="s1">
</span></div>
<div class="p1">
<span class="s1">到 Hello World</span></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjAubp7yPpef2J0c4Elq6C-cMLra0ODo4t26G5Bn5HaDTvFmBkPCf_DnzpGFwLjqtyrE-LoWe4ouIgnAvBmZrZ6sNmUqCLQkM7YkMfvHQTB_SCwqTze-9d6xUSJpBKkT54_ufvIuRCO-fyG/s1600/IMG_0252.jpg" imageanchor="1"><img border="0" height="640" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjAubp7yPpef2J0c4Elq6C-cMLra0ODo4t26G5Bn5HaDTvFmBkPCf_DnzpGFwLjqtyrE-LoWe4ouIgnAvBmZrZ6sNmUqCLQkM7YkMfvHQTB_SCwqTze-9d6xUSJpBKkT54_ufvIuRCO-fyG/s1600/IMG_0252.jpg" width="480" /></a></div>
<div class="p1">
<span class="s1">
</span></div>
<div class="p1">
<span class="s1">和 Wei Wei 為了這個會議特地打造的 Game App</span></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgnUPmUpFttHbpdGxSolt4L1Sv8NKHIaoZ5cX_9oaqWqkLSjePm58ZgzOcpYuKzKJCZzH4z2X6z_-_JJB6Yolhh9vG2MI2QwLMuG76Kj3Igkn-MQw1tQdxjLnlDj-V9uSDTpuAhS3FHo7IK/s1600/IMG_0253.JPG" imageanchor="1"><img border="0" height="480" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgnUPmUpFttHbpdGxSolt4L1Sv8NKHIaoZ5cX_9oaqWqkLSjePm58ZgzOcpYuKzKJCZzH4z2X6z_-_JJB6Yolhh9vG2MI2QwLMuG76Kj3Igkn-MQw1tQdxjLnlDj-V9uSDTpuAhS3FHo7IK/s1600/IMG_0253.JPG" width="640" /></a></div>
<div class="p1">
<span class="s1">
</span></div>
<div class="p1">
<span class="s1">可見 Wei Wei 對於這個會議的用心並讓我們了解到 Corona SDK 的簡單和強大。</span></div>
<div class="p1">
<span class="s1">最後 Wei Wei 讓我們了解到 Corona SDK 並不是只能開發 Game 一般的 Application 也是可以辦到的。</span></div>
<div class="separator" style="clear: both; text-align: center;">
<span id="goog_260607727"></span><span id="goog_260607728"></span><br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEixM1TwwvSmAgNRxf8eYy4HmdGqGpJIbLbJBaMWDS0ZD_icrHYsM68e6HP1bgSYApD5BXmvPJDQtf8oXYfpu_HZmFJgnOVVpn1u3Mh_0u4LhMom2-mdV_3_Kwgi_H-P2Lwc5LyjOt-aaa_-/s1600/IMG_0255.JPG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="480" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEixM1TwwvSmAgNRxf8eYy4HmdGqGpJIbLbJBaMWDS0ZD_icrHYsM68e6HP1bgSYApD5BXmvPJDQtf8oXYfpu_HZmFJgnOVVpn1u3Mh_0u4LhMom2-mdV_3_Kwgi_H-P2Lwc5LyjOt-aaa_-/s640/IMG_0255.JPG" width="640" /></a></div>
<br />
<br />
<div class="p1">
<span class="s1">雖然 Corona SDK 目前還有一些缺點,比如直接在 Device 上面 Debug 等,但是就 Game 而言,硬體的 Sensor 等等的支援不是這麼的必要。好玩最要緊。</span></div>
<div class="p1">
<span class="s1">當天的投影片,已放到網路上。http://www.slideshare.net/scentsome/superstar-dj-pdf</span></div>
<div class="p1">
<span class="s1">想跟著 Wei Wei 一起學習 Game App 的開發嗎?最新的課程 http://www.zencher.com/#game</span><br />
<br /></div>
花絮:<br />
等待中的大家<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj6Lm_S7ie8_QhNx5hPJVgDaoSUhhUAqi9J50llNXE7CTUIGfY6AG6jU8-j-NXvs1AgTATnaZX88ed8hY6NTso9Pc7JKX4i9E12GgZNeyg-0K-riKZMnG9YYpEDhbGLaQ9EZXEfC8oAXgGv/s1600/IMG_0245.JPG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="480" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj6Lm_S7ie8_QhNx5hPJVgDaoSUhhUAqi9J50llNXE7CTUIGfY6AG6jU8-j-NXvs1AgTATnaZX88ed8hY6NTso9Pc7JKX4i9E12GgZNeyg-0K-riKZMnG9YYpEDhbGLaQ9EZXEfC8oAXgGv/s640/IMG_0245.JPG" width="640" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
聽課中的大家<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiWug5rN9_Zoxjljp6FEhtjg5GP8fQ3mdmkPJnL_iYX1soUw5D1mgyXRNmhnycQULsAaTFm9BIQ2IPTsjZKjTQvRFBgC2eLMuhRYPMuDGl4sXQwelyogJ7Dv-ljEbQa9RojXTVs4J_TbDok/s1600/IMG_0250.JPG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="480" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiWug5rN9_Zoxjljp6FEhtjg5GP8fQ3mdmkPJnL_iYX1soUw5D1mgyXRNmhnycQULsAaTFm9BIQ2IPTsjZKjTQvRFBgC2eLMuhRYPMuDGl4sXQwelyogJ7Dv-ljEbQa9RojXTVs4J_TbDok/s640/IMG_0250.JPG" width="640" /></a></div>
<br />麥克http://www.blogger.com/profile/01716179223350987115noreply@blogger.com0tag:blogger.com,1999:blog-3469754461280175671.post-18509556000164206442013-02-21T19:32:00.001-08:002013-02-21T19:32:29.387-08:00局部畫面是要用 View 來呈現,還是要用 View Controller + View ?我們先來看一下這個例子,預設的Yahoo 的股票軟體如下<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgfkZoOlSizx3kEZSK3XB-kSnDitQD9Ny24i4FQLYhTjS4R0pfeFovF-fwB5D7dQ5XrHASUL0nhC3yQTfxCOBP_xNaqa2XVdS5Z29q6LllQDgueRQPq_tvKWJMlbbF-Zm5VossE2O1hupfa/s1600/IMG_0220.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="640" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgfkZoOlSizx3kEZSK3XB-kSnDitQD9Ny24i4FQLYhTjS4R0pfeFovF-fwB5D7dQ5XrHASUL0nhC3yQTfxCOBP_xNaqa2XVdS5Z29q6LllQDgueRQPq_tvKWJMlbbF-Zm5VossE2O1hupfa/s640/IMG_0220.PNG" width="360" /></a></div>
<br />
<div class="separator" style="clear: both; text-align: left;">
這次討論的主角是在下半部,也就是大家看到的折線圖這個區塊。如下,這篇文章討論的是局部的畫面切換。</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEibYgYlfKipwgHplx28Kq-Vwbw4H6QVPpiqgvlTiV8MVaPX3SeV3kGYu-FiPJLn31pYITcWfqMi67ox1M9osxUWxxbLWZDKiSg7-Wi4giGNwO2spa99qBapHGwK8dw3sYYwDYCgZwCQ426o/s1600/IMG_0220.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="640" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEibYgYlfKipwgHplx28Kq-Vwbw4H6QVPpiqgvlTiV8MVaPX3SeV3kGYu-FiPJLn31pYITcWfqMi67ox1M9osxUWxxbLWZDKiSg7-Wi4giGNwO2spa99qBapHGwK8dw3sYYwDYCgZwCQ426o/s640/IMG_0220.PNG" width="360" /></a></div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
這個區塊如果要 Michael 來寫的話,是用 View 去呈現還是要再多寫一個 View Controller 呢?在下決定之前不能只看這個區塊,要再觀察之後的畫面,和使用者互動的過程,怎麼說呢?在這個 App,下方這個區塊,不是只可以呈現折線圖,還可以左右滑動來看其他的資訊,如下。<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi5-clIKyDAgfMimOi3ozf_f-L11fiBh5AzvwMhATdfkThRww7wHU4r2CRcYVziZXngUatjmfQcVZYCgz6VYPuKBjFwaER2_JKobDm_CZtrwXVsAFWYDydZ2Cy3yIP9WAcRt5gJPCi6xp04/s1600/IMG_0221.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="640" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi5-clIKyDAgfMimOi3ozf_f-L11fiBh5AzvwMhATdfkThRww7wHU4r2CRcYVziZXngUatjmfQcVZYCgz6VYPuKBjFwaER2_JKobDm_CZtrwXVsAFWYDydZ2Cy3yIP9WAcRt5gJPCi6xp04/s640/IMG_0221.PNG" width="360" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
或者是如下<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjU6obw19A4nZmeBHFS_AtFJh6Sa_ytVNBQ1FnoDWS1qD_MN3TOtKsVMitMiszww8pbCKbiKckvsYjK3pxKdJ2ru_QWKk2wXt0ne-iJEsZ9jx75O4MRer899A_1vQcwu2otCwAlOxDFMhFk/s1600/IMG_0222.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="640" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjU6obw19A4nZmeBHFS_AtFJh6Sa_ytVNBQ1FnoDWS1qD_MN3TOtKsVMitMiszww8pbCKbiKckvsYjK3pxKdJ2ru_QWKk2wXt0ne-iJEsZ9jx75O4MRer899A_1vQcwu2otCwAlOxDFMhFk/s640/IMG_0222.PNG" width="360" /></a></div>
<br />
<div class="separator" style="clear: both; text-align: left;">
簡單畫一個Flow char 就是如下</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgt21HnKJh5INr53FzDh39yrTRnTMmPWvDeSjAXatKKa7MjFipsqX2xjsxdFYtEi0SlEc0fHRbHxJnrZPwm6b73BNsqF-xvGlUVoK6LFaTX0EKZXm_gOhyjMQ8vfPlXJ14Qbsc1D30oScu2/s1600/flowChart.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="520" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgt21HnKJh5INr53FzDh39yrTRnTMmPWvDeSjAXatKKa7MjFipsqX2xjsxdFYtEi0SlEc0fHRbHxJnrZPwm6b73BNsqF-xvGlUVoK6LFaTX0EKZXm_gOhyjMQ8vfPlXJ14Qbsc1D30oScu2/s640/flowChart.png" width="640" /></a></div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
好我們知道整個操作的流程之後,我們來分析一下。比較複雜和比較單純的畫面<br />
就我理解,比較複雜的是如下<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgFU6KxzNW-v2XgL-WV71ltX5dbCuDFbDBqs-b11TGVvsgCYGfVmnEfZCrSramG8Wd50z23-xVRb0l4Cu1RqQgcLKULdOghDssAPZbbpBryYaqiaA3M4kP-oF2Cb5UmmGtw5qp-uf2UIVmC/s1600/complicated.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="234" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgFU6KxzNW-v2XgL-WV71ltX5dbCuDFbDBqs-b11TGVvsgCYGfVmnEfZCrSramG8Wd50z23-xVRb0l4Cu1RqQgcLKULdOghDssAPZbbpBryYaqiaA3M4kP-oF2Cb5UmmGtw5qp-uf2UIVmC/s640/complicated.png" width="640" /></a></div>
理由是,左邊要畫線圖還要和使用者互動,右邊要呈現滿多筆資料也要和使用者互動,這兩個畫面是用不同的邏輯思考去操作的。我們暫且稱,左邊的是 Chart View。右邊的是 List View。<br />
比簡單的就是<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEicNXMMZ0uY7ghtgtrz4x_JVrfYYjYbhH9CSH3OGvwuHXZk8E4SohzIKhimKEnKFWmXwMp6KU8WZFl7ObabeTpyCQ1BWkU8fYWGBXXKLHXpvzHCZX0xn6_h5uWd1P1feB9seEDN1KbLBU94/s1600/info.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEicNXMMZ0uY7ghtgtrz4x_JVrfYYjYbhH9CSH3OGvwuHXZk8E4SohzIKhimKEnKFWmXwMp6KU8WZFl7ObabeTpyCQ1BWkU8fYWGBXXKLHXpvzHCZX0xn6_h5uWd1P1feB9seEDN1KbLBU94/s1600/info.png" /></a></div>
這個畫面不用和使用者互動(暫不理會,底下的 Yahoo! 和 Information 按鈕,這兩個按鈕是把整個 Yahoo 股票首頁都換掉)。暫說這個為 Info View<br />
看完整個流程,還是沒說要用 View 還是 View + View Controller 呢?<br />
Michael 的看法是這樣<br />
<br />
<ul>
<li>如果只有一個畫面,也不切換,或是切換的畫面和 Info View 這樣比較簡單沒有互動的話,就用 View 就可以了</li>
<li>如果切換過去的畫面,比較複雜如 List View 和 Chart View,這樣就建議用 View Controller + View。</li>
</ul>
<br />
再來就是說說,在 Storyboard 裡面,可以呈現切換局部的,View Controller 的新元件。叫 Container View 。如下<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgz0YCWAdyx9-P3ET0Wi_XHBag-SI7hiZz6W20L_-KsiUS5B0PjLzetsXuCOrhEP1Jm-Z778DujwoNOiAeIha_FCCqPu513JiEtoTs3g6pF4H-8Mpy4YqiBzdEWcCkVypRthUnjFtiHxBUF/s1600/%E8%9E%A2%E5%B9%95%E5%BF%AB%E7%85%A7+2013-02-22+%E4%B8%8A%E5%8D%8811.29.02.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="306" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgz0YCWAdyx9-P3ET0Wi_XHBag-SI7hiZz6W20L_-KsiUS5B0PjLzetsXuCOrhEP1Jm-Z778DujwoNOiAeIha_FCCqPu513JiEtoTs3g6pF4H-8Mpy4YqiBzdEWcCkVypRthUnjFtiHxBUF/s320/%E8%9E%A2%E5%B9%95%E5%BF%AB%E7%85%A7+2013-02-22+%E4%B8%8A%E5%8D%8811.29.02.png" width="320" /></a></div>
把這個元件的拉到某一個 View 上面會看到如下的畫面。<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjPWWmumZE-4O6gBWNPXHbAH5xUZVwxyYokwkcDPF_VtLCANM5PZiQIfN5vMwicBOil7xpxlmBm9ciSbjhWPWbdnMWlXG1uU1IQo3yQN4xLMwXwV3AvQXD3wiZwweD1KpnfJEmsA4J6Oz-8/s1600/%E8%9E%A2%E5%B9%95%E5%BF%AB%E7%85%A7+2013-02-22+%E4%B8%8A%E5%8D%8811.30.39.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="524" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjPWWmumZE-4O6gBWNPXHbAH5xUZVwxyYokwkcDPF_VtLCANM5PZiQIfN5vMwicBOil7xpxlmBm9ciSbjhWPWbdnMWlXG1uU1IQo3yQN4xLMwXwV3AvQXD3wiZwweD1KpnfJEmsA4J6Oz-8/s640/%E8%9E%A2%E5%B9%95%E5%BF%AB%E7%85%A7+2013-02-22+%E4%B8%8A%E5%8D%8811.30.39.png" width="640" /></a></div>
自動地產生一個 View Controller 在右邊,代表著這個 View Controller 的 View 就是會被畫在左邊 Container 的區塊。麥克http://www.blogger.com/profile/01716179223350987115noreply@blogger.com0tag:blogger.com,1999:blog-3469754461280175671.post-47011495536129905322013-01-02T06:38:00.000-08:002013-01-02T06:38:00.410-08:00Git : 動手做看看 - Xcode 裡的 Git繼上篇我們了解檔案在 Git 管理的資料夾裡面的角色之後,來看一下 Xcode 是如何和 git 互動的。<br />
<div>
首先開啟一個 Xcode project 選擇 Single View Application 如下圖。命名為 GitPractice </div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgLo_WyF-eRVmR5VveDZYdOXy-YDubWGB1k8CYS3onIpTKyfoPwFyVFScHI3qEuPj0rG8CWvO-tH4dGOaf_CyDueJzBPMATL_SjbNGa6dzIXb7qgto0FU1x4PMNoHajGdPQHnjzW8QeTpWo/s1600/%25E8%259E%25A2%25E5%25B9%2595%25E5%25BF%25AB%25E7%2585%25A7+2013-01-02+%25E4%25B8%258B%25E5%258D%25886.55.09.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="432" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgLo_WyF-eRVmR5VveDZYdOXy-YDubWGB1k8CYS3onIpTKyfoPwFyVFScHI3qEuPj0rG8CWvO-tH4dGOaf_CyDueJzBPMATL_SjbNGa6dzIXb7qgto0FU1x4PMNoHajGdPQHnjzW8QeTpWo/s640/%25E8%259E%25A2%25E5%25B9%2595%25E5%25BF%25AB%25E7%2585%25A7+2013-01-02+%25E4%25B8%258B%25E5%258D%25886.55.09.png" width="640" /></a></div>
<div>
<br /></div>
<div>
選擇下一步,記得不要存在任何 git init 過的資料夾底下,要存在其他的資料夾,然後會有一個 source control 可以勾選。如下圖</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhCsHUT_T0rAkQsblaBi70oRW_jreSlj9_5bMrQ1ndQMuSyQwiurqT2Q_bzOsw54gklEFDxYftOsUIwmSZ6pmAlDTqotz_1Rk1fOjWrY_H5yZyLEDQd8yHZ43xRmN9xdTj8p1A_nuBzPuPm/s1600/%E8%9E%A2%E5%B9%95%E5%BF%AB%E7%85%A7+2013-01-02+%E4%B8%8B%E5%8D%887.16.19.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="486" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhCsHUT_T0rAkQsblaBi70oRW_jreSlj9_5bMrQ1ndQMuSyQwiurqT2Q_bzOsw54gklEFDxYftOsUIwmSZ6pmAlDTqotz_1Rk1fOjWrY_H5yZyLEDQd8yHZ43xRmN9xdTj8p1A_nuBzPuPm/s640/%E8%9E%A2%E5%B9%95%E5%BF%AB%E7%85%A7+2013-01-02+%E4%B8%8B%E5%8D%887.16.19.png" width="640" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div>
接下來就會出現 Xcode 編輯的主畫面。如下 </div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg3OmYdvVtRT7_ZUYMR6l3DTHGFcYDh1GpBNdbOpex6BfZF-Vj-Kp2Hpd8QdiDMIVxwgDAt1hclRp9kuv_EBtT1o0zxHigNFrYpvUfh8OONZgx7tt7v5NaWqQWQIydZvqhZ52uZ0nC3aqjW/s1600/%E8%9E%A2%E5%B9%95%E5%BF%AB%E7%85%A7+2013-01-02+%E4%B8%8B%E5%8D%887.18.18.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="416" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg3OmYdvVtRT7_ZUYMR6l3DTHGFcYDh1GpBNdbOpex6BfZF-Vj-Kp2Hpd8QdiDMIVxwgDAt1hclRp9kuv_EBtT1o0zxHigNFrYpvUfh8OONZgx7tt7v5NaWqQWQIydZvqhZ52uZ0nC3aqjW/s640/%E8%9E%A2%E5%B9%95%E5%BF%AB%E7%85%A7+2013-01-02+%E4%B8%8B%E5%8D%887.18.18.png" width="640" /></a></div>
<div>
看起來沒什麼特別的,我們用 Terminal 來檢視一下這個資料夾。</div>
<div>
用 Terminal 移到這個 Xcode Project 的根目錄。</div>
<div>
然後輸入</div>
<blockquote class="tr_bq">
ls -al</blockquote>
<div>
會看到如下</div>
<div>
<blockquote class="tr_bq">
total 16<br />
drwxr-xr-x 6 chronoer staff 204 1 2 19:20 .<br />
drwx------+ 78 chronoer staff 2652 1 2 19:18 ..<br />
-rw-r--r--@ 1 chronoer staff 6148 1 2 19:20 .DS_Store<br />
drwxr-xr-x 13 chronoer staff 442 1 2 19:20 <span style="color: red;"><b>.git</b></span><br />
drwxr-xr-x 13 chronoer staff 442 1 2 19:17 GitPractice<br />
drwxr-xr-x 5 chronoer staff 170 1 2 19:17 GitPractice.xcodeproj</blockquote>
Xcode 自動對這個資料夾做了 git init 的動作了。我們再用<br />
<blockquote class="tr_bq">
git log</blockquote>
來看看 Xcode 做了什麼事,會得到<br />
<blockquote class="tr_bq">
<span style="color: #bf9000;">commit</span> <span style="color: #bf9000;">823888353b2d6d19426b56658b90db86f2f2ab8a</span><br />
Author: chronoer <scentsome@gmail.com><br />
Date: Wed Jan 2 19:17:17 2013 +0800<br />
Initial Commit </blockquote>
Xcode 己經幫我們把 Single View Application 的檔案都加入給 git 管理並 commit 目前狀態為歷史的節點了。<br />
接下來我們要看一下git 的狀態用<br />
git status<br />
會得到如下的結果<br />
<br />
# On branch master<br />
# Untracked files:<br />
# (use "git add <file>..." to include in what will be committed)<br />
#<br />
#<span class="Apple-tab-span" style="white-space: pre;"> </span><span style="color: #cc0000;">GitPractice.xcodeproj/project.xcworkspace/</span><br />
#<span class="Apple-tab-span" style="white-space: pre;"> </span><span style="color: #cc0000;">GitPractice.xcodeproj/xcuserdata/</span><br />
nothing added to commit but untracked files present (use "git add" to track)<br />
原來 Xcode 預設沒有把這兩個資料夾加入 git 管理。那到底我們需不需要這兩個資料夾裡面的東西加入呢?當然是不需要,這些都是 Xcode 的衍生物和源始碼沒有太大的關係。好,那就這樣放著不管嗎? untracked files,如果之後有新增的 file 是想要加入 git 管理,又忘了加入,這樣就會和這兩個資料夾被列在 git status 的結果,一旦檔案多了,就會把真的想要加入和不想加入的搞混了,好像不怎麼好。git 有沒有方式可以設定,不想要 git 管理又不要出現在 untracked files 這邊的方法?有。要新增一個檔案名為 .gitignore。<br />
所以我們新增 .gitignore 和 .git 同一個目錄下。內容如下<br />
<br />
<blockquote class="tr_bq">
*.xcworkspace/<br />
xcuserdata/</blockquote>
對應到上方紅色的兩個目錄。接著我們要把新增的 .gitignore 加入 git 管理然後 commit 做一個歷史記錄如下輸入<br />
git add .gitignore<br />
然後 commit<br />
git commit .gitignore -m "add ignore file"<br />
這樣一來,再做一次<br />
<blockquote class="tr_bq">
git status</blockquote>
就會看到<br />
<blockquote class="tr_bq">
# On branch master<br />
nothing to commit (working directory clean)</blockquote>
這樣 git 穩定的狀態<br />
除了這兩個資料夾,還有沒有要忽略的?有的。<br />
那是哪些?其實我也是從 github 裡找到比較有名的幾個 project 來參考的。如下列出來給大家參考<br />
<br />
<blockquote>
.DS_Store<br />
.localized<br />
# Build directory<br />
build/<br />
# XCode user specific files<br />
xcuserdata/<br />
*.xcworkspace/<br />
*.mode1v3<br />
*.mode2v3<br />
*.perspectivev3<br />
*.pbxuser<br />
output</blockquote>
可以當成一個 Xcode project 用 Git 要忽略的範本了。請加到 .gitignore 。然後再一次 git add ,git commit。<br />
加了 ignore 之後,我們來看Xcode 如何處理 modified file 。首先在 AppDelegate.m 的 application:didFinishLaunchingWithOptions: 新增程式碼。如下</div>
<pre><code class="objectivec">
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// Override point for customization after application launch.
<span style="color: #6aa84f;">NSLog(@"first line");</span>
return YES;
}
</code></pre>
我們新增一行 code 在上面綠色之處。存好檔之後會在 Xcode 左邊 project navigator 欄看到在 AppDelegate.m 旁邊有一個小 m 的符號。如下圖。
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjs7vV2Dw90cXwpCj0eDUAMsWuXJ00vdVJzPNJWniTS47dZmGywAEEmPAlP9Y7HvaK28y140dpXhtAiYbCZx9ZPaKmfDlFmx8LBLpBV8xOs5XyAFsZt1RoEFPwalkpTVlSR594twP0CTYGM/s1600/%E8%9E%A2%E5%B9%95%E5%BF%AB%E7%85%A7+2013-01-02+%E4%B8%8B%E5%8D%889.39.04.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="456" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjs7vV2Dw90cXwpCj0eDUAMsWuXJ00vdVJzPNJWniTS47dZmGywAEEmPAlP9Y7HvaK28y140dpXhtAiYbCZx9ZPaKmfDlFmx8LBLpBV8xOs5XyAFsZt1RoEFPwalkpTVlSR594twP0CTYGM/s640/%E8%9E%A2%E5%B9%95%E5%BF%AB%E7%85%A7+2013-01-02+%E4%B8%8B%E5%8D%889.39.04.png" width="640" /></a></div>
代表這個 AppDelegate.m 有被修改過。此時,我們在 Terminal 輸入以下指令<br />
git status<br />
會看到<br />
<br />
# On branch master<br />
# Changes not staged for commit:<br />
# (use "git add <file>..." to update what will be committed)<br />
# (use "git checkout -- <file>..." to discard changes in working directory)<br />
#<br />
#<span class="Apple-tab-span" style="white-space: pre;"> </span><span style="color: #cc0000;">modified: GitPractice/AppDelegate.m</span><br />
#<br />
no changes added to commit (use "git add" and/or "git commit -a")<br />
和之前用 Terminal 改變 file 的一樣情況。AppDelegate.m 變成了 unstaged file。所以我們要怎麼做?git add or 直接 git commit 是吧?那是在 Terminal 的情況下,如果用了 Xcode 這種比較方便的 IDE 都會整合起來,比如在現在的情況下,只要在 AppDelegate.m 按下右鍵然後選 source control -> commit selected files 如下圖<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgWXETu3e7DJzcgKPjqvziNKPiJuWhkgczeCY-u0BSvgUxVujnRMB1Qa4xTD6MkY23irXHA3eCLgCMW_8AaYW7RCyFiXWO7YPxHGgDgTSfJeMHEMcvoyZ0s6Ro-Lkn3XiLr29589vse3gUA/s1600/commit.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="640" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgWXETu3e7DJzcgKPjqvziNKPiJuWhkgczeCY-u0BSvgUxVujnRMB1Qa4xTD6MkY23irXHA3eCLgCMW_8AaYW7RCyFiXWO7YPxHGgDgTSfJeMHEMcvoyZ0s6Ro-Lkn3XiLr29589vse3gUA/s640/commit.png" width="570" /></a></div>
然後就會跳出另一個視窗讓我們輸入此次歷史節點的註解。如下圖。<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgrkG3lGWTWFoDF6FODSFJ3Gx-SfN2IOEA6w3hr1rx9bXFlsRM6MSU5uccmKJZE7jQ8Oyu00N-a8iEs96zzdu6xzFSG2nwCImZbcs3BVtT3ZQYgTmvp1SyCpmF5R-A5Dp3t_iQWEwydGt8W/s1600/%E8%9E%A2%E5%B9%95%E5%BF%AB%E7%85%A7+2013-01-02+%E4%B8%8B%E5%8D%889.49.17.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="416" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgrkG3lGWTWFoDF6FODSFJ3Gx-SfN2IOEA6w3hr1rx9bXFlsRM6MSU5uccmKJZE7jQ8Oyu00N-a8iEs96zzdu6xzFSG2nwCImZbcs3BVtT3ZQYgTmvp1SyCpmF5R-A5Dp3t_iQWEwydGt8W/s640/%E8%9E%A2%E5%B9%95%E5%BF%AB%E7%85%A7+2013-01-02+%E4%B8%8B%E5%8D%889.49.17.png" width="640" /></a></div>
主要分幾個部分,最左邊是有修改過的檔案,中間是目前修改過的程式碼,右邊是之前最後一次的 commit 記錄。還可以往下拉看看有什麼其他的地方不一樣的。藍色的框框是小紅槓,標示著目前和最近 commit 兩個檔案不同之處,底下的紅色框框,就是寫註解的地方。如果按下右下 commit 1 file 這個按鈕,就會看到 Xcode 裡 AppDelegate.m 這個旁的小m 不見了,代表是己經 commit 的穩定狀態。如下圖<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjELoRaLahGEB95hMhcOxnC7LgWldF4E7aS0ri5s2djXt7PyCafGuXDhh83Dxa_REULvW_gEL1PGTG-1GY_aW8X1qjumDPTXSg0aW0gSKhhztMBqN5wKkNOW4HEFWf0Ids9mATXWodSF_yb/s1600/%E8%9E%A2%E5%B9%95%E5%BF%AB%E7%85%A7+2013-01-02+%E4%B8%8B%E5%8D%889.54.02.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="512" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjELoRaLahGEB95hMhcOxnC7LgWldF4E7aS0ri5s2djXt7PyCafGuXDhh83Dxa_REULvW_gEL1PGTG-1GY_aW8X1qjumDPTXSg0aW0gSKhhztMBqN5wKkNOW4HEFWf0Ids9mATXWodSF_yb/s640/%E8%9E%A2%E5%B9%95%E5%BF%AB%E7%85%A7+2013-01-02+%E4%B8%8B%E5%8D%889.54.02.png" width="640" /></a></div>
再把鏡頭轉到 Terminal,目前為止我們利用 Xcode 很方便的工具做了 git commit 的動作,用 Terminal 檢視一下,輸入<br />
<blockquote class="tr_bq">
git log</blockquote>
出現<br />
<br />
<blockquote class="tr_bq">
<span style="color: #bf9000;">commit 47454d43e92257d81a3a26ed1e8c69a5b4cbafbc</span>Author: chronoer <scentsome@gmail.com><br />Date: Wed Jan 2 21:53:58 2013 +0800<br /> add first line<br />commit 00544060eb268857bda27c13f392feaeb3d9ed09<br />Author: chronoer <scentsome@gmail.com><br />Date: Wed Jan 2 21:12:51 2013 +0800<br /> add ignore file<br />commit 823888353b2d6d19426b56658b90db86f2f2ab8a<br />Author: chronoer <scentsome@gmail.com><br />Date: Wed Jan 2 19:17:17 2013 +0800<br /> Initial Commit</blockquote>
最上方就是最新的,也就是剛剛用 Xcode commit 的結果。<br />
如果修改了很多個檔案才 commit ,比如我們在 AppDelegate.m 和 ViewController.m 都加一些程式碼如下,我們看到有兩個小m。<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjwNCr364-pxBvjbjnTiy_G_m9r1ocHI8y661sIjuyHzPjqloa0gX0qoE162C59t4QCfQJ6XitDpbR5K-vls1o1JGI-cqr5Rl5nYl1QyZoe9RXS81JdQkYrQJQh84Zth7M8Il5TbTDJNO5n/s1600/%E8%9E%A2%E5%B9%95%E5%BF%AB%E7%85%A7+2013-01-02+%E4%B8%8B%E5%8D%8810.03.23.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="225" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjwNCr364-pxBvjbjnTiy_G_m9r1ocHI8y661sIjuyHzPjqloa0gX0qoE162C59t4QCfQJ6XitDpbR5K-vls1o1JGI-cqr5Rl5nYl1QyZoe9RXS81JdQkYrQJQh84Zth7M8Il5TbTDJNO5n/s320/%E8%9E%A2%E5%B9%95%E5%BF%AB%E7%85%A7+2013-01-02+%E4%B8%8B%E5%8D%8810.03.23.png" width="320" /></a></div>
<br />
<br />
commit 的時候可以選擇多個檔案之後,再用剛剛的做法,或是在 menu 上面 File -> Source Control -> Commit 也可以一次把所有有修改的檔案列出來。如下圖<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgsvRMyQUvZAp8aRreXUfLdWukUuhy59SBllmrFi6UhPmRJiHbw3vPRGSNz3JDr-4QB6DKjJGA95a43oRLhNqNAiwuGKGSciZKX2WfXvduSZBKxHOWYcpWnEnHBQo8m54rCoaTmp9BeOFk9/s1600/file_commit.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="640" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgsvRMyQUvZAp8aRreXUfLdWukUuhy59SBllmrFi6UhPmRJiHbw3vPRGSNz3JDr-4QB6DKjJGA95a43oRLhNqNAiwuGKGSciZKX2WfXvduSZBKxHOWYcpWnEnHBQo8m54rCoaTmp9BeOFk9/s640/file_commit.png" width="538" /></a></div>
然後會出現寫註解的畫面,最左邊也會列出有多少個 File 需要被 commit 如下圖。<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiOTVG-Qwxc0-9rcM1g4v_7lPKdvyh-GJgDri2JUTn12W6GRGr2uAQfBC2Muep2k89wKz9Eq99AxsdVU9XU2-vVykieRsmRakxmgnxoyJK_AQP-IJV3YTuBPU8uMl3HD3_bSgrb2bmD-VJ5/s1600/%E8%9E%A2%E5%B9%95%E5%BF%AB%E7%85%A7+2013-01-02+%E4%B8%8B%E5%8D%8810.05.35.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="626" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiOTVG-Qwxc0-9rcM1g4v_7lPKdvyh-GJgDri2JUTn12W6GRGr2uAQfBC2Muep2k89wKz9Eq99AxsdVU9XU2-vVykieRsmRakxmgnxoyJK_AQP-IJV3YTuBPU8uMl3HD3_bSgrb2bmD-VJ5/s640/%E8%9E%A2%E5%B9%95%E5%BF%AB%E7%85%A7+2013-01-02+%E4%B8%8B%E5%8D%8810.05.35.png" width="640" /></a></div>
<br />
可以勾選,也可以一一檢查改變的地方。Commit 之後我們用 Terminal 來檢查狀態如下輸入。<br />
<blockquote class="tr_bq">
git log</blockquote>
會看到<br />
<br />
<blockquote class="tr_bq">
commit e5f66ee471d2944f6faabfaac8256e748c5df3b9<br />Author: chronoer <scentsome@gmail.com><br />Date: Wed Jan 2 22:07:06 2013 +0800<br /> add code in appDelegate.m and ViewController.m<br />commit 47454d43e92257d81a3a26ed1e8c69a5b4cbafbc<br />Author: chronoer <scentsome@gmail.com><br />Date: Wed Jan 2 21:53:58 2013 +0800<br /> add first line<br />commit 00544060eb268857bda27c13f392feaeb3d9ed09<br />Author: chronoer <scentsome@gmail.com><br />Date: Wed Jan 2 21:12:51 2013 +0800<br /> add ignore file<br />commit 823888353b2d6d19426b56658b90db86f2f2ab8a<br />Author: chronoer <scentsome@gmail.com><br />Date: Wed Jan 2 19:17:17 2013 +0800<br /> Initial Commit</blockquote>
然後再輸入<br />
<blockquote class="tr_bq">
git status</blockquote>
會看到<br />
<br />
<blockquote class="tr_bq">
# On branch master<br />nothing to commit (working directory clean)</blockquote>
這樣又回到常態了。<br />
在編輯程式碼的時候隨時都可以和之前 commit 過的版本做個比較,要用到 Xcode 右上的按鈕,如下圖<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiljoWv3fG7En8QYOzjq_VfNnIAHXkIAzN3PQft1gHB600muHLIsvCDI2J3hAFwc2YaB0gDR-Dcgh6dQW7tDskRR1geovo-FbDsBEUJAW0hL46vATJb8-9zxc7pguZzLYSRHrgVCqH6ixUr/s1600/%E8%9E%A2%E5%B9%95%E5%BF%AB%E7%85%A7+2013-01-02+%E4%B8%8B%E5%8D%8810.21.28.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="85" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiljoWv3fG7En8QYOzjq_VfNnIAHXkIAzN3PQft1gHB600muHLIsvCDI2J3hAFwc2YaB0gDR-Dcgh6dQW7tDskRR1geovo-FbDsBEUJAW0hL46vATJb8-9zxc7pguZzLYSRHrgVCqH6ixUr/s320/%E8%9E%A2%E5%B9%95%E5%BF%AB%E7%85%A7+2013-01-02+%E4%B8%8B%E5%8D%8810.21.28.png" width="320" /></a></div>
比如我們選定 AppDelegate.m 然後按下上面的按鈕就會看到如下畫面<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjub3V177EpFWIJGnD-37JAu-nHNwYuA13GLvJh-JkmrjLO9TURrCF5oCBeOM4vvNWX5yP9w_D-r9X7R32wyUDoKQ8oW6nALF21CCNJlFnY94mczQU6zhTdR4DzB7Y2GN76ngy4Ymt_hLG8/s1600/%E8%9E%A2%E5%B9%95%E5%BF%AB%E7%85%A7+2013-01-02+%E4%B8%8B%E5%8D%8810.23.31.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="416" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjub3V177EpFWIJGnD-37JAu-nHNwYuA13GLvJh-JkmrjLO9TURrCF5oCBeOM4vvNWX5yP9w_D-r9X7R32wyUDoKQ8oW6nALF21CCNJlFnY94mczQU6zhTdR4DzB7Y2GN76ngy4Ymt_hLG8/s640/%E8%9E%A2%E5%B9%95%E5%BF%AB%E7%85%A7+2013-01-02+%E4%B8%8B%E5%8D%8810.23.31.png" width="640" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
中間編輯區有兩大塊,左邊就是目前的狀態,右邊就是最近 commit 的狀態,因為剛剛 commit 完沒有修改所以兩邊看起來一樣,而且還可以比較目前的和之前 commit 的狀態,可以按下上圖紅色地方,會長出一條黑黑的有很多間格。如下<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhwybVoBoJdPBB2769C_8zgF-jgyjOlQkR2TclCS0S8VvHjQtLlfSKQUkF5Fb6fdKiWm1sGGYRUjb0lfM9yjo9otPzRn4Iq7OrkPmUroIZo4TQFQdKNTpvq-QzmHUlGcKjJrwr89Wg7k2SG/s1600/%E8%9E%A2%E5%B9%95%E5%BF%AB%E7%85%A7+2013-01-02+%E4%B8%8B%E5%8D%8810.27.17.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="548" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhwybVoBoJdPBB2769C_8zgF-jgyjOlQkR2TclCS0S8VvHjQtLlfSKQUkF5Fb6fdKiWm1sGGYRUjb0lfM9yjo9otPzRn4Iq7OrkPmUroIZo4TQFQdKNTpvq-QzmHUlGcKjJrwr89Wg7k2SG/s640/%E8%9E%A2%E5%B9%95%E5%BF%AB%E7%85%A7+2013-01-02+%E4%B8%8B%E5%8D%8810.27.17.png" width="640" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
然後可以變動圖中右邊的白色三角形,上圖紅色處,就可以看到之前 commit 版本和現在不一樣,移動到如下圖<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiD5pN5L_VRQgWd1UG6O8G_hYiHP_zCEbfH3dCShwZuw8K70HNXbU1qml5r0jBYqllUPEGd9lWP2FVF_joWVPtAoFT2K4J9Y73r1xlOeh0XVkYgO7_UX3fjBDsIN1g7p5I3ihATVBkfcyAB/s1600/%E8%9E%A2%E5%B9%95%E5%BF%AB%E7%85%A7+2013-01-02+%E4%B8%8B%E5%8D%8810.30.10.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="532" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiD5pN5L_VRQgWd1UG6O8G_hYiHP_zCEbfH3dCShwZuw8K70HNXbU1qml5r0jBYqllUPEGd9lWP2FVF_joWVPtAoFT2K4J9Y73r1xlOeh0XVkYgO7_UX3fjBDsIN1g7p5I3ihATVBkfcyAB/s640/%E8%9E%A2%E5%B9%95%E5%BF%AB%E7%85%A7+2013-01-02+%E4%B8%8B%E5%8D%8810.30.10.png" width="640" /></a></div>
如紅色所框左邊的 line 17 是和之前不一樣的地方。<br />
還記得我們曾用 Terminal 利用<br />
git checkout <commit id> <file name><br />
回到之前 commit 的版本吧?目前 Xcode 4 還沒支援直接 checkout 到舊的版本,不過可以從上圖看到某個版本的一些資訊包含 commit id ,然後再回到 Terminal 來 checkout 到舊的版本。<br />
如下圖,mouse 移到中間白色橫槓,就會變大,然後出現資訊。<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgwj2HHLyXpzUGbP8Zw7PY2a0chLKDLSEuntb_8NElnGV0Wf46oav4vdRtaj73b9kJKdMLjYUqunpFBJMEz-7ASzxcA9md3DZz9ymgwU23hZnNEdAHW1ywTvW5oD_zJfmZ2C8pOZvMWWoP9/s1600/%E8%9E%A2%E5%B9%95%E5%BF%AB%E7%85%A7+2013-01-02+%E4%B8%8B%E5%8D%8810.33.40.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="550" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgwj2HHLyXpzUGbP8Zw7PY2a0chLKDLSEuntb_8NElnGV0Wf46oav4vdRtaj73b9kJKdMLjYUqunpFBJMEz-7ASzxcA9md3DZz9ymgwU23hZnNEdAHW1ywTvW5oD_zJfmZ2C8pOZvMWWoP9/s640/%E8%9E%A2%E5%B9%95%E5%BF%AB%E7%85%A7+2013-01-02+%E4%B8%8B%E5%8D%8810.33.40.png" width="640" /></a></div>
這就是 Xcode 的 Git 對檔案的基本操作,目前為止我們把所有檔案版存在 .git 檔裡,之後會再討論怎麼和 Server 溝通。我們下次見。<br />
<br />
<br />
麥克http://www.blogger.com/profile/01716179223350987115noreply@blogger.com0tag:blogger.com,1999:blog-3469754461280175671.post-54184583032209486892012-12-29T10:23:00.001-08:002012-12-29T10:23:29.753-08:00Git : 動手做看看 - 基本觀念版本控制在管理源始碼的時候非常重要,可以讓開發者了解目前進度,共同開發時候也可以幫助開發者整合不同的源始碼。<br />
日前因為 GitHub 的火紅帶領了 Git 這個工具的流行,而大部分的開發 IDE 也都支援 Git 的功能,這篇文章透過筆者的觀點來解釋 git 的運作方式,進而利用 Xcode 來說明圖形介面的操作和其相對應底層的機制是如何。<br />
<br />
首先是<br />
<h3>
安裝 Git </h3>
<br />
<ol>
<li>可以從最新的 Xcode 安裝或</li>
<li>到這裡下載 <a href="http://code.google.com/p/git-osx-installer/">http://code.google.com/p/git-osx-installer/</a> (雖然檔案寫的是 snow leopard 但是 lion, mountain lion 都也可以用。)</li>
</ol>
<br />
1. 用 Xcode 安裝非常簡單,打開 Xcode 的 Preferences。如下<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh0It3DxxPhx-Bhy98B6qIiAAyIZTVKdK2fPjJhF-MTihUB3TX6Z09wbKhO15ZCL24nQ5kEpb1agHTp4dO6i1AveKtPAnYJnNqYJVif0XmDQ9v5FuUlgNlBonVPWjJrAOmdt2qkYmT-evcc/s1600/%E8%9E%A2%E5%B9%95%E5%BF%AB%E7%85%A7+2012-12-29+%E4%B8%8B%E5%8D%884.18.25.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="24" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh0It3DxxPhx-Bhy98B6qIiAAyIZTVKdK2fPjJhF-MTihUB3TX6Z09wbKhO15ZCL24nQ5kEpb1agHTp4dO6i1AveKtPAnYJnNqYJVif0XmDQ9v5FuUlgNlBonVPWjJrAOmdt2qkYmT-evcc/s640/%E8%9E%A2%E5%B9%95%E5%BF%AB%E7%85%A7+2012-12-29+%E4%B8%8B%E5%8D%884.18.25.png" width="640" /></a></div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgfpilHZhMs5Rttr3wgLnO8xnpLhP8_PclH3zN2V0WyPWABPClzo0a0XImHZLerhWnQTpsyYX4fBhQfc3gt1-snACF17OP1ho7i7yCt_9xGqM88HcdmQX0JKfe7JfAf-ZT5BpsHqUfGIyMd/s1600/%E8%9E%A2%E5%B9%95%E5%BF%AB%E7%85%A7+2012-12-29+%E4%B8%8B%E5%8D%884.18.14.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="616" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgfpilHZhMs5Rttr3wgLnO8xnpLhP8_PclH3zN2V0WyPWABPClzo0a0XImHZLerhWnQTpsyYX4fBhQfc3gt1-snACF17OP1ho7i7yCt_9xGqM88HcdmQX0JKfe7JfAf-ZT5BpsHqUfGIyMd/s640/%E8%9E%A2%E5%B9%95%E5%BF%AB%E7%85%A7+2012-12-29+%E4%B8%8B%E5%8D%884.18.14.png" width="640" /></a></div>
看到視窗後選 Downloads 的 Tab 然後安裝 Command Line Tools 如下。<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiQCEiCDF6okr6qYNm52YattFLHHUx9d8QDPaAHGn09aHGBT9KInHORwjk3R4i6sL4INikSmsloXl9P7r23neoIw2pHuGxHM-NIXyvbMFCfeWhtzv6mK5hhaVv5n3QbJuI_ovBo3A6XaYGs/s1600/%E8%9E%A2%E5%B9%95%E5%BF%AB%E7%85%A7+2012-12-29+%E4%B8%8B%E5%8D%884.21.04.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="490" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiQCEiCDF6okr6qYNm52YattFLHHUx9d8QDPaAHGn09aHGBT9KInHORwjk3R4i6sL4INikSmsloXl9P7r23neoIw2pHuGxHM-NIXyvbMFCfeWhtzv6mK5hhaVv5n3QbJuI_ovBo3A6XaYGs/s640/%E8%9E%A2%E5%B9%95%E5%BF%AB%E7%85%A7+2012-12-29+%E4%B8%8B%E5%8D%884.21.04.png" width="640" /></a></div>
這樣就好了在終端機輸入<br />
<blockquote class="tr_bq">
git --version</blockquote>
會看到如下,那就是安裝成功了<br />
<blockquote class="tr_bq">
git version 1.7.10.2 (Apple Git-33)</blockquote>
可以直接跳過下面,到 Git - 基本操作。<br />
<br />
用 2. 的方法安裝的話 Mountain Lion 的朋友要注意一下,不是從 Apple Mac App 下載的安裝程式會被預設的安全機制給拒絕安裝,這時候就要打開,系統偏好設定。從左上方的蘋果圖示按下。<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj0q5pz0iRrell-nrdLYcMLvNAKsrntY7vy6LQEDcxMgV6m7TrgbtB6QLv34CE-IRkLvFuUY0GJUbMoJpekSnGVAjOqIzNbjq4rvjyM_nPkW41h9i6_d7uKU-4zwXnB98AVvT11kWoTkFvZ/s1600/%E8%9E%A2%E5%B9%95%E5%BF%AB%E7%85%A7+2012-12-29+%E4%B8%8B%E5%8D%884.04.14.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj0q5pz0iRrell-nrdLYcMLvNAKsrntY7vy6LQEDcxMgV6m7TrgbtB6QLv34CE-IRkLvFuUY0GJUbMoJpekSnGVAjOqIzNbjq4rvjyM_nPkW41h9i6_d7uKU-4zwXnB98AVvT11kWoTkFvZ/s1600/%E8%9E%A2%E5%B9%95%E5%BF%AB%E7%85%A7+2012-12-29+%E4%B8%8B%E5%8D%884.04.14.png" /></a></div>
<br />
接著選擇安全性與隱私<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjBGuSQa_80xjAGxMb5B4pdrIjsQFQcWy4Fltq68f6wNaP7FMXm37AHFeLd3WMo54bk_GVjfBh7um6OVTQlbGbWgyxlwI_ZE6FqweNLMMad_JUtRCIVUFRvv1g8_8U7ASeYz9XLm4OpJnvR/s1600/%E8%9E%A2%E5%B9%95%E5%BF%AB%E7%85%A7+2012-12-29+%E4%B8%8B%E5%8D%884.05.57.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="467" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjBGuSQa_80xjAGxMb5B4pdrIjsQFQcWy4Fltq68f6wNaP7FMXm37AHFeLd3WMo54bk_GVjfBh7um6OVTQlbGbWgyxlwI_ZE6FqweNLMMad_JUtRCIVUFRvv1g8_8U7ASeYz9XLm4OpJnvR/s640/%E8%9E%A2%E5%B9%95%E5%BF%AB%E7%85%A7+2012-12-29+%E4%B8%8B%E5%8D%884.05.57.png" width="640" /></a></div>
看到如下畫面如果左下的鎖是鎖起來,點選,然後輸入密碼打開它<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg0IPhQP4UexMpNgTNs-_LM637DH3zJiuqM-tHPTs-KmF1Q6DhRXLNS3Qzc3svKHNFnY8kcTShsmzET5KSDk-2fpyLm5PISnumXDucMGtkeHt-YsMWu2_jNpPEqwuV9nyYyKk-5HlHkAGKV/s1600/%E8%9E%A2%E5%B9%95%E5%BF%AB%E7%85%A7+2012-12-29+%E4%B8%8B%E5%8D%884.07.50.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="520" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg0IPhQP4UexMpNgTNs-_LM637DH3zJiuqM-tHPTs-KmF1Q6DhRXLNS3Qzc3svKHNFnY8kcTShsmzET5KSDk-2fpyLm5PISnumXDucMGtkeHt-YsMWu2_jNpPEqwuV9nyYyKk-5HlHkAGKV/s640/%E8%9E%A2%E5%B9%95%E5%BF%AB%E7%85%A7+2012-12-29+%E4%B8%8B%E5%8D%884.07.50.png" width="640" /></a></div>
然後允許任何來源<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjF-AgkCxE-cPhLYt5mDl0b9b-7wdpR4g5FhoES3BJ5fGyD7N-6EP51IjiKKY_BS4Tp0cqbtCwk7VNfJk-BfBe0uTvezmvVhsItxlXvdZGDDFvr9Jj1DioRgxztQGCm6BXM0S7x__cps0iM/s1600/%E8%9E%A2%E5%B9%95%E5%BF%AB%E7%85%A7+2012-12-29+%E4%B8%8B%E5%8D%884.09.23.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="520" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjF-AgkCxE-cPhLYt5mDl0b9b-7wdpR4g5FhoES3BJ5fGyD7N-6EP51IjiKKY_BS4Tp0cqbtCwk7VNfJk-BfBe0uTvezmvVhsItxlXvdZGDDFvr9Jj1DioRgxztQGCm6BXM0S7x__cps0iM/s640/%E8%9E%A2%E5%B9%95%E5%BF%AB%E7%85%A7+2012-12-29+%E4%B8%8B%E5%8D%884.09.23.png" width="640" /></a></div>
這樣就可以安裝 Git。安裝好之後,把終端機(Terminal),打開輸入<br />
<blockquote class="tr_bq">
git --version</blockquote>
一樣要看到如下<br />
<blockquote class="tr_bq">
git version 1.7.10.2 (Apple Git-33)</blockquote>
安裝完成後,就來操作一下 git。<br />
在終端機輸入<br />
<blockquote class="tr_bq">
mkdir gitpractice</blockquote>
<h3>
Git - 基本操作</h3>
<br />
來產生一個資料夾,這只是一個資料夾沒有什麼特別的,<br />
再輸入<br />
<blockquote class="tr_bq">
cd gitpractice</blockquote>
進入剛剛產生的 gitpractice 之內<br />
這個裡面就是空的,沒有任何東西輸入<br />
<blockquote class="tr_bq">
ls -al</blockquote>
來確認一下,得到<br />
<br />
<blockquote class="tr_bq">
total 0<br />drwxr-xr-x 2 chronoer staff 68 12 29 16:30 .<br />drwxr-xr-x+ 52 chronoer staff 1768 12 29 16:30 ..</blockquote>
這樣的結果。現在要把這個資料夾轉成 git 可以處理的資料夾,輸入<br />
<blockquote class="tr_bq">
git init</blockquote>
會得到如下<br />
<blockquote class="tr_bq">
Initialized empty Git repository in /Users/chronoer/gitpractice/.git/</blockquote>
再用<br />
<blockquote class="tr_bq">
ls -al</blockquote>
來看看這個資料夾有什麼變化,得到如下<br />
<blockquote class="tr_bq">
total 0<br />drwxr-xr-x 3 chronoer staff 102 12 29 16:31 .<br />drwxr-xr-x+ 52 chronoer staff 1768 12 29 16:30 ..<br />drwxr-xr-x 10 chronoer staff 340 12 29 16:31 <b><span style="color: blue;">.git</span></b></blockquote>
多了一個 .git 這個資料夾,這個 .git 就是存放和 git 的操作相關所有的在這個 gitpractice 底下所有檔案改成的歷史資料都存在這。而這個資料夾的存在,就代表,gitpractice 這個資料夾是受到 git 控管的,任何的檔案結構的改變都可以記錄下來,存放在 .git 這個資料夾裡面。有兩點要注意<br />
<blockquote class="tr_bq">
Git 控管的資料夾只有根目錄會有 .git 不是每個子目錄都有。這個和 svn 做法不同</blockquote>
比如說 gitpractice 底下還有個 src 和 doc,架構如下<br />
<blockquote class="tr_bq">
gitpractice/<br /> src/<br /> doc/</blockquote>
只有 gitpractice 裡面會有 .git/ 這個資料夾,src 和 doc 裡不會有,雖然 src 和 doc 這兩個也都受 git 控管,裡面檔案的變化也會記錄在其 git 根目錄 gitparctice 的 .git 之下。換言之如果把 .git 整個移除,那麼就無法得知 gitpractice 底下所有檔案變化,gitpractice 就回到一般目錄的身份。<br />
另一個要注意的是<br />
<blockquote class="tr_bq">
Git 不會自動控管檔案,而要手動加入。用 git add <fileName> 來把 <fileName> 加入 git 管理的隊伍中。</blockquote>
那我們就來動手加入一個檔案試。首先新增一個文字檔在 gitpractice/ 裡。輸入<br />
<blockquote class="tr_bq">
echo "first line" > sample.txt</blockquote>
然後用<br />
<blockquote class="tr_bq">
ls -al</blockquote>
看是不是多了一個文字檔如下<br />
<br />
<blockquote class="tr_bq">
total 8<br />drwxr-xr-x 4 chronoer staff 136 12 29 16:57 .<br />drwxr-xr-x+ 52 chronoer staff 1768 12 29 16:30 ..<br />drwxr-xr-x 10 chronoer staff 340 12 29 16:31 .git<br />-rw-r--r-- 1 chronoer staff 11 12 29 16:57 sample.txt</blockquote>
<br />
再用<br />
<blockquote class="tr_bq">
cat sample.txt </blockquote>
把這個文字檔的內容呈現在螢幕上<br />
<blockquote class="tr_bq">
first line</blockquote>
這樣子我們新增一個檔案在 gitpractice/ 裡面,那這個檔案有被 git 所管理了嗎?NO 當然沒有,我們可以下<br />
<blockquote class="tr_bq">
git status</blockquote>
來看 git 管理的檔案目前的狀態。如下<br />
<blockquote class="tr_bq">
# On branch master<br />#<br /># Initial commit<br />#<br /># Untracked files:<br /># (use "git add <file>..." to include in what will be committed)<br />#<br />#<span class="Apple-tab-span" style="white-space: pre;"> </span><span style="color: red;">sample.txt</span></blockquote>
<div>
出現 untracked files: 然後有一個檔案就是剛剛新增的叫 sample.txt。</div>
<div>
先插開一下話題,設定一下 git,如果要讓 git 的一些常用的輸出有顏色出現比較好看的話,可以做如下設定。可以參考這個<a href="http://blog.longwin.com.tw/2009/05/git-initial-env-setup-2009/">網頁</a>。</div>
<div>
<blockquote class="tr_bq">
git config --global color.diff auto # git diff 要顯示顏色<br />git config --global color.status auto # git status 要顯示顏色<br />git config --global color.branch auto<br />git config --global color.log auto</blockquote>
</div>
<div>
OK 回到正題,我們新增一個檔案但是不被 git 所管理,那這種檔案叫 untracked file 。那怎麼變成 tracked 呢?就要如下輸入</div>
<blockquote class="tr_bq">
git add sample.txt</blockquote>
<div>
然後再輸入</div>
<blockquote class="tr_bq">
git status</blockquote>
<div>
就會得到如下</div>
<div>
<blockquote class="tr_bq">
# On branch master<br />#<br /># Initial commit<br />#<br /># Changes to be committed:<br /># (use "git rm --cached <file>..." to unstage)<br />#<br />#<span class="Apple-tab-span" style="white-space: pre;"> </span><span style="color: lime;">new file: sample.txt</span><br />#</blockquote>
</div>
<div>
沒有出現 untracked 字樣,這樣就是表示這個檔案 sample.txt 已為 git 所管理。</div>
<div>
接下來我們要把這個檔案目前的樣子,記錄在 git 的歷史裡面,做為一個歷史的截圖,要輸入以下指令</div>
<blockquote class="tr_bq">
git commit sample.txt -m "add first line"</blockquote>
<div>
其中用了 -m 這個 option 指的是對這個歷史節點,一定要留下註解,就是寫在 -m 之後的字串。然後會得到如下回應</div>
<div>
<blockquote class="tr_bq">
[master (root-commit) 56f3cf3] add first line<br /> 1 file changed, 1 insertion(+)<br /> create mode 100644 sample.txt</blockquote>
</div>
<div>
然後再用 </div>
<blockquote class="tr_bq">
git log </blockquote>
<div>
把歷史記錄呈現出來,如下</div>
<div>
<blockquote class="tr_bq">
<span style="color: #bf9000;">commit</span> <span style="color: #bf9000;">56f3cf35c041a80f71393649cab87bbcab5d40bb</span><br />Author: chronoer <scentsome@gmail.com><br />Date: Sat Dec 29 21:56:25 2012 +0800<br /> add first line</blockquote>
</div>
<div>
記得 git log 只會呈現 git commit 之後的狀態,沒有 commit 的檔案是不會出現在 git log 列表裡的。</div>
<div>
稍微看一下歷史記錄的內容,第一個 row 看到一堆奇怪的編碼如下</div>
<blockquote class="tr_bq">
<span style="color: #bf9000;">commit</span> <span style="color: #bf9000;">56f3cf35c041a80f71393649cab87bbcab5d40bb</span></blockquote>
這個代表 git 把 sample.txt 的整份檔案做一個 HASH 的動作,然後存成一個 40 character 的字串,就代表了那個時候的檔案狀態 (<span style="color: blue;">讀者產生的 40 字元的字串會因內容不同而產生不同的字串</span>)。接下來看到的是<br />
<blockquote class="tr_bq">
Author: chronoer <scentsome@gmail.com></blockquote>
這個就是作者和 e-mail ,可以從這兩個指令設定<br />
<br />
<blockquote class="tr_bq">
git config --global user.name "chronoer"<br />git config --global user.email "scentsome@gmail.com"</blockquote>
接下來就是記錄的日期<br />
<blockquote class="tr_bq">
Date: Sat Dec 29 21:56:25 2012 +0800</blockquote>
和當初 commit 時候的註解<br />
<br />
<blockquote class="tr_bq">
add first line</blockquote>
接著我們再用<br />
<blockquote class="tr_bq">
git status</blockquote>
來看一下git 的狀態<br />
會得到<br />
<blockquote class="tr_bq">
# On branch master<br />nothing to commit (working directory clean) </blockquote>
表明目前工作的目錄沒什麼改變是一個常態。<br />
那我們就來改變一下 sample.txt 的內容<br />
輸入<br />
<blockquote class="tr_bq">
echo "second line" >> sample.txt </blockquote>
注意是兩個">" 意思是把 "second line" 接到 sample.txt 的最後一行後面一行。用<br />
<blockquote class="tr_bq">
cat sample.txt </blockquote>
就會看到如下 sample.txt 的內容<br />
<br />
<blockquote class="tr_bq">
first line<br />second line</blockquote>
此時我們改變了 sample.txt 然後用<br />
<blockquote class="tr_bq">
git status</blockquote>
來查看目前 git 所管理的檔案的狀態,得到如下反應<br />
<blockquote class="tr_bq">
# On branch master<br /># Changes not staged for commit:<br /># (use "git add <file>..." to update what will be committed)<br /># (use "git checkout -- <file>..." to discard changes in working directory)<br />#<br />#<span class="Apple-tab-span" style="white-space: pre;"> </span><span style="color: #cc0000;">modified: sample.txt</span>#</blockquote>
任何被 git add 加入的檔案有改變,而還沒 commit 之前,都會如上表示,為 modified 代表有被修改。<br />
這邊還有一個 Changes not staged for commit 的敘述,是什麼意思呢,之後再解釋,現在我們要把 sample.txt 變成 <b><span style="color: blue;">staged</span></b> 或是 <span style="color: blue;"><b>index</b></span> 如下輸入<br />
<blockquote class="tr_bq">
git add sample.txt</blockquote>
再一次把修改中或是 unstaged 的檔案加入到 git 系統<br />
然後我再用<br />
<blockquote class="tr_bq">
git status</blockquote>
來看檔案的狀態會得到如下的回應<br />
<br />
<blockquote class="tr_bq">
# On branch master<br /># Changes to be committed:<br /># (use "git reset HEAD <file>..." to unstage)<br />#<br />#<span class="Apple-tab-span" style="white-space: pre;"> </span><span style="color: #6aa84f;">modified: sample.txt</span><br />#</blockquote>
<br />
就不是 unstaged 狀態了。<br />
為了把目前修改的 sample.txt 內容當成一個歷史的節點存下來,要用<br />
<blockquote class="tr_bq">
git commit sample.txt -m "second line"</blockquote>
就會得到如下的回應<br />
<br />
<blockquote class="tr_bq">
[master c1c905f] second line<br /> 1 file changed, 1 insertion(+)</blockquote>
再次用<br />
<blockquote class="tr_bq">
git status </blockquote>
看一下目前的狀態,會得到<br />
<br />
<blockquote class="tr_bq">
# On branch master<br />nothing to commit (working directory clean)</blockquote>
把剛剛修改的 sample.txt 再一次加入到了歷史的節點,工作目錄底下也就回到常態。到目前為止我們 commit 了兩次,用<br />
git log<br />
就可以看到兩個 commit 的記錄,就有兩個 HASH 之後的 40 個字元的字串。如下<br />
<br />
<blockquote class="tr_bq">
<span style="color: #bf9000;">commit</span> <span style="color: #bf9000;">c1c905f0d0f19a3ba750c3571d845bcc967c9ae6</span><br />Author: chronoer <scentsome@gmail.com><br />Date: Sat Dec 29 23:33:35 2012 +0800<br /> second line<br /><span style="color: #bf9000;">commit</span> <span style="color: #bf9000;">56f3cf35c041a80f71393649cab87bbcab5d40bb</span><br />Author: chronoer <scentsome@gmail.com><br />Date: Sat Dec 29 21:56:25 2012 +0800<br /> add first line</blockquote>
比較新的放在上方。就有兩筆 commit log。<br />
到目前為止的動作整理一下<br />
<br />
<ol>
<li>新增資料夾 - mkdir gitpractice</li>
<li>進入資料夾 - cd gitpractice</li>
<li>把資料夾轉成 git 可以管理的 - git init </li>
<li>新增檔案 - echo "first line" > sample.txt</li>
<li>加入 git 管理 - git add sample.txt</li>
<li>記錄歷史節點 - git commit sample.txt -m "first line"</li>
<li>修改檔案 - echo "second line" >> sample.txt</li>
<li>變成 staged 檔案 - git add sample.txt </li>
<li>記錄歷史節點 - git commit sample.txt -m "second line" (若沒做 8. 會一起執行)</li>
</ol>
<div>
以上就是基本用 git 來做版本控管的方式,每一個 commit log 都可以視為一個版本。</div>
<div>
如何檢視各個版本就是用 </div>
<blockquote class="tr_bq">
git log</blockquote>
會得到如下的結果<br />
<blockquote class="tr_bq">
<span style="color: #bf9000;">commit</span> <span style="color: #bf9000;">c1c905f0d0f19a3ba750c3571d845bcc967c9ae6</span>Author: chronoer <scentsome@gmail.com><br />Date: Sat Dec 29 23:33:35 2012 +0800<br /> second line<br /><span style="color: #bf9000;">commit</span> <span style="color: #bf9000;">56f3cf35c041a80f71393649cab87bbcab5d40bb</span>Author: chronoer <scentsome@gmail.com><br />Date: Sat Dec 29 21:56:25 2012 +0800<br /> add first line</blockquote>
把各個 commit 版本呈現出來。每個版本的代號就是 40 個字的字串。如果想要比較兩個不同版本的差異可以用<br />
git diff <commit id> <commit id><br />
其中<commit id> 可以用原本的 40 字的前 7 個來代表比如上面的例子,要比較<br />
<blockquote class="tr_bq">
<span style="color: blue;">c1c905f</span><span style="color: #bf9000;">0d0f19a3ba750c3571d845bcc967c9ae6</span><span style="color: blue;">56f3cf3</span><span style="color: #bf9000;">5c041a80f71393649cab87bbcab5d40bb</span></blockquote>
可以用 <span style="color: blue;">c1c905f </span>和<span style="color: blue;"> </span><span style="color: blue;">56f3cf3 </span>寫成<br />
<blockquote class="tr_bq">
git diff c1c905f 56f3cf3</blockquote>
得到如下結果<br />
<blockquote class="tr_bq">
diff --git a/sample.txt b/sample.txt<br />index 06fcdd7..08fe272 100644<br />--- a/sample.txt<br />+++ b/sample.txt<br /><span style="color: #3d85c6;">@@ -1,2 +1 @@</span><br /> first line<br /><span style="color: red;">-second line </span></blockquote>
也就是 a/sample.txt, c1c905f 比 b/sample.txt 56f3cf3 少了一行 <span style="color: red;">second line</span><br />
如果把比較的檔案位子交換成<br />
git diff 56f3cf3 c1c905f<br />
就會得到<br />
<br />
<blockquote class="tr_bq">
diff --git a/sample.txt b/sample.txt<br />index 08fe272..06fcdd7 100644<br />--- a/sample.txt<br />+++ b/sample.txt<br />@@ -1 +1,2 @@<br /> first line<br /><span style="color: #6aa84f;">+second line</span></blockquote>
也就是 56f3cf3 比 c1c905f 多了一行 <span style="color: #6aa84f;">second line</span><br />
有了版本,就會想到要之前的版本,在 git 可以這麼做<br />
<blockquote class="tr_bq">
git checkout <commit id> <file name></blockquote>
<file name> 就會回到 <commit id> 的狀態,比如在這個例子,我們這麼做<br />
<blockquote class="tr_bq">
git checkout 56f3cf3 sample.txt</blockquote>
然後再用<br />
<blockquote class="tr_bq">
cat sample.txt</blockquote>
看看 sample.txt 的內容得到<br />
<blockquote class="tr_bq">
first line</blockquote>
回到了 56f3cf3 的狀態了<br />
很方便的版本管理可以任意回到之前的版本。<br />
最後筆者來整理一下檔案在 git 資料夾裡的狀態。<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhFOlOOPt8BiAh5cghriRIMqVmL-A1Fw7OdXkjI6lSZdG7f3LbtOgHO8TbOnP-Jtw0tRS5hpCHFZ9Ze7jxBOPm96Lh3emqX_FEbAIddQR5MalK4cyyQgjX7cBbRGJY-70ovlAqQak6-Ehdg/s1600/file_state.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="640" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhFOlOOPt8BiAh5cghriRIMqVmL-A1Fw7OdXkjI6lSZdG7f3LbtOgHO8TbOnP-Jtw0tRS5hpCHFZ9Ze7jxBOPm96Lh3emqX_FEbAIddQR5MalK4cyyQgjX7cBbRGJY-70ovlAqQak6-Ehdg/s640/file_state.png" width="568" /></a></div>
剛剛我們用 git checkout <commit id> <file name> 之後會回到之前 commit 狀態,此時如果用<br />
<blockquote class="tr_bq">
git status </blockquote>
來看狀態會是如下<br />
<br />
<blockquote class="tr_bq">
# On branch master<br /># Changes to be committed:<br /># (use "git reset HEAD <file>..." to unstage)<br />#<br />#<span class="Apple-tab-span" style="white-space: pre;"> </span><span style="color: #6aa84f;">modified: sample.txt</span><br />#</blockquote>
<br />
是回到一個 staged 的 sample.txt,如果再對這個檔案修改。比如用 vi 存完檔<br />
就會發生一個很有趣的狀況<br />
用<br />
<blockquote class="tr_bq">
git status </blockquote>
來看會看到<br />
<br />
<blockquote class="tr_bq">
# On branch master<br /># Changes to be committed:<br /># (use "git reset HEAD <file>..." to unstage)<br />#<br />#<span class="Apple-tab-span" style="white-space: pre;"> </span><span style="color: #6aa84f;">modified: sample.txt</span>#<br /># Changes not staged for commit:<br /># (use "git add <file>..." to update what will be committed)<br /># (use "git checkout -- <file>..." to discard changes in working directory)<br />#<br />#<span class="Apple-tab-span" style="white-space: pre;"> </span><span style="color: #cc0000;">modified: sample.txt</span>#</blockquote>
同一個檔案有兩個狀態,一個是 staged 另一個是 unstaged 可以直接 commit 或是先把 unstaged 用 git add 變成 staged<br />
之後如果 commit 就會再多出一個 commit log。<br />
以上是 git 的簡介,下一篇會來探討Xcode 的 GUI 和 git 運作的相關功能。<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
麥克http://www.blogger.com/profile/01716179223350987115noreply@blogger.com0tag:blogger.com,1999:blog-3469754461280175671.post-77433456112402408632012-12-02T01:14:00.000-08:002012-12-02T01:14:14.389-08:00Objective-C naming convention 的重要性 - ARC class 呼叫 non-ARC Class methodMichael 上課的時候都會和學員提到命名的習慣,每個語言都有其命名的習慣。了解習慣可以幫助初學者很快的進入這個環境想要傳答的意圖是什麼。<br />
命名的範圍很廣,從 Source Code 的檔案名稱到 Class 的名稱,Variable 的命稱,Method 的名稱等等都有其命名的習慣。
打個比方,在寫 Java 時,Java 的 Class 如下定義
<br />
<pre><code class="java">class Car {
}
</code></pre>
產生一個 Car class 其第一個字大寫是 Java 的命名習慣,而相對應這個 Source code 的檔案,也常命名為 Car.java,習慣就是 Java 的檔案名稱和其內容的第一個 Class 命稱一樣,大寫字開頭,這個是 Java 語言中的習慣。<br />
<blockquote class="tr_bq">
習慣指的是,即使語法沒有強制說不行,但是大家都這樣做。</blockquote>
而 Objective-C 沒有這樣的習慣。<br />
<br />
上述的 class 在 Objective-C 是這樣命名<br />
<pre><code class="objectivec">
@interface Car
@end
@implementation Car
@end
</code></pre>
Class 的定義在 Objective-C 是分開的分為
<br />
<pre><code class="objectivec">
@interface Car
@end
</code></pre>
和
<br />
<pre><code class="objectivec">
@implementation Car
@end
</code></pre>
兩個部分。<br />
再來看看另一個語言 C# 是如下定義
<br />
<pre> <code class="cs">
class Car{
}
</code></pre>
看起來和 Java 有點像,也沒有檔案名稱要和 class 命稱一樣命名的習慣。<br />
我們再來看看 Class 的內部命名習慣。<br />
把剛剛的 class 加上 ivar 和 method.<br />
Java 部分<br />
<pre> <code class="java">
class Car {
String name;
String getName(){
return name;
}
void setName(String newName){
name = newName;
}
}</code></pre>
新增了 name 這個變數 和 getName 還有 setName 這兩個 method。 getName 稱為 getter 而 setName
則為 setter 。
<br />
Java 的命名習慣就是,第一個字小寫,不論 variable 或是 method 的名稱。名詞和名詞之間後面接的名詞第一個字大寫,這種命西習慣稱為 <a href="http://zh.wikipedia.org/wiki/%E9%A7%9D%E5%B3%B0%E5%BC%8F%E5%A4%A7%E5%B0%8F%E5%AF%AB">CamelCase</a> 駝峰式大小寫 <br />
再來看看 Objective-C 的命名習慣。
<br />
<pre><code class="objectivec">
@interface Car:NSObject{
NSString * name;
}
@end
@implementation Car
-(NSString *) name{
return name;
}
-(void) setName:(NSString *) newName{
name = newName;
}
@end
</code> </pre>
Objective-C 的命名習慣和 Java 差不多,但是 getter 的地方不一樣,在 Objective-C getter 的名稱和要讀的變數名稱一樣。在這個例子兩者都叫 name。
<br />
我們再來看看類似的 class 在 C# 如何表現?<br />
<pre> <code class="cs">
class Car{
string name;
public string GetName(){
return name;
}
public void SetName(string newName){
name = newName;
}
}
</code></pre>
在 C# 中新增了 name 這個變數和 GetName 還有 SetName 這兩個 method。在這邊可以注意到 C# 的變數名稱是小寫開頭而 method 的名稱習慣用大寫字開頭。<br />
三種語言都有其個自的習慣,雖然不按照這個習慣,大部分情況下程式也是可以執行,但是在 Objective-C 如果不照者命名的習慣會造成一些奇怪的問題產生。<br />
就讓我們來進入這篇文章的主題,開啟一個 ARC enable 的 Project 再加入一個 none ARC 的 class 就用上述的 Car 再加上兩個 method,讓我們一步一步來。<br />
首先開啟一個 Single View Application 的專案如下<br />
記得要勾選 Automatic Reference Counting<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgUnX_DYfiCve9fcXen7QJUlDIIKIfUAk4xEHtdZHFdkCqVgU-bg2FqoKVUthU459ljw1aqi2PpNzz-sDPkpVxe0H8mVXVB9aH7i30MCVZJE35QsawSwQi-IG-nTuC-QC6dBohHxt0qqZsq/s1600/%E8%9E%A2%E5%B9%95%E5%BF%AB%E7%85%A7+2012-12-02+%E4%B8%8B%E5%8D%883.43.05.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="432" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgUnX_DYfiCve9fcXen7QJUlDIIKIfUAk4xEHtdZHFdkCqVgU-bg2FqoKVUthU459ljw1aqi2PpNzz-sDPkpVxe0H8mVXVB9aH7i30MCVZJE35QsawSwQi-IG-nTuC-QC6dBohHxt0qqZsq/s640/%E8%9E%A2%E5%B9%95%E5%BF%AB%E7%85%A7+2012-12-02+%E4%B8%8B%E5%8D%883.43.05.png" width="640" /></a></div>
新增一個 Class 名為 Car 繼承 NSObject 如下是 Car.h<br />
<pre> <code class="objectivec">
@interface Car : NSObject
-(id) newName;
-(id) newname;
@end
</code></pre>
Car.m 如下
<br />
<pre> <code class="objectivec">
@implementation Car
-(id) newName{
return [NSString stringWithFormat:@"Honda"];
}
-(id) newname{
return [NSString stringWithFormat:@"Honda"];
}
@end
</code></pre>
新增兩個 method 這兩個 method 的名稱很像,一個叫 newName 一個叫 newname 差別在於一個 name 有大寫的 N 另一個沒有。換句話說,newName 是有依照習慣命名,newname 是沒有的。
接下我們要把這個 Car.m 設定為不使用 ARC。
<br />
如下設定。<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiYopIii8Ly70EpL6ELJt_WD82_1LPTVDyUOmEX46oBRL-OMWAUiccJvRYFsW1w5ihU5DO40ewus-AcAyNd7CnQNBdyz47X4Zlxa9iPTl_Lk76Y0xumIaeARt095detHmCFNn5S-CKbkFJa/s1600/%E8%9E%A2%E5%B9%95%E5%BF%AB%E7%85%A7+2012-12-02+%E4%B8%8B%E5%8D%884.39.55.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="122" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiYopIii8Ly70EpL6ELJt_WD82_1LPTVDyUOmEX46oBRL-OMWAUiccJvRYFsW1w5ihU5DO40ewus-AcAyNd7CnQNBdyz47X4Zlxa9iPTl_Lk76Y0xumIaeARt095detHmCFNn5S-CKbkFJa/s640/%E8%9E%A2%E5%B9%95%E5%BF%AB%E7%85%A7+2012-12-02+%E4%B8%8B%E5%8D%884.39.55.png" width="640" /></a></div>
再新增一個 Button 在 畫面上,然後 Button 按下去 action 如下<br />
<pre> <code class="objectivec">
- (IBAction)testCar:(id)sender {
Car * car = [Car new];
NSLog(@"Car name %@", [car newName]);
}
</code></pre>
我們先使用這個 newName method。一執行,發現馬上就 crash。
但是如果換成 newname 這個 method 就是正常執行。
為什麼呢?
因為對於 non-ARC 的 method,<br />
<blockquote class="tr_bq">
ARC 會看其 method 的開頭是不是 alloc, new 或 copy (所謂開頭指的是符合 CamelCase 的第一個詞)</blockquote>
或是<br />
<blockquote class="tr_bq">
第一個是 mutable 第二個是 copy ,也就是寫成 mutableCopy 。</blockquote>
此時 ARC 會自動認為這個 method 是會產生一個沒有加到 autorelease pool 的物件,然後就自行決定加上 release 在這個例子使用 newName 就 crash 是因為 newName 回傳的是<br />
<blockquote class="tr_bq">
[NSString stringWithFormat:@"Honda"] </blockquote>
這個有加到 autorelease pool 的物件。而 ARC 自動多 release 一次了。這個例子是要提示大家,在寫 method 的時候要注意 CamelCase 第一個字,這個就是 ARC 的習慣。
麥克http://www.blogger.com/profile/01716179223350987115noreply@blogger.com0tag:blogger.com,1999:blog-3469754461280175671.post-33875445407560675582012-11-19T07:18:00.003-08:002012-11-19T07:18:47.837-08:00Objective-C 如 printf() 或是 NSLog() 不定參數的寫法<br />
參考<br />
<a href="http://www.numbergrinder.com/2008/12/variable-arguments-varargs-in-objective-c/">http://www.numbergrinder.com/2008/12/variable-arguments-varargs-in-objective-c/</a><br />
<br />
準備 interface
<pre> <code class="objective-c">
@interface Car : NSObject {
}
-(void)addCars:(NSString * ) title, ... ;
@end
</code> </pre>
<br />
<br />
addCars: 這個 method 有一個 NSString 的參數,第二個參數是 ...
<br />
<div>
<br />
也就是 variable arguments 重要語法</div>
<div>
<br />
先要確定 addCars: 怎麼用</div>
<div>
<br />
我們假設是如下使用<br />
<br />
[car addCars:@"Toyota", @"Honda", @"BMW", nil ];<br />
<br />
<br />
可以輸入多個 NSString 但是要 nil 結束。<br />
再來就是 implementation
<pre> <code class="objective-c">
@implementation Car
-(void) addCars:(NSString *)title, ...{
va_list args;
va_start(args, title);
NSLog(@"%@", title);
NSString * car;
while ((car = va_arg(args, NSString *))) {
NSLog(@"%@",car);
}
va_end(args);
}
@end
</code> </pre>
<br />
在上方的 addCars: 裡我們用到了</div>
<div>
<br />
va_list 這個型別,用來代表 ... 的所有內容<br />
<br />
用 args 指向 ... 的內容<br />
<br />
再來看到 va_start(args, title)<br />
<br />
明確告知 args 放的內容是 title 之後的內容<br />
<br />
然後把每一個 args 都用 NSString * 來表示, 轉型成 NSString *<br />
<br />
用 car = va_arg(args, NSString *) 把位置給 car 變數<br />
<br />
到 car 是 nil 為止,所以用 while<br />
<br />
最後是用 va_end(args); 結尾。<br />
<br />
<br />
在 main 就可以如下輸入
<pre> <code class="objective-c">
Car * obj = [Car new];
[obj addCars:@"Toyota",@"Honda",@"Benz",@"BMW",nil];
</code> </pre>
Console 結果是<br />
<br />
Toyota<br />
Honda<br />
Benz<br />
BMW<br />
<br />
用 nil 結尾主要是可以知道 ... 何時結束<br />
<br />
也可以用參考提到的,<br />
<br />
第一個參數是數字如下<br />
<br />
<br />
- (NSNumber *) addValues:(NSNumber *) firstNumber, …<br />
<br />
<br />
就交給大家試試了</div>
麥克http://www.blogger.com/profile/01716179223350987115noreply@blogger.com0tag:blogger.com,1999:blog-3469754461280175671.post-22272263227092492482012-10-27T03:20:00.003-07:002012-10-27T03:21:00.154-07:00iOS App 開發者交流會 - 2012-10-17 at Apple Inc. Taiwan 會議記錄<h4>
致謝</h4>
首先當然感謝 Apple 給 Michael 這個機會可以在 Apple 公司裡舉辦 iOS App 開發者的交流會。再來就是感謝極電資訊的幫忙可以促成這次會議的圓滿。<br />
<br />
<h4>
Announcement</h4>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhjYIJyBekhLycxkXjSsw6wjtPcQn3xSw2pHCFR03kRgRe5lDp9Qgh2L4UsnJkl9PN-8yz1WJIEkLFRPGYR2r2Pn0Bhq-IEQzlup6HQtMdEu8ZyRAz2e_dgONMk_SJOxgCtq1pznJb0om0w/s1600/all.JPG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="480" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhjYIJyBekhLycxkXjSsw6wjtPcQn3xSw2pHCFR03kRgRe5lDp9Qgh2L4UsnJkl9PN-8yz1WJIEkLFRPGYR2r2Pn0Bhq-IEQzlup6HQtMdEu8ZyRAz2e_dgONMk_SJOxgCtq1pznJb0om0w/s640/all.JPG" width="640" /></a></div>
<span style="font-weight: normal;">一開始由 Apple 的 Kelvin 向大家介紹一下整個 Apple 內的環境,剛搬到這個新的地點,還是第一次舉辦開發者相關的研討會。Kelvin 主要是負責企業和教育市場,有這方面開發需求的開發者或是想要佈署相關設備的企業單位可以寫信給 Michael 幫各位轉介給 Kelvin. 有與會的朋友們己經有 Kelvin 的連絡方式了,就可以自己連絡。</span><br />
<h4>
Opening</h4>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgsFiqPKplsNRoR863NNIolk-90WQbOy7o98J4Cw-a5Tz2r1U0IaP4sdPjpG7dp1ZaSnBjCW6G3Lq0Y_L1qMp0Qgs7ruQPOp7VyIcQm08MiQnSrOt7EtMWUxil9A3PS9JFlbB1q1N2nYd1n/s1600/michael.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="360" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgsFiqPKplsNRoR863NNIolk-90WQbOy7o98J4Cw-a5Tz2r1U0IaP4sdPjpG7dp1ZaSnBjCW6G3Lq0Y_L1qMp0Qgs7ruQPOp7VyIcQm08MiQnSrOt7EtMWUxil9A3PS9JFlbB1q1N2nYd1n/s640/michael.jpg" width="640" /></a></div>
由小弟我來給大家一個開場,主要是提到 iOS 的現況還有幾個主要的技術分享<br />
<ul>
<li>使用 Xcode 4.5 在 iOS 5 執行環境,需要注意的地方</li>
<li>UIViewController 有關 Rotation 的改變</li>
<li>Auto Layout</li>
</ul>
等等的技術分享,投影片可以在這邊看到。http://slidesha.re/RYkbs5<br />
<h4>
與創投打交道 - Hoku</h4>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjBTw08tERGJi1tYHbruGuB7AIp7UpOVETbZxEUD-7KMHNju36BNr5sxgMjv6k9pR0xMGUfpZjoUwF2sRrTlR2P4shedbp1z-KiwqMpDzqOEzgGt4xPw52Jg2mlpqWEDaHjIFC_XEkv_CHb/s1600/hoku.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="360" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjBTw08tERGJi1tYHbruGuB7AIp7UpOVETbZxEUD-7KMHNju36BNr5sxgMjv6k9pR0xMGUfpZjoUwF2sRrTlR2P4shedbp1z-KiwqMpDzqOEzgGt4xPw52Jg2mlpqWEDaHjIFC_XEkv_CHb/s640/hoku.jpg" width="640" /></a></div>
(Hoku有交帶,不方便露臉)。<br />
重點節錄<br />
<ul>
<li>創投不會只想把公司吃掉</li>
<li>不會惡意想要搞垮公司</li>
<li>創投可以在初期就幫助公司,也可以在後期幫助公司上市</li>
<li>誠實的老闆是創投最喜歡投資的對象</li>
<li>請不要太工程師導向或是業務導向</li>
</ul>
有關更多的資訊,可以參考。http://slidesha.re/RYoq7e<br />
<h4>
學員成果分享 - Jacky</h4>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhul8REOP10EB2uT35gYiJeDTh8vSRgoId1LbuGNDlNjngA_l-3f8jMDTYs8BVpyVFddaSKCpurkX0tdgU8k0YxfceL5O4MNshuzXivqblOUj1sN08Jwivh33heti8LqMKpXrdD3BY8K9xC/s1600/jakcy.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="360" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhul8REOP10EB2uT35gYiJeDTh8vSRgoId1LbuGNDlNjngA_l-3f8jMDTYs8BVpyVFddaSKCpurkX0tdgU8k0YxfceL5O4MNshuzXivqblOUj1sN08Jwivh33heti8LqMKpXrdD3BY8K9xC/s640/jakcy.jpg" width="640" /></a></div>
很高興可以邀請到 Jacky 巨狐資訊科技有限公司 GZFOX, INC. 執行長和大家分享,自行開發 App 的經驗。<br />
其中 Michael 印象比較深的是和公家單位打交道時的過程:<br />
Jacky 有寫一個台鐵的 App,一開始自已去連網站,發現不行,然後寫信給台鐵,一來一往之後,過了很久,才有回覆,才可以拿到比較完整的資料。<br />
還有就是有一個利用 App 可以線上沖洗自己手機裡的照片成真實的像片。<br />
完整的投影片在。http://slidesha.re/RYrpMK<br />
<br />
<h4>
學員成果分享 - Wei Wei</h4>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjBsh0OWnMkisXU6hRMBQqruHtuKOBiwv4HrGhgfT5Z8yKe-flrwQTf29aBVFWxQYoxP7EIrgcqKMS8jMWUS_LwVug7F4CwOfmZudKdmE9kELxox5Xmp4qZoi1scmLufcrAlg5Ey3TGozXg/s1600/wei_wei.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="360" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjBsh0OWnMkisXU6hRMBQqruHtuKOBiwv4HrGhgfT5Z8yKe-flrwQTf29aBVFWxQYoxP7EIrgcqKMS8jMWUS_LwVug7F4CwOfmZudKdmE9kELxox5Xmp4qZoi1scmLufcrAlg5Ey3TGozXg/s640/wei_wei.jpg" width="640" /></a></div>
Wei Wei 算是學員中最傳奇的人物了,當過 DJ,出過唱片,書本,會英法德語,還寫了好幾個 App 的 Game。Michael 覺得世上怎麼會有如此厲害的人物。<br />
除了當天就發表一款新遊戲之外,還和大家分享有趣的音樂生成軟體,還有介紹幾本好書。<br />
演講的過程整個充滿熱鬧的氣氛,希望 Wei Wei 不要來搶我的飯碗 orz。<br />
投影片,可以在此下載。 http://slidesha.re/RYsx2V<br />
<br />
<h4>
學員成果分享 - Steven</h4>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEihqKsQVlPA3uEnFB3qvbJOTVoWuSd3tDvKfF3NhSYzYM_RV5VrYOLUHth0ztZZQLJgzIsDg0jXuBwETHwIWIb_wFFdTYM3LTNSIRkyKFMvL537cf5C9ck15lrnnyTqoLwUw_JPrvds4MUj/s1600/steven.JPG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="480" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEihqKsQVlPA3uEnFB3qvbJOTVoWuSd3tDvKfF3NhSYzYM_RV5VrYOLUHth0ztZZQLJgzIsDg0jXuBwETHwIWIb_wFFdTYM3LTNSIRkyKFMvL537cf5C9ck15lrnnyTqoLwUw_JPrvds4MUj/s640/steven.JPG" width="640" /></a></div>
Steven 寫了二個評價不錯 App 和電子商務有關,也都有廣告商找上門來要配合放廣告,其中比價撿便宜寫下了免費的第二名的佳績,然後呢就決定轉成付費,再來看一下消費者對於定價的觀感,從 90 元 到 30 元的定價比較,來分析如何的定價才是最適當的。<br />
在分享過程中 Kelvin 和 Michael 都各自提過一個 Promote 自己 App 的方法,分享給大家。Promotion 是非常重要的在現在 App 海的社會中。<br />
完整的投影片在此可以下載。http://slidesha.re/RYuEnv<br />
<br />
<h4>
One more thing - Michael</h4>
<br />
Michael 成立了<b>友教有限公司</b>,專門服務企業內部的訓練和提供專業的 App 開發和整合服務給企業朋友們。有興趣的朋友可以寫信給 <br />
Michael - scentsome@gmail.com<br />
除了提供軟體服務外,友教和極電合作,提供好的硬體服務環境給客戶,軟體加硬體的服務希望可以增進企業導入 App 開發和產品合作。<br />
<b>極電資訊</b> - 20 年的 Apple 授權經銷商 02-2705-6216<br />
<br />
<h4>
ADB - Arthur</h4>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjcVQ8UHxPSHhQEQs0YFMx-uRtAo5dObgrTEURThmPnE5wL3viXTL8fVwTaddabxVhSo3nrN6obVfyGOeTOdcHz3kpUcRTr6BLC_SOG0n-2Q_dKIZJvUy7rDsxNTYcZZH2Y84xodtWsWezg/s1600/arhur.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="360" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjcVQ8UHxPSHhQEQs0YFMx-uRtAo5dObgrTEURThmPnE5wL3viXTL8fVwTaddabxVhSo3nrN6obVfyGOeTOdcHz3kpUcRTr6BLC_SOG0n-2Q_dKIZJvUy7rDsxNTYcZZH2Y84xodtWsWezg/s640/arhur.jpg" width="640" /></a></div>
除了 App 之外,Michael 向大家推薦 Arthur 最新研發出的產品 ADB 是一個 Module 可以透過耳機孔提供 App 和 UART 的雙向傳輸資料的服務。Square 是一個著名的成功例子,只在信用卡的金流,而 Arthur 的 ADB 可以應用在各式各樣的服務,甚至包含 Android 等有耳機孔的系統,都可以透過 App - ADB - Any Device 溝通。<br />
完整的投影片在- http://slidesha.re/RYvS25<br />
<br />麥克http://www.blogger.com/profile/01716179223350987115noreply@blogger.com0tag:blogger.com,1999:blog-3469754461280175671.post-24666458990978979452012-09-11T09:05:00.000-07:002012-09-11T09:05:28.625-07:00聊天廣播 - iOS 與 HTML 利用 Socket.io在日前某篇的文章中,筆者教大家如何建立一個 Node.js 寫的 Server 利用 socket.io 這個 module 讓不同的 Browser 可以即時聊天。這篇是介紹大家怎麼在 iOS 的環境下,也利用 socket.io 讓手機可以和 Browser 一起聊天。這篇文章,我們利用到幾個 OpenSource 的套件,分別是 <a href="https://github.com/pkyeck/socket.IO-objc">socket.io-objc</a> 和 <a href="https://github.com/square/SocketRocket">SocketRocket</a>,這兩個,其中 socket.io-objc 是主要我們直接使用的套件,其利用 SocketRocket 然後包成一個很好用的介面讓我們使用,也是 Socket.IO 在<a href="https://github.com/LearnBoost/socket.io/wiki"> GitHub Wiki</a> 提到 Objective-C 語言的實作範例。而這次要用 JSON 的格式來傳遞資料,所以我們要來修改一下<a href="http://iosdevelopersnote.blogspot.tw/2012/09/socketio.html">前篇</a>提到的 chatServer.js 和 chat.html。chatServer.js 更改如下
<br />
<pre> <code class="javascript">
var app = require('http').createServer(handler)
, io = require('socket.io').listen(app)
, fs = require('fs');
app.listen(8124);
function handler (req, res) {
fs.readFile(__dirname + '/chat.html', function (err, data) {
if (err) { res.writeHead(500);
return res.end('Error loading chat.html');
}
res.writeHead(200);
res.end(data); });
}
io.sockets.on('connection', function (socket) {
socket.on('addme',function(jsonMessage) {
socket.username = jsonMessage.name;
var toClient = { sender : "SERVER", message : "Good to see your "+ jsonMessage.name};
socket.emit('chat', toClient);
toClient.message = jsonMessage.name + " is on the Desk";
socket.broadcast.emit('chat', toClient);
});
socket.on('sendchat', function(data) {
io.sockets.emit('chat', { sender : socket.username, message : data.message});
});
socket.on('disconnect', function() {
var bye = { sender : "SERVER", message : socket.username+"has left the building"};
io.sockets.emit('chat', bye);
});
});
</code> </pre>
所有的訊息資料的傳遞都用 JSON 格式,和之前不一樣的地方特別提出來說明一下。先看 addme 這個事件<br />
<pre><code class="javascript">socket.on('addme',function(jsonMessage) {
socket.username = jsonMessage.name;
var toClient = { sender : "SERVER", message : "Good to see your "+ jsonMessage.name};
socket.emit('chat', toClient);
toClient.message = jsonMessage.name + " is on the Desk";
socket.broadcast.emit('chat', toClient);
});</code></pre>
除了 event 的名稱外,只接收一個參數,jsonMessage,因為 JSON 格式本身就可以帶很多資訊,所以 function 不需要多個參數。接著把要傳給 client 的訊息,裡面包含 sender 和 message 的訊息,一樣用 JSON 包起來存在 toClient,然後先 emit 給這個 socket 的 client,再更改 toClient 的 message,然後 broadcast 給所有的 client。<br />
再來看到 sendchat 事件<br />
<pre><code class="javascript">socket.on('sendchat', function(data) {
io.sockets.emit('chat', { sender : socket.username, message : data.message});
});</code></pre>
聽到 sendchat 事件後,就把 sender 和 message 的訊息用 chat 這個事件傳給所有的 client。
最後看一下 disconnect 事件<br />
<pre><code class="javascript">socket.on('disconnect', function() {
var bye = { sender : "SERVER", message : socket.username+"has left the building"};
io.sockets.emit('chat', bye);
});</code></pre>
都是包成 JOSN 包含 sender 和 message。如此 Server 就修改完了,接下來看看 client,chat.html<br />
<pre><code class="html">
<html lang="en">
<head>
<meta charset="utf-8">
<title>Chat Room</title>
<script src="http://localhost:8124/socket.io/socket.io.js"></script>
<script>
var socket = io.connect('http://localhost:8124');
socket.on('connect', function() {
socket.emit('addme',{name : prompt('Who are you?')});
});
socket.on('chat',function(data) {
var p = document.createElement('p');
p.innerHTML = data.sender + ': ' + data.message;
document.getElementById('output').appendChild(p);
});
window.addEventListener('load',function() {
document.getElementById('sendtext').addEventListener('click',function() {
var text = document.getElementById('data').value;
socket.emit('sendchat', {message : "say "+text});
}, false);
}, false);
</script>
</head>
<body>
<div id="output"></div> <div id="send">
<input type="text" id="data" size="100" /><br />
<input type="button" id="sendtext" value="Send Text" /> </div>
</body> </html>
</code></pre>
也都是把交換的訊息用 JSON 格式來傳遞。一個個來看一下<br />
<pre><code class="javascript">socket.on('connect', function() {
socket.emit('addme',{name : prompt('Who are you?')});
});</code></pre>
我們可以直接把 prompt() 的結果放在 JSON 物件內,再傳出去<br />
<pre><code class="javascript">socket.on('chat',function(data) {
var p = document.createElement('p');
p.innerHTML = data.sender + ': ' + data.message;
document.getElementById('output').appendChild(p);
});</code></pre>
收到 data 可以直接用 data.message 就把真正的訊息抓出來了。<br />
<pre><code class="html"> document.getElementById('sendtext').addEventListener('click',function() {
var text = document.getElementById('data').value;
socket.emit('sendchat', {message : "say "+text});
}, false);</code></pre>
當網頁上的 Send Text 按鈕被按下的時候,會把 Text Field 的內容,包成 JSON 格式,在這裡,Text Field 的內容存在 text 它不是 string, 在放到 JSON 之前加上 "say " 就會被轉成 string。<br />
接下來就是 iOS Client 的部分了,首先下載這兩個 Proejct,<a href="https://github.com/pkyeck/socket.IO-objc">socket.io-objc </a> 和 <a href="https://github.com/square/SocketRocket">SocketRocket</a> 下載完了,先來開啟一個 Single View 的 Project,命名為 ChatClient<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgg_nPPeOr4TMld1_s4v58XpQSpPDpQslgw7m18SJM2uFcRbsmtiVSvcL_Etail3Uq2ZWSJlOMWWmbYKENOAEDypupPuJlqYbjMftQSkk8zvd8rUodQuW6jFwiCHcj7ZRGKctUqs5O5gezv/s1600/Screen+Shot+2012-09-11+at+7.14.05+PM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="432" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgg_nPPeOr4TMld1_s4v58XpQSpPDpQslgw7m18SJM2uFcRbsmtiVSvcL_Etail3Uq2ZWSJlOMWWmbYKENOAEDypupPuJlqYbjMftQSkk8zvd8rUodQuW6jFwiCHcj7ZRGKctUqs5O5gezv/s640/Screen+Shot+2012-09-11+at+7.14.05+PM.png" width="640" /></a></div>
並把 Class Prefix 命名為 CC。如下<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEihyFgDhtqUIGDuXPhk-SfutnzS8hZFFfvuQjED5rbI6WlPCQAO5BHnN4A9PNXS8FY4-VOpxhzQ6RcY6rQ4KPRXeKHFKM6gGPIgWUoc5WW76JjrYqhGL-0ofmoxNf71yDmfpgpJdEMexUe_/s1600/Screen+Shot+2012-09-11+at+7.17.24+PM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="428" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEihyFgDhtqUIGDuXPhk-SfutnzS8hZFFfvuQjED5rbI6WlPCQAO5BHnN4A9PNXS8FY4-VOpxhzQ6RcY6rQ4KPRXeKHFKM6gGPIgWUoc5WW76JjrYqhGL-0ofmoxNf71yDmfpgpJdEMexUe_/s640/Screen+Shot+2012-09-11+at+7.17.24+PM.png" width="640" /></a></div>
接著可以把需要的 .h/.m 從,<a href="https://github.com/pkyeck/socket.IO-objc">socket.io-objc </a> 和 <a href="https://github.com/square/SocketRocket">SocketRocket</a> 把它們拉到 ChatClient 這個 Project 裡如下。<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiaHk63D3tdtBfONg6uqVyOk03LXkygQKfi0kt7J9nM0hzp3VovUqS8Vwdr-l1VK_40fFYcjoqtk6lRHmQLmdRUXWt8hbYacgXSTwa2IpY13eJSoCLKP8P4d1YysbHWdCmFTUb3VWcf2pEW/s1600/Screen+Shot+2012-09-11+at+7.29.26+PM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="372" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiaHk63D3tdtBfONg6uqVyOk03LXkygQKfi0kt7J9nM0hzp3VovUqS8Vwdr-l1VK_40fFYcjoqtk6lRHmQLmdRUXWt8hbYacgXSTwa2IpY13eJSoCLKP8P4d1YysbHWdCmFTUb3VWcf2pEW/s640/Screen+Shot+2012-09-11+at+7.29.26+PM.png" width="640" /></a></div>
最左邊的是我們的 Project : ChatClient。右邊兩個分別是 Socket.io-objc 和 SocketRocket 的 source code。<br />
接著我們要為 ChatClient 新增一些 Framework,這些是 SocketRocket <a href="https://github.com/square/SocketRocket">必要</a>的,所以要先新增,如下陳列。<br />
<ul>
<li>libicucore.dylib</li>
<li>CFNetwork.framework</li>
<li>Security.framework</li>
<li>Foundation.framework</li>
</ul>
其中 Fundation.framework 已經有了,就不用再新增,新增 Framework 可以在下圖的地方新增。<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj2v3Jbc_QGjaYbMog14zzjHElkQODpsiqndO7ZCh_G1hHQjP4CDnL2wBIbo5efqQe-kJKCaXcubD-nIiLaQ6EpI2nIgb-v82JOiwZ7BRpJp_9bqTAP-D4tSGdatkEySUobC5IjBTUSY2Dk/s1600/Screen+Shot+2012-09-11+at+10.19.12+PM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="358" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj2v3Jbc_QGjaYbMog14zzjHElkQODpsiqndO7ZCh_G1hHQjP4CDnL2wBIbo5efqQe-kJKCaXcubD-nIiLaQ6EpI2nIgb-v82JOiwZ7BRpJp_9bqTAP-D4tSGdatkEySUobC5IjBTUSY2Dk/s640/Screen+Shot+2012-09-11+at+10.19.12+PM.png" width="640" /></a></div>
新增三個,新增完如下。<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiVI6OiaThBRQW0rGl0An53WXBRgj1scU9B9fzG28jKzKyY8CTD8COhYptdWdY5mWubuRjPwLzEwXE4BrkF1zb2DjQYB5exdwbjKKLOk0gTTycT4P4Nv27dqg3T6lfP1ltAjT_nJ595GFLR/s1600/Screen+Shot+2012-09-11+at+10.22.06+PM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="404" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiVI6OiaThBRQW0rGl0An53WXBRgj1scU9B9fzG28jKzKyY8CTD8COhYptdWdY5mWubuRjPwLzEwXE4BrkF1zb2DjQYB5exdwbjKKLOk0gTTycT4P4Nv27dqg3T6lfP1ltAjT_nJ595GFLR/s640/Screen+Shot+2012-09-11+at+10.22.06+PM.png" width="640" /></a></div>
這樣一來就準備好了,我們先 run 一下,看看有沒有錯誤?如果成功執行也沒有任何的 Warning 或是 Error 那就可以了。如下的畫面<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh4-r5zp24krfyyoCaOnQIXN_eyqRQHK4wU__rmV4rcP3RBeztVqH79WHwvnKUi_JUa3fIALty7_UibNs6TiK3vy44JNtjDZNMK_Q0ZNLlfBLCOJRk-CCa5_N0b5dFS4ehdU8KXcUmk_FY6/s1600/Screen+Shot+2012-09-11+at+10.24.04+PM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="640" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh4-r5zp24krfyyoCaOnQIXN_eyqRQHK4wU__rmV4rcP3RBeztVqH79WHwvnKUi_JUa3fIALty7_UibNs6TiK3vy44JNtjDZNMK_Q0ZNLlfBLCOJRk-CCa5_N0b5dFS4ehdU8KXcUmk_FY6/s640/Screen+Shot+2012-09-11+at+10.24.04+PM.png" width="340" /></a></div>
首先來新增一些 UI 元件,打開 Storyboard,從 Library 放上如下元件<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiJ6HiNtut1-r7YNZauoEbKaaf6hZ_q2oZtTbF5GmvU5xjbHKfxNODBP3p_5aHj9IBdej3kh5cgNOht5SWmb3KFQhoF1nAbVxEJgwjpbXhyphenhyphenoVtJ3S6CTjCud5yCgVF7j9t7R1JgitigZTAj/s1600/Screen+Shot+2012-09-11+at+10.27.12+PM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiJ6HiNtut1-r7YNZauoEbKaaf6hZ_q2oZtTbF5GmvU5xjbHKfxNODBP3p_5aHj9IBdej3kh5cgNOht5SWmb3KFQhoF1nAbVxEJgwjpbXhyphenhyphenoVtJ3S6CTjCud5yCgVF7j9t7R1JgitigZTAj/s320/Screen+Shot+2012-09-11+at+10.27.12+PM.png" width="214" /></a></div>
右上是兩個 UIButton,左邊上方是一個 UITextField 下方是一個 UITextView。把 Text Field 和一個 IBOutlet 關連在一起,命名為 inputTextField。如下<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg6RT3gq2yWr1MIF4laD0KyXNEvauRkwE7XmQ0XuCzrK79Bt6J3s8bZgQ8CERGU54kbsXzCCb4XtzqVotL5hfLuhhkS-h-McXBqvOXVleN_0qlJuekCE8wLzEhwKvz49ccKdFPOmD89TtCs/s1600/Screen+Shot+2012-09-11+at+10.35.23+PM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="276" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg6RT3gq2yWr1MIF4laD0KyXNEvauRkwE7XmQ0XuCzrK79Bt6J3s8bZgQ8CERGU54kbsXzCCb4XtzqVotL5hfLuhhkS-h-McXBqvOXVleN_0qlJuekCE8wLzEhwKvz49ccKdFPOmD89TtCs/s640/Screen+Shot+2012-09-11+at+10.35.23+PM.png" width="640" /></a></div>
接著再連結底下的 Text View。命名為 resultTextView 。如下表示<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh_dlUhlMxb42FNdXEfoAlWttKaKJKnrPhqgIVm3HhyWzgQoJDFMMt-p9s-hQODvmPCdb4YFGHFHXm-3nQHAW3qUO46cHEjj_L_Qs2IofmCxIZE70TyyokXtiBZ7La3Oauk6ZYNDGYOsQbk/s1600/Screen+Shot+2012-09-11+at+10.45.11+PM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="386" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh_dlUhlMxb42FNdXEfoAlWttKaKJKnrPhqgIVm3HhyWzgQoJDFMMt-p9s-hQODvmPCdb4YFGHFHXm-3nQHAW3qUO46cHEjj_L_Qs2IofmCxIZE70TyyokXtiBZ7La3Oauk6ZYNDGYOsQbk/s640/Screen+Shot+2012-09-11+at+10.45.11+PM.png" width="640" /></a></div>
接著將 Login 的 UIButton 和 IBAction login: 連結在一起,如下<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjTdAgiOk_RemS9Uhu0XecTTl9ZxZdJ-WDg6e4RrbXx8QKKjct3YwzoQbgdVp1UyMNh28AUW2pGDjPKr_BbRoCx8p3Nro3mQPwnUoOOXVfoYIuVo6lb9Vl94kKEnijQ3nDzrJqBTPUaNq8D/s1600/Screen+Shot+2012-09-11+at+10.48.33+PM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="252" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjTdAgiOk_RemS9Uhu0XecTTl9ZxZdJ-WDg6e4RrbXx8QKKjct3YwzoQbgdVp1UyMNh28AUW2pGDjPKr_BbRoCx8p3Nro3mQPwnUoOOXVfoYIuVo6lb9Vl94kKEnijQ3nDzrJqBTPUaNq8D/s640/Screen+Shot+2012-09-11+at+10.48.33+PM.png" width="640" /></a></div>
<br />
下方的 Send UIButton 則和名為 sendText: 的 UIAction 連結。如下<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiZYCpo7e1tBXpA7jyjUW8fZ_tfxzahrcx32tU0WQCY1ijPpjNCBKDJjMhyKCwdFeXoQ3xLWfzdumC3Dbs3ZUU6oCfluh67mSCPlyk0sTR1b9RUOIeFRJH0IzbNAEu6R9rwfUn6M-iozvEC/s1600/Screen+Shot+2012-09-11+at+10.50.14+PM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="280" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiZYCpo7e1tBXpA7jyjUW8fZ_tfxzahrcx32tU0WQCY1ijPpjNCBKDJjMhyKCwdFeXoQ3xLWfzdumC3Dbs3ZUU6oCfluh67mSCPlyk0sTR1b9RUOIeFRJH0IzbNAEu6R9rwfUn6M-iozvEC/s640/Screen+Shot+2012-09-11+at+10.50.14+PM.png" width="640" /></a></div>
因為要引用 Socket.IO 我們在 CCViewController.h 加入如下程式碼。
<br />
<pre> <code class="objectivec">
#import <UIKit/UIKit.h>
<b>#import "SocketIO.h"</b>
@interface CCViewController : UIViewController<<b>SocketIODelegate</b>>
@property (weak, nonatomic) IBOutlet UITextField *inputTextField;
@property (weak, nonatomic) IBOutlet UITextView *resultTextView;
@end
</code> </pre>
我們 import SockeIO.h 和採用 SocketIODelegate 這個 protocol
接下來準備兩個 ivar,寫在 CCViewController.m 如下
<br />
<pre> <code class="objectivec">
#import "CCViewController.h"
@interface CCViewController (){
NSMutableString * resultString;
SocketIO *socketIO;
}
@end
</code> </pre>
其中 resultString 是用在保存從 server 來的訊息,而 socketIO 是主要的變數用來和 server 溝通。接下來就是初始化這兩個物件,如下我們寫在 CCViewController.m 的 viewDidLoad 如下
<br />
<pre> <code class="objectivec">
- (void)viewDidLoad
{
[super viewDidLoad];
socketIO = [[SocketIO alloc] initWithDelegate:self];
resultString = [[NSMutableString alloc] initWithCapacity:10];
// Do any additional setup after loading the view, typically from a nib.
}
</code> </pre>
主要就是 socketIO 初始化就把 delegate 物件設定好。socketIO 在這個例子會用到的 delegate method 如下列出。
<br />
<pre> <code class="objectivec">
- (void) socketIO:(SocketIO *)socket didReceiveEvent:(SocketIOPacket *)packet;
- (void) socketIODidConnect:(SocketIO *)socket;
- (void) socketIODidDisconnect:(SocketIO *)socket;
</code> </pre>
之後我們會用到。
接著就是第一個動作,筆者寫在 login: 這個 method,按下之後先跳出一個 UIAlertView 讓使用者輸入名稱,然後按下確定後,才真正傳訊息給 server,如下表示
<br />
<pre> <code class="objectivec">
- (IBAction)login:(id)sender {
UIAlertView * nameAlert = [[UIAlertView alloc] initWithTitle:@"Welcome to ChatRoom"
message:@"Who are you" delegate:self cancelButtonTitle:nil
otherButtonTitles:@"OK", nil];
nameAlert.alertViewStyle = <b>UIAlertViewStylePlainTextInput</b>;
[nameAlert show];
}
-(void) alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex{
NSString * inputName = [alertView <b>textFieldAtIndex</b>:<b>0</b>].text;
[socketIO <b>connectToHost</b>:@"localhost" onPort:8124];
NSDictionary * hello = [NSDictionary dictionaryWithObjectsAndKeys:inputName,@"<b>name</b>", nil];
[socketIO <b>sendEvent</b>:@"<b>addme</b>" <b>withData</b>:hello ];
}
</code> </pre>
這個 UIAlertView 是一個 UIAlertViewStylePlainTextInput,當使用者按下 Alert View 的確定後,會先 connect 到 server, 再準備一個 hello dictionary 當資料,key 為 name,然後用 sendEvent:withData 把 event 名稱寫成 addme 再把 hello 傳給 server。然後我們準備接收從 server 來的訊息需要 implement delegate method。如下
<br />
<pre> <code class="objectivec">
-(void) updateResult{
resultTextView.text = resultString;
}
#pragma SocketIO Delegate
- (void) socketIO:(SocketIO *)socket didReceiveEvent:(SocketIOPacket *)packet{
if ([packet.name isEqualToString:@"chat"]) {
NSDictionary * stringData = (NSDictionary *) [NSJSONSerialization JSONObjectWithData:
[packet.data dataUsingEncoding:NSUTF8StringEncoding] options:
NSJSONWritingPrettyPrinted error:NULL];
NSDictionary * messageData = [[stringData objectForKey:@"args"] objectAtIndex:0];
[resultString appendFormat:@"%@ say %@\n",
[messageData objectForKey:@"sender"],[messageData objectForKey:@"message" ]] ;
}
[self updateResult];
}
</code> </pre>
我們必需要 implement socketIO:didReceiveEvent: 才會收到 server 來的資料,在這個 method 裡,先用 data.name 來得知 event 的名稱,再判斷是不是 chat,
由 packet.data 包起來的訊息有點複雜我們來看其原本的樣子。如下
<br />
<pre><code class="javascript">
{"name":"chat","args":[{"sender":"SERVER","message":"Good to see your iOS"}]}
</code></pre>
接著來解釋一下 didReceiveEevent中的幾個動作
<br />
<pre><code class="objectivec"> NSDictionary * stringData = (NSDictionary *) [NSJSONSerialization JSONObjectWithData:
[packet.data dataUsingEncoding:NSUTF8StringEncoding] options:
NSJSONWritingPrettyPrinted error:NULL]; </code></pre>
上方的程式碼是把 packet.data 轉成 NSData 再轉成 JSONObject 最後轉成 NSDictionary * stringData 的方法,為什麼這麼麻煩是 packet.data 是一個由 JSON 轉成的 string,我們將之轉成 dictionary 會比較好取得其內容。<br />
<pre><code class="objectivec"> NSDictionary * messageData = [[stringData objectForKey:@"args"] objectAtIndex:0];
[resultString appendFormat:@"%@ say %@\n",
[messageData objectForKey:@"sender"],[messageData objectForKey:@"message" ]] ;</code></pre>
上方的程式碼是把 packet.data 中 args 這個 key 的值取出來,再把其中的 message key的值取出來接在 resultString 的最後面。最後會用到 [self updateResult]; 這個 method 也只是把 resultString 放到 resultTextView.text 變數裡。<br />
如此一來就可以先打開 server ,用 node chatServer.js 執行後,就可以執行 iOS ,正常的情況下就會出現如下畫面。<br />
先輸入名稱<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjSzNx6h0CDEkD22N6BQaAnBDMqpoQtdy4MPymimxNJbRf23L1nUYwWAVNnmBlVG0Lp-kLmLfCplvS0QU_lQmM1BjvORGtabKcyODGcQu89UZDF_picBxlsvXVTZ3VaW263HepCCIhXOXIW/s1600/Screen+Shot+2012-09-11+at+11.45.10+PM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="640" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjSzNx6h0CDEkD22N6BQaAnBDMqpoQtdy4MPymimxNJbRf23L1nUYwWAVNnmBlVG0Lp-kLmLfCplvS0QU_lQmM1BjvORGtabKcyODGcQu89UZDF_picBxlsvXVTZ3VaW263HepCCIhXOXIW/s640/Screen+Shot+2012-09-11+at+11.45.10+PM.png" width="340" /></a></div>
<br />
看到 Server 來的訊息 <br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhai7ZcUCQj5DTtsrSq2BEe294ISWiPYL0zUHK59BVCqPVKn0QZ3Mo9OFR0zF8bDBVFEjvYTeSHMJ9BZbt3rQ8FgdLhlp2OQvYVyn_qE9Xz0jSDM674txHKhmb95JzXCXJrXQwDOiBop_l-/s1600/Screen+Shot+2012-09-11+at+11.44.57+PM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="640" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhai7ZcUCQj5DTtsrSq2BEe294ISWiPYL0zUHK59BVCqPVKn0QZ3Mo9OFR0zF8bDBVFEjvYTeSHMJ9BZbt3rQ8FgdLhlp2OQvYVyn_qE9Xz0jSDM674txHKhmb95JzXCXJrXQwDOiBop_l-/s640/Screen+Shot+2012-09-11+at+11.44.57+PM.png" width="340" /></a></div>
這樣協定的部分差不多是可以了,再來就是從 iOS 發出訊息的部分,我們寫在 CCViewController.m 如下
<br />
<pre><code class="objectivec">
- (IBAction)sendText:(id)sender {
NSDictionary * chat = [NSDictionary dictionaryWithObjectsAndKeys:inputTextField.text,@"message", nil];
[socketIO sendEvent:@"sendchat" withData:chat];
[inputTextField resignFirstResponder];
}
</code></pre>
單純地把 inputTextField.text 包在 Dictionary 裡面,用 message 當 key 就送給 server。簡單地動作,重新執行後,這樣一來就可以和 Brwoser 溝通了,如下畫面<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjn2VCsLsTaJD5HnLaK2W2npiiob-N2Db1qjS2kaFVAivH_iiTfR3cEAGheoDA9nI37pEbHNqXsl2LRp9FLkfPgEl6kRtk5pKFiiQi5ehdLRgAIcki8x3Lp1IRe6H2FtyIEouQ-OKskDEil/s1600/Screen+Shot+2012-09-11+at+11.47.59+PM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="396" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjn2VCsLsTaJD5HnLaK2W2npiiob-N2Db1qjS2kaFVAivH_iiTfR3cEAGheoDA9nI37pEbHNqXsl2LRp9FLkfPgEl6kRtk5pKFiiQi5ehdLRgAIcki8x3Lp1IRe6H2FtyIEouQ-OKskDEil/s640/Screen+Shot+2012-09-11+at+11.47.59+PM.png" width="640" /></a></div>
當然看著做一定會順暢,筆者還是建議把兩個 delegate method 補上,這樣可以知道那邊出了問題。如下
<pre><code class="objectivec">
- (void) socketIODidConnect:(SocketIO *)socket{
NSLog(@"did connect to %@", socket);
}
- (void) socketIODidDisconnect:(SocketIO *)socket{
NSLog(@"did disconnect to %@", socket);
}
</code></pre>
一樣,程式碼都放在 GitHub,包含了 Server,Client,iOS。Good to see you again, Bye.
麥克http://www.blogger.com/profile/01716179223350987115noreply@blogger.com3tag:blogger.com,1999:blog-3469754461280175671.post-9979419062281564142012-09-10T04:25:00.001-07:002012-09-11T00:35:06.420-07:00聊天廣播 - Socket.io關於網路的架構,Server-Client 這樣的結合己經延用己久,相信大家會了解 HTTP 的協定是 stateless,一但 client 和 server 通訊完,server 就不知道 client 跑去那了?如果 server 有更新想要 Push 給 client,就要反過來從 Client 定期的去向 server 要求。今天要來使用 socket.io 這個 module 就是可以達到 server 和 client 的雙向溝通,只要一建立連線 server 可以一直傳訊息給 client,反過來也行,直接某一方斷線為止。先姑且不論 socket.io 底層是什麼,我們就用一個聊天廣播室來建立這個服務。client 端我們用 HTML 來實現,建立如下的 HTML,命名為 chat.html。<br />
<pre> <code>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Chat Room</title>
</head>
<body>
<div id="output">
</div>
<div id="send">
<input type="text" id="data" size="100" /><br />
<input type="button" id="sendtext" value="Send Text" />
</div>
</body>
</html>
</code> </pre>
這個 HTML 在 Browser 執行會是如下畫面<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhLs9jQ8x1XdJEIt9VQ2FBoeo_tUxy3Tj-6zZ3p1QYWMLWUNssPTeG9coZrsDSGH7AhyphenhyphenRe2S8t-jHc5-d3Fij9q55vKQcR9t3RrXKW5AmJCtQrqvF1_8t3HTDG9fonMOLrZkqQGXyXJsRTx/s1600/Screen+Shot+2012-09-10+at+5.51.30+PM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="71" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhLs9jQ8x1XdJEIt9VQ2FBoeo_tUxy3Tj-6zZ3p1QYWMLWUNssPTeG9coZrsDSGH7AhyphenhyphenRe2S8t-jHc5-d3Fij9q55vKQcR9t3RrXKW5AmJCtQrqvF1_8t3HTDG9fonMOLrZkqQGXyXJsRTx/s640/Screen+Shot+2012-09-10+at+5.51.30+PM.png" width="640" /></a></div>
有一個 Text Field 可以輸入文字然後有一個按鈕<br />
接著加上一些 javascript,在 <head> </head> 中間。如下<br />
<pre> <code class="html">
<head>
<meta charset="utf-8">
<title>Chat Room</title>
<script src="http://localhost:8124/socket.io/socket.io.js"></script>
<script>
var socket = io.connect('http://localhost:8124');
socket.on('connect', function() {
socket.emit('addme', prompt('Who are you?'));
});
socket.on('chat',function(username, data) {
var p = document.createElement('p');
p.innerHTML = username + ': ' + data;
document.getElementById('output').appendChild(p);
});
window.addEventListener('load',function() {
document.getElementById('sendtext').addEventListener('click',function() {
var text = document.getElementById('data').value;
socket.emit('sendchat', text);
}, false);
}, false);
</script>
</head>
</code></pre>
一開始這個文件用了 <script src="http://localhost:8124/socket.io/socket.io.js"> 因為要用到 socket.io 的功能,所以要指向 server 的 socket.io.js ,在 chat.html 使用 。<br />
我們一一來看一下。 <br />
<pre><code class="javascript">var socket = io.connect('http://localhost:8124');
socket.on('connect', function() {
socket.emit('addme', prompt('Who are you?'));
});</code></pre>
這個是要用 io 去連結 server,然後就等待, connect 這個事件,也就是和 server 連上線後,就會觸發。觸發的動作就是發送一個訊息給 server 事件名為 addme,內容為 prompt('Who are you?')。prompt() 的用途是會跳出一個需要輸入的視窗,然後把使用者在這個視窗輸入的值,再經由 socket.emit 傳給 server。接著再來看一下,下一個 socket 事件<br />
<pre><code class="javascript">socket.on('chat',function(username, data) {
var p = document.createElement('p');
p.innerHTML = username + ': ' + data;
document.getElementById('output').appendChild(p);
});</code></pre>
在 socket.on 等待 chat 事件,然後 server 會傳兩個值過來分別存在 username 和 data 這兩個變數中,接著更新網頁內容在 <output> 裡多加一個 <p> 內容就是某個 user 打了什麼字,這樣。<br />
在 Client 端最後,我們看到<br />
<pre><code class="javascript">window.addEventListener('load',function() {
document.getElementById('sendtext').addEventListener('click',function() {
var text = document.getElementById('data').value;
socket.emit('sendchat', text);
}, false);
}, false);</code></pre>
這個是新增按鈕按下去的觸發動作,主要就是把 TextField 中使用者輸入的文字,抓出來<br />
<pre><code class="javascript">var text = document.getElementById('data').value; </code></pre>
然後再利用 socekt.emit 送給 server。這樣 Client 端就完成了。
Server 端也非常之簡單,如下就是全部了,存成 chatServer.js。
<br />
<pre><code class="javascript">
var app = require('http').createServer(handler)
, io = require('socket.io').listen(app)
, fs = require('fs');
app.listen(8124);
function handler (req, res) {
fs.readFile(__dirname + '/chat.html', function (err, data) {
if (err) {
res.writeHead(500);
return res.end('Error loading chat.html');
}
res.writeHead(200);
res.end(data);
});
}
io.sockets.on('connection', function (socket) {
socket.on('addme',function(username) {
socket.username = username;
socket.emit('chat', 'SERVER', 'You have connected');
socket.broadcast.emit('chat', 'SERVER', username + ' is on deck');
});
socket.on('sendchat', function(data) {
io.sockets.emit('chat', socket.username, data);
});
socket.on('disconnect', function() {
io.sockets.emit('chat', 'SERVER', socket.username + ' has left the building');
});
});
</code></pre>
一開始需要用到 http,socket.io,fs 等 module 就引用就好了。然後來看一下 handler 這個 function 被用在一開始的 require('http').createServer(handler) 。<br />
<pre><code class="javascript">function handler (req, res) {
fs.readFile(__dirname + '/chat.html', function (err, data) {
if (err) {
res.writeHead(500);
return res.end('Error loading chat.html');
}
res.writeHead(200);
res.end(data);
});
}</code></pre>
這個 funciton 主要是說不管 client 端在 Browser 輸入什麼路徑,都是執行這個 function ,無論是輸入<br />
http://127.0.0.1:8124/ashdf;asdlf/has;dfasdf<br />
或是<br />
http://127.0.0.1:8124/<br />
結果都是一樣的,就是把同一個目錄下的 chat.html 讀出來然後送給 Browser, 也就是說,現在 Browser 看到的就是一開始我們寫的 chat.html ,一個 Text Field 和一個按鈕的樣式。<br />
<pre><code class="javascript">io.sockets.on('connection', function (socket) {
socket.on('addme',function(username) {
socket.username = username;
socket.emit('chat', 'SERVER', 'You have connected');
socket.broadcast.emit('chat', 'SERVER', username + ' is on deck');
});
socket.on('sendchat', function(data) {
io.sockets.emit('chat', socket.username, data);
});
socket.on('disconnect', function() {
io.sockets.emit('chat', 'SERVER', socket.username + ' has left the building');
});
});</code></pre>
接著看到一滿大的 function,就是 io.socket.on 等待 connection 事件,
然後會得到一個 socket 也就是和一個 client 連上線。要注意的是這個 socket 會因為不同的 client 而有不同的記憶體
接著看裡面一點
<br />
<pre><code class="javascript"><code class="javascript">socket.on('addme',function(username) {
socket.username = username;
socket.emit('chat', 'SERVER', 'You have connected');
socket.broadcast.emit('chat', 'SERVER', username + ' is on deck');
});</code> </code></pre>
然後這個 socket 會等待 addme 這個事件,會得到一個 username 再存到 socket.name 保存下來。
然後用 socket.emit 回給連上來的 client,再用 socket.broadcast.emit 廣播給所有在線上<span style="color: blue;"><b>其他</b></span>的client。接著再往下看。<br />
<pre><code class="javascript">socket.on('sendchat', function(data) {
io.sockets.emit('chat', socket.username, data);
});</code></pre>
這個 socket 等待 sendchat 的事件,然後收到資料後用 io.<b>sockets</b>.emit 廣播給所有線上的 client。說到這裡,我們來看這三個相似的 function<br />
<ul>
<li>socket.emit - 對於一個特定的 socket 傳訊息</li>
<li>socket.broadcast.emit - 對於除了目前這個 socket 之外所有線上的 socket 傳訊息</li>
<li>io.sockets.emit - 對於所有線上 socket 傳訊息</li>
</ul>
然後就是最後一個 function 了<br />
<pre><code class="javascript">socket.on('disconnect', function() {
io.sockets.emit('chat', 'SERVER', socket.username + ' has left the building');
});</code></pre>
也就是 socket 等待 disconnect 之後,就廣播給其他 client 說某 socket 離線了。 先執行 node chatServer.js<br />
測試的方法就是用兩個不同的 Browser 試試連看看,這樣就可以互相聊天了。<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhFSZ3BYBFQrAaMf5ajxX85oObD85w8TNzBryCusHw67lqpTQW0VcqUGQFP5ethFpR2w4wU6j9Gn2ZungT40whWHj7ZTL87yLV1E-Y3C3gfs2Gb0tWBqGku5xmooMrJxF89Ta-P8nQaCmT9/s1600/Screen+Shot+2012-09-10+at+7.21.47+PM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="360" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhFSZ3BYBFQrAaMf5ajxX85oObD85w8TNzBryCusHw67lqpTQW0VcqUGQFP5ethFpR2w4wU6j9Gn2ZungT40whWHj7ZTL87yLV1E-Y3C3gfs2Gb0tWBqGku5xmooMrJxF89Ta-P8nQaCmT9/s640/Screen+Shot+2012-09-10+at+7.21.47+PM.png" width="640" /></a></div>
原始碼都放在 <a href="https://github.com/Scentsome/Developer-s-Note/tree/master/socketIO">GitHub</a>。<br />
補充說明一下,在這個例子我們都使用 socket.emit() 來傳送訊息,也許讀者有參考到其他教學有用到 socket.send() 這個 function,我們用一個例子來解釋<br />
<blockquote class="tr_bq">
socket.send("Hi") - 相當於 socket.emit("message", "Hi");</blockquote>
也就是說,socket.send("Hi") 省略了 socket.emit("message", "Hi"); 前面的第一個 "message",而接受的一端就是用
<pre><code class="javascript">
socket.on('message', function (message) {
console.log(message);
});</code></pre>
來接收 socket.send("Hi") 的訊息。
麥克http://www.blogger.com/profile/01716179223350987115noreply@blogger.com7tag:blogger.com,1999:blog-3469754461280175671.post-21442215806582943822012-09-08T09:44:00.001-07:002012-09-10T06:52:45.789-07:00NoSQL Database - MongoDB & Node.js<div class="tr_bq">
這篇文章主要是介紹 MongoDB 這個資料庫,也會用 Node.js 與其相連,不過在這之前,先來解說一下一些名詞,首先就是 <a href="http://zh.wikipedia.org/zh-tw/NoSQL">NoSQL</a>,從 Wikipedia 看來就是不用 SQL 當做查詢的語言,換句話說,如果用 MongoDB 就不用學 SQL 了,呵。看來不錯,再來看看 MongoDB 有什麼特色,<a href="http://zh.wikipedia.org/wiki/MongoDB">MongoDB</a> 是一種文件導向的資料庫,什麼文件?長什麼樣子?舉一個例子來說</div>
<blockquote class="tr_bq">
<span style="font-family: inherit;">{<br /> name : "Michael"<br /> gender : "Male"<br />}</span></blockquote>
長得和 JSON 好像啊,嗯啊,可以直接把 JSON 丟到 MongoDB 裡,這個就是文件,還可以很複雜如下<br />
<blockquote class="tr_bq">
<pre class="prettyprint"><span class="pln"> </span><span style="font-family: inherit;"><span class="pun">{</span><span class="pln">
</span><span class="str">"query"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"Pizza"</span><span class="pun">,</span><span class="pln">
</span><span class="str">"locations"</span><span class="pun">:</span><span class="pln"> </span><span class="pun">[</span><span class="pln">
1210054</span><span class="pun">,</span><span class="pln">
</span><span class="lit">1201167</span><span class="pln">
</span><span class="pun">]</span><span class="pln">
</span><span class="pun">}</span></span></pre>
</blockquote>
就是一個 JSON 所以用 Javascript 存取 MongoDB 非常直覺。OK 讓我們來安裝一下 MongoDB<br />
安裝有幾種方式比如有 Homebrew 直接用<br />
<blockquote class="tr_bq">
<pre>brew install mongodb</pre>
</blockquote>
或者<a href="http://www.mongodb.org/downloads">到這</a>安裝執行檔,有幾個作業系統可以選如下<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjhaNn3UCl3ghiuc-v7h5Ou_pSlfL37TqU6w7Tivc7GfUNoIyWHJzZtqKeo7g4lPqhMMTs_SLYqH256UNzc2L2E85sctL284mROhRyR2O7tc3Wf1R2ioiPonpIctsZoeV2k3t-fJiiPe-PH/s1600/Screen+Shot+2012-09-06+at+5.39.30+PM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="273" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjhaNn3UCl3ghiuc-v7h5Ou_pSlfL37TqU6w7Tivc7GfUNoIyWHJzZtqKeo7g4lPqhMMTs_SLYqH256UNzc2L2E85sctL284mROhRyR2O7tc3Wf1R2ioiPonpIctsZoeV2k3t-fJiiPe-PH/s400/Screen+Shot+2012-09-06+at+5.39.30+PM.png" width="400" /></a></div>
安裝完之後在 Terminal 可以輸入以下指令,來確定安裝成功。<br />
<blockquote class="tr_bq">
mongod --version</blockquote>
可以看到如下畫面<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhdeMCmvH3tKj_n5NWGDDOGFGYvWO67bS5z3UvCdSPypaxPD1BHGAKpttXGlk6h_NLTDa83cbql1rfycIykxO1rJUdqszwVO2q9QUs_oRNf7KFw9EkBf6bKUmycQpnFg8Mw33PbIVaz5_bb/s1600/Screen+Shot+2012-09-06+at+5.41.18+PM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="283" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhdeMCmvH3tKj_n5NWGDDOGFGYvWO67bS5z3UvCdSPypaxPD1BHGAKpttXGlk6h_NLTDa83cbql1rfycIykxO1rJUdqszwVO2q9QUs_oRNf7KFw9EkBf6bKUmycQpnFg8Mw33PbIVaz5_bb/s400/Screen+Shot+2012-09-06+at+5.41.18+PM.png" width="400" /></a></div>
這樣就是成功安裝了。好吧,我們開啟這個 MongoDB 服務,很簡單,直接輸入 mongod 這樣就可以了<br />
<blockquote class="tr_bq">
mongod</blockquote>
不過,讀者有可能會看到以下的畫面,代表<b>沒有</b>開啟服務成功<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEihcobzDDPhycqnx5SAStLe6c7SX6oleGsBO-7hrDrMbchUE9TjvKdIdbgc8NPa3VW3qjm1dLBAyobw0KzfcYBh7y_fJEqzaeO1s2n-1KtPpw4RP3aG_PRXh7jU5NRaJ9vmfYbinKczsAXU/s1600/Screen+Shot+2012-09-06+at+5.54.52+PM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="235" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEihcobzDDPhycqnx5SAStLe6c7SX6oleGsBO-7hrDrMbchUE9TjvKdIdbgc8NPa3VW3qjm1dLBAyobw0KzfcYBh7y_fJEqzaeO1s2n-1KtPpw4RP3aG_PRXh7jU5NRaJ9vmfYbinKczsAXU/s400/Screen+Shot+2012-09-06+at+5.54.52+PM.png" width="400" /></a></div>
紅色框起來的部分,寫著<br />
<blockquote class="tr_bq">
MongoDB starting : pid=4116 port=27017 <b>dbpath=/data/db/</b> 64-bit host=chronoers-MacBook-Air.local</blockquote>
MongoDB 想要在 port 27017 開啟,而且把資料放在 /data/db/ 這個資料夾下,然後就是shutdown 了,很有可能就是 port 27017 被別人用了,或是 dbpath 這個目錄不存在,一開始沒有別的服務的話,dbpath 的這個目錄不存在的可能性很高,這篇文章的做法就是先在當下目錄下建立一個新的目錄名為 dbs,如下指令<br />
<blockquote class="tr_bq">
mkdir dbs</blockquote>
然後再啟動 MongoDB ,這次要改變一些參數,如下<br />
<blockquote class="tr_bq">
mongod --dbpath=dbs</blockquote>
這樣一來 mongod 就會用當前的目錄下的 dbs (剛剛我們建立的),來存放資料,執行成功後會看到如下畫面<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEidoc6TUWJDEMvomn3cv2fAkfF5iE7RyEPkf6jortY7FgLvDrShru47vnWgwzdOoGbkBSGfqWksxmjMcXe2Q_S6D_rdBb4Swq3nsst591jT4XUDE9Ci8dYPwjNj2Edlpq3DYtI_mZ4QrR5z/s1600/Screen+Shot+2012-09-06+at+6.05.53+PM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="235" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEidoc6TUWJDEMvomn3cv2fAkfF5iE7RyEPkf6jortY7FgLvDrShru47vnWgwzdOoGbkBSGfqWksxmjMcXe2Q_S6D_rdBb4Swq3nsst591jT4XUDE9Ci8dYPwjNj2Edlpq3DYtI_mZ4QrR5z/s400/Screen+Shot+2012-09-06+at+6.05.53+PM.png" width="400" /></a></div>
最後寫 waiting for connections on port 27017 然後整個游標停在那,這樣就表示正常啟動了。<br />
我們先用 Terminal 來新增一些資料到 dbs 裡面,在先前有提過 <b>document</b> 的觀念,基本上可以視為一個 JSON 物件。接者要來說另一個名詞叫 <b>collection</b>,很多筆 document 就組合成一個 collection ,所以我們要新增一筆 document 之前要先建立一個 document ,這是 MongoDB 的架構,OK。<br />
怎麼新增呢?我們在安裝完成後,除了有 mongod 這個指令外,還有另一個叫做 mongo 要注意,只差一個 d,符號,mongo 就是要和 mongod 來溝通的指令,首先先來看一下版本<br />
直接執行<br />
<blockquote class="tr_bq">
mongo</blockquote>
會看到如下畫面<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEglXNAPxO6sRbBfbWjxhw5Idyi5gmt7f-x5FCzYYU7x_eTU1Zxj1S38d4v3knyaVhieOxg4IvvkMGiGjMc3rJ4jIEL8E-oFG592bwMMfrYvQsUJX8S-OoW6WKtGi1eyeWowLn6yYpF8l2KT/s1600/Screen+Shot+2012-09-06+at+6.22.52+PM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="242" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEglXNAPxO6sRbBfbWjxhw5Idyi5gmt7f-x5FCzYYU7x_eTU1Zxj1S38d4v3knyaVhieOxg4IvvkMGiGjMc3rJ4jIEL8E-oFG592bwMMfrYvQsUJX8S-OoW6WKtGi1eyeWowLn6yYpF8l2KT/s400/Screen+Shot+2012-09-06+at+6.22.52+PM.png" width="400" /></a></div>
游標停在 > 之後,代表可以直接輸入指令,控制 mongod,如下輸入<br />
show dbs<br />
代表把所有的 database 呈現在 Terminal 上,應該會看到如下<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi8W9IGN8PQ9mGMw33JvdzeoCW00WA_8oM88dVsJor03RZ4zatFfISh44RzdOZSh-yXC9UrA2wlAvxfSTm5kxT652xQN1TD2GyH_TBYzpESesnycEDYybcsi8h9BgtL4-XMxKDWzM1cI6JY/s1600/Screen+Shot+2012-09-06+at+6.25.44+PM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="131" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi8W9IGN8PQ9mGMw33JvdzeoCW00WA_8oM88dVsJor03RZ4zatFfISh44RzdOZSh-yXC9UrA2wlAvxfSTm5kxT652xQN1TD2GyH_TBYzpESesnycEDYybcsi8h9BgtL4-XMxKDWzM1cI6JY/s400/Screen+Shot+2012-09-06+at+6.25.44+PM.png" width="400" /></a></div>
<br />
<br />
local (empty) 就是有一個資料庫名為 local 但是沒有資料在裡面。我們就來新增一個資料庫名為 mydb,如下輸入<br />
use mydb<br />
會看到<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjXvq24i-_t42VpkafXPNndeynyC7FNe3dZSIMZST4p-jlFnYlJQyPvb4_7NTcwoF0t56ikSqHglBZJ2BwPVXUe8nXoWrnW2kFxWEmxvhlaTVOAz1AZaareUdfjm9oDJdvihDYfrkdu8-wR/s1600/Screen+Shot+2012-09-06+at+6.50.25+PM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="148" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjXvq24i-_t42VpkafXPNndeynyC7FNe3dZSIMZST4p-jlFnYlJQyPvb4_7NTcwoF0t56ikSqHglBZJ2BwPVXUe8nXoWrnW2kFxWEmxvhlaTVOAz1AZaareUdfjm9oDJdvihDYfrkdu8-wR/s400/Screen+Shot+2012-09-06+at+6.50.25+PM.png" width="400" /></a></div>
雖然沒有明確看到建立的動作, 因為 mydb 裡面如果沒有 document 或 collection 是沒有意義的,暫時這樣,先來看一下這個 mydb 一些基本內容<br />
<blockquote class="tr_bq">
db.stats()</blockquote>
這樣會看到<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhDk3DmXO9-PbU4LcVTtXk3bqoQHYRfWB7zDmFycRQiX-6OTGZ87GMFY5zxPvEgVTe_0DzqWSFe56TVZysRASPz3cVh4n877-Awro5FIc911lUsf4gIPnGE29veY5bNcyOo1fNDkxuRQ66U/s1600/Screen+Shot+2012-09-06+at+6.54.46+PM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhDk3DmXO9-PbU4LcVTtXk3bqoQHYRfWB7zDmFycRQiX-6OTGZ87GMFY5zxPvEgVTe_0DzqWSFe56TVZysRASPz3cVh4n877-Awro5FIc911lUsf4gIPnGE29veY5bNcyOo1fNDkxuRQ66U/s1600/Screen+Shot+2012-09-06+at+6.54.46+PM.png" /></a></div>
還記得 collection 和 document 嗎?我們就來建立一下。輸入<br />
<blockquote class="tr_bq">
db.users.insert({name : "Michael", gender : "Male"})</blockquote>
如果沒有發生錯誤,就會看到如下<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhzaCbUQ4bUiWGZXdcBmjVY2F_Z5Z2DXQWDaASwiVSC72BQaHVagSgdPxgdUL-G-UTRcScyeUe5nHaNeWhRzwPp6h32oYLR9RJE7jV65Gq89olZguid9b8PWgQYnQZFXR811iWoxw1zEw2Z/s1600/Screen+Shot+2012-09-06+at+6.57.26+PM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="51" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhzaCbUQ4bUiWGZXdcBmjVY2F_Z5Z2DXQWDaASwiVSC72BQaHVagSgdPxgdUL-G-UTRcScyeUe5nHaNeWhRzwPp6h32oYLR9RJE7jV65Gq89olZguid9b8PWgQYnQZFXR811iWoxw1zEw2Z/s640/Screen+Shot+2012-09-06+at+6.57.26+PM.png" width="640" /></a></div>
在 <b>users</b><b> </b>這個就是<b> collection</b> 而 {name : "Michael", gender : "Male"} 就是 document ,insert 就是寫入的動作,就這樣很直接的寫就可以新增資料進入 mydb 了,我們用<br />
show collectons 就可以看到如下,有出現 users<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgXXP4rN-ZjiZtnRZbmhs9KIgddXsmeXNNEuLE8E317RaGjq0dr4mOucELZaOv-DUC5GVU8rBQM9mo7n19xBrVqqzTgwKRZ16pddxLnEyoOpbDpU_VRDJBE3N1gginkKK6hcPkAk5A2YjpR/s1600/Screen+Shot+2012-09-06+at+7.00.57+PM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="178" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgXXP4rN-ZjiZtnRZbmhs9KIgddXsmeXNNEuLE8E317RaGjq0dr4mOucELZaOv-DUC5GVU8rBQM9mo7n19xBrVqqzTgwKRZ16pddxLnEyoOpbDpU_VRDJBE3N1gginkKK6hcPkAk5A2YjpR/s400/Screen+Shot+2012-09-06+at+7.00.57+PM.png" width="400" /></a></div>
<br />
users 真得有寫入 mydb 接下來看 users 這個 collection 裡面的內容,使用如下指令<br />
<blockquote class="tr_bq">
db.users.find()</blockquote>
會看到如下<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhLiypRyuvMDD1BYvWShNypwmnJr0H2uMxrPzgijZ_MQrgic4qKIjWg_GONuNTRJy6cZkIE9Muv_dRCUrUoYY09Ko_VofAL_ePlNqnTvTqMw67WICWtWUxVXG16ecHcWBkeeMK88bl7jZEK/s1600/Screen+Shot+2012-09-06+at+7.06.35+PM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="48" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhLiypRyuvMDD1BYvWShNypwmnJr0H2uMxrPzgijZ_MQrgic4qKIjWg_GONuNTRJy6cZkIE9Muv_dRCUrUoYY09Ko_VofAL_ePlNqnTvTqMw67WICWtWUxVXG16ecHcWBkeeMK88bl7jZEK/s640/Screen+Shot+2012-09-06+at+7.06.35+PM.png" width="640" /></a></div>
真得有一筆資料,接下來我們做個實驗<br />
再輸入一次<br />
<blockquote class="tr_bq">
db.users.insert({name : "Michael", gender : "Male"})</blockquote>
再用<br />
<blockquote class="tr_bq">
db.users.find()</blockquote>
會發生什麼事?<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhm3rOH1ey_n-YKFa1rOXw4HtHeOReoY3XsceyaGaT_KxLnAJaXKbQXK8HCu0J6DUWEfo1z1WFX4ocPKk2iP6fmnnRaq6_97dKTm95w42pOC3K3Le4iudAOk3i8c433sv_ydoc0S_F99xcf/s1600/Screen+Shot+2012-09-06+at+7.09.50+PM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="35" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhm3rOH1ey_n-YKFa1rOXw4HtHeOReoY3XsceyaGaT_KxLnAJaXKbQXK8HCu0J6DUWEfo1z1WFX4ocPKk2iP6fmnnRaq6_97dKTm95w42pOC3K3Le4iudAOk3i8c433sv_ydoc0S_F99xcf/s640/Screen+Shot+2012-09-06+at+7.09.50+PM.png" width="640" /></a></div>
<br />
兩筆我們認為一樣的資料寫入了,MongoDB 不會因為 name, gender 一樣的值就自行判斷不加入哦,對 MongoDB 而言就是兩筆資料,而兩筆的 _id 是不一樣的,那接下來把其中一個刪除吧。用下列指令<br />
<blockquote class="tr_bq">
db.users.<b>remove</b>({"_id" : ObjectId("5048d6b704f6c8a66750e12b")})</blockquote>
因為 _id 才是可以辦識不一樣的key 所以要輸入 _id 才可以明確指出要 remove 那一筆 document 。接下來我們來更改一下內容,用 update,如下<br />
<blockquote class="tr_bq">
db.users.<b>update</b>({gender : "Male"}, { <b>$set </b>:{ name : "James" }})</blockquote>
再用<br />
db.users.find()<br />
把結束呈現出來會看到下方結果<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi-RGOOYd_hG_3sUPqbqZHeoZT0jHn2xacdSxExfKcCEWsGLjKXP5JNQqcYh8d_hGzQteIpyn50swiU28lkQHRHGsVNc3xHkjSdfoN5XoahDGp4FdYvM6YIDBNSjGeasfK_y1Y6bYnHE0Re/s1600/Screen+Shot+2012-09-07+at+1.09.31+AM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="48" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi-RGOOYd_hG_3sUPqbqZHeoZT0jHn2xacdSxExfKcCEWsGLjKXP5JNQqcYh8d_hGzQteIpyn50swiU28lkQHRHGsVNc3xHkjSdfoN5XoahDGp4FdYvM6YIDBNSjGeasfK_y1Y6bYnHE0Re/s640/Screen+Shot+2012-09-07+at+1.09.31+AM.png" width="640" /></a></div>
<br />
name 改成了 James 了,我們來看一下 update 這個 function 有兩個參數,第一個參數是要尋找的條件,我們設定為 {gender : "Male"} 目前也只有一個 document 符合,接下來第二個參數是<br />
<blockquote class="tr_bq">
{ <b>$set</b> : { name : "James"}} </blockquote>
前面要加上 $set 會依照原本的值 {name : "Michael", gender : "Male"} 做為改變的依據,把 name 這個值改成 James,如果沒有加上 $set 比如說<br />
<blockquote class="tr_bq">
db.users.<b>update</b>({gender : "Male"}, { name : "James" })</blockquote>
則會把原本的 {name : "Michael", gender : "Male"} 用 { name : "James" } 取代,也就是說 { gender : "Male "} 並不會被保留。<br />
好我們介紹了 insert, find, update, remove 這幾個操作就是有名的 CRUD - (Create,Read,Update,Delete),接下來我們來看一下怎麼從 Node.js 來存取 MongoDB 的資料。<br />
首先把上面的 MongoDB 停止,需要輸入 Ctrl + c。然後再另一個資料夾再開啟一個新的 Server。我們就建立一個名為 mongoDemo 的資料夾,然後在 mongoDemo 之下再建立 dbs 。然後開啟 MongoDB 如下輸入<br />
<blockquote class="tr_bq">
mongod --dbpath dbs</blockquote>
我們必需要為 Node.js 準備一個 MongoDB 的 Driver,名為 mongojs 如下安裝<br />
<blockquote>
npm install -g mongojs</blockquote>
也許有些網友比較熟 <a href="http://www.mongodb.org/display/DOCS/node.JS;jsessionid=A598F2D091B3CFE8B3577E7DF7B38B1D">mongodb</a> 這個官方網站介紹的 module ,不過這篇文章開頭是用 mongo 這個 interactive shell 來介紹存取 MongoDB 的而 mongojs 的寫法和 mongo 比較像,所以就先採用這個,mongojs 的官方 GItHub 在<a href="https://github.com/gett/mongojs">這</a>。而對官方的 mongodb 比較有興趣的讀者可以看這篇<a href="https://www.10gen.com/presentations/mongohamburg-2011/an-introduction-nodejs-mongodb-driver">解釋</a>。<br />
接著準備一個名為 accessMongo.js 的檔案, 先輸入如下內容。<br />
<pre><code>
var express = require("<b>express</b>");
var app = express();
app.listen(8800);
var <b>config</b> = {
"hostname":"localhost",
"port":27017,
"db":"mydb"
}
var dbURL = "mongodb://" + config.hostname + ":" + config.port + "/" + config.db;
var collections = ["<b>users</b>"];
var db = require("<b>mongojs</b>").<b>connect</b>(<b>dbURL</b>, collections);
</code></pre>
一開始筆者還是使用了 express,為了方便可以讓使用者看到結果。預計用<br />
<blockquote class="tr_bq">
http://127.0.0.1:8800/create<br />
http://127.0.0.1:8800/read<br />
http://127.0.0.1:8800/update<br />
http://127.0.0.1:8800/remove</blockquote>
這四個 GET 連結來來代表對 Database 執行 CRUD 四個動作。<br />
接著我們會看到 config 這個 JSON Object 就是關於要存取 MongoDB Database 和 Collection 的設定。最後利用 mongojs 的 connect 和 MongoDB 連上線並設定要存取的 collection 是 <b>users</b>。接下來寫 read 的部分。<br />
<br />
<pre><code class="javascript">
app.get("/create",function (req, res) {
db.users.save({name: "Michael", gender: "Male"}, function(err, saved) {
if( err || !saved ) console.log("User not saved");
else {
res.send("User saved");
}
res.end();
});
});
</code></pre>
在這裡我們可以看到 db.users.save() 幾乎和 mongo 終端機的寫法一樣,第一個參數就是 document 也就是 <b>{name: "Michael", gender: "Male"},</b>如果 saved 是正確的,代表寫入正確<b>。</b>再來我們來寫 read,如下。<br />
<pre><code>
app.get("/read",function (req, res) {
<b>db.users.find</b>(<b>{}</b>, function(err, <b>users</b>) {
if( err || !users) console.log("No users found");
else <b>users</b>.<b>forEach</b>( function(user) {
res.send(JSON.stringify(user)+"\n");
} );
res.end();
});
});</code></pre>
用 db.users.find() 第一個參數是 {} 代表任一個 document 都符合,然後第二個參數是個 function,其中 users 就是從 users.find() 找到的所有的 document 就會存在這個 users array 裡面。然後用 users.forEach() 把每個 document 送給 Browser 呈現出來。如此一來就可以先用 Browser 測試,記得要先開啟 mongod 才能執行 node accessMongo.js。<br />
在 Browser 輸入 http://127.0.0.1:8800/create<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiniynlzGHba-495zCGY8S2IHdn6mHh9jzVqm5WSeF4BrKvThTlOB1omEA4NjP7hlDVUuhvuN0gfbGhWQjWGccZ-T9SI1Mm0YhV_sBMQNr8O05zLgazMA1XJonfagCQnUdbPOd0ttSSKvoK/s1600/Screen+Shot+2012-09-09+at+12.30.20+AM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="122" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiniynlzGHba-495zCGY8S2IHdn6mHh9jzVqm5WSeF4BrKvThTlOB1omEA4NjP7hlDVUuhvuN0gfbGhWQjWGccZ-T9SI1Mm0YhV_sBMQNr8O05zLgazMA1XJonfagCQnUdbPOd0ttSSKvoK/s320/Screen+Shot+2012-09-09+at+12.30.20+AM.png" width="320" /></a></div>
再來測試 read http://127.0.0.1:8800/read<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgKvcQsLmpput9LCE1iXMdFcS-lrLitMjF5E6hT7OQS23__tjxynEGN1Dhoy1obcP-rc_3CNVoUFqcn5wtrE13rezZMo0m_42rT8tMbGX6zyWaV4IXyZMWzygOjqBpP2J0WuT0U7Ozr-AqI/s1600/Screen+Shot+2012-09-09+at+12.32.48+AM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="63" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgKvcQsLmpput9LCE1iXMdFcS-lrLitMjF5E6hT7OQS23__tjxynEGN1Dhoy1obcP-rc_3CNVoUFqcn5wtrE13rezZMo0m_42rT8tMbGX6zyWaV4IXyZMWzygOjqBpP2J0WuT0U7Ozr-AqI/s400/Screen+Shot+2012-09-09+at+12.32.48+AM.png" width="400" /></a></div>
恭喜大家表示成功了。<br />
再來就是 update。新增程式碼如下。<br />
<pre><code class="javascript">
app.get("/update",function (req, res) {
<b>db</b>.<b>users</b>.<b>update</b>(<b>{name: "Michael"}, {$set: {name: "James"}</b>}, function(err, updated) {
if( err || !updated ) console.log("User not updated");
else {
res.send("User updated");
res.end();
}
});
});</code></pre>
和 mongo 的 interactive shell 非常像用 db.users.update 然後第一,二個參數就是 <b>{name: "Michael"}, {$set: {name: "James"}</b>} 和 Terminal 寫法一樣。沒有錯誤的話,<br />
輸入 http://127.0.0.1:8800/update 就會在 Browser 看到如下<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiXAb0kGcChWoJj2wrpwwoq8wKiFqSlaWuJR5aZZc9xKyVYDVlsS6vQU9rk8sL1794WwLZHQmqbPswdrNRHttpw932gpGE7fb9HNuYJdhulO9Aq-L9WZI4r9ifvANgtky0mEhml5Q54iQgr/s1600/Screen+Shot+2012-09-09+at+12.36.40+AM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiXAb0kGcChWoJj2wrpwwoq8wKiFqSlaWuJR5aZZc9xKyVYDVlsS6vQU9rk8sL1794WwLZHQmqbPswdrNRHttpw932gpGE7fb9HNuYJdhulO9Aq-L9WZI4r9ifvANgtky0mEhml5Q54iQgr/s1600/Screen+Shot+2012-09-09+at+12.36.40+AM.png" /></a></div>
表示 update 成功,然後再輸入 http://127.0.0.1:8800/read<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj7zimFXb8bnUy_LdBP3pDdGupIxW943mnqzDsaGx594v0dUC77YXGt76Q_L2Ymjlc1OLiYBF5NQUPngy7W4QvkovWYDuM-czmfwrWn3fHTMUq190XzWVehCQi8c0n6qE4-m0ZWbFsU_sLk/s1600/Screen+Shot+2012-09-09+at+12.37.29+AM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="68" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj7zimFXb8bnUy_LdBP3pDdGupIxW943mnqzDsaGx594v0dUC77YXGt76Q_L2Ymjlc1OLiYBF5NQUPngy7W4QvkovWYDuM-czmfwrWn3fHTMUq190XzWVehCQi8c0n6qE4-m0ZWbFsU_sLk/s400/Screen+Shot+2012-09-09+at+12.37.29+AM.png" width="400" /></a></div>
同一個 id 名字改成了 James。<br />
最後我們要來測試一下 Delete ,新增如下程式碼<br />
<pre><code class="javascript">
app.get("/remove",function (req, res) {<br />
db.users.<b>remove</b>({gender: "Male"});<br />
res.send("removed ");<br />
res.end();<br />
});</code></pre>
remove 這個 function 就是把尋找的條件丟入,就可以了,如果用 {} 表示要刪除全部的 documents。在Browser 輸入 http://127.0.0.1:8800/remove<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh8ogq6gnAXnxA6tv2GG38WwKLk_lQHAW6LJ3PAZFFQJfPU-ft01oJUp4ggBEkbUznyxRGKWipQBBIHCGBemMnR5rnANNBBEBjUUpii1K0X8vOpETUUvxRBz_twNbghzWM0WnBdM3i8rp4n/s1600/Screen+Shot+2012-09-09+at+12.40.11+AM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh8ogq6gnAXnxA6tv2GG38WwKLk_lQHAW6LJ3PAZFFQJfPU-ft01oJUp4ggBEkbUznyxRGKWipQBBIHCGBemMnR5rnANNBBEBjUUpii1K0X8vOpETUUvxRBz_twNbghzWM0WnBdM3i8rp4n/s1600/Screen+Shot+2012-09-09+at+12.40.11+AM.png" /></a></div>
然後用 read 確定,輸入<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEis_WyGRnk1KYqIj_UPxx4YKUTg6Q49sLmEUQP8g-UTZoYDEwXNanBKhqtwGvdWd6ZfJaNWlDECggHCjiWiF1kqMK9W98RN5Cn7xODhn1oazpgrNA_dq4AFkuIK6CsIi1InJGvDw6-bsJsY/s1600/Screen+Shot+2012-09-09+at+12.40.57+AM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEis_WyGRnk1KYqIj_UPxx4YKUTg6Q49sLmEUQP8g-UTZoYDEwXNanBKhqtwGvdWd6ZfJaNWlDECggHCjiWiF1kqMK9W98RN5Cn7xODhn1oazpgrNA_dq4AFkuIK6CsIi1InJGvDw6-bsJsY/s1600/Screen+Shot+2012-09-09+at+12.40.57+AM.png" /></a></div>
空空如也,就是我們要的了。好吧,今天就到此了,咱們下次見。例子一樣放在 <a href="https://github.com/Scentsome/Developer-s-Note/tree/master/mongoDemo">GitHub</a>。謝謝各位看倌們。麥克http://www.blogger.com/profile/01716179223350987115noreply@blogger.com0tag:blogger.com,1999:blog-3469754461280175671.post-68181419668420536432012-09-07T07:30:00.000-07:002012-09-10T04:34:14.886-07:00解析 XML - Node.js這篇文章我們會來解析一份 HTML 文件,因為一份寫得完整的 HTML 就是一份 XML 文件,而 HTML 文件比 XML 文件取得容易,因為網頁的原始碼就是 HTML 文件。不過話說在前頭,有些寫的很差的 HTML 是沒辦法當成 XML 來解析的,雖然 Browser 看得懂,至少要是 一個 <a href="http://en.wikipedia.org/wiki/Well-formed_element"> Well Formed</a> 的 HTML。通常來說如果不是手寫的,用程式產生出來的 HTML 符合 XML Well Fromed 規則的機會是很高的。那我們就先來看一下,今天要被解析的主角<br />
<pre> <code class="XML">
<html>
<body>
<table>
<tr>
<td>Apples</td>
<td>44%</td>
</tr>
<tr>
<td>Bananas</td>
<td>23%</td>
</tr>
<tr>
<td>Oranges</td>
<td>13%</td>
</tr>
<tr>
<td>Other</td>
<td>10%</td>
</tr>
</body>
</html>
</code> </pre>
把這個檔案命名為 hello.html 存在資料夾中,這份 HTML 執行出來是這樣的結果<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEje4QgwT3XItktKAY44s_rAa3rATglqbYF8HLhLqKUCCuruf803gYK6bMorhu8twZ1HI7kndNx0c2MgrH-XCgyYPH1i9yX8s-x2X6y5_KcDukV-vW6eed_7F16AwmkgWnHWi6IK9rsY1dyV/s1600/Screen+Shot+2012-09-07+at+9.24.16+PM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="287" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEje4QgwT3XItktKAY44s_rAa3rATglqbYF8HLhLqKUCCuruf803gYK6bMorhu8twZ1HI7kndNx0c2MgrH-XCgyYPH1i9yX8s-x2X6y5_KcDukV-vW6eed_7F16AwmkgWnHWi6IK9rsY1dyV/s320/Screen+Shot+2012-09-07+at+9.24.16+PM.png" width="320" /></a></div>
不過結果不重要,我們要用 Node.js 來解析它,Node.js 需要一個 module 名為 libxmljs,用 npm install 它,如下<br />
<blockquote class="tr_bq">
npm install -g libxmljs </blockquote>
然後產生一個新的 js 檔名為 parseXML.js,內容如下
<pre> <code class="javascript">
var libxmljs = require("<b>libxmljs</b>");
var fs = require('<b>fs</b>');
fs.<b>readFile</b>("hello.html", 'utf8', function(err, <b>data</b>) {
if (err) throw err;
var xmlDoc = libxmljs.<b>parseXmlString</b>(data);
// xpath queries
var gchild = xmlDoc.find('//td');
for (var i = 0; i < gchild.length; i++){
console.log(gchild[i].text());
};
}); </code> </pre>
一開始需要 libxmljs 這個 module ,接下來還需要 fs 這個 module,然後就用 fs 的 readFile 指定第一個參數就是我們要解析的 hello.html,第二個參數是這個檔案的文字編碼為 utf8 ,第三個參數就是處理從 disk 讀到的 hello.html,放在 data 這個變數裡。<br />
data 被 parseXMLString 這個 function 解析完成之後會把整個 XML 文件架構放在 xmlDoc裡,需要去尋找某一個 tag 的時候,利用 find() 這個 function,裡面的參數是用 XPath 來描述的,在這個例子是 <b>//td</b>,代表的意思就是整份文件裡的 td tag,然後把結果放到 gchild 這個變數。 find() 這個 function 會回傳一個 array 筆者就用 for-loop 把它的結果用 console.log() show<br />
在 Terminal 上面,會看到<br />
<blockquote class="tr_bq">
Apples<br />
44%<br />
Bananas<br />
23%<br />
Oranges<br />
13%<br />
Other<br />
10%</blockquote>
的結果,是這樣沒錯,寫到這讀者應該會查覺,要找到任一個 tag 就要用 XPath 去描述,有關 XPath 的說明可以參考<a href="http://zh.wikipedia.org/zh-tw/XPath">這邊</a>。接下來我們再試試一個例子。<br />
需求是這樣的。想要找到包含 Apple 這個 tag 之後的第一個 tag 的值。在我們的例子就是想要找到下圖框起來的部分。<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiQYWZHY3OuTQNNRq0pNn4GrzoWNVHkkIu8MjqCEWIh5KLQX1Ub87p6IWvE7zC4rA2mfXwYhZ-M0Frrn0SvSmULb-a9qrfyqAAD8Mqzmb7wLuPcVgN0I2ny_M4Zx5CgU25kktIA8aZ3vqe1/s1600/Screen+Shot+2012-09-07+at+9.24.16+PM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="287" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiQYWZHY3OuTQNNRq0pNn4GrzoWNVHkkIu8MjqCEWIh5KLQX1Ub87p6IWvE7zC4rA2mfXwYhZ-M0Frrn0SvSmULb-a9qrfyqAAD8Mqzmb7wLuPcVgN0I2ny_M4Zx5CgU25kktIA8aZ3vqe1/s320/Screen+Shot+2012-09-07+at+9.24.16+PM.png" width="320" /></a></div>
我們要這樣想,先找到一個 tag 的值是 Apple ,然後再去找這個 tag 的下一個 tag 的值。這樣的 XPath 寫法會是。<br />
<blockquote class="tr_bq">
var xpath = '<b>//tr[td = "Apples"]</b>';</blockquote>
這個寫法的意思是先找到 <td> 的 text 中為 "Apples" 的 <tr>,然後再用 XML Parser 執行,<br />
<blockquote class="tr_bq">
var tr = xmlDoc.get(xpath);</blockquote>
然後我們用 console.log() 印出來。<br />
<br />
<blockquote class="tr_bq">
console.log("Up node is : " + tr.text());<br />
<br />
console.log("Child nodes : "+tr.childNodes()); </blockquote>
<br />
這樣會在 Terminal 看到<br />
<br />
<blockquote class="tr_bq">
Up node is : Apples44%<br />
<br />
Child nodes : <td>Apples</td>,<td>44%</td>,</blockquote>
第一個是用 <b>text()</b>,直接印出,第二個是用 childNodes 其輸出是個 array ,然後被 Node.js 當成 string 就會看到<br />
<blockquote class="tr_bq">
<td>Apples</td>,<td>44%</td>,</blockquote>
的結果,接著我們可再從 tr.childNodes() 找出我們要的 tag,Apples 是第一個,那下一個就是第二個,就是我們要的,就用<br />
<blockquote class="tr_bq">
console.log("Next nodes : "+tr.childNodes()[1].text());</blockquote>
就會看到<br />
<blockquote class="tr_bq">
Next nodes : 44%</blockquote>
不過這個前提是 XML 檔或是 HTML 檔中 tag 和 tag 中沒有其他的文字,比如上方一開始的 HTML 要改成如下,才會解析成功。<br />
<blockquote class="tr_bq">
<html><br />
<body><br />
<table><br />
<tr><td>Apples</td><td>44%</td><br />
</tr><br />
<tr><br />
<td>Bananas</td><br />
<td>23%</td><br />
</tr><br />
<tr><br />
<td>Oranges</td><br />
<td>13%</td><br />
</tr><br />
<tr><br />
<td>Other</td><br />
<td>10%</td><br />
</tr><br />
</table><br />
</body><br />
</html> </blockquote>
筆者刻意把看到第一個 <tr> 和 <td> 中間沒有任何的空間,也許這樣對人眼是不好看的,但是它還是一個合法的 XML 而且更適合解析。<br />
今天解析 XML 就到這,咱們下次見,檔案一樣放在 <a href="https://github.com/Scentsome/Developer-s-Note/tree/master/xmlParser">GitHub</a>。 麥克http://www.blogger.com/profile/01716179223350987115noreply@blogger.com0tag:blogger.com,1999:blog-3469754461280175671.post-1322741372610292182012-09-02T09:13:00.000-07:002012-09-02T09:29:53.921-07:00Hosting Service - Node.js寫 Web 的服務,筆者是覺得總會經過利用別人的 Hosting Service 這個過程。Web Service 最麻煩的就是管理和擴增服務,有許多流量和後台的程式要處理,對Node.js 來說其標榜就是可以很簡單地分散式處理,而管理的部分交由第三方的平台來管理對中小型服務來說是一個非常好的起點。 <br />
今天要介紹一個不錯的,Node.js Hosting 的服務,名為 <a href="http://nodejitsu.com/">Nodejitsu</a>。這個 Repository 好用的地方是設定簡單,支援的 Module 也不少,對於 iOS Developer 來說,最重要的 Server 服務之一就是 <a href="http://developer.apple.com/library/mac/#documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/ApplePushService/ApplePushService.html">APNS</a> 了,筆者測試過,是可以成功執行沒有擋掉 APNS 需要的 Port。<br />
好那我們就登入 Nodejitsu 的首面看到下面的頁面,Try Nodejitsu for free.<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiLUMJ4_qze7-8Z-8H-D-jTIx51htNT4V3GtqGbRfBy7ZMbAeZKczaWRZ4NEzQ9saqiePOY0_pLMj76bp5S49bTR-ZYsJeQree8yfVvGjf3z5eiAjsKR2H9zbuRJ6Yvv-H5-aC8x5JyrYif/s1600/Screen+Shot+2012-09-02+at+4.50.26+PM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="262" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiLUMJ4_qze7-8Z-8H-D-jTIx51htNT4V3GtqGbRfBy7ZMbAeZKczaWRZ4NEzQ9saqiePOY0_pLMj76bp5S49bTR-ZYsJeQree8yfVvGjf3z5eiAjsKR2H9zbuRJ6Yvv-H5-aC8x5JyrYif/s400/Screen+Shot+2012-09-02+at+4.50.26+PM.png" width="400" /></a></div>
接下來會有幾個步驟需要填寫,如下,填寫 useranme 只允許字母數字和- <br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhvp9h6IXi_zDMW5PUs1sqTy_93GXSr3Ep2NSZ1XMCWHxDDqlJ0CdTozSGrjZPq2rThnWEdbv0QV7JBpC0Be5RL66tuLSssP2z3AKkKoq4POc2Sv4kftQUNvQzs7Hv_LTvdLFX2ILfpENRT/s1600/Screen+Shot+2012-09-02+at+4.56.56+PM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="191" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhvp9h6IXi_zDMW5PUs1sqTy_93GXSr3Ep2NSZ1XMCWHxDDqlJ0CdTozSGrjZPq2rThnWEdbv0QV7JBpC0Be5RL66tuLSssP2z3AKkKoq4POc2Sv4kftQUNvQzs7Hv_LTvdLFX2ILfpENRT/s400/Screen+Shot+2012-09-02+at+4.56.56+PM.png" width="400" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
寫 e-mail address<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjfcohcSygXfwMbdF53FyEP7iOobhsuD5GtXYwjbRh18qFU3aJjwf_L5MkEHtTutmQLGIGTL9YKTlsX-Bsk0rFkFhg95NQyTYMFXiemsUxmV823z1AIz-gjma3G7lI0FUWH4Zbq5klbOFc8/s1600/Screen+Shot+2012-09-02+at+4.53.05+PM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="191" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjfcohcSygXfwMbdF53FyEP7iOobhsuD5GtXYwjbRh18qFU3aJjwf_L5MkEHtTutmQLGIGTL9YKTlsX-Bsk0rFkFhg95NQyTYMFXiemsUxmV823z1AIz-gjma3G7lI0FUWH4Zbq5klbOFc8/s400/Screen+Shot+2012-09-02+at+4.53.05+PM.png" width="400" /></a></div>
填寫你想要建立的 Node.j 服務的類型和任何其他在使用的技術,也可以不寫。<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhP6985blxwW3q_bxZfoI7xN2_eEFSRkNvmtoLJsDWrzwxIsIiCo7hbGTNRFxcC1pZZqk6G-xKm8hyphenhyphendGrRmdk7dD6oEuJmsBQcm4MrUcXv-jdVWIKtiX_RPTEDQlWvyoPcnhxTvyIFZ_v5z/s1600/Screen+Shot+2012-09-02+at+4.54.52+PM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="355" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhP6985blxwW3q_bxZfoI7xN2_eEFSRkNvmtoLJsDWrzwxIsIiCo7hbGTNRFxcC1pZZqk6G-xKm8hyphenhyphendGrRmdk7dD6oEuJmsBQcm4MrUcXv-jdVWIKtiX_RPTEDQlWvyoPcnhxTvyIFZ_v5z/s400/Screen+Shot+2012-09-02+at+4.54.52+PM.png" width="400" /></a></div>
好了之後應該會在 mail 收到 Nodejitsu 寄來的信,類似下方。<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgC6Foov7maGN3U3IbjqxKddB6kDDyWtoXNJzNBxFdELCqRRNzr86E-N3kGHTGsS4zZbcosEpt7vaSpoJA0wVt-V8n-Ij-8WKsKoEn4A_lpMd1xnO2LNsbUilQRylJxvOc4_-Gd8OAX5Kz7/s1600/Screen+Shot+2012-09-02+at+5.00.08+PM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="147" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgC6Foov7maGN3U3IbjqxKddB6kDDyWtoXNJzNBxFdELCqRRNzr86E-N3kGHTGsS4zZbcosEpt7vaSpoJA0wVt-V8n-Ij-8WKsKoEn4A_lpMd1xnO2LNsbUilQRylJxvOc4_-Gd8OAX5Kz7/s400/Screen+Shot+2012-09-02+at+5.00.08+PM.png" width="400" /></a></div>
信用還提到第一次啟用帳號要做的事情如下<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiOT9T3tE2MKxLAG2cQ7b9wGw2kdc8dcppez_bv5g6smCBG2dKjY3o0Te5E7u3J8t7Go58Qujz1feNYP5zLafhorpoQQfesbDcraR6l7e9A0Z_dZb6FU2-UEXoElRoiRdWM__GPgjfkruEA/s1600/Screen+Shot+2012-09-02+at+5.01.37+PM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="175" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiOT9T3tE2MKxLAG2cQ7b9wGw2kdc8dcppez_bv5g6smCBG2dKjY3o0Te5E7u3J8t7Go58Qujz1feNYP5zLafhorpoQQfesbDcraR6l7e9A0Z_dZb6FU2-UEXoElRoiRdWM__GPgjfkruEA/s400/Screen+Shot+2012-09-02+at+5.01.37+PM.png" width="400" /></a></div>
<br />
在終端機執行下列指令,<br />
<ol>
<li>sudo npm install jitsu -g </li>
<li>jitsu users confirm <帳號> <認證碼></li>
</ol>
這樣就可以了,實際情況如下,步驟 2,執行後會問一些問題,第一個是設定密碼<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjBmndD59Gj6LXK7JWaHFDaxdeWe5RzGd38Wjg9qbUpBvMHeBlnlcMURu2qxpRySjxunaJwl61JLuftGaWpWkbQAdB9XR637BATlLV8LEdOPAt1dE-2goAiNhxVBuo36eSGRbLdNwcKkroP/s1600/Screen+Shot+2012-09-02+at+5.12.12+PM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="91" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjBmndD59Gj6LXK7JWaHFDaxdeWe5RzGd38Wjg9qbUpBvMHeBlnlcMURu2qxpRySjxunaJwl61JLuftGaWpWkbQAdB9XR637BATlLV8LEdOPAt1dE-2goAiNhxVBuo36eSGRbLdNwcKkroP/s400/Screen+Shot+2012-09-02+at+5.12.12+PM.png" width="400" /></a></div>
設定完之後,再輸入一次,就啟用完成,如下<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgF8SQiqJe3zosnUdEV1oXAwmo4Te1KU_6OKccUzRXfhMEG3-hGDxsJAO_rAi0s3H45LDNpoRZra8TET4gBxQMl_ywdOmjjUOwCievGCWUF7LAroZAFzyf3ACZvQStEC0gQ6-gFfVs3djWh/s1600/Screen+Shot+2012-09-02+at+5.14.28+PM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="83" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgF8SQiqJe3zosnUdEV1oXAwmo4Te1KU_6OKccUzRXfhMEG3-hGDxsJAO_rAi0s3H45LDNpoRZra8TET4gBxQMl_ywdOmjjUOwCievGCWUF7LAroZAFzyf3ACZvQStEC0gQ6-gFfVs3djWh/s400/Screen+Shot+2012-09-02+at+5.14.28+PM.png" width="400" /></a></div>
接下我們就要放一個簡單的 web server 到 Nodejitsu 上面去。<br />
先用 mkdir ( make directory ) 在任一目錄下建立一個新的目錄如下<br />
<blockquote class="tr_bq">
mkdir myJitsu</blockquote>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgJm6I3zPIwP3lRuywCDPu0kmQR2KusKN2zCgXVPNkhkvA8lCw3sIiPnvw4nhf4QC6I6JuxxYN_Q_YvIgcMO-CZThLmbep_keN_MUl0odsCPO4E7zWHl2hPb-qGI8nqXBWzA5FAK5KdPTHc/s1600/Screen+Shot+2012-09-02+at+5.07.43+PM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="37" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgJm6I3zPIwP3lRuywCDPu0kmQR2KusKN2zCgXVPNkhkvA8lCw3sIiPnvw4nhf4QC6I6JuxxYN_Q_YvIgcMO-CZThLmbep_keN_MUl0odsCPO4E7zWHl2hPb-qGI8nqXBWzA5FAK5KdPTHc/s400/Screen+Shot+2012-09-02+at+5.07.43+PM.png" width="400" /></a></div>
然後進入用 cd myJitsu 如下<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjSmNRZ4rbyafT6kUlgHTm-xmDHXVl5FvqLsuBXvS5iNWyU-n9Z4ubfZbyk2oZu8XtQtR_V8o5KHCFuw1qLRbusCPRHGSoxi9_JP5RZYfA-we-lyWdOaN3zGCsJnRe-2Tgu2rh1ricKVMy1/s1600/Screen+Shot+2012-09-02+at+5.16.28+PM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="47" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjSmNRZ4rbyafT6kUlgHTm-xmDHXVl5FvqLsuBXvS5iNWyU-n9Z4ubfZbyk2oZu8XtQtR_V8o5KHCFuw1qLRbusCPRHGSoxi9_JP5RZYfA-we-lyWdOaN3zGCsJnRe-2Tgu2rh1ricKVMy1/s400/Screen+Shot+2012-09-02+at+5.16.28+PM.png" width="400" /></a></div>
我們新增 <a href="http://iosdevelopersnote.blogspot.tw/2012/08/nodejs.html">Node.js 學習資源</a>這篇提到的 hello.js ,到這個資料夾下,也取名為 hello.js<br />
<blockquote class="tr_bq">
<pre class="de1"><span class="kw2">var</span> http <span class="sy0">=</span> require<span class="br0">(</span><span class="st0">'http'</span><span class="br0">)</span><span class="sy0">;</span>
http.<span class="me1">createServer</span><span class="br0">(</span><span class="kw2">function</span> <span class="br0">(</span>request<span class="sy0">,</span> response<span class="br0">)</span> <span class="br0">{</span>
response.<span class="me1">writeHead</span><span class="br0">(</span><span class="nu0">200</span><span class="sy0">,</span> <span class="br0">{</span><span class="st0">'Content-Type'</span><span class="sy0">:</span> <span class="st0">'text/plain'</span><span class="br0">}</span><span class="br0">)</span><span class="sy0">;</span>
response.<span class="me1">end</span><span class="br0">(</span><span class="st0">'<b>Hello World</b><span class="es0">\n</span>'</span><span class="br0">)</span><span class="sy0">;</span>
<span class="br0">}</span><span class="br0">)</span>.<span class="me1">listen</span><span class="br0">(</span><span class="nu0">8000</span><span class="br0">)</span><span class="sy0">;</span></pre>
</blockquote>
然後執行佈署的指令,如下<br />
<blockquote class="tr_bq">
jitsu deploy</blockquote>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjbxH950IWVU8YagQeXZyEQw3knHKqMi3qwrv_Dl4LkZnhtHqQ6uhdcGBUfoyMTIEFHW5A7mgB2NxrRe-tkg1oTDe8_hqqXtBijqfYgJMLnY55-sMsMMUdxI7CT0uOXi9gzbxfl1AGSNIox/s1600/Screen+Shot+2012-09-02+at+5.24.02+PM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="53" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjbxH950IWVU8YagQeXZyEQw3knHKqMi3qwrv_Dl4LkZnhtHqQ6uhdcGBUfoyMTIEFHW5A7mgB2NxrRe-tkg1oTDe8_hqqXtBijqfYgJMLnY55-sMsMMUdxI7CT0uOXi9gzbxfl1AGSNIox/s400/Screen+Shot+2012-09-02+at+5.24.02+PM.png" width="400" /></a></div>
接著會看到 jitsu 和 Server 溝通,會再問一些問題,第一個是 App Name: 在其後有一個 (myJitsu) 的意思是,如果不寫,就用這個名字,我們來改一下,用 MyJitsu<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgaGercVjpaMSPPo-urIXq_o7QREjmAmNXrDlOOTfSpzwheGkcAKuY6aCamURHftefYqqeEcD2u9tMy57YGQWXQMs0DlV6W3wFRpHSmTZ7b5-EOyrBIIWgW2DfUE-s2GZsmp0rkl6nedlVe/s1600/Screen+Shot+2012-09-02+at+5.26.43+PM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="125" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgaGercVjpaMSPPo-urIXq_o7QREjmAmNXrDlOOTfSpzwheGkcAKuY6aCamURHftefYqqeEcD2u9tMy57YGQWXQMs0DlV6W3wFRpHSmTZ7b5-EOyrBIIWgW2DfUE-s2GZsmp0rkl6nedlVe/s400/Screen+Shot+2012-09-02+at+5.26.43+PM.png" width="400" /></a></div>
再來是 subdomain 的名稱,這個不改也 ok<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiTUUf7pM9PKAzYF2e7mEFWjEPebesDhlLAviLxM08FxiutnPghxdk6uJOzTdzzXPd6km5-pSk9nR8for5lrV_x8qqbygk4_PTThD4SSBJf3t_0ZhkUv35cwms5vIJxEOSIwYGfU1pXAulJ/s1600/Screen+Shot+2012-09-02+at+5.27.26+PM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="46" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiTUUf7pM9PKAzYF2e7mEFWjEPebesDhlLAviLxM08FxiutnPghxdk6uJOzTdzzXPd6km5-pSk9nR8for5lrV_x8qqbygk4_PTThD4SSBJf3t_0ZhkUv35cwms5vIJxEOSIwYGfU1pXAulJ/s400/Screen+Shot+2012-09-02+at+5.27.26+PM.png" width="400" /></a></div>
接下來比較重要,是一開始要執行的 script,也就是 hello.js,如下設定<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgU5SnisDfsZThvLVvixqf8FNpVhHvXHg18Z-9tswPUJEgIQBBC1LEJF95etqkDdrs3QWQIBmdtDfHFJokMD4E7oEhqaDbB2sLoiAen0La9xZ8I70dm3qA8-70vIgg5Jl5OE11z0lVlCMFU/s1600/Screen+Shot+2012-09-02+at+5.29.12+PM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="76" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgU5SnisDfsZThvLVvixqf8FNpVhHvXHg18Z-9tswPUJEgIQBBC1LEJF95etqkDdrs3QWQIBmdtDfHFJokMD4E7oEhqaDbB2sLoiAen0La9xZ8I70dm3qA8-70vIgg5Jl5OE11z0lVlCMFU/s400/Screen+Shot+2012-09-02+at+5.29.12+PM.png" width="400" /></a></div>
<br />
再來設定版本,我們設定為 0.0.1,如下<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjbruSwKB1bjXiH8q91aGFNh2EZSxueSTXWeuhrYoBYJmSrOvXmByrrO5wJQAOSFO3p9c_dOA9q-fCwJTR90dYRiMZoSo469Ol_oCRJcggN196olagHCr7vwq-AW7nAUo-_hFlb0-CPNYfj/s1600/Screen+Shot+2012-09-02+at+5.30.55+PM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="54" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjbruSwKB1bjXiH8q91aGFNh2EZSxueSTXWeuhrYoBYJmSrOvXmByrrO5wJQAOSFO3p9c_dOA9q-fCwJTR90dYRiMZoSo469Ol_oCRJcggN196olagHCr7vwq-AW7nAUo-_hFlb0-CPNYfj/s400/Screen+Shot+2012-09-02+at+5.30.55+PM.png" width="400" /></a></div>
最後是需要的 Node.js 版本,這個例子為 0.8.x<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhi0CFR_lgJDbPCXAngV0SRe0cmxnteZTwJpaVEntfCBZ1oFp71Tdv4J1JyO-eAuKAoU-omROsnU3kcDrG9Qe3wgoeoKyCw18N0XjlJiwUgyeU1HYu6fExoWKGX0WdVoYHMHmKspnXVZqmY/s1600/Screen+Shot+2012-09-02+at+5.31.32+PM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="55" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhi0CFR_lgJDbPCXAngV0SRe0cmxnteZTwJpaVEntfCBZ1oFp71Tdv4J1JyO-eAuKAoU-omROsnU3kcDrG9Qe3wgoeoKyCw18N0XjlJiwUgyeU1HYu6fExoWKGX0WdVoYHMHmKspnXVZqmY/s400/Screen+Shot+2012-09-02+at+5.31.32+PM.png" width="400" /></a></div>
完成後,會提醒我們做確認的動作,並把剛剛的設定都寫入一個 package.json 的檔案中,<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjiyrZs03uUB-JnvLAKqVdOGLWXDh5Gghmrta_nu0WpEtwEGeUgQ_SeZcg_Y_OFDFai4SKBXM7QZjZhVz2FNrh9_TMMTunMMbQYp7BTwgr3gVWXtv4Xbz7dMuDpyhIpN8XG_ARDHMSpBSTj/s1600/Screen+Shot+2012-09-02+at+5.32.29+PM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="110" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjiyrZs03uUB-JnvLAKqVdOGLWXDh5Gghmrta_nu0WpEtwEGeUgQ_SeZcg_Y_OFDFai4SKBXM7QZjZhVz2FNrh9_TMMTunMMbQYp7BTwgr3gVWXtv4Xbz7dMuDpyhIpN8XG_ARDHMSpBSTj/s400/Screen+Shot+2012-09-02+at+5.32.29+PM.png" width="400" /></a></div>
寫 yes 之後,就會看到這樣的裝態,就是完成了<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg-JNNgJwF3h1G8mFqxUH45nWjzVCyswooxynOvsP0UCfkh17ynZVwpGdiVT_XSvXD0Z8AD40KYH4uF6N1bl3V3WGIxzB7-9M4v70OefDcNprUFM8gmjTxuhB2B2PoW16f2xMQ-B5lY3QRW/s1600/Screen+Shot+2012-09-02+at+5.34.22+PM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="151" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg-JNNgJwF3h1G8mFqxUH45nWjzVCyswooxynOvsP0UCfkh17ynZVwpGdiVT_XSvXD0Z8AD40KYH4uF6N1bl3V3WGIxzB7-9M4v70OefDcNprUFM8gmjTxuhB2B2PoW16f2xMQ-B5lY3QRW/s400/Screen+Shot+2012-09-02+at+5.34.22+PM.png" width="400" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
上圖用白色框起來的部分,就是這個服務的網址,試著在Browser 的網址中輸入<br />
<blockquote class="tr_bq">
http://incensome.myjitsu.jit.su/</blockquote>
就會看到如下<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi-_FEFnyvfvWBVnx5ENGFL3WtmKu0ZB0YiQI5h1BW3XAxr7uUocR1skIrmo5wGsItsRmcIBq8Rk-1zHW6IhsvLOycSmE06ArL4UgMgqP-Y2E2NsaWpS8pQIxWJciMdJ9aLFtWqpEVv5PX9/s1600/Screen+Shot+2012-09-02+at+11.53.44+PM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="130" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi-_FEFnyvfvWBVnx5ENGFL3WtmKu0ZB0YiQI5h1BW3XAxr7uUocR1skIrmo5wGsItsRmcIBq8Rk-1zHW6IhsvLOycSmE06ArL4UgMgqP-Y2E2NsaWpS8pQIxWJciMdJ9aLFtWqpEVv5PX9/s400/Screen+Shot+2012-09-02+at+11.53.44+PM.png" width="400" /></a></div>
然後我們回到 http://nodejitsu.com/ 來看一下剛剛佈署到 Nodejitsu 的服務,如下,按右上的 Login。<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjesnaDWdLTlMH2PJlPuijqBFiNrllUBJX4QSnOv1rEd29gggLZSVvxSYUYt8JlglzSG8D5734C6ocs_lEU3yCuZJmA8n-jGopeaOsCNbT9wRCq-MEijTgu6NWsQpjFIovjQux3Z_92-A3-/s1600/Screen+Shot+2012-09-02+at+11.55.39+PM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="247" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjesnaDWdLTlMH2PJlPuijqBFiNrllUBJX4QSnOv1rEd29gggLZSVvxSYUYt8JlglzSG8D5734C6ocs_lEU3yCuZJmA8n-jGopeaOsCNbT9wRCq-MEijTgu6NWsQpjFIovjQux3Z_92-A3-/s400/Screen+Shot+2012-09-02+at+11.55.39+PM.png" width="400" /></a></div>
輸入帳號密碼之後,就會看到如下<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhgQMaAvqw9fTAQIrfs7m7eHULJp-99iZYTeRvSmV8HP6MIA8UqPi14V8tBWdN32mGP-zJOkMVG9WQXzMNIQE9fN3dSbLETgrxbTgeySzrqK3dL721pQV6WLdR5rTjz1U9CdvshC3HH59b-/s1600/Screen+Shot+2012-09-02+at+11.56.34+PM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="132" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhgQMaAvqw9fTAQIrfs7m7eHULJp-99iZYTeRvSmV8HP6MIA8UqPi14V8tBWdN32mGP-zJOkMVG9WQXzMNIQE9fN3dSbLETgrxbTgeySzrqK3dL721pQV6WLdR5rTjz1U9CdvshC3HH59b-/s400/Screen+Shot+2012-09-02+at+11.56.34+PM.png" width="400" /></a></div>
一個上線的服務完成了,重點是右邊視窗有一個 Log 的 Tab,點選後如下<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgaSp4Rg-k9BuZ5LCkW8PiaBRz5RbXFwXaSpRbbprQw3-qhCsW00lf1VHGicqmZha_vOn0qn4m9VGcfILqL2gi9jUXUuA2whkTKOHX96WUF2OA3-LtgrtE0JtAAl1RXwbsEWWSKKvV7WWvD/s1600/Screen+Shot+2012-09-02+at+11.58.08+PM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="131" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgaSp4Rg-k9BuZ5LCkW8PiaBRz5RbXFwXaSpRbbprQw3-qhCsW00lf1VHGicqmZha_vOn0qn4m9VGcfILqL2gi9jUXUuA2whkTKOHX96WUF2OA3-LtgrtE0JtAAl1RXwbsEWWSKKvV7WWvD/s400/Screen+Shot+2012-09-02+at+11.58.08+PM.png" width="400" /></a></div>
這裡可以看到在程式中 console.log 的結果,雖然目前我們並沒有用到 console.log。<br />
接下來我們要來看一下這個檔案 package.json<br />
<blockquote class="tr_bq">
{<br />
"name": "MyJitsu",<br />
"subdomain": "incensome.myJitsu",<br />
"scripts": {<br />
"start": "hello.js"<br />
},<br />
"version": "0.0.1",<br />
"engines": {<br />
"node": "0.8.x"<br />
}<br />
}</blockquote>
這個檔案描述這個 Node.js 服務的設定,這個例子只用到 http 的 module,如果用到其他的,不是包在 Node.js 核心的東西,比如<a href="http://iosdevelopersnote.blogspot.tw/2012/08/nodejs-mvc-express.html">這篇</a>提到的 Express,就會看到如下的 package.json<br />
<blockquote class="tr_bq">
{<br />
"name": "TeachRepo",<br />
"subdomain": "incensome.teachrepo",<br />
"scripts": {<br />
"start": "web.js"<br />
},<br />
"version": "0.0.1-3",<br />
"engines": {<br />
"node": "0.8.x"<br />
},<br />
"<b>dependencies</b>": {<br />
"<b>express</b>": "<b>*</b>"<br />
}<br />
}</blockquote>
jitsu 會自行去看 *.js 檔然後把 dependencies 加到 package.json 上,在上面的例子是寫,<br />
<blockquote class="tr_bq">
"<b>dependencies</b>": {<br />
"<b>express</b>": "<b>*</b>"<br />
}</blockquote>
代表會用到 express 而不拘版本,(其實,3.0 和 2.0 有差別,有注意一下)。<br />
那如果 hello.js 有更新怎麼辦?在同一個目錄下,用 jitsu deploy 就可以了,jitsu 會自動把版號加上-1,比如原本是 0.0.1 會變成 0.0.1-1。<br />
<br />
最後我們來討論一下,Nodejitsu 這個服務,付費的時候怎麼計算?<br />
從這個<a href="http://nodejitsu.com/paas/pricing.html">網頁</a>我們可以看到,個人,或是小型公司來說 一個 drone 一個月只要 $3 美金,是相當便宜。如下是Individual Plan<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhloV54RlB41NcUO4A954WgC1mcQmo6rUdQgvbvhCIqHFFmSekyjKq6n_F01F6MEC6mWmmwIXFQwgZMzQ0Mt7BWSUyiXiCrUTjeS1JMOq5OgHvF8035ZPZTc4qV4EFoVMry8m7ZZtkOEt3q/s1600/Screen+Shot+2012-09-03+at+12.14.59+AM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="151" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhloV54RlB41NcUO4A954WgC1mcQmo6rUdQgvbvhCIqHFFmSekyjKq6n_F01F6MEC6mWmmwIXFQwgZMzQ0Mt7BWSUyiXiCrUTjeS1JMOq5OgHvF8035ZPZTc4qV4EFoVMry8m7ZZtkOEt3q/s400/Screen+Shot+2012-09-03+at+12.14.59+AM.png" width="400" /></a></div>
那什麼是 Drone 呢?在這 <a href="http://nodejitsu.com/paas/faq.html">FAQ</a> 有提到,就是指處理 App 的能力,當然它有提到通常一個 App 就只會需要一個 drone,同理可推是一個 App。同時平行處理的話也許會需要比較多 drone, 不過 Node.js 本身就是 None-blocking,比較不需要自行再產生 Thread 之類的,但真正計算的方法,也只能靠經驗法則了。<br />
那試用版的可以用多久呢? FAQ 有提到,現在是 3 個月,過期怎麼辦?再申請一個 e-mail 就好了 orz.. <br />
好,那咱們就下次見。 麥克http://www.blogger.com/profile/01716179223350987115noreply@blogger.com0tag:blogger.com,1999:blog-3469754461280175671.post-74389286150346352502012-08-29T02:05:00.002-07:002012-09-10T08:06:03.153-07:00iOS - 下載 Zip File 並解壓縮今天要來介紹一個方法,讓 iOS App 可以解開 Zip File,這樣一來 Server 端要給 iOS App 的文件就可以打包成比較小的 size ,而且可以減少 Server 傳資料到 iOS App 的時間。首先要準備好一個 Zip 檔和 Server 端的程式,可以參考<a href="http://iosdevelopersnote.blogspot.tw/2012/08/file-nodejs.html">這篇文章</a>,把 mime type 改成 application/zip 就可以了。如下的設定<br />
<pre><code class="javascript">
fs.stat(filename, function(error, stat) {<br /> if (error) { throw error; }<br /> res.writeHead(200, {<br /> 'Content-Type' : '<b>application/zip</b>',<br /> 'Content-Length' : stat.size<br /> });<br /> }); </code></pre>
Node.js 檔和 iOS 專案筆者放在 <a href="https://github.com/Scentsome/Developer-s-Note/tree/master/iOS_unzip">GitHub/iOS_unzip</a>。<br />
<br />
接下來就是 iOS 的部分了,筆者採用的 Zip/Unzip 套件為 <a href="http://code.google.com/p/ziparchive/">ZipArchive</a> 提供的第三方套件。下載後會看到如下的資料夾內容。<br />
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiqyjXBv2EQL1MHtsuQGHKATBHnacDknwGmaXe1NJ51kexCYK9VDKOcE-Qlcvl3J-wTXM7RoLSG_nB0DXxV0NKUlCWhSIxFGTH9PMhmVKdbArLnT7vOi4Wa3Eiw0HUDcgrbeSGGc95quw5c/s1600/Screen+Shot+2012-08-29+at+4.04.03+PM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="325" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiqyjXBv2EQL1MHtsuQGHKATBHnacDknwGmaXe1NJ51kexCYK9VDKOcE-Qlcvl3J-wTXM7RoLSG_nB0DXxV0NKUlCWhSIxFGTH9PMhmVKdbArLnT7vOi4Wa3Eiw0HUDcgrbeSGGc95quw5c/s400/Screen+Shot+2012-08-29+at+4.04.03+PM.png" width="400" /></a></div>
接下來開啟 Single View Application。如下,命名為 UnzipDemo<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjgd_uo4-LVvVAKYZdaBtLXmM9ez8YMHuLPOjRCvp7MJkNkADVb69Ltz3qLnkrhhtJxhFFlJzNzLbIUDRa5ApwUBV5pEfDijBJBkPc8s0RIpcdboXfNN12_6LJOQIIPk0rgvMR-ziviXNLg/s1600/Screen+Shot+2012-08-29+at+3.29.10+PM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="270" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjgd_uo4-LVvVAKYZdaBtLXmM9ez8YMHuLPOjRCvp7MJkNkADVb69Ltz3qLnkrhhtJxhFFlJzNzLbIUDRa5ApwUBV5pEfDijBJBkPc8s0RIpcdboXfNN12_6LJOQIIPk0rgvMR-ziviXNLg/s400/Screen+Shot+2012-08-29+at+3.29.10+PM.png" width="400" /></a></div>
把 ZipArchive 的檔案全加到 UnzipDemo 裡。如下<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjzVBNNVYp3NIPvb2vduQGsq0s44EPxiRpwc9rYve6nyDCXcC2V93vndRMi5DJ_RuL3GMl7UUAREXYFzG1VAhBGpCJdgbWoJsIcLoNe7h9xl_0Qa3zlxLBj4VENPStZpXQi0If6bfwQwwQS/s1600/Screen+Shot+2012-08-29+at+4.10.56+PM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="400" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjzVBNNVYp3NIPvb2vduQGsq0s44EPxiRpwc9rYve6nyDCXcC2V93vndRMi5DJ_RuL3GMl7UUAREXYFzG1VAhBGpCJdgbWoJsIcLoNe7h9xl_0Qa3zlxLBj4VENPStZpXQi0If6bfwQwwQS/s400/Screen+Shot+2012-08-29+at+4.10.56+PM.png" width="308" /></a></div>
在 ZipArchive 的<a href="http://code.google.com/p/ziparchive/wiki/PageName">文件</a>有提到,要在存案加入 libz.dylib。如下<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi4H4qmKB8ucQx1zJwzQYDq_1NsJILnhcyhM82EjsaOnJShUgalUY0xWmHkpwGEny3uy2IWNOV_8_sxRbXuFAeDx5WTAAv1EXLioUASSFCl3PiTmREI5JgrvhvW9CYCj7k5HihWwqg143hx/s1600/Screen+Shot+2012-08-29+at+4.14.58+PM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="242" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi4H4qmKB8ucQx1zJwzQYDq_1NsJILnhcyhM82EjsaOnJShUgalUY0xWmHkpwGEny3uy2IWNOV_8_sxRbXuFAeDx5WTAAv1EXLioUASSFCl3PiTmREI5JgrvhvW9CYCj7k5HihWwqg143hx/s400/Screen+Shot+2012-08-29+at+4.14.58+PM.png" width="400" /></a></div>
<br />
加入之後會看到如下圖,才是正確的<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgBNAH-lkJmwMaDjN83yrC9upq4lKnCtMAN0wPCrMXuOsTcmBOX4h3cdMxzhCHVoPul93b_kRA3fY71V7KODIXExzjDBeR61-LVGqMMOnposTMOjrg6G3uJBnVaneg8Qa7-trZBk1Y9rPmG/s1600/Screen+Shot+2012-08-29+at+4.16.11+PM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="132" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgBNAH-lkJmwMaDjN83yrC9upq4lKnCtMAN0wPCrMXuOsTcmBOX4h3cdMxzhCHVoPul93b_kRA3fY71V7KODIXExzjDBeR61-LVGqMMOnposTMOjrg6G3uJBnVaneg8Qa7-trZBk1Y9rPmG/s400/Screen+Shot+2012-08-29+at+4.16.11+PM.png" width="400" /></a></div>
加入之後,我們立馬 Compile 執行看看,卻看到如下的 Error<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEggHnHSBGAKuh6FiKIlHO3CSkYENOlUS-_ls6lMg_OtEihazkbw81dDM_K32Imj2-HRBOb-aObk9_SO2kEHi_-MM8n5pJu4eUVMG0egVNdOGZQOWV73zLf6SubCbpnnmONSRjGDnkjqKQ_p/s1600/Screen+Shot+2012-08-29+at+4.36.44+PM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="111" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEggHnHSBGAKuh6FiKIlHO3CSkYENOlUS-_ls6lMg_OtEihazkbw81dDM_K32Imj2-HRBOb-aObk9_SO2kEHi_-MM8n5pJu4eUVMG0egVNdOGZQOWV73zLf6SubCbpnnmONSRjGDnkjqKQ_p/s400/Screen+Shot+2012-08-29+at+4.36.44+PM.png" width="400" /> </a> </div>
<br />
原來是 ZipArchive 這個專案有用到 dealloc 等 ARC 開啟時不允許的關鍵字,因為引入的檔案不多,直接把某幾個 file 設定會不支援 ARC 就好了,如下設定。<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhYTuo3rJXoiQk9OcUNdP_pge5E6fPYERbRvRf55LJm6z1IZqbd-XzC7maJoeINtiCmhddPILYuX3qwH4W0qH1RV7hsGafjY4NvL8SSqvx4PJhXembuu1dSuVF8QeAqyK0ctahx9VUHsObN/s1600/Screen+Shot+2012-08-29+at+4.40.03+PM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="238" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhYTuo3rJXoiQk9OcUNdP_pge5E6fPYERbRvRf55LJm6z1IZqbd-XzC7maJoeINtiCmhddPILYuX3qwH4W0qH1RV7hsGafjY4NvL8SSqvx4PJhXembuu1dSuVF8QeAqyK0ctahx9VUHsObN/s400/Screen+Shot+2012-08-29+at+4.40.03+PM.png" width="400" /></a></div>
<br />
現在 Compile 就可以成功執行了。 <br />
接者來看到功能面,新增一個 UIButton 和連結一個 IBAction 如下圖<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiwOxmkcIRLR1WDtaORugTK5RN4fdETpz-s0Z_QzEsnNYr3z1-I07zowsSi1iEQ814uB4n_WOan_4zVeYJKwV8D83rhw0KiorxPpFPpSIlt8A87EfgjRs7GYxk1-QHUbKKhAntZ2UHPK3m5/s1600/Screen+Shot+2012-08-29+at+4.21.42+PM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="272" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiwOxmkcIRLR1WDtaORugTK5RN4fdETpz-s0Z_QzEsnNYr3z1-I07zowsSi1iEQ814uB4n_WOan_4zVeYJKwV8D83rhw0KiorxPpFPpSIlt8A87EfgjRs7GYxk1-QHUbKKhAntZ2UHPK3m5/s400/Screen+Shot+2012-08-29+at+4.21.42+PM.png" width="400" /></a></div>
在 ViewController.m 新增如下程式碼,先來把 Server 的 developer.zip 下載到 Mac 某個資料夾。在這邊的例子,是用 GET 連結 <b>http://127.0.0.1:8800/data?fileName=developer.zip</b> 然後存到 <b>/Users/chronoer/developer/developer.zip</b> ,先做這樣的測試。
<pre><code class="objectivec">
<b>#define SERVER @"http://127.0.0.1:8800/data"</b><br />@interface ViewController (){<br /> <b>NSString * fileName;</b><br /> <br />}<br /><b>@property (strong) NSMutableData * tmpData;</b><br />@end<br /><br />@implementation ViewController<br />@synthesize tmpData;<br />- (IBAction)<b>downZip</b>:(id)sender {<br />
fileName = @"developer.zip";
NSURL * fileURL = [NSURL URLWithString:[SERVER stringByAppendingFormat:@"?fileName=%@", fileName]];
NSURLRequest * urlRequest = [NSURLRequest requestWithURL:fileURL];
[NSURLConnection connectionWithRequest:urlRequest delegate:self];
}
-(void) connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{
<b>[self.tmpData appendData:data];</b>
}
-(void) connectionDidFinishLoading:(NSURLConnection *)connection{
NSString * filePath = [[self fakeDoc] stringByAppendingFormat:@"/%@"fileName];
[[NSFileManager defaultManager] createFileAtPath:filePath contents:self.tmpData attributes:nil];
}
-(NSString *) fakeDoc{
return @"/Users/chronoer/developer";
}</code></pre>
首先有一個 ivar fileName 用來設定要抓取的 zip 檔名,還有一個 tmpData,要去收集從 Server 來的 binary 的片段。在 downZip: 這個 IBAction 我們看到用 NSURLConnection 去連結 <b>http://127.0.0.1:8800/data?fileName=developer.zip </b>然後指定 delegate 為 self,所以接下來至少要實作<b> </b>
<pre> <code class="objectivec">
-(void) connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{
<b>[self.tmpData appendData:data];</b>
}</code></pre>
和
<pre> <code class="objectivec">
-(void) connectionDidFinishLoading:(NSURLConnection *)connection{
<b>NSString * filePath = [[self fakeDoc] stringByAppendingFormat:@"/%@", fileName];
[[NSFileManager defaultManager] createFileAtPath:filePath contents:self.tmpData attributes:nil];</b>
}</code></pre>
這兩個 delegate method。其中
<pre><code class="objectivec">
<b>[self.tmpData appendData:data];</b></code></pre>
就是把每個 zip file 小片斷收集到 tmpData<b>,</b>而整個檔案都收集完整就會呼叫 connectionDidFinishLoading: ,如下我們就把 self.tmpData 的完整資料存在 /Users/chronoer/developer/developer.zip。<br />
<pre><code class="objectivec">
<b>NSString * filePath = [[self fakeDoc] stringByAppendingFormat:@"/%@", fileName];
[[NSFileManager defaultManager] createFileAtPath:filePath contents:self.tmpData attributes:nil];</b></code></pre>
developer.zip 要預先放在 server 的資料夾,然後 /Users/chronoer/developer 這個目錄也要存在。<br />
最後就是 unzip 的部分了,在 ViewController.m import 如下<br />
<pre><code class="objectivec">
#import "ZipArchive.h" </code></pre>
然後新增一個 method 為 unzipFile<br />
<pre><code class="objectivec">
-(void) unzipFile:(NSString * ) zipFilePath{
ZipArchive *zipArchive = [[ZipArchive <b>alloc</b>] init];
[zipArchive <b>UnzipOpenFile</b>:zipFilePath ];
[zipArchive <b>UnzipFileTo</b>:[self fakeDoc] overWrite:YES];<br /> [zipArchive <b>UnzipCloseFile</b>];
} </code></pre>
非常單純的 4 個步驟,先 alloc ZipArchive 的物件,然後指定要開啟的 zip 檔,用 <br />
<pre><code class="objectivec">
[zipArchive <b>UnzipOpenFile</b>:zipFilePath ];</code></pre>
再來就是把這個 zip 檔解壓縮到指定的目錄底下,並設定為 overWrite <br />
<pre><code class="objectivec">
[zipArchive <b>UnzipFileTo</b>:[self fakeDoc] overWrite:YES];</code></pre>
結束就是關掉和這個 zip 相關連結,<br />
<pre><code class="objectivec">
[zipArchive <b>UnzipCloseFile</b>];</code></pre>
好啦,今天教學到這,咱們下次見。 麥克http://www.blogger.com/profile/01716179223350987115noreply@blogger.com0tag:blogger.com,1999:blog-3469754461280175671.post-30763398649357836632012-08-26T18:56:00.001-07:002012-08-28T21:34:48.851-07:00上下傳 file 範例 - Node.js一開始先要用 express 來產生 client 端上傳的 html form。如下。<br />
<blockquote class="tr_bq">
var express = require('express');<br />
<br />
var app = express( );<br />
app.configure(function() {<br />
app.use(express.bodyParser({uploadDir: './'}));<br />
});<br />
app.listen(8800);<br />
<br />
app.get('/upload', function(req, res) {<br />
res.write('<html><body><form method="post" enctype="multipart/form-data" action="/fileUpload">'<br />
+'<input type="file" name="uploadingFile"><br>'<br />
+'<input type="submit">'<br />
+'</form></body></html>');<br />
res.end();<br />
});</blockquote>
// 將檔案命名為 fileServer.js<br />
如此一來,可以用 Browser 打開 http://127.0.0.1:8800/upload 這樣一來就是執行上方的程式碼<br />
<blockquote class="tr_bq">
app.get('/upload', function(req, res) {<br />
res.write('<html><body><form method="post" enctype="multipart/form-data" action="/fileUpload">'<br />
+'<input type="file" name="uploadingFile"><br>'<br />
+'<input type="submit">'<br />
+'</form></body></html>');<br />
res.end();<br />
});</blockquote>
也就是回傳一個 HTML 的內容給 Browser<br />
<blockquote class="tr_bq">
<html><br />
<body><br />
<form <b>method="post"</b> <b>enctype="multipart/form-data"</b> <b>action="/fileUpload"</b>><br />
<input type="file" name="uploadingFile"><br><br />
<input type="submit"> <br />
</form><br />
</body><br />
</html> </blockquote>
在 Firefox 上面會看到<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjGkR2msXMubU_IEwZA_SQZ3QXLlbiqhoR6yOLqz8YShbd19kKL7ZKgXzFE0IyzbD7ToJUKqg0sIIuhSqeWSpQea8w4389wTaGLBE6cxXBSzCR95w-jQV0NJm1qJii6wOK6TZxeBYFYgxtl/s1600/Screen+Shot+2012-08-27+at+8.59.13+AM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="105" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjGkR2msXMubU_IEwZA_SQZ3QXLlbiqhoR6yOLqz8YShbd19kKL7ZKgXzFE0IyzbD7ToJUKqg0sIIuhSqeWSpQea8w4389wTaGLBE6cxXBSzCR95w-jQV0NJm1qJii6wOK6TZxeBYFYgxtl/s320/Screen+Shot+2012-08-27+at+8.59.13+AM.png" width="320" /></a></div>
這個 submit 按下去之後會去連結 /fileUpload 這個路徑,所以接下來就是要來處理這個 post method。在 fileServer.js 加入以下程式碼<br />
<blockquote class="tr_bq">
var fs = require('fs'); </blockquote>
<blockquote class="tr_bq">
app.post('/fileUpload', function(req, res) {<br />
<br />
var uploadedFile = <b>req.files</b>.uploadingFile;<br />
var tmpPath = uploadedFile.path;<br />
var targetPath = './' + uploadedFile.name;<br />
<br />
fs.rename(tmpPath, targetPath, function(err) {<br />
if (err) throw err;<br />
fs.unlink(tmpPath, function() {<br />
<br />
console.log('File Uploaded to ' + targetPath + ' - ' + uploadedFile.size + ' bytes');<br />
});<br />
});<br />
res.send('file upload is done.');<br />
res.end();<br />
});</blockquote>
在這我們會用到檔案處理,所以要require('fs') <br />
對於上傳檔案來說,express 會用 req.files 來暫存有關上傳檔案的資訊,在這個例子中會看到如下的訊息<br />
{ <b>uploadingFile</b>: <br />
{ size: 15198,<br />
<b>path</b>: '7fedaa5a4b44a499e2cfd29fc7c3be71',<br />
name: 'object.png',<br />
type: 'image/png',<br />
hash: false,<br />
lastModifiedDate: Mon Aug 27 2012 09:06:45 GMT+0800 (CST),<br />
_writeStream: <br />
{ path: '7fedaa5a4b44a499e2cfd29fc7c3be71',<br />
fd: 10,<br />
writable: false,<br />
flags: 'w',<br />
encoding: 'binary',<br />
mode: 438,<br />
bytesWritten: 15198,<br />
busy: false,<br />
_queue: [],<br />
_open: [Function],<br />
drainable: true },<br />
length: [Getter],<br />
filename: [Getter],<br />
mime: [Getter]<br />
}<br />
}<br />
在這個 json 物件裡,第一個出現的 key 是 uploadingFile 也就是寫在 client form 的<br />
<blockquote class="tr_bq">
<html><br />
<body><br />
<form method="post" enctype="multipart/form-data" action="/fileUpload"><br />
<b><input type="file" name="uploadingFile"></b><br><br />
<input type="submit"> <br />
</form><br />
</body><br />
</html> </blockquote>
上方的 name 之後的值,接著比較重要的是 express 會把收到的 file 放在一個暫存的路徑,寫在 req.files 中的 path 之後,為<br />
<blockquote class="tr_bq">
<b>path</b>: '7fedaa5a4b44a499e2cfd29fc7c3be71',</blockquote>
我們接著看 fileServer.js 裡的程式碼<br />
<blockquote class="tr_bq">
var uploadedFile = <b>req.files</b>.uploadingFile;<br />
var tmpPath = uploadedFile.path;<br />
var targetPath = './' + uploadedFile.name;</blockquote>
由 uploadingFile.path 取到的是 tmp 的資料夾,把這裡的東西搬到 targetPath 這裡。前提是 express 要做一個設定 <br />
<blockquote class="tr_bq">
var app = express( );<br />
app.configure(function() {<b><br />
app.use(express.bodyParser({uploadDir: './'}));</b><br />
});<br />
app.listen(8800);</blockquote>
設定 body 中 uploadDir 的真實路徑。<br />
<blockquote class="tr_bq">
fs.rename(tmpPath, targetPath, function(err) {<br />
if (err) throw err;<br />
fs.unlink(tmpPath, function() {<br />
<br />
console.log('File Uploaded to ' + targetPath + ' - ' + uploadedFile.size + ' bytes');<br />
});<br />
});</blockquote>
利用 fs.rename 把在 tmp 路徑的上傳檔案移到 targetPath 中。最後用 unlink 移除 在 tmpPath 裡的檔案。這樣上傳的 post 就處理完成了。可以上傳看看,會放在和 fileServer.js 同一個目錄。<br />
再來處理下載的部分,新增程式碼在 fileServer.js<br />
<br />
app.get('/data', function(req,res) {<br />
if (req.query.fileName) {<br />
<br />
var filename = req.query.fileName;<br />
console.log(filename);<br />
<br />
var mimeType = "image/png";<br />
res.writeHead(200, mimeType);<br />
<br />
var fileStream = fs.createReadStream(filename);<br />
fileStream.on('data', function (data) {<br />
res.write(data);<br />
});<br />
fileStream.on('end', function() {<br />
res.end();<br />
});<br />
<br />
}else{<br />
res.writeHead(404,{"Content-Type": "application/zip"});<br />
res.write("error");<br />
res.end();<br />
}<br />
});<br />
<br />
下載的部分打算用 get 來完成,如果使用者在 Browser 下達<br />
<blockquote class="tr_bq">
http://127.0.0.1:8800/data?fileName=object.png</blockquote>
就是準備要下載 object.png 這個檔案,所以一開始用<br />
<blockquote class="tr_bq">
if(req.query.fileName)</blockquote>
來確定有指定 file name <br />
用<br />
<blockquote class="tr_bq">
var mimeType = "image/png";<br />
res.writeHead(200, mimeType);</blockquote>
來和 Browser 說接下來要傳的是一個 png 檔<br />
<blockquote class="tr_bq">
var fileStream = fs.createReadStream(filename);<br />
fileStream.on('data', function (data) {<br />
res.write(data);<br />
});<br />
fileStream.on('end', function() {<br />
res.end();<br />
});</blockquote>
上方的部分是開始一個 write stream,然後在 data 的狀態下,利用 res.write() 把資料寫給 Browser。在 end 的時候結束和 browser 的通訊。<br />
在 Server 的部分通常會先和 Client 說 Client 想下載的檔案大小如何,請如下指定<br />
<blockquote class="tr_bq">
fs.<b>stat</b>(<b>filename</b>, function(error, stat) {<br /> if (error) { throw error; }<br /> res.writeHead(200, {<br /> 'Content-Type' : 'image/png',<br /> '<b>Content-Length</b>' : <b>stat.size</b><br /> });<br />
});</blockquote>
其中 filename 指的是 request 想要下載的檔案路徑。<br />
如此一來,下載的部分也完成了。一樣,程式碼放在 <a href="https://github.com/Scentsome/Developer-s-Note">GitHub</a>。麥克http://www.blogger.com/profile/01716179223350987115noreply@blogger.com0