2016年2月24日 星期三

Swift Vapor Framework on Heroku

大家早,之前文章得到大家的喜愛,雖然沒有超過 300 人回覆,但有超過 300 人看過,筆者為了感謝大家的支持,想介紹如何把 Vapor 的專案放到 Heroku 這個有名的平台上面。讀者應該也是面臨後 Parse 時代,正在尋找替代方案,筆者個人覺得 Heroku 是個不錯的選擇,也撐超過 5 年呢,應該還蠻可靠的。
首先就是先註冊 Heroku 帳號,登入之後,有個 heroku 的工具程式要下載,在這邊下載。下載安裝完在 Terminal 執行 heroku login 就可以登入使用 heroku 的資源,大部分的設定都可以在本機,用 Heroku 的工具程式就可以完成,所以這個程式很重要。
還記得上一篇的 Vapor 專案嗎?如下
swiftServer/
├── Package.swift
├── Resources
│   └── index.html
└── Sources
    └── main.swift

因爲我們要部署到 Heroku 所以需要一個 Procfile
新增如下

swiftServer/
├── Procfile
├── Package.swift
├── Resources
│   └── index.html
└── Sources
    └── main.swift

Procfile 內容如下

web: SwiftServer --port=$PORT

簡單的一行,存檔就行了
接著我們要把這個專案部署到 Heroku
詳細的內容可以參考 Heroku 的教學

接著我們先把 Vapor 專案用 Git 來管理
在 Terminal 輸入
git init
得到
Initialized empty Git repository in /Users/michael/Documents/project/heroku/<某資料夾>/.git/

再輸入
git add .
目的是加入檔案到 git repo
最後要 commit 輸入
git commit -m "first on heroku"
得到

6 files changed, 42 insertions(+)
 create mode 100644 .DS_Store
 create mode 100644 Package.swift
 create mode 160000 Packages/vapor-0.2.3
 create mode 100644 Procfile
 create mode 100644 Resources/index.html
 create mode 100644 Sources/main.swift



然後輸入 heroku create,藉以產生一個 heroku的專案,會看到如下結果。
https://stark-journey-93380.herokuapp.com/ | https://git.heroku.com/stark-journey-93380.git其中  stark-journey-93380 是 heroku 隨機產生的專案名稱,之後還可以改。
回到 Heroku 網頁看一下,會看到如下圖片


有看到一個同樣名稱的 Heroku App在上面。
然後為了可以讓 Heroku 認得是 Swift 的專案
要利用這個 heroku 的 buildpack
在 Terminal 輸入
heroku buildpacks:set https://github.com/kylef/heroku-buildpack-swift.git --app <專案名稱> 
最後的專案名稱就是 heroku 一開始產生給我們的,在這個例子是 stark-journey-93380
得到如下回覆

Buildpack set. Next release on stark-journey-93380 will use https://github.com/kylef/heroku-buildpack-swift.git.
Run git push heroku master to create a new release using this buildpack.

因為 Heroku 有提供 git 機制,所以我們要把這個 local 的 git 和 remote heroku 的 git 結合
利用 git remote -v 來看一下遠端有什麼可以結合的 git repositoty
會看到如下
heroku    https://git.heroku.com/stark-journey-93380.git (fetch)
heroku    https://git.heroku.com/stark-journey-93380.git (push)

