好吧,讓我們從頭來過。什麼是對象編程編程呢?

先讓我們從上往下理解:人是一個喜歡歸類的動物,所以有生物學上的綱目。綱,即是我們編程里的基類(也叫父類);目,即是我們編程里的派生類(也叫子類)。基類與派生類是相對而言的,但跟綱目一樣,都是以相同特徵、行為來劃分的集合,同時派生類是基類特徵、行為的延伸與拓展。特徵在這裡叫屬性,行為在這裡叫方法,這也是類的基本。另外,同生物學上的趨同進化一樣,不同的類需要共同的方法時,我們使用了協議。所以協議是一種誇類別的同一行為。再讓我們從下往上理解:當多行語句需要重複使用,我們把它們歸集在一起,稱為函數。當多個函數需要聯合使用時,我們又把它們歸集在一起,有的稱為,有的稱為。單說類這邊,為了有所區別,只好稱類裡面的函數為方法,共用的變數為屬性。到了這一步,我們只能基於集合(類)調用方法與屬性了,稱為封裝;而有些方法、屬性只是用於內部流程,所以有多事的分了私有公有;用著用著,感覺以前的函數、變數能全局範圍里直接使用也挺好,於是來一個靜態方法、靜態變數;當類多了起來,又看到某些類有共同部分,於是又把它歸集出來,稱為基類;剩下各自不同的部分稱為派生類;派生類不想把相同的部分都寫上,於是用繼承;即然它們有共同的部分,派生類一定範圍上可作基類用,稱為多態;基類的部分方法只做約定,於是有了抽象方法;有抽象方法的類叫抽象類;忽然發現某派生類繼承的方法不適當怎麼辦?那重寫唄,所以有重寫;一個類不僅與甲有相同部分還與乙也有相同部分,怎麼辦?那就來個多繼承;哎呀呀,多繼承又出錯了,怎麼辦?精簡,跟乙的相同部分不能太具體,好,就這樣定了,再改過名字稱為協議。同樣的事情要針對不同的數據類型寫好幾個方法稱為重載;這樣太麻煩了,搞出個泛型……。之如此類,所以我一直認為類是過度集合的產物。新的編程語言,有一些不再拘泥面向對象編程,它們同時支持函數式編程與面向對象編程。Swift是其中之一,所以我們是幸福的。

到這裡,我們將開始軍團化作戰訓練。你沒看錯,面向對象編程,就是程序開發中的軍團化作戰。所以你就是那未來的海陸空三軍(Enumeration 枚舉、Structure 結構體、 Class 類)總司令。矮油,統一全宇宙的任務就交給你了。

所以,這一章我們將學習Enumeration 枚舉、Structure 結構體、 Class 類。讓我們一起加油吧!

慢著,如果你有面向對象編程的開發經驗,你可能只需要了解下面12點。其中Enumeration 枚舉有4點,Structure 結構與Class 類有6點,其餘的有2點。

Enumeration 枚舉、Structure 結構體,是值類型,Class是引用類型。值類型定義為常量後不能修改其元素值,引用類型則可以。

Structure 結構體、Class 類,最大的不同之處在於前者不能繼承,其餘的在應用上基本相同。泛型、協議上與其它語言沒什麼區別。
  1. Enumeration 枚舉可定義為不同類型。

enum Direction1{
case east, south, west, north
}
// print(Direction1.north.rawValue)
// Value of type Direction1 has no member rawValue

enum Direction2:Int{
case east = 1, south, west, north
}
print(Direction2.north.rawValue)
// Prints 4

enum Direction3:String{
case east, south, west, north
}
print(Direction3.north.rawValue)
// Prints north

2. Enumeration 枚舉可以嵌套。

enum LoopDoll{
enum Disneyland{
case A
case B
}
enum Barbie{
case X
case Y
}
}

3. Enumeration 枚舉可使用參數,這一點極大豐富了枚舉的應用。

enum Name{
case first(String)
case last(String)
}

var name = Name.first("Jiang")

switch name {
case .first(let s):
print("Your first name is (s)")
case .last(let s):
print("Your last name is (s)")
}
// Prints Your first name is Jiang

