Alex Liang

Swift Closure 介紹

Closure在Swift有二層意思。第一種是一般認知的global或nested functions,函式中擁有free variable可被nested function存取;或作為變數傳給另一個function

第二種為closure expression,在function的定義後加上簡短表示式。在Swift中,closure通常是指closure expression

Read More

Swift的?與!

在靜態語言中,nil or null的判斷是件重要卻常被忽略的事
當一個object或變數為null而沒被排除時,程式很有可能crash或出現莫明其妙的bug
Swift增加了optional幫助程式設計師避掉可能的crash,語法雖然簡單卻需要時間理解觀念

Optionals

當資料可能為nil時,我們在宣告變數時加入?。

1
2
3
4
5
var customerName: String? 		// nil
var regards = "Thank you! "
if let name = customerName { // skip
regards += name
}

當執行到if let時,因為customerName為nil而判斷式不會成立
沒有給定初始值的變數最好加上optional,在程式剛執行時還未給予值的情況下可避免crash

另一種情況是function的回傳值可能為nil時,使用optional會是合理的選擇

1
2
3
4
5
6
7
8
9
10
11
12
13
14
func findStore(storeName: String) -> String? {
storeList = ["7-11", "FamilyMarket", "Hi-life"]
for store in storeList {
if store == storeName {
return "Find \(storeName)!"
}
}

return nil
}

if let result = findStore("Poya") {
print(result)
}

findStore的回傳值為optionals,當storeList沒有符合的字串時則回傳nil
上例中result為nil,所以不會印出任何值

Unwrap

變數為optional時,使用!可以強制unwrap並存取資料

1
2
let result = findStore("7-11")			
result! // Find 7-11!

如上例,但此操作不符合官方的建議用法,同時也失去安全性

Downcast

當使用某些資料型態的值做為變數時,需要資料型態轉換。
如果不確定轉換是否成功,使用as?會回傳optional,如此可避免nil帶來的問題

1
2
3
4
5
6
7
8
9
10
11
12
13
let playlistDictionary = [
[
"title": "Native",
"artist": "OneRepublic"
]
[
"title": "V",
"artist": "Maroon 5"
]
]

let playlist = playlistDictionary[0]
let title = playlist["title"] as? String

當我們存取playlist的title時,由於不確定回傳的值是否為nil,所以使用as?做型態轉換

參考資料:
Type Casting官方文件

Swift Enumerations

在C/C++中, 使用enum可提升程式碼的可讀性
而Swift的enum擴充原本的功能,使用上有更多的彈性

Syntax

1
2
3
4
5
6
7
8
9
enum ProgrammingLanguage {
case C
case CPP
case Objective-C
case Swift
}

let firstLanguage = ProgrammingLanguage.Swift
let secondLanguage = .CPP

不同於數C和Objetive-C,Swift的enum並沒有預設的整數值,也就是說case C並不隱含整數0的概念
使用時除了第一個比較傳統的寫法外;你也可以省略enum的型別名稱,這也是Swift的syntax sugar

1
2
3
4
5
6
7
8
9
10
let secondLanguage = .CPP

func selectLanguage(language: ProgrammingLanguage) {
switch language {
case .C: print("You chose C language")
case .CPP: print("You chose C++ language")
case .Objective-C: print("You chose Objective-C language")
case .Swift: print("You chose Swift language")
}
}

使用switch做判斷時一樣能用簡短的表示法。因為使為enum當參數,compiler會檢查型別,switch也能省去default的值

Associated Values

這是Swift的enum和其它語言不一樣的地方,我們能為enum創造關聯性的值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
enum CurrencyType {
case USD(Double)
case TWD(Int)
}

var price = CurrencyType.USD(33.99)

func payCheck(price: CurrencyType) {
switch price {
case .USD(let usDollar):
let ntDollar = usDollar * 33
print("總共為: \(ntDollar) NTD")
case .NTD(let ntDollar):
print("總共為: \(ntDollar) NTD")
}
}

CurrencyType包含二種貨幣,美金和新台幣. 而前者需要Double來表示;後者則是Int
在宣告price時,我們給予一個Double的值表示美金的價錢;而switch case需要加上對應的參數
這項特性讓我們使用enum時除了表示狀態,還能給予相對應的值