這個 local 的 Vapor 專案就是要和 stark-journey-93380結合
輸入
heroku git:remote -a stark-journey-93380
會看到
set git remote heroku to https://git.heroku.com/stark-journey-93380.git
代表結合成功了
最後一個步驟就是把程式碼 push 到 Heroku 上面,輸入
git push heroku master
會得到如下回覆
Counting objects: 10, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (6/6), done.
Writing objects: 100% (10/10), 1.22 KiB | 0 bytes/s, done.
Total 10 (delta 0), reused 0 (delta 0)
remote: Compressing source files... done.
remote: Building source:
remote:
remote: -----> Fetching set buildpack https://github.com/kylef/heroku-buildpack-swift.git... done
remote: -----> Swift app detected
remote: Cloning into 'swiftenv'...
remote: -----> Installing DEVELOPMENT-SNAPSHOT-2016-02-08-a
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
remote: DEVELOPMENT-SNAPSHOT-2016-02-08-a has been installed.
remote: -----> Installing clang-3.7.0
remote: -----> Building Package
remote: Cloning https://github.com/tannernelson/vapor.git
remote: Using version 0.2.4 of package vapor
remote: Compiling Swift Module 'Vapor' (33 sources)
remote: Linking Library:  .build/release/Vapor.a
remote: Compiling Swift Module 'SwiftServer' (1 sources)
remote: Linking Executable:  .build/release/SwiftServer
remote: -----> Copying dynamic libraries
remote: -----> Copying binaries to 'bin'
remote:
remote: -----> Discovering process types
remote:        Procfile declares types -> web
remote:
remote: -----> Compressing...
remote:        Done: 7.8M
remote: -----> Launching...
remote:        Released v3
remote:        https://stark-journey-93380.herokuapp.com/ deployed to Heroku
remote:
remote: Verifying deploy... done.
To https://git.heroku.com/stark-journey-93380.git
 * [new branch]      master -> master

有點長
因為我們有寫 Heroku buildpack,所以除了把 code 放到 heroku 之外,heroku 還會利用 Swift Package Manager 去下載 compiler 再來 compiler 我們的 Vapor 專案
在 Terminal 可以輸入
heroku open
就可以用預設 Browser 打開 Heroku App 會看到


為什麼事 Page not found 是因為我們沒有定義 / 要做什麼事
所以我們更改路徑爲
https://stark-journey-93380.herokuapp.com/welcome
就會看到

再測試 https://stark-journey-93380.herokuapp.com/view
就會看到

如此,部署完成。
之後如果程式有什麼修改
就用
1. git commit -a
2. git push heroku master

這兩個指令就可以了
如果要更改 Heroku app 名稱要用
heroku apps:rename <新的名稱>
不過要注意名稱不能被其他 Heroku 使用者使用,會更改不成功,多注意回覆就好了。


2016年2月23日 星期二

用 Swift 開發 Server Side App 並利用 Swift Package Manager - Vapor Framework

首先和各位看官說聲 2016 新年快樂。希望大家在新的一年立訂新的目標。這篇文章要如何用 Vapor 這個 Framework 來開發 Server Side App。Vapor 使用 Swift 來架構而成,個人覺得使用起來比起 Swifton 或是 Kitura 還有 Perfect 來的簡潔。有興趣的可以去多多比較。
而 Vapor 剛好利用 Swift Package Manager 的方式來管理套件,有別於比較常見的 CocoaPods 和 Carthage 來管理,Vapor 使用 Apple 提出的 Swift Package Manager 方式來管理。
要提醒讀者,Vapor 更新還蠻頻繁的所以 Google 到的寫法可能是舊的,請看好 Vapor 的版本,這篇用的是 0.2.3 。筆者會持續更新。

安裝 Swift 3.0 Dev


準備工具:Swift 3.0 Dev 從這邊下載,目前 Swift 2.2 Release 還沒支援 Swift Package Manager 所以要下載 Latest Development Snapshot 。
作業環境: OS X El Capitan 10.11.2
下載完會看到一個 swift-DEVELOPMENT-SNAPSHOT-2016-02-08-a-osx.pkg
點兩下安裝。如果沒有看到如下安裝畫面
 
這是安全的考量,Mac 認為這個程式不是認證的開發者???
要手動打開,系統偏好設定->安全性與隱私
安裝好之後會在
 /Library/Developer/Toolchains/
看到這幾個資料夾
swift-DEVELOPMENT-SNAPSHOT-2016-02-08-a.xctoolchain/
swift-latest.xctoolchain/
之後就用 swift-latest.xctoolchain/ 就可以了
試一下 Swift 版本
打開 Terminal 輸入以下指令
 /Library/Developer/Toolchains/swift-latest.xctoolchain/usr/bin/swift --version
看結果是不是以下
Apple Swift version 3.0-dev (LLVM a7663bb722, Clang 4ca3c7fa28, Swift 1c2f40e246)
Target: x86_64-apple-macosx10.9
如果是 3.0-dev 就沒有問題了。
為了省輸入時間可以在.profile 檔加上
export PATH=/Library/Developer/Toolchains/swift-latest.xctoolchain/usr/bin:$PATH
開啟 Terminal 就執行  .profile 這樣就直接用 swift 就好了。