4. Enumeration 枚舉可使用遞歸 ,方法是:枚舉使用的參數,設置其類型為當前枚舉類型。

enum Climb{
case level(Int)
indirect case upper(Climb)
}

func motion(_ climb:Climb)->Int{
switch climb {
case .level(let i):
return i
case .upper(let start):
return motion(start) + 1
}
}

let level = Climb.level(12)
let upper = Climb.upper(level)
let m = motion(upper)
print(m)
// print 13

5. Structure 結構體 、Class 類,使用init作構造函數,使用self指實例自身。

struct Student {
let name:String
init(name: String){
self.name = name
}
}

6. Structure 結構體 、Class 類, init方法可重載,只需要參數名不同即可。init方法中重載的需要調用Designated的,其中Class 類中需要使用關鍵詞convenience.

struct Student {
let name:String
init(name: String){ // Designated
self.name = name
}
init(alias: String){ // convenience
self.init(name: alias)
}
}

class Student{
let name:String
init(name: String){ // Designated
self.name = name
}
convenience init(alias: String){ // convenience
self.init(name: alias)
}
}

7. Structure 結構體 、Class 類,不初始化的變數需添可選值符號「?」。未標記的則需在初始化時賦值。

struct Student {
let name:String
var arg:Int? // 可選值符號
init(name: String){
self.name = name
}
}

8. Structure 結構體 、Class 類,支持subscript 下標語法。使用字典或數組的方式賦值與取值。

class City{
var province:String
var info:Dictionary<String, String>?
init(province: String){
self.province = province
self.info = [:]
}
// 這裡是使用下標語法
subscript(city: String) -> String {
get {
return self.info?[city] ?? ""
}
set {
self.info?[city] = newValue
}
}
}

let city = City(province:"HuBei")
city["Daye"] = "Daye is a top 100 city"
print(city.province)
// Prints HuBei
print(city["Daye"])
// Prints Daye is a top 100 city

9. Structure 結構體 、Class 類, 支持Extension 拓展。使用關鍵詞extension對已有結構體與類實現新的功能。

extension Int {
func add(_ number:Int)-> Int {return self + number }
}
print(8.add(10))
// Prints 18

10. Structure 結構體 、Class 類,支持Delegate 委託 、 Protocol 協議。Delegate 委託在實際開發中需要使用,這裡介紹一下。

/** 1. 定義一下協議 **/
protocol MyDelegate {
func doSomething(str:String) -> ()
}

/** 2. 應用這個協議 **/
class Part {
var delegate:MyDelegate?
func show() {
delegate?.doSomething(str: "Hello world!")
}
}

/** 3. 實現這個協議 **/
class Layout: MyDelegate {
let part:Part
init(){
self.part = Part()
part.delegate = self;

}
func doSomething(str: String) {
print("Layout: " + str)
}
}

let layout = Layout()
layout.part.show()
// Prints Layout: Layout: Hello world!

11. Optional Chaining 可選鏈,使用「?」、「!」。這個非常強大。「?」表示:沒有返回nil,有返回值。「!」表示:確定有,沒有返回系統錯誤。

let result = ["data":["user":["name":"Jiang Youhua"]]]
if let name = result["data"]?["user"]?["name"] {
print("Welcome to (name)")
}else{
print("Result is error")
}
// Prints Welcome to Jiang Youhua

12. as,is 。as的作用:從子類對象轉換為父類對象,向上轉型使用;消除二義性,數值類型轉換。is的作用:判斷某個對象是否是某個特定類的對象。

let result:Dictionary<String, Any> = ["code":1, "info":"succeed", "data":["id":1, "username":"Jiang Youhua","role":1]]
if let data = result["data"] as? [String: Any] {
let name = data["username"]as! String
print("Welcome to (name)")
}else{
print("Json turned to Dictionary error")
}
// Prints Welcome to Jiang Youhua

好,我們還是假裝以前沒學過編程,較細緻的來一遍。主要涉及Enumeration 枚舉、Structure 結構體、Class 類型。在Swift中Enumeration 枚舉、Structure 結構體經過了強化,能承擔更多的工作。

一、Enumeration 枚舉,枚舉是有並列特性的數據的集合。

