用Swift開發macOS程式, 三、Swift 的進階
好吧,讓我們從頭來過。什麼是物件程式設計程式設計呢?
先讓我們從上往下理解:人是一個喜歡歸類的動物,所以有生物學上的綱目。綱,即是我們程式設計裡的基類(也叫父類);目,即是我們程式設計裡的派生類(也叫子類)。基類與派生類是相對而言的,但跟綱目一樣,都是以相同特徵、行為來劃分的集合,同時派生類是基類特徵、行為的延伸與拓展。特徵在這裡叫屬性,行為在這裡叫方法,這也是類的基本。另外,同生物學上的趨同進化一樣,不同的類需要共同的方法時,我們使用了協議。所以協議是一種誇類別的同一行為。
再讓我們從下往上理解:當多行語句需要重複使用,我們把它們歸集在一起,稱為
函式
。當多個函式需要聯合使用時,我們又把它們歸集在一起,有的稱為
庫
,有的稱為
類
。單說類這邊,為了有所區別,只好稱類裡面的函式為
方法
,共用的變數為
屬性
。到了這一步,我們只能基於集合(類)呼叫方法與屬性了,稱為
封裝
;而有些方法、屬性只是用於內部流程,所以有多事的分了
私有
與
公有
;用著用著,感覺以前的函式、變數能全域性範圍裡直接使用也挺好,於是來一個
靜態方法、靜態變數
;當類多了起來,又看到某些類有共同部分,於是又把它歸集出來,稱為
基類
;剩下各自不同的部分稱為
派生類
;派生類不想把相同的部分都寫上,於是用
繼承
;即然它們有共同的部分,派生類一定範圍上可作基類用,稱為
多型
;基類的部分方法只做約定,於是有了
抽象方法;
有抽象方法的類叫
抽象類;
忽然發現某派生類繼承的方法不適當怎麼辦?那重寫唄,所以有
重寫
;一個類不僅與甲有相同部分還與乙也有相同部分,怎麼辦?那就來個
多繼承
;哎呀呀,多繼承又出錯了,怎麼辦?精簡,跟乙的相同部分不能太具體,好,就這樣定了,再改過名字稱為
協議
。同樣的事情要針對不同的資料型別寫好幾個方法稱為
過載;
這樣太麻煩了,搞出個
泛型……。
之如此類,所以我一直認為類是過度集合的產物。
新的程式語言,有一些不再拘泥面向物件程式設計,它們同時支援函數語言程式設計與面向物件程式設計。Swift是其中之一,所以我們是幸福的。
到這裡,我們將開始軍團化作戰訓練。你沒看錯,面向物件程式設計,就是程式開發中的軍團化作戰。所以你就是那未來的海陸空三軍(Enumeration 列舉、Structure 結構體、 Class 類)總司令。矮油,統一全宇宙的任務就交給你了。
所以,這一章我們將學習Enumeration 列舉、Structure 結構體、 Class 類。讓我們一起加油吧!
慢著,如果你有面向物件程式設計的開發經驗,你可能只需要瞭解下面12點。其中Enumeration 列舉有4點,Structure 結構與Class 類有6點,其餘的有2點。
Enumeration 列舉、Structure 結構體,是值型別,Class是引用型別。值型別定義為常量後不能修改其元素值,引用型別則可以。
Structure 結構體、Class 類,最大的不同之處在於前者不能繼承,其餘的在應用上基本相同。
泛型、協議上與其它語言沒什麼區別。
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
}
(
Direction2
。
north
。
rawValue
)
// Prints 4
enum
Direction3
:
String
{
case
east
,
south
,
west
,
north
}
(
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
):
(
“Your first name is
\(
s
)
”
)
case
。
last
(
let
s
):
(
“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
)
(
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”
(
city
。
province
)
// Prints HuBei
(
city
[
“Daye”
])
// Prints Daye is a top 100 city
9. Structure 結構體 、Class 類, 支援Extension 拓展。使用關鍵詞extension對已有結構體與類實現新的功能。
extension
Int
{
func
add
(
_
number
:
Int
)->
Int
{
return
self
+
number
}
}
(
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
)
{
(
“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”
]
{
(
“Welcome to
\(
name
)
”
)
}
else
{
(
“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
(
“Welcome to
\(
name
)
”
)
}
else
{
(
“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
:
(
“Direction in the east”
)
case
。
south
:
(
“Direction in the south”
)
case
。
west
:
(
“Direction in the west”
)
case
。
north
:
(
“Direction in the north”
)
}
// Prints Direction in the south
Enumeration 列舉在Swift支援不同的資料型別。這個可能是為支援引數化與巢狀的產物,否則我沒看出其有積極意義。
/** 定義Int值的列舉 **/
enum
Direction1
:
Int
{
case
east
=
1
,
south
,
west
,
north
}
(
Direction1
。
north
。
rawValue
)
// Prints 4
// 定義Int值的列舉,後續元素按順序賦值。
/** 定義String值的列舉 **/
enum
Direction2
:
String
{
case
east
,
south
,
west
,
north
}
(
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
):
(
“UPC:
\(
numberSystem
)
,
\(
manufacturer
)
,
\(
product
)
,
\(
check
)
。”
)
case
。
qrCode
(
let
productCode
):
(
“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
)
(
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
(){
// 方法
(
“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
(){
// 方法
(
“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
){
// 有方法
(
“
\(
self
。
name
)
said that
\(
anything
)
”
)
}
}
extension
Student
{
// 支援拓展
func
about
()
{
(
“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
(){
(
“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 // 讀檔案內容山,轉為配置引數,這裡未實現
(
“This is derived class”
)
}
deinit
{
// 支援解構
if
let
oft
=
fd
{
close
(
oft
)
// 關閉檔案控制代碼
}
(
“Into IniConfig。deinit()”
)
}
}
var
config
=
Config
(
path
:
“config”
)
config
。
formatConfig
()
// Prints This is base class
if
config
is
IniConfig
{
(
“config is the base class instance”
)
// is 的使用,判斷是否為該類物件
}
else
{
(
“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
)
// 不同點 沒有建構函式,也能使用引數進行例項
(
square
。
origin
。
x
,
square
。
origin
。
y
,
square
。
width
,
square
。
height
)
// Prints 75。0 45。0 90。0 150。0
square
。
origin
=
p
(
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
)
// 不同點 沒有建構函式,也能使用引數進行例項
(
square
。
origin
。
x
,
square
。
origin
。
y
,
square
。
width
,
square
。
height
)
// Prints 75。0 45。0 90。0 150。0
square
。
origin
=
p
(
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
(){
(
“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
(){
(
“
\(
self
。
weight
)
kg of
\(
self
。
color
)
fish is called zi~~~”
)
}
// 覆蓋了基類的方法
override
func
move
()
{
(
“
\(
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
)
{
(
“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
{
(
“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
)
{
(
“~~~”
,
value
,
number
)
}
didSet
{
(
“。。。”
,
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
}
}
(
SomeStruct
。
store
,
SomeStruct
。
compute
)
// Prints Some value。 1
(
SomeEnum
。
store
,
SomeEnum
。
compute
)
// Prints Some value。 6
(
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
(
“。。。”
,
i
)
return
i
+
3
}
// Prints
// 。。。 1
// 。。。 2
// 。。。 3
/** lazy,這事就這麼定了,沒有處理 **/
let
map2
=
numbers
。
lazy
。
map
{
(
i
:
Int
)
->
Int
in
(
“~~~”
,
i
)
return
i
+
3
}
// 沒有被呼叫,所以沒有輸出
/** 遍歷map1 **/
for
i
in
map1
{
(
“。。。”
,
i
)
}
// Prints
// 。。。 4
// 。。。 5
// 。。。 6
/** 遍歷map2, 現在一起處理吧 **/
for
i
in
map2
{
(
“~~~”
,
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”
]{
(
“Your name is
\(
name
)
”
)
}
else
{
(
“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
{
(
“
\(
student
。
name
)
‘s
\(
stationery
)
!”
)
}
else
{
(
“Error”
)
}
// Prints Jiang YouHua’s 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
{
(
“You are not logged in, please login”
)
}
catch
ShopError
。
notMemberError
{
(
“You are not a member, please join the membership”
)
}
catch
ShopError
。
insufficientAmountError
(
let
score
){
(
“Your score are not enough, the score is only 10
\(
score
)
”
)
}
catch
{
(
“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
){
(
“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
)
(
“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介面設計。
讓我們在這裡,遇見明天的自己!姜友華