建立 Vapor 專案

建立一個資料夾名為 swiftServer,其架構如下
swiftServer/
├── Package.swift
├── Resources
│   └── index.html
└── Sources
    └── main.swift

Package 內容如下
 
import PackageDescription

let package = Package(
name: "SwiftServer",
dependencies: [
.Package(url: "https://github.com/tannernelson/vapor.git", majorVersion: 0)
 ]
)
稍微解釋一下,Package.swift 的語法也是 Swift 一開始要 import PackageDescription 接著就可以用 Package 這個 class 有興趣的讀者可以在這看到 Package 的全貌。majorVersion 爲 0 因為現在 Vapor 的版本是 0.2.1 majorVersion 爲 0 依照 source code 會找 0..<1 的版本

main.swift 內容如下
 
import Vapor

let app = Application()
app.get("welcome") { request in
 return "Hello Vapor"
}
app.start(port:8080) 
Vapor 裡面有 Application 這個 class 也就 Server 一直執行的 Daemon 。然後綁定在 port 8080,
接著我們利用 app.get 來定義 HTTP GET Method 的 Path。目在這個例子新增 /welcome 這個 Path。
是時候在本機來執行這個 Vapor server 。
在 Package.swift 同一個目錄下,在 Terminal 輸入以下指令。
swift build

成功之後會看到

Cloning https://github.com/tannernelson/vapor.git
Using version 0.2.3 of package vapor
Compiling Swift Module 'Vapor' (32 sources)
/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
        var handler = { request in
        ~~~ ^
        let
Linking Library:  .build/debug/Vapor.a
Compiling Swift Module 'SwiftServer' (1 sources)
Linking Executable:  .build/debug/SwiftServer

看到目前的 Vapor 版本為 0.2.3
然後這理有個 Warning 是 Swift compiler 常見的 Warning ,建議把 var 改成 let 。雖然我們有 source code 但是還是不建議自己改,就給 Vapor 維護者在一個版本改。最後一行是 Linking Executable:  .build/debug/SwiftServer 就是代表 compiler 完的執行檔。
只要在 Terminal 直接執行
.build/debug/SwiftServer
會看到
Server has started on port 8080

然後打開本機的 Browser 在 URL 的地方輸入
http://localhost:8080/welcome

就會看到如下



幾件事提醒

1. 如果有修改 code 要重新利用 swift build 來 compile 之前,請先用 rm .build/debug/SwiftServer 把舊的執行檔刪除。
2. 程式碼的 app.start(port:8080) 請務必是最後一行。
3. 也可以輸出 JSON 比如直接回傳 [ "name":"Michael"],留給讀者自行嘗試。

HTML 結合

輸出的結果有時候需要是 html ,Vapor 也可以輸出已經編寫好的 html 檔案,但必須放在 Resources 這個資料裡面,在看一次專案結構圖。
swiftServer/
├── Package.swift
├── Resources
│   └── index.html
└── Sources
    └── main.swift

在這 index.html 的內容為

 <html>
    <body>
        <h1>Vapor Server</h1>
This is tutorial for building Vapor Server.
    </body>
</html>
輸入好之後在 main.swift 新增一個 Path
修改完如下
 
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) 
我們新增了 /view 這個 path。 要注意的是 View 這個 class 的 init?(path:) 會丟出 error,所以要用 do-try-catch 來承接。
最後把 unwrapped 的 okView 回傳就好了。
先把 Server 停掉,ctrl+C
記得
1. rm  .build/debug/SwiftServer
2. swift build

再重新執行
.build/debug/SwiftServer

打開 Browser 輸入,http://localhost:8080/view
會看到


如果這篇很多人回應我們再來分享一篇,如何結合 Database。很多人是多少呢?超過 300 人吧?試試人氣。


2015年10月12日 星期一

螢幕的轉向

螢幕的轉向

要設定app支援的轉向,有兩個要素。分別是”裝置”與”View Controller”。兩者設定的交集,才是真正最後能呈現的轉向。

裝置支援的方向
要設定裝置所支援的方向,只要在專案Device Orientation勾選要支援的方向。 



其所修改後,其實是對應到Info.plist中,Supported interface orientations的設定。 