Raw Values

除了上述的Associated Values, Swift的enum也有C/C++給予初值的特性

1
2
3
4
5
6
enum HTTPResponse {
case Continue = 100
case Success = 200
case MultipleChoices = 300
case NotFound = 404
}

這個例子中,HTTPResponse一開始就給予raw value. 而raw value只能為字串,字元,浮點數或整數,並且不能重複

Initializing from a Raw Value

假如定義了一組enum含有raw value,則我們能使用raw value查循狀態

1
2
3
4
5
6
7
8
9
enum HTTPResponse {
case Continue = 100
case Success = 200
case MultipleChoices = 300
case NotFound = 404
}

let statusCode = 300
let httpResponse = HTTPResponse(rawValue: statusCode) // MultipleChoices

將外界傳來status code回應HTTPResponse相對應的狀態;假如沒找到則回傳nil
rawValue回傳的值為optional,即使沒找到也不會讓程式hang住
下一篇接著介紹optional

參考來源:
官方文件

Swift Protocol Oriented Programming

上一篇介紹Swift Protocol基本的用法和優點。
這篇接著討論Swift使用protocol帶來的設計觀念:Protocol Oriented Programming

Composition

在物件導向設計中,繼承(inheritance)是常被誤用的觀念。
當二個”看起來”類似的類別套入繼承的關係後,有些操作便不適合
例如:圓形和橢圓形適合使用繼承的關係嗎? 前者有半徑;後者有長軸和短軸
如果將橢圓形繼承圓形,則初始化時,半徑為無意義的值

所以在重構時,很重要的一點是分㦚二個類別是”Is A”或”Has A”的關係
如同前例,橢圓形並”不是”一種圓形;相反的,跑車”是”車子的一種
於是討論這類問題時,組合(composition)是一個更適合的pattern

Protocol Inheritance

由於Swift的class只能繼承單一class,但class能擁有多個protocol
所以在設計class時,我們傾向將各特性分為各種protocol
而protocol能繼承另一個protocol,可以組合各種小功能的protocol完成有彈性的設計

1
2
3
4
5
6
protocol Movable {
func move()
}
protocol FastMovable: Movable {
func move()
}

Standard Library Protocols

Swift語言包含各種protocol,可分為三種類別:”Can Do”, “Is A”, “Can Be”
這三種protocol的命名方式也是Swift的慣例

  1. Can Do: 表示object可以做什麼,例如Standard Library中Equatable表示物件能判斷是否相等
  2. Is A: 表示object是某種形態,例如IntegerType
  3. Can Be: 表示object可以轉化成某種形態,例如ArrayLiteralConvertible表示物件能轉成array形態

Protocol Oriented Programming

假如有個class為Car,我們可以利用protocol組合該類別的基本行為

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
enum Direction {
case Up, Down, Left, Right
}

struct Position {
let x: Int
let y: Int
}

protocol Movable {
func move(direction: Direction, distance: Int)
}

protocol VehicleType {
var hoursePower: Int { get set }
var price: Double { get set }

init(position: Position)
}