Enumeration 枚舉最原始的作用是:讓數據具有可讀性。通過下面示例可以看出枚舉在這方面的優勢。

/** 定義方向枚舉 **/
enum Direction{
case east // 東方
case south // 南方
case west // 西方
case north // 北方
}

/** 應用枚舉 **/
let direction = Direction.south
switch direction {
case .east:
print("Direction in the east")
case .south:
print("Direction in the south")
case .west:
print("Direction in the west")
case .north:
print("Direction in the north")
}
// Prints Direction in the south

Enumeration 枚舉在Swift支持不同的數據類型。這個可能是為支持參數化與嵌套的產物,否則我沒看出其有積極意義。

/** 定義Int值的枚舉 **/
enum Direction1:Int{
case east = 1, south, west, north
}
print(Direction1.north.rawValue)
// Prints 4
// 定義Int值的枚舉,後續元素按順序賦值。

/** 定義String值的枚舉 **/
enum Direction2:String{
case east, south, west, north
}
print(Direction2.north.rawValue)
// Prints north
// 定義String值的枚舉,元素值為元素名的字元串。

Enumeration 枚舉嵌套,嵌套讓枚舉有了多重歸集的功能。

/** 枚舉嵌套 **/
enum LoopDoll{
enum Disneyland{
case A
case B
}
enum Barbie{
case X
case Y
}
}
let doll = LoopDoll.Barbie.X // doll is X

Enumeration 枚舉支持元素帶參數,我把它近似的理解成函數類型。

/** 枚舉支持元素帶參數 **/
enum Barcode {
case upc(Int, Int, Int, Int)
case qrCode(String)
}

/** 參數在枚舉中傳遞 **/
let productBarcode = Barcode.qrCode("ABCDEFGHIJKLMNOP")
switch productBarcode {
case .upc(let numberSystem, let manufacturer, let product, let check):
print("UPC: (numberSystem), (manufacturer), (product), (check).")
case .qrCode(let productCode):
print("QR code: (productCode).")
}
// Prints QR code: ABCDEFGHIJKLMNOP.

Enumeration 枚舉支持元素帶參數,並形成遞歸應用。這一個要仔細看,有點繞。

/** 遞歸枚舉 **/
enum ArithmeticExpression {
case number(Int)
indirect case addition(ArithmeticExpression, ArithmeticExpression)
indirect case multiplication(ArithmeticExpression, ArithmeticExpression)
}
// indirect 可以不寫, 可以認為number, addition, multiplication帶了不同的數據

let five = ArithmeticExpression.number(5)
let four = ArithmeticExpression.number(4)
let sum = ArithmeticExpression.addition(five, four)
let product = ArithmeticExpression.multiplication(sum, ArithmeticExpression.number(2))

func evaluate(_ expression: ArithmeticExpression) -> Int {
switch expression {
case let .number(value): // 路由1
return value
case let .addition(left, right): // 路由2
return evaluate(left) + evaluate(right)
case let .multiplication(left, right): // 路由3
return evaluate(left) * evaluate(right)
}
}
let i = evaluate(product)
print(i)
// Prints 18

// 解釋:
// 1. 根據"路由1" 得到evaluate(.number(value)) = value。
// 2. 根據product的值,確定函數進入「路由3",返回evaluate(left) * evaluate(right)。同時確定left=sum right=.number(2)。
// 3. 根據解釋1和right=.number(2), 得出right = 2,
// 4. 根據sum的值,確定函數進入「路由2",返回evaluate(left) + evaluate(right)。同時確定left=five right=four
// 5. 根據解釋1和left=five, right=four, 得出five = 5, four = 4.
// 6. 根據解釋4,確定sum = 5 + 4, 所以得出sum = 9
// 7. 根據解釋2?6?3, 確定evaluate(product) = 9 * 2, 所以 i = 18

二、Structure 結構體 、 Class 類、Protocol協議。

  • Structure 結構體定義與調用。

/** Structure 結構體 **/
struct Point{
let x:Int // 屬性
let y:Int
init(x:Int, y:Int){ // 構造方法
self.x = x
self.y = y
}
func out(){ // 方法
print("Point.x is (self.x), point.y is (self.y)")
}
}
let point = Point(x: 120, y: 200) // 實例化
point.out() // 調用結構體的方法
// Prints Point.x is 120, point.y is 200

  • Class 類定義與調用。