View Controller 支援的方向
View Controller以呈現類型,大致上有三種。分別是UINavigationController, UITabBarController與的UIViewController。

  • UINavigationController

若以UINavigationController管理多個UIViewController,則以UINavigationController統一管理支援的方向。
  • UITabBarController

若以UITabBarController管理多個UIViewController,則以UITabBarController統一管理支援的方向。
  • UIViewController

單獨的UIViewController則自己處理支援的方向。若前面UINavigationController或UITabBarController以presentViewController方式呈獻UIViewController,則此UIViewController不受UINavigationController或UITabBarController所管理。

從iOS 7到目前的iOS9有兩個method要去實作,分別表示
  • shouldAutorotate() -> Bool :表示是否支援轉向
  • supportedInterfaceOrientations() -> UIInterfaceOrientationMask :列出所支援的轉向


說明
若裝置支援的方向勾選Portrait, Landscape Left, Landscape Right。用如下支援轉向的method寫在不同地方做說明。

    override func shouldAutorotate() -> Bool {
        return true
    }

    override func supportedInterfaceOrientations() -> UIInterfaceOrientationMask {
        return [.Portrait]
    }

  • 使用UINavigationController,但支援轉向的method寫在其中的一個UIViewController中。進行轉向的結果如下。可以看出已被轉向。表示method限制只支援Portrait的設定無作用。


  • 使用UINavigationController,但新增繼承UINavigationController的Class,將支援轉向的method寫在裡面。並使用這個新增的class管理Navigation。可以看得出來,轉向被限制在Portrait有起作用。


有興趣可以動手對UITabBarController做一樣的嘗試。可以發現,原來UINavigationController與UITabBarController除了管理UIViewController之外,還負責轉向的支援。















2015年9月17日 星期四

從 Swift 1.x 到 Swift 2.0

從 Swift 1.x 到 Swift 2.0 轉換時候,有什麼要注意的呢?

一般 Apple 的做法是新的 IDE 才會支援新的語法或是平台,所以要測試就拿新的 Xcode 7 來測試一下
不過在測試之前要先來討論一下,用 Xcode 6 開發 和用 Xcode 7 開發差別在哪?
對於開發者而言,最重要的事情就是 Xcode 可以支援的平台 OS 是幾版到幾版?
以 Apple 的做法來說,非常舊版本的 Xcode。以這次 iOS 9 的更新來說我用 Xcode 6 就無法把 App 部署到 iOS 9 的 Device 上面,所以來比較一下可以部署的平台
用 Xcode 7 打開一個專案如下可以看到

圖片無法 Copy 到下方的版本,Xcode 7 可以支援的就是
iOS 6.0 - 9.1
另外再來看一下 Xcode 6.4
同樣的地方
會看到是
iOS 6.0 - 8.4
換句話說,如果要寫 App 支援 iOS 6.0 之前的版本, Xcode 6.4也不能部署
要開發 iOS 9 請用 Xcode 7




接著回到正題,拿一個現成的 Project 來轉看看 這個 App 操作方式如下
 https://www.youtube.com/watch?v=gIyhpRXP8so
先用 Xcode 7 打開

 就會看到轉換成最新 Swift  語言的提示
接著按下一步
接著選擇要檢視的 Target
PS. 如果沒有自動出現的話可以手動啟動這個 Convert 機制如下圖

接著就會出現比較的畫面,我們一一來看一下
第一個我們看到的不一樣
Swift 2.0 之後,都用  print() 來取代之前的 print() 和 println()
差別在 2.0 的 
print() 
代表 1.x 的 
println()
而 1.0 的 
print() 
在 2.0 要寫成 
print("Hello", terminator: "")
或者你也可以想成 2.0 的 
print() 
其實是
 print("Hello", terminator: "\n") 
的簡寫

接著看下一個
 這點是如果 var some = "Hello",對於 some 這個變數,如果之後的程式碼沒有用到,Xcode 就會強烈建議把 var 改成 let ,也就是會自動把 

var some = "Hello"

轉成

let some = "Hello"

 接著看下一個
這個原因是
在 1.x 的時候,有一個 
var name:String = "Michael"
 name 的型別是 String
如果我們想要用 for-in 走訪這個 string 把大寫的字找出來
在 1.x 會這樣寫