class Car: VehicleType, Movable {
var hoursePower: Int = 200
var price: Double = 4000.25

required init(position: Position) {
self.position = position
}

func move(direction: Direction, distance: Int) {
switch direction {
case .Up: position.y += distance
case .Down: position.y -= distance
case .Left: position.x -= distance
case .Right: position.x += distance
}
}

使用protocol的組合可以讓class的可讀性增加,並且更有彈性。

參考來源:
What the 55 Swift Protocols taught me
官方文件

Swift Value and Reference Types

初次接觸Swift時,少了pointer的機制,讓人好奇過去function call的call by value, call by reference是否有所不同
而Swift除了class外,其餘資料型態皆為value type。
這篇文章整理Swift Value and Reference type的差異

Value Type

當copy value type資料時,Swift其實是創造一份獨立的”拷貝”,任何改變原本變數的值不會影響拷貝的資料

1
2
3
4
5
struct Person { var name: String = "Alex" }
let somePerson = Person()
let otherPerson = somePerson
somePerson = "John"
print("\(somePerson.name), \(otherPerson.name)) // print John, Alex

當somePerson的name被改變時,otherPerson並不受影響

Reference Type

當copy reference type資料時,Swift只是增加一個reference指向原本的資料

1
2
3
4
5
class Person { var name: String = "Alex" }
let somePerson = Person()
let otherPerson = somePerson
somePerson = "John"
print("\(somePerson.name), \(otherPerson.name)) // print Alex, Alex

由以上例子可知,在使用function時要注意是否需要改變傳入的值
因為大多數的資料型態為value type,傳入function後無法修改原本的值
在multi-thread的環境中,這也一定確保資料的完整性,降低debug的困難度

如何選擇?

使用value type:

  1. 比較拷貝的資料(==)是合理行為
  2. 拷貝的複本有獨立的狀態
  3. 資料可能會被多個執行緒使用

使用reference type:

  1. 比較拷貝的識別(===)是合理的行為
  2. 想創造可共享和改變的狀態

參考來源:
Apple官方文件

Swift Protocol 介紹

在design pattern中,interface是一個重要的概念。
它能夠將class之間共通但些許不同的行為提升到更高的層次,解決物件導向裡容易誤用繼承的問題
而Swift提供protocol做為interface和資料型態,讓物件導向設計變得更直觀

Declare

1
2
3
4
5
6
7
8
9
10
11
12
13
14
protocol ExampleProtocol {
var someProperty: String { get } // someProperty is gettable
var otherProperty: String { get set} // otherProperty is gettable and settable
func concatenate(x: String, y: String) -> String
}

class TestClass: ExampleProtocol {
var someProperty: String = "A test class"
var otherProperty: String

func concatenate(x: String, y: String) -> String {
return "\(x) \(y)"
}
}

protocol的property可宣告為gettable或settable,後者同時也可使用get
而function只需要宣告輸入參數和回傳型態,不需要實作

class一旦使用protocol,就必須提供其property和function的實作。

Usage

使用protocol在實務上有什麼好處呢?
假如我們有二個class,Employee和HourlyEmployee,分別為正職和計時人員。 以及計算員工薪資的function

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class Employee {
let name: String
let address: String

init(name: String, address: String) {
self.name = name
self.address = address
}

func pay() -> (salary: Double, bonus: Double) {
return (3000, 400)
}
}

class HourlyEmployee: Employ {
var hourlyWage: Double = 15.00
var hoursWorked: Double = 8

override pay() -> (salary: Double, bonus: Double) {
return (hourlyWage * hoursWorked, 0)
}
}

func payEmployee(employee: Employee) {
let paycheck = employee.pay()
}

假如之後程式修改時,不小心將HourlyEmployee的pay刪除,則呼叫payEmployee時會回傳Employee的結果
這在程式碼增長的情況下是很容易出現的bug

使用protocol可減少此類bug的發生

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
protocol Payable {
func pay() -> (salary: Double, bonus: Double)
}

class Employee: Payable {
//略
func pay() -> (salary: Double, bonus: Double) {
return (3000, 400)
}
}

class HourlyEmployee: Payable {
//略

override pay() -> (salary: Double, bonus: Double) {
return (hourlyWage * hoursWorked, 0)
}
}

func payEmployee(employee: Employee) {
let paycheck = employee.pay()
}

假如HourlyEmployee的pay function被刪,complier會提示error。

Types

protocol不只能當interface使用,也能做type結合array、dictionary使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
protocol Flyable {
func fly()
}

class Human {
let name: String
}

class Bird: Flyable {
let name: String

func fly() {
print("I believe I can fly!")
}
}

func makeActions(action: [Flyable]) {

}

let human = Human(name: "John")
let sparrow = Bird(name: "GOT")

let animals: [Flyable] = [human, sparrow] // Error, human is not Flyable

指定animals為Flyable的array時,compiler會檢查array的值是否符合
這也類似有經驗的C/C++ programmer會將使用enum當function的參數,否則各參數都是int時,容易讓使用者誤用
protocol能幫我們減少無謂的bug及提高程式碼的可讀性

Swift Tuples介紹

在objective-c當中,如果method需要回傳二個以上的值,則可以使用帶有二個property的object或dictionary
而Swift新增tuples此資料型態,可一次回傳多個值

Unnamed Tuples

1
2
3
4
let stockProfolio = (3000, 3.4)

stockProfolio.0 // 3000
stockProfolio.1 // 3.4

此為無識別字的tuple,在存取時不方便使用。
我們可透過以下方式給予識別

1
2
3
let (share, price) = stockProfolio
share // 3000
price // 3.4

Named Tuples

上述的使用方式不夠直觀,建議使用named tuples增加可讀性

1
2
3
let stockProfolio = (share: 3000, price: 3.4)
stockProfolio.share // 3000
stockProfolio.price // 3.4

Swift Class 介紹

Class 宣告

Swift宣告class相當簡單,只要使用class這個關鍵字和class名稱

1
2
3
class TaxCalculator {

}

跟objective-c的宣告方式比較,不需要繼承NSObject或?生的類別。

Property

class的property則需要賦與初始值或使用init做初始化

1
2
3
4
5
6
7
8
9
10
11
class TaxCalculator {
let salary: Int
let taxRate: Double
var tax: Int

init(salary: Int, taxRate: Double) {
self.salary = salary
self.taxRate = taxRate
tax = Int(salary * taxRate)
}
}

由於init的參數和property名稱一樣,所以使用self來區別
另一個要注意的是,class可以有多個init函式,但使用的參數必須不同 (和c的overloading function一樣)

Methods

1
2
3
4
5
6
class TaxCalculator {
// 略
func calcSalary(tax: Int, taxRate: Double) -> Int {
return Int(tax / taxRate)
}
}

method的宣告需要加上回傳值的資料型態,若無回傳值可省略。

使用

1
2
let taxCalc = TaxCalculator(salary: 55000, taxRate: 0.15)
taxCalc.calcSalary(tax: 2000, taxRate: 0.15)

參考來源:
Swift 2 Tutorial

Swift 基本型態筆記

let vs. var

一開始接觸新語言時,首先會碰到各種資料型態的用法。
在c/c++ java這些較早的靜態語言,資料型態的宣告是一件需要斤斤計較的事
特別是資源有限的平台,變數如果佔太大的記憶體會是潛在的問題,更別說因裝入超過表示範圍的值產生的bug

Swift為變數宣告提供二種方式: let和var
前者為constant,也就是不可變動的值;後者則是一般的宣告
一般是建議能使用let就使用,complier會自動做最佳化的動作

如果想特別指定資料型態,也可用

1
2
let total : Int = 0  // 強制宣告total為Int
let taxRate = 0.13 // 一般的宣告方式

但如果沒特殊理由,使用預設的宣告方式以加強可讀性

Array and Dictionaries

1
2
3
let taxRate = [0.15, 0.18, 0.20]		// array
let emptyArray = [String]() // create an empty array with String type
var arr = [[Int]]() // 2 dimensional array of arrays of Ints

另一個資料型態為dictionary,這個在Python、Ruby都有類似的型態。
基本上就是一個key搭配value,可供建表及查循

1
var dict = [Int: Double]()					// dictionary

參考來源:
Swift 2 Tutorial

[Code School] Rails API Routing Note

假如web application需要提供API時,為了讓routing有效率及提升routing table的可讀性
原本的routes.rb如下

routes.rb
1
2
3
4
5
6
7
Rails.application.routes.draw do	
resources :posts # http://your-domain/posts

constraints subdomain: 'api' do
resources :contents # http://api.your-domain/contents
end
end

為了讓controller的目錄更有組織性,可以加上namespace

routes.rb
1
2
3
4
5
6
7
8
9
Rails.application.routes.draw do	
resources :posts

constraints subdomain: 'api' do
namespace :api do
resources :contents # http://api.your-domain/api/contents
end
end
end

雖然controller目錄變乾淨,但URL卻多出個api/contents。
加入path

routes.rb
1
2
3
4
5
6
7
8
9
Rails.application.routes.draw do	
resources :posts

constraints subdomain: 'api' do
namespace :api, path: '/' do
resources :contents # http://api.your-domain/contents
end
end
end

最後用一行搞定

routes.rb
1
2
3
4
5
6
7
Rails.application.routes.draw do	
resources :posts

namespace :api, path: '/', constraints: { subdomain: 'api' } do
resources :contents
end
end