/** Class 類 **/
class Point{
let x:Int // 屬性
let y:Int
init(x:Int, y:Int){ // 構造方法
self.x = x
self.y = y
}
func out(){ // 方法
print("Point.x is (self.x), point.y is (self.y)")
}
}
let point = Point(x: 120, y: 200) // 實例化
point.out() // 調用類的方法
// Prints Point.x is 120, point.y is 200

  • Protocol 協議定義。協議等待著被實現。

/** Protocol 協議 **/
protocol Point{
var x:Int {set get} // 屬性
var y:Int {set get}
func out() // 方法
}

  • Structure 結構體、Class 類,結構體與類的共同點:

Properties 屬性。可以有屬性,等同於變數與常量。

Methods 方法。可以有方法,等同於函數。Subscripts 下標語法。支持下標語法,類似於數組、字典的元素賦值與取值。Initialization 構造 。可以有構造函數,如果未定義,則系統分配init()作構造函數。Extensions 拓展。支持拓展,可向一個已有的類、結構體添加新功能。

Protocols 協議。支持實現協議。

下示例中用來說明Structure 結構體與Class 類相同部分。可將嘗試運行後,再將關鍵字struct換為class運行。

/** 協議 **/
protocol ManProtocol{
var age:Int { get set }
func say(anything:String)
}

/** 結構體與類相同的部分,可將關鍵詞struct換為class **/
struct Student:ManProtocol { // 支持實現協議
var age: Int // 有屬性
let name:String
var school:String{ // 只讀屬性
return "Experimental primary school"
}
var attribute:Dictionary<String, Any>
init(name: String, age:Int){ // 有init構造函數
self.name = name
self.age = age
self.attribute = [:]
}
subscript(key: String) -> Any { // 支持下標語法
get {
return self.attribute[key] ?? 0
}
set {
self.attribute[key] = newValue // 系統提供,相當於set(key, value)的value
}
}
func say(anything: String){ // 有方法
print("(self.name) said that (anything)")
}
}

extension Student{ // 支持拓展
func about() {
print("My name is (self.name)")
}
}

var student = Student(name: "Jiang Youhua", age: 18) // 初始化應用
student.say(anything: "who you are")
// Prints Jiang Youhua said that who you are
student["gender"] = "man" // 下標語法應用
let gender = student["gender"]
// gender is man
student.about() // 拓展的方法應用
// Prints My name is Jiang Youhua

  • Class 類,特有的特徵:

Inheritance 繼承,類有可繼承性。

Deinitialization 析構,通過析構函數來實現,類實例消毀時調用。Type Casting 類型轉換,判斷實例的類型,通過is, as將其看做是父類或者子類的實例。Automatic Reference Counting 自動引用計,所以納入了自動內存管理機制。

// 示例為讀取配置文件的類。將文件數據轉為配置字典未實現。

/** 基類,獲取配置 **/
class Config{
var setting:Dictionary<String,Any>
var path:String
init(path:String){
self.path = path
self.setting = [:]
}

subscript(key:String)->Any{
get {
return self.setting[key] ?? 0
}
set {
self.setting[key] = newValue
}
}

func formatConfig(){
print("This is base class")
}
}

/** 派生類,通ini文件獲取配置 **/
class IniConfig:Config { // 繼承
var fd : Int32? //文件描述符
override func formatConfig() { // 支持覆蓋
let ret = open(self.path, O_RDONLY) // 讀取文件,保留句柄
if ret == -1 {
fd = nil
}else{
fd = ret
}
// TODO // 讀文件內容山,轉為配置參數,這裡未實現
print("This is derived class")
}

deinit { // 支持解構
if let oft = fd {
close(oft) // 關閉文件句柄
}
print("Into IniConfig.deinit()")
}
}

var config = Config(path: "config")
config.formatConfig()
// Prints This is base class

if config is IniConfig {
print("config is the base class instance") // is 的使用,判斷是否為該類對象
}else{
print("config is not the base class instance")
}
// Prints config is not the base class instance