for c:Character in name {
    var str = "\(c)"
    if str == str.uppercaseString {
        print("\(str) is uppercase ")
    }
}

直接把 name 好像當成集合用,
而在 2.0 會改成
for c:Character in name.characters {
    var str = "\(c)"
    if str == str.uppercaseString {
        print("\(str) is uppercase ")
    }
}
這樣比較合理一點,用 characters 來回傳一個由 Character 所組成的 Array 用 for-in來 iterate 所有的元素


有學員提供這個問題
在 1.x 的時候這樣寫

var urlStr = "http://www.apple.com"

var iphoneURL = urlStr.stringByAppendingPathComponent("iphone")

stringByAppendingPathComponent 本來是 NSString 常用的 method
Swift 的 String 也是可以用
但是這樣 urlStr 裡面是 http:// 開頭的,也就是意圖要處理一個 URL
此時用 Xcode 7 打開,Convert 到新的 Syntax 就會出現下圖

也就是目前 Xcode 7 建議先轉成 NSString 再做 stringByAppendingPathComponent
這樣一來可以正常的執行
如果不聽,在 Xcode 7 就會直接給錯誤如下圖

 它就直接出現錯誤,stringByAppendingPathComponent 不可以在 String 用了,建議用 NSURL 來處理 path component 的問題



String 的 toInt() 被拿掉了如下圖
 在 2.0 如果要把數字集合而成的 String 轉成 Int 要就要用 Int 的 constructor
Int("12345")


/* by MusiCabbage */
好康道相報!!  SequenceType 本來一些奇怪的方法,變得更直覺了!!

例如說,本來我們想知道Array中有沒有某個Object,
必需這樣寫…

var array = ["a", "b", "c", "d", "e"]

contains(array, "a")

在 swift 2.0 的世界裡,我們只需要這樣寫…

array.contains("a")

類似的做法,CollectionType 裡面那些奇怪的方法,也一起跟進了!!
以前如果想要找到某個Object在Array中的Index,
是這樣寫…

var index = find(array, "c")

在 swift 2.0,完全換了個方法,但更容易懂了…

array.indexOf("c")





持續更新中

2014年12月7日 星期日

Swift - Struct 與 Class 的差異性

Hi, There。今天這個篇文章要來介紹 Swift 中 struct 和 class 有什麼不一樣的地方?首先要先和大家提到一個觀念,Value Type 和 Reference Type 其中 struct 是 Value Type 而 class 是 Reference Type 所以這篇文章呈現的 struct 的行為也可以套用到所有的 value type 物件,相同地 class 的行為也可以套用到 reference type 的物件上。

我們來建立一個自已的 struct 名稱為 SRectangle 。程式碼如下呈現。

struct SRectangle {
 var width = 200
}
這個 struct 有一個 property 名為 width 。
再來建立一個 class 名為 CRectangle 也有一個叫做 width 的 property 程式碼如下

class CRectangle {
 var width = 200
}
準備好了,我們先來看第一個差異點

1. 建構物件時

struct


var sRect = SRectangle()
// 或者 

sRect = SRectangle(width:300)

sRect.width // 結果就是 300

class


var cRet = CRectangle()
// class 不能直接用 CRectangle(width:300) 必需要定義一個 constructor
cRect.width // 為 200

主要的差別就是 class 在產生物件時不能很自然把 property 放在 constructor 的參數裡

2. 指定給另一個變數的行為不同

struct


var sRect = SRectangle()
// 或者 

var sRect2 = sRect

sRect2.width // 目前值是 200,因為 sRect 直接 copy 一份完整記憶體給 sRect2
sRect2.width = 500
sRect.width // sRect.width 值不受 sRect2 影響還是 200

class


var cRect = CRectangle()
// 或者 

var cRect2 = cRect

cRect2.width // 目前值是 200,因為 sRect 直接 copy 一份完整記憶體給 sRect2
cRect2.width = 500
cRect.width // cRect.width 也改變成了 500

以上是 value type 和 reference type 最大的不同,在每次做 assignment 的時候,value type 都會複製一份完整相同的內容給另一個變數,而 class 則是把記憶體的位置給變數。所以 reference type 大多會保有一份記憶體而重覆利用。

