Swift相比於Objective-C的語法來說更加的酷炫,不過也更加的複雜的難懂,爲了將Swift的各類語法可以穿插起來,作一個綜合筆記,這裏實現一個應用多種Swift特性來實現的小功能。git
假設如今有一個零售水果的在線商店,須要頻繁的用到不一樣的重量單位,可是最終服務器都是以kg來計算和存儲,因此須要頻繁的將其餘的重量單位轉爲kg來作爲數據的存儲,又須要頻繁的將kg轉換成其餘的重量單位來做爲顯示。github
這裏採用封裝爲一個結構體的方式實現:編程
struct Kilogram {
var kg: Double
var lb: Double {
set {
self.kg = newValue * 0.45359237
}
get {
self.kg * 2.20462262
}
}
init(_ kg: Double) {
self.kg = kg
}
init() {
self.kg = 0
}
}
複製代碼
上面結構體中,只有kg是存儲屬性,而lb是計算屬性,這樣就實現咱們上面的需求:實現以kg爲基礎單位的存儲功能,同時實現其餘單位的換算。api
如今結構體能夠經過初始化方法初始化:bash
var w1 = Kilogram()
var w2 = Kilogram(2)
print(w1.kg, w1.lb) // 0.0 0.0
print(w2.kg, w2.lb) // 2.0 4.40924524
複製代碼
爲了更方便的直接經過字面量初始化,能夠實現對應類型的協議:服務器
struct Kilogram: ExpressibleByFloatLiteral, ExpressibleByIntegerLiteral, ExpressibleByStringLiteral, ExpressibleByNilLiteral {
var kg: Double
var lb: Double {
set {
self.kg = newValue * 0.45359237
}
get {
self.kg * 2.20462262
}
}
init(_ kg: Double) {
self.kg = kg
}
init() {
self.kg = 0
}
init(floatLiteral value: Double) {
self.kg = value
}
init(integerLiteral value: Int) {
self.kg = Double(value)
}
init(stringLiteral value: String) {
self.kg = Double(value) ?? 0
}
init(nilLiteral: ()) {
self.kg = 0
}
}
複製代碼
如今就能夠直接經過字面量初始化:app
var w1: Kilogram = nil
var w2: Kilogram = "1"
var w3: Kilogram = 2
var w4: Kilogram = 3
print(w1.kg, w2.kg, w3.kg, w4.kg) // 0.0 1.0 2.0 3.0
複製代碼
已知兩個Kilogram的實例,求他們的重量和,並得轉換爲lb是多少:函數式編程
var w1: Kilogram = 3
var w2: Kilogram = 4
var sum = w1.kg + w2.kg
var w3 = Kilogram(sum)
print(w3.kg, w3.lb)
複製代碼
Swift支持運算符重載,爲Kilogram結構體添加如下方法:函數
static func + (v1: Kilogram, v2: Kilogram) -> Kilogram {
Kilogram(v1.kg + v2.kg)
}
static func - (v1: Kilogram, v2: Kilogram) -> Kilogram {
Kilogram(v1.kg - v2.kg)
}
static func * (v1: Kilogram, v2: Kilogram) -> Kilogram {
Kilogram(v1.kg * v2.kg)
}
static func / (v1: Kilogram, v2: Kilogram) -> Kilogram {
Kilogram(v1.kg / v2.kg)
複製代碼
下面計算兩個Kilogram實例的和:ui
var w1: Kilogram = 3
var w2: Kilogram = 4
var w3 = w1 + w2
print(w3.kg, w3.lb) // 7.0 15.432358339999999
複製代碼
配合字面量協議支持複雜的運算:
var w1: Kilogram = 3
var w2: Kilogram = 4
var w3: Kilogram = 2
var w4 = w1 + w2 * w3 - 8 // 3 + 4 * 2 - 8 = 3
print(w4.kg, w4.lb) // 3.0 6.613867859999999
複製代碼
Swift的枚舉相比Objective-C增長很是多的新特性,關聯值就是其中之一,關鍵值能夠將枚舉的成員值和其餘類型的值關聯並存儲在一塊兒,一樣枚舉也支持範型、方法(包括初始化方法)、屬性(計算實例屬性、計算類型屬性、存書類型屬性)。
按照零售水果的開發需求,須要封裝一個水果類,這個水果類須要如下基本信息:名稱和單價,而因爲平臺稱重單位有兩種,因此單價便可能是kg也多是lb,這裏假設一種水果有且只有一種重量單位的計價方式。那麼這個類按照Objective-C的設計方式,起碼須要兩個屬性來表示單價:
typedef NS_ENUM(NSInteger, FruitPriceType) {
FruitPriceByKg,
FruitPriceByLb
};
@interface Fruit: NSObject
@property (nonatomic, assign) double price;
@property (nonatomic, assign) FruitPriceType priceType;
@end
複製代碼
經過Swift枚舉類型關聯值,只須要一個屬性就能夠實現:
class Fruit {
enum Price<Double> {
case kg(Double)
case lb(Double)
}
var name: String
var price: Price<Double>
init(name: String, price: Price<Double>) {
self.name = name
self.price = price
}
}
複製代碼
在須要計算總價時,只須要判斷枚舉的類型,取出對應關聯值便可:
var apple = Fruit(name: "🍎", price: .lb(3))
var appleWeight: Kilogram = 10
func originalPrice(_ fruit: Fruit, _ weight: Kilogram) -> Double{
switch fruit.price {
case let .kg(kg):
return kg * weight.kg
case let .lb(lb):
return lb * weight.lb
}
}
var price = originalPrice(apple, appleWeight)
print(price) // 66.13867859999999
複製代碼
下面開始實現水果的總價計算,當用戶選擇了必定重量的水果,須要根據重量計算出須要支付的總價,假如計算總價的規則以下:
單價 * 重量 * 折扣率 - 優惠金額
這裏假設有以下幾個拆分的計算總價的分步方法:
/// 原價 = 單價 * 重量
func originalPrice(_ weight: Kilogram, _ fruit: Fruit) -> Double{
switch fruit.price {
case let .kg(kg):
return kg * weight.kg
case let .lb(lb):
return lb * weight.lb
}
}
/// 折扣價 = 原價 * 折扣率
func discountPrice(_ originPrice: Double, _ rate: Double) -> Double {
return originPrice * rate
}
/// 最終價 = 折扣價 - 優惠金額
func reductionPrice(_ discountPrice: Double, _ reduction: Double) -> Double {
return discountPrice - reduction
}
複製代碼
假如當前蘋果的單價爲3/lb,折扣爲0.9,優惠金額固定爲3,那麼10kg蘋果的價格計算以下:
var apple = Fruit(name: "🍎", price: .lb(3))
var appleWeight: Kilogram = 10
var price = reductionPrice(discountPrice(originalPrice(apple, appleWeight), 0.9), 3) // 56.52481073999999
複製代碼
這樣的方法調用會嵌套不少層,可讀性差,而且很難體現出計算過程。
下面將上面三個分步計算方法分別重寫爲如下三個方法:
func originalPrice(_ fruit: Fruit) -> (_ weight: Kilogram) -> Double {
{
weight in
switch fruit.price {
case let .kg(kg):
return kg * weight.kg
case let .lb(lb):
return lb * weight.lb
}
}
}
func discountPrice(_ rate: Double) -> (_ originPrice: Double) -> Double {
{
originPrice in
rate * originPrice
}
}
func reductionPrice(_ reduction: Double) -> (_ discountPrice : Double) -> Double {
{
discountPrice in
discountPrice - reduction
}
}
複製代碼
將每個方法都重寫爲傳入一個參數,返回一個須要傳入一個參數而且有一個返回值的方法。
以originalPrice方法爲例:
func originalPrice(_ weight: Kilogram, _ fruit: Fruit) -> Double{
switch fruit.price {
case let .kg(kg):
return kg * weight.kg
case let .lb(lb):
return lb * weight.lb
}
}
func originalPrice(_ fruit: Fruit) -> (_ weight: Kilogram) -> Double {
{
weight in
switch fruit.price {
case let .kg(kg):
return kg * weight.kg
case let .lb(lb):
return lb * weight.lb
}
}
}
複製代碼
原方法須要傳入水果實例和重量兩個參數,返回對應的原價。重寫後方法改成:傳入水果實例,返回一個函數,這個函數傳入重量,而且返回原價。這樣至關於將一個函數拆分爲兩步返回,第一步返回一個函數,由第一步返回的函數完成最終的函數功能:
var price = originalPrice(apple, appleWeight) // 66.13867859999999
var fn = originalPrice(apple)
price = fn(appleWeight) // 66.13867859999999
複製代碼
接下來,在建立一個自定義運算符,功能就是將兩個函數合併成一個函數:
infix operator >>> : AdditionPrecedence
func >>><A, B, C>(_ f1: @escaping (A) -> B, _ f2: @escaping (B) -> C) -> (A) -> C {
{
a in
f2(f1(a))
}
}
複製代碼
將上面三個函數的計算過程,合併成一個函數:
var applePriceFuction = originalPrice(apple) >>> discountPrice(0.9) >>> reductionPrice(3)
複製代碼
接下來,計算10kg蘋果的重量,只須要一行代碼就能夠完成:
var price = applePriceFuction(10) // 56.52481073999999
複製代碼
上面將三個函數分別重寫後的形式,就是函數的柯里化。這裏將上面三個函數和自定義運算符函數,分別作語法糖的代碼簡化:
func originalPrice(_ fruit: Fruit) -> (Kilogram) -> Double {
{
switch fruit.price {
case let .kg(kg):
return kg * $0.kg
case let .lb(lb):
return lb * $0.lb
}
}
}
func discountPrice(_ rate: Double) -> (Double) -> Double {{ rate * $0 }}
func reductionPrice(_ reduction: Double) -> (Double) -> Double {{ $0 - reduction }}
infix operator >>> : AdditionPrecedence
func >>><A, B, C>(_ f1: @escaping (A) -> B, _ f2: @escaping (B) -> C) -> (A) -> C { { f2(f1($0)) } }
複製代碼
上面三個函數其實目的都是將一個傳入兩個參數的函數,柯里化成傳入一個參數,返回一個須要一次參數的函數,而函數的實際過程自己就是原函數的執行步驟,惟一不一樣的就是函數參數類型不一樣而已。針對這個共性,能夠將函數柯里化過程,封裝爲一個函數:
prefix func ~<A, B, C>(_ fn: @escaping (A, B) -> C) -> (B) -> (A) -> C {
{
b in
{
a in
fn(a, b)
}
}
}
複製代碼
該方法傳入一個有兩個參數和一個返回值的函數,A、B、C三個範型爲傳入參數的兩個參數和一個返回值的類型。返回一個新的函數,新的函數有一個參數和一個返回值。新的函數參數類型爲原函數第二個參數的類型。新的函數的返回值是一個須要傳入一個參數的函數,該函數傳入的參數類型爲原函數第一個函數的類型,返回值類型爲原函數的返回值類型。
這個函數的做用就是:將須要兩個A類型、B類型參數,並返回C類型值的原函數Z,轉變成另外一個函數X。X函數只有一個B類型參數,而且返回一個須要傳入A類型參數,而且返回C類型值的函數Y。在函數Y中,將X函數的B類型參數b,和Y函數中的A類型參數a,做爲原函數Z的兩個參數,調用原函數Z,而且返回C類型的返回結果。
將原來的分步柯里化函數都刪除或者註釋掉,直接使用柯里化函數來柯里化對應的函數,並將返回的函數直接組合:
var applePriceFuction = (~originalPrice)(apple) >>> (~discountPrice)(0.9) >>> (~reductionPrice)(3)
print(applePriceFuction("5")) // 26.762405369999996
print(applePriceFuction(4.5)) // 23.786164832999997
print(applePriceFuction(10)) // 56.52481073999999
複製代碼