(IniConfig(path: "config.ini") as Config).formatConfig() // as 向上轉型,PlayGround中無效,
// Prints This is derived class // 調用子類方法
// Prints Into IniConfig.deinit() // 未賦給變數,所以及時在之內存中釋放,調用了析構函數

當你不需要繼承,Swift建議你使用Structure替代Class,這樣性能更高。

  • 除了上面大方向的相同與不同,其實在編寫時還存在下面一些相同與不同點:

當Structure 結構體、Class 類,沒有實現構造函數時,系統隱式的提供一個無參數的構造函數。

在Structure 結構體、Class 類中,以let定義的屬性,可以在構造函數里再賦值。我喜歡這個。在Structure 結構體、Class 類中,不想被初始化的屬性,需要使用var定義,並在最後加上"?"。在Structure 結構體、Class 類中,系統為subscript賦值,提供關鍵詞「newValue「,表示set(key, value)中發value。結構體實例賦予常量時,實例的屬性值不能改變,而類則可以。

結構體在沒有構造函數時也能使用參數進行實例,而類不可以。

結構體內部方法給其或其屬性賦值時,該方法需要添加」mutating「限定詞,Enum枚舉也一樣

Structure 結構體的應用實例:

struct Point{
let x:Double // 相同點:常量可以初始仳時賦值
let y:Double
var z:Double? // 相同點:不想在初始化的值需要加"?"
init(x:Double, y:Double) {
self.x = x
self.y = y
}
init(x:Double, y:Double, z:Double){ // 相同步:支持多構造函數
self.init(x: x, y: y)
self.z = z
}
}

extension Point{ // 相同步:支持拓展
init(width:Double, height:Double){ // 不同點:convenience 系統提供 Struct不需要
self.init(x: height, y: width)
}
}

struct Rect {
var width = 0.0
var height = 0.0
/** area屬性是Read-Only,只能讀,不能賦值 **/
var area: Double {
return width * height;
}
var origin: Point {
get {
return Point(x: height / 2, y: width / 2)
}
set {
width = newValue.y * 2 // 相同點 newValue 系統提供
height = newValue.x * 2
}
}
/** 內部方法改其屬性值時,需要添加mutating限定詞 **/
mutating func moveBy(width:Double, height:Double) { // 不同點 mutating 系統提供,Struct、Enum需要
self.width = width
self.height = height
}
}

let p = Point(x:15, y:15)
// p.z = 20 // 不同點 常量p,Struct不能改屬性值
// Cannot assign to property: x is a let constant的

var square = Rect(width: 90.0, height: 150.0) // 不同點 沒有構造函數,也能使用參數進行實例
print(square.origin.x, square.origin.y, square.width, square.height)
// Prints 75.0 45.0 90.0 150.0

square.origin = p
print(square.origin.x, square.origin.y, square.width, square.height)
// Prints 15.0 15.0 30.0 30.0

class 類的應用實例。

class Point{
let x:Double // 相同點:常量可以初始仳時賦值
let y:Double
var z:Double? // 相同點:不想在初始化的值需要加"?"
init(x:Double, y:Double) {
self.x = x
self.y = y
}
convenience init(x:Double, y:Double, z:Double){ // 相同步:支持多構造函數
self.init(x: x, y: y)
self.z = z
}
}

extension Point{ // 相同步:支持拓展
convenience init(width:Double, height:Double){ // 不同點:convenience 系統提供 Class需要
self.init(x: height, y: width)
}
}

struct Rect {
var width = 0.0
var height = 0.0
/** area屬性是Read-Only,只能讀,不能賦值 **/
var area: Double {
return width * height;
}
var origin: Point {
get {
return Point(x: height / 2, y: width / 2)
}
set {
width = newValue.y * 2 // 相同點 newValue 系統提供
height = newValue.x * 2
}
}
/** 內部方法改其屬性值時,需要添加mutating限定詞 **/
mutating func moveBy(width:Double, height:Double) { // 不同點 mutating 系統提供,Struct、Enum需要
self.width = width
self.height = height
}
}

let p = Point(x:15, y:15)
p.z = 20 // 不同點 常量p,Class能改屬性值
// Cannot assign to property: x is a let constant的