3. 關於 immutable 變數

在 Swift 這個語言的特色之一就是可變動內容的變數和不可變動內容的變數用 var 和 let 來區別。如果一開始用 let 來修飾變數就會造成 compiler 的錯誤。如下

var name = "Michael"
name.write(" Pan") // 這樣是正常

let name = "Michael"
name.write(" Pan") // 會造成 compile 錯誤

struct

大至也遵循這個規則如下

let sRect = SRectangle()

sRect.width = 500 // 會造成錯誤

class


let cRect = CRectangle()

cRect.width = 500 // 預設可以直接更改雖然是 let 修飾的 cRect

let 這個效果無法延申至 class 。不過要提醒大家在 Swift 常用的 String, Array, Dictionary 都是 struct 所以 let 是會有效果的

4. mutating function

我們先要為 struct 和 class 新增一個 method 名為 changeWidth() 而且我們在不改變原始的程式碼為前提下,新增一個 method。如下

struct


struct SRectangle {
    var width = 200
    
}

extension SRectangle {
    mutating func  changeWidth(width:Int){
        self.width = width
    }
}

class


class CRectangle {
    var width = 0
}

extension CRectangle {
    func changeWidth(width:Int){
        self.width = width
    }
}

這裡用了 extension 新增 class 裡的 method 而不需要改到原本的程式碼。 以。struct 和 class 的差別是 struct 的 function 要去改變 property 的值的時候需要加上 mutating 字樣,而 class 不用這樣做

5. Struct 和 Class 裡的 function 共同特色 - Implicit External Parameter Name

function 在定義時,有一種特別的寫法叫做 external parameter name 舉例來說 一般寫法

func setSize( width:Int, height:Int) {
    println("width \(width), height \(height)")
}

setSize(50, 100) // 直接丟入參數
External Parameter Name

func setSize(width width:Int, height height:Int) {
    println("width \(width), height \(height)")
}

setSize(width:50, height:100) // 必需在參數值前加上 external parameter name

另一個簡寫

func setSize(#width:Int, #height:Int) {
    println("width \(width), height \(height)")
}

setSize(width:50, height:100) // 當 External name 和 internal name 一樣的時候可以在 internal name 前面加上 # 就可以少寫 external name

這樣的行為對於一般 function 並沒有強制一定要寫 external name 不過在 struct 和 class 裡是強制使用的。如下

struct


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)


class


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)

無論 struct 或是 class 裡的 function 第二個參數開始都強制加入 external parameter name。在定義的時候不用特別寫,就會有了。

6. 繼承

物件導向語言,令人覺得強大的能力莫過於繼承,而 struct 沒有 繼承的功能,只有 class 有。如下

class


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


以上是簡單常用到的行為裡 struct 和 class 常令使用者搞混的情況,希望對大家有幫助。 歡迎提供更多建議。

2014年10月27日 星期一

關於 Swift 繼承的兩三事

談到繼承就要用先寫一下 Swift 這個語言的 class 的寫法:

class Rectangle {  // 非正確寫法
  var width:Double
  var height:Double
}

這個就是 swift class 的定義方式,名稱為 Rectangle。有兩個 property 各是 width 和 height。 但是這樣寫並不算完成,因為 Swift 要求在產生這個 class 的實體(instance) 的時候所有的 property 都要有明確的值。所以我們來加上初始值

class Rectangle {
  var width:Double = 200.0 
  var height:Double = 300.0
}

這樣簡單的 class 就準備完成,然後我們需要來產生實體如下

var rect = Rectangle()
這樣就可以用 rect 來使用實體,然後改變 property 的內容如下

var rect = Rectangle()
rect.width = 50.0
Rectangle() 做了什麼事?讀者一定很好奇。其實對熟悉 Objective-C 的開發者來說,他就是 alloc - init 這兩個動作的簡化。 在 Swift 裡 Rectangle() 就會對應到 class 裡面的 init()。比如我們改寫一下 Rectangle 成

class Rectangle {
  var width:Double
  var height:Double
  init(){
    width = 200.0
    height = 300.0
  }
}