var square = Rect(width: 90.0, height: 150.0) // 不同點 沒有構造函數,也能使用參數進行實例
print(square.origin.x, square.origin.y, square.width, square.height)
// Prints 75.0 45.0 90.0 150.0

square.origin = p
print(square.origin.x, square.origin.y, square.width, square.height)
// Prints 15.0 15.0 30.0 30.0

  • class 類的繼承,Designated、Convenience、Required。

Designated:是Swift走向有序化的最重要的一步。保證初始化時非選值屬性被賦值。

Convenience:一種便捷方式。原則,是需要調用本類的Designated的初始化方法。Required:確定該初始方法一定要在派生類中重寫。

/** 基類 **/
class Fish{
let weight:Double
var length:Double?

// a designated initializer.
init(weight:Double) {
self.weight = weight;
}

// every subclass of the class must implement that initializer
// A convenience initializer must call another initializer from the same class.
// A convenience initializer must ultimately call a designated initializer.
required convenience init(weight:Double, length:Double){
self.init(weight: weight)
self.length = 0.01
}

func move(){
print("move fast")
}
}

/** 派生類 **/
class Clownfish:Fish{
var color:String
// Setting a Default Property Value with a Closure or Function
var age:UInt = {
return 8
}()
// A designated initializer must call a designated initializer from its immediate superclass.
init(weight:Double, color:String){
self.color = color
super.init(weight: weight)
}

// a convenience initializer.
required convenience init(weight: Double, length: Double) {
self.init(weight:weight, color:"red")
self.length = 0.01
}

func sound(){
print("(self.weight ) kg of (self.color) fish is called zi~~~")
}
// 覆蓋了基類的方法
override func move() {
print("(self.weight ) kg of (self.color) fish move slow")
}
// 析構函數
deinit {
// TODO
}
}

let clown = Clownfish(weight:0.02, color:"blur")
clown.move()
clown.sound()
// Prints 0.02 kg of blur fish move slow
// Prints 0.02 kg of blur fish is called zi~~~

  • 協議與委託。

我們定義了一個聊天的組件,它將應用於聊天室的頁面上。

我們需要像下面這樣定義委託,才能使用組件上輸入的信息顯示在頁面上。

/** 1. 定義一下協議 **/
protocol ChatDelegate {
func submitContent(str:String) -> ()
}

/** 2. 應用這個協議 **/
class ChatPart {
var delegate:ChatDelegate?
func show(word:String) {
delegate?.submitContent(str: word)
}
}

/** 3. 實現這個協議 **/
class ChatRoom: ChatDelegate {
let part:ChatPart
init(){
// 添加組件到頁面上
self.part = ChatPart()
// 在頁面上監聽該組件的委託
part.delegate = self;

}
// 實現委託定的協議方法,用來處理組件傳來的數據
func submitContent(str: String) {
print("Layout: " + str)
}
}

let room = ChatRoom()
room.part.show(word:"Hi, Jiang Youhua")
// Prints Layout: Hi, Jiang Youhua

  • init?、init! 可能失敗的初始化。

init? 表示可失敗初始化器,失敗的時候,return nil。

init! 表示可失敗初始化器,只不過這個失敗要求觸發斷言。

import MySQL
class DB {
let mysql:Database
init?(){
do {
mysql = try Database(host:"localhost",
user:"root",
password:"root",
database:"test")
try mysql.execute("SELECT @@version")
} catch {
print("Unable to connect to MySQL: (error)")
return nil
}
}
}

let db:DB? = DB()
// 這個db可能是空

  • Property Observer 屬性觀察員,提代兩個方法willSet、didSet來監聽屬性值的變化。

class observer {
var number: Int = 0 {
willSet(value) {
print("~~~", value, number)
}
didSet {
print("...", number, oldValue) // oldValue 系統提供
}
}
}
let obj = observer()
obj.number = 10
// Prints
// ~~~ 10 0
// ... 10 0

  • static 靜態屬性,理論是一種全局數據,通過定義的名稱來調用。

struct SomeStruct {
static var store = "Some value."
static var compute: Int {
return 1
}
}
enum SomeEnum {
static var store = "Some value."
static var compute: Int {
return 6
}
}
class SomeClass {
static var store = "Some value."
static var compute: Int {
return 27
}
}
print(SomeStruct.store, SomeStruct.compute) // Prints Some value. 1
print(SomeEnum.store, SomeEnum.compute) // Prints Some value. 6
print(SomeClass.store, SomeClass.compute) // Prints Some value. 27

  • private 、public、open,修飾類的屬性與方法。

private 當前類內使用

public 可以在其他作用域被訪問,但是不能在override、extension中被訪問open 可以在其他作用域被訪問

三、一些語法糖特徵:

  • lazy 延遲載入,用的時候才賦值(分配內存),相當於這個事就這麼定了,到用的時間再處里。

let numbers = 1...3
/** 常規 **/
let map1 = numbers.map { (i: Int) -> Int in
print("...",i)
return i + 3
}
// Prints
// ... 1
// ... 2
// ... 3

/** lazy,這事就這麼定了,沒有處理 **/
let map2 = numbers.lazy.map { (i: Int) -> Int in
print("~~~",i)
return i + 3
}
// 沒有被調用,所以沒有輸出

/** 遍歷map1 **/
for i in map1 {
print("...",i)
}
// Prints
// ... 4
// ... 5
// ... 6

/** 遍歷map2, 現在一起處理吧 **/
for i in map2 {
print("~~~",i)
}
// Prints
// ~~~ 1
// ~~~ 4
// ~~~ 2
// ~~~ 5
// ~~~ 3
// ~~~ 6

  • 可選型鏈。

使用「?」,這是我最喜歡的語法糖。特別是從Go的JSON解析中走出來的人,在這裡再也不用一層層去判斷了。

「?」是判斷行不行,不行就返nil,行就繼續。「!」是一定可以的,不行則返回系統錯誤。

/** 多層的Dictionary, 用Go里想死的心都有,在這裡就簡單多了,那一層沒有,就直接回空。 **/
var countrys = ["china" : ["hubai" : ["daye" : ["ID1997":["name":"JiangYouhua"]]]]];
if let name = countrys["china"]?["hubai"]?["daye"]?["ID1997"]?["name"]{
print("Your name is (name)")
}else{
print("Without your name")
}
// Prints Your name is JiangYouhua

/** 類里的應用 **/
class Stationery{
let name: String
init(name: String) {
self.name = name
}
}

class Student{
let name: String
init(name: String){
self.name = name
}
var stationery : Stationery?
}

let student = Student(name: "Jiang YouHua")
student.stationery = Stationery(name: "pen")
if let stationery = student.stationery?.name{
print("(student.name)s (stationery)!")
}else{
print("Error")
}
// Prints Jiang YouHuas pen!

  • Error Handling 錯誤處理。錯誤處理有四種處理方式:

向調用者執出錯誤。使用調用者時,仍需要處理錯誤。

使用do-catch語句處理。完全處理錯誤。將錯誤作為可選值處理。使用try?,有錯誤,返回nil。無錯誤,返回值。斷言錯誤不會發生。使用try!,有錯誤,拋出系統錯誤,無錯誤,返回值。

下面是一個用戶在商店用積分購買商品提交訂單的示例。用戶購物時有四種狀態,未登錄、登錄但不是會員、會員購買的商品不需要積分,會員的積分不夠購買商品。本例用Error Handling來的四種方式來處理,當然你也可以用其它方式來處理。

import Cocoa

/** 一個購物的示例 **/
enum ShopError: Error { // 定義購買錯誤
case notLoggedError // 未登錄錯誤
case notMemberError // 不是會員錯誤
case freeToUseError // 免費使用
case insufficientAmountError(score:Int) // 積分不足錯誤
}

struct User{ // 定義用戶
var uid:Int // 用戶ID
var role:Int // 許可權值,許可權值為0,則為普通用戶,非會員
var score:Int // 積分值
}