多了一個 init() 而且 var width 和 var height 之後並沒有給值,這樣可以嗎? 這樣是可行的,因為在真正使用實體前,init() 會最先被呼叫,所以在 init() 裡面把 propety 值都設定好,就可以保證之後存取 property 都不怕沒有值。 讀者可以直接在 playground 上面試試,init() 這個第一個被執行的 method 也有一個別名叫做建構子 - constructor。 然後我們來新增一個 method 名為 area 如下

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 
以上,就完成一個很基本的 Class 的定義。接下來才是繼承的問題 XD 所以我們來產生另一個 class 名為 Square,繼承 Rectangle 如下

class Square : Rectangle {
    
}
var s = Square()
s.height = 20.0
s.width = 20.0
s.area()
Square 裡面空空如也,只有一個 : Rectangle 的宣告,這樣一來 Square 就擁有 Rectangle 所有內容,包含 width 和 height 的 property 還有 area() 這個 method 自然也可以直接使用 這個就是繼承的好處 接下來我們來把 Square 做一些修改,比較符合我們的期待。

class Square : Rectangle { // 非正確寫法
    var side:Double
}

新增一個 side 的 variable 這樣一來要記得在 init() 指定給 side 值。 但是因為 Square 繼承 Rectangle ,已經有一個 init() 所以在 Square 的 init() 前面要加上 override (覆寫) 字樣。如下

class Square : Rectangle {
    var side:Double
    override init(){
        side = 50
    }
}
當子類別(指 Square) 要重寫一個和父類別(指 Rectangle)一樣的 method 的時候就要加上 override 的字樣,這樣執行的時候就是採用子類別的 method 程式碼而不是父類別的程式碼。 這樣一來就可以執行如下程式碼

class Square : Rectangle {
    var side:Double
    override init(){
        side = 50
        // 最後會執行 super.init()
    }
}
var s = Square()
s.area() // 得到 60000
有沒有發現一個奇怪的地方?按照推理 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 如下

class Square : Rectangle {
    var side:Double
    override init(){
        side = 50
    }
    override  func area() -> Double {
        return side * side
    }
}
所以我們覆寫了 area(),讓它和 side 有關。這樣如下的程式碼在 Square 的用法上就更合理

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
So far, So good 看起來很不錯,但還是有一個小小的缺點。也就是當我們用 Square 的 side 的時候,也可以用 width 和 height,這兩個從 Rectangle 得到的變數,但這三個就是不同的變數,這樣一來觀念上很奇怪。比較好的方式是希望使用 Square 的 width 的時候也就是和 side 有連帶關係。拿 width 來說,如果設定 width 的值同時就是設定 side 的值,取得 width 的值也就是取得 side 的值這樣會比較合理。於是我們要來用到 Swift 語言裡很特別的功能,就是 override property。如下

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
        }
    }
}
覆寫 property var width:Double 的方法如下
  
    override var width:Double{
        get{
            return side
        }
        set{
            side = newValue
        }
    }
前面也要加上一個 override 然後要用 {} 包起來,裡面有 get 和 set 關鍵字。 get 很好理解就回傳 side 的值,set 的區塊裡面要用 newValue 這個保留字代表要設定給 width 的新值,然後指定給 side 相同地 height 也照辦如下
  
override var height:Double{
        get{
            return side
        }
        set{
            side = newValue
        }
}
這樣一來不論是存取 width 或是 height 都是存取 side。 很理想,來測試一下
  
var s = Square()
s.width = 50.0
s.area() // 得到 2500.0
做個這篇的小結

  1. 簡單 class 定義方式, 建構子 init() 使用時機 
  2. 繼承的寫法,init() 在子類別和父類別的使用時機
  3. 覆寫 method 和 property 的方法 
謝謝各位,大家下次見

2014年10月7日 星期二

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

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

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

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

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

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

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


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

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


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

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

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

var myCar = Car.sharedInstance()

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

在使用

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

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

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

2014年3月9日 星期日

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

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

與使用者合作

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

2013年8月9日 星期五

Node Pattern - Recursive Loop

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

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

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

var fs = require("fs");

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

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

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

完整的程式如下。

var fs = require("fs");

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


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

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

2013年7月12日 星期五

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

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


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

按下之後會出現 App Store 字樣

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

幾個設定說明一下

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

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

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



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

int main(int argc, const char * argv[])
{

return;
中間 比如接下來 Chapter 1 在第 3 頁的

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