class Shopping{ // 定義購買類
let user:User
var total:Int = 0
init(user:User){
self.user = user
}

// 處理購物單
func handleOrder(commodity:Dictionary<String, Int>)->(code:Int, info:String){
guard user.uid > 0 else {
return (code:1, info:"Not Logged in")
}
guard user.role > 0 else {
return (code:2, info:"Not a Member")
}

// 計算總價
for (_, i) in commodity {
self.total += i
}

guard self.total > 0 else {
return (code:3, info:"Free to Use")
}
guard user.score > total else {
return (code:3, info:"Insufficient Amount")
}
// TODO
return (code:0, info:"Purchase success")
}

// 提交購物單,處理伺服器返回結果
func submitoOrder(commodity:Dictionary<String, Int>)throws->Int{
let result = self.handleOrder(commodity: commodity)
switch result.code {
case 1:
throw ShopError.notLoggedError
case 2:
throw ShopError.notMemberError
case 3 :
throw ShopError.insufficientAmountError(score:user.score)
default:
return self.total
}
}
}

/** 處理方式一:錯誤向上層轉移,即未處理 **/
func buy1() throws {
let user = User(uid: 1997, role: 1, score: 0)
let order = ["pen":12,"book":17]
let shop = Shopping(user: user)
try shop.submitoOrder(commodity: order)
}
// buy1()
// 無法調用,因為上一層也需要處理錯誤
// Playground execution terminated: An error was thrown and was not caught.

/** 處理方式二:使用do-catch語句處理 **/
func buy2(){
let user = User(uid: 1997, role: 0, score: 0)
let order = ["pen":12,"book":17]
let shop = Shopping(user: user)
do {
try shop.submitoOrder(commodity: order)
}catch ShopError.notLoggedError{
print("You are not logged in, please login")
}catch ShopError.notMemberError{
print("You are not a member, please join the membership")
}catch ShopError.insufficientAmountError(let score){
print("Your score are not enough, the score is only 10 (score)")
}catch{
print("Unexpected error: (error).")
}
}
buy2()
// 處理了所有錯誤
// Prints You are not a member, please join the membership

/** 處理方式三:將錯誤作為可選值處理, **/
func buy3(){
let user = User(uid: 1997, role: 1, score: 200)
let order = ["pen":12,"book":17]
let shop = Shopping(user: user)
if let result = try? shop.submitoOrder(commodity: order){
print("Please pay (result) yuan")
}
}
buy3()
// 有錯就返回nil
// Prints Please pay 29 yuan

/** 處理方式四:斷言錯誤不會發生 **/
func buy4(){
let user = User(uid: 1997, role: 1, score: 1000)
let order = ["pen":12,"book":17, "toy":479]
let shop = Shopping(user: user)
let result = try! shop.submitoOrder(commodity: order)
print("Please pay (result) yuan")
}
buy4()
// 有錯就產生系統錯誤,Fatal error: try! expression unexpectedly raised an error.
// Prints Please pay 508 yuan

  • 泛型,是為了解決類似於方法重載的問題。

/** 定義一個交換值的方法, 需要為Int,Double之類各寫一個, 共計n個 **/
func swapTwoInt(_ a: inout Int, _ b: inout Int) {
let temporaryA = a
a = b
b = temporaryA
}

func swapTwoStrings(_ a: inout String, _ b: inout String) {
let temporaryA = a
a = b
b = temporaryA
}

// ……

/** 只需要定義一個泛型的, 1 = n **/
func swapTwoValues<T>(_ a: inout T, _ b: inout T) {
let temporaryA = a
a = b
b = temporaryA
}

var num1 = 100
var num2 = 200
swapTwoValues(&num1, &num2)
// num1 is 200, num2 is 100

var str1 = "A"
var str2 = "B"
swapTwoValues(&str1, &str2)
// str1 = "B", str2 = "A"

泛型約束。類型約束指定了一個必須繼承自指定類的類型參數,或者遵循一個特定的協議或協議構成。

func someFunction<T: SomeClass, U: SomeProtocol>(someT: T, someU: U) {
// 這裡是泛型函數的函數體部分
}
// 上面這個函數有兩個類型參數。
// 第一個類型參數 T,有一個要求 T 必須是 SomeClass 子類的類型約束;
// 第二個類型參數 U,有一個要求 U 必須符合 SomeProtocol 協議的類型約束。

下一篇,MacOS界面設計。

讓我們在這裡,遇見明天的自己!姜友華


推薦閱讀:
相关文章