一: 可選鏈
之前寫的代碼都是這樣調(diào)用:
如果person
是可選類型,就會(huì)報(bào)錯(cuò):
解決這種報(bào)錯(cuò)有兩種方式,一種是在person
后面加感嘆號(hào)!
,進(jìn)行強(qiáng)制解包:
使用感嘆號(hào)!
強(qiáng)制解包這種做法不安全,一旦person
對(duì)象為nil
,程序就會(huì)崩潰.
另一種是加問號(hào)?
:
加?
的意思時(shí)會(huì)先判斷person
對(duì)象是否為nil
,如果為nil
,就不會(huì)再調(diào)用age
,直接返回nil
;如果person
對(duì)象不為nil
,才會(huì)調(diào)用age
,正常返回;那什么類型既能接受nil
,又能接受Int
呢?當(dāng)然是可選項(xiàng)類型.
所以,只要通過person?
調(diào)用的屬性,方法,下標(biāo)等等,它們的結(jié)果要么是nil
,要么就被包裝成可選項(xiàng)類型:
class Dog{
var name: String = "大黃"
}
class Cat{
var color: String = "橘色"
}
class Person{
var name: String = ""
var age: Int = 0
var dog: Dog = Dog()
var cat: Cat? = Cat()
func eat(){
print("eat")
}
func height() -> Int{
180
}
subscript(index: Int) -> Int{
index
}
}
var person: Person? = nil
var name = person?.name //String?
var age = person?.age//Int?
var color = person?.cat?.color //String?
var height = person?.height()// Int?
var index = person?[3] // Int?
上面代碼person?.cat?.color
使用多個(gè)?
鏈接在一起就被稱為可選鏈
.如果可選鏈的任何一個(gè)節(jié)點(diǎn)為nil
,整個(gè)調(diào)用就會(huì)失敗.
所以我們拿到調(diào)用結(jié)果使用的時(shí)候就要解包了,一般用可選項(xiàng)綁定判斷一下:
if let a = age{
print("age 的值為",a)
}else{
print("age 為nil")
}
同樣也可以判斷方法調(diào)用是否成功:
if let result = person?.eat(){
print("調(diào)用成功",result)
}else{
print("調(diào)用失敗")
}
eat()
方法并沒有返回值,為什么也可以用可選項(xiàng)綁定來判斷呢.因?yàn)闊o返回值就是Void
,而Void
的本質(zhì)就是空元組.所以如果eat()
調(diào)用成功就會(huì)返回一個(gè)空元組,調(diào)用失敗就返回nil
.所以可以使用可選項(xiàng)綁定判斷.
思考一下下面代碼的打印結(jié)果:
var a: Int? = 10
a? = 5
print("a",a)
var b: Int? = nil
b? = 5
print("b",b)
打印結(jié)果為a Optional(5)
, b nil
;因?yàn)?code>b?會(huì)先判斷b
是否為nil
,如果為nil
就不會(huì)執(zhí)行= 5
的賦值運(yùn)算.
二: 協(xié)議(Protocol)
swift 中的協(xié)議可以定義屬性 , 方法 , 下標(biāo)
的聲明,協(xié)議可以被結(jié)構(gòu)體,枚舉,類遵守.
我們?cè)诙x協(xié)議時(shí)要遵守以下規(guī)則:
協(xié)議定義屬性時(shí)只能用 var , 不能用 let
實(shí)現(xiàn)協(xié)議時(shí)的屬性權(quán)限要不小于協(xié)議中定義的屬性權(quán)限
如上圖所示,協(xié)議中定義的slogan
屬性權(quán)限是set , get
.而Person
實(shí)現(xiàn)協(xié)議時(shí)的屬性權(quán)限只有get
,就會(huì)報(bào)錯(cuò).
3.協(xié)議中的類型屬性,類型方法,類型下標(biāo)只能用 static 定義,不能使用 class
我們知道class
和static
都可以用來定義類型計(jì)算屬性,類型方法,類型下標(biāo).但是如果要在協(xié)議中定義類型方法,類型屬性只能用static
.因?yàn)閰f(xié)議可以被類,結(jié)構(gòu)體,枚舉遵守.而class
只能在類中使用.
- 如果要在值類型
(結(jié)構(gòu)體 , 枚舉)
實(shí)現(xiàn)的協(xié)議方法中修改自身內(nèi)存,必須在定義協(xié)議方法前加上mutating
關(guān)鍵字.不能在實(shí)現(xiàn)協(xié)議方法時(shí)添加,那樣無效.
- 協(xié)議中定義的初始化器
init
,非final
類實(shí)現(xiàn)協(xié)議時(shí)必須加上required
.
這樣做是為了保證所有的子類都有此初始化器.
協(xié)議的繼承
協(xié)議之間可以彼此繼承,一個(gè)協(xié)議可以繼承另一個(gè)協(xié)議.
協(xié)議的組合
可以通過不同的協(xié)議組合來規(guī)范參數(shù)類型或者返回值類型,也可以和類組合使用,但最多組合一個(gè)類
protocol Study {
func read()
}
protocol Sport{
func run()
}
class Person: Sport{
func run() {
}
func read() {
}
}
func test1(obj: Study){
//接受遵守Study協(xié)議的實(shí)例
}
func test2(obj: Study & Sport){
//接受遵守Study 和 Sport協(xié)議的實(shí)例
}
func test3(obj: Person & Study & Sport){
//接受遵守Study 和 Sport協(xié)議,并且是Person或者其子類的實(shí)例
}
注意: 只能用 & 組合協(xié)議,不能使用 | 組合協(xié)議,因?yàn)槭褂?| 組合還要單獨(dú)區(qū)分具體是哪個(gè)類,這樣做毫無意義.
Any , AnyObject
Swift 中提供了兩種特殊的類型Any
,AnyObject
Any
:可以代表任意類型(枚舉, 結(jié)構(gòu)體,類,函數(shù)類型
)
AnyObject
:代表任意類類型(也就是使用class
創(chuàng)建的任意類型)
例如創(chuàng)建一個(gè)可以添加任意類型的數(shù)組:
//添加任意類型的數(shù)組
var array = Array<Any>()
array.append(1) // Int
array.append("2")// 字符串
array.append(Person())// 類對(duì)象
array.append(Season.spring) // 枚舉
創(chuàng)建數(shù)組還有另一種寫法:
var array2 = [Any]()
array2.append("hello")
注意: 如果在協(xié)議后面加上 AnyObject 或者 class , 代表只有類類型才能遵守協(xié)議
is , as , as? , as!
is
: 判斷是否為某種類型
as
: 用來做強(qiáng)制轉(zhuǎn)換
如果使用as!
強(qiáng)制解包,沒有值的情況下會(huì)直接崩潰,所以我們解包的時(shí)候建議還是使用as?
as
: 如果能確定百分百轉(zhuǎn)換成功的,可以不加符號(hào),直接使用as
,比如:
var num1 = 10 as Float
var num2 = num1 as Any
注意: 任何類型都可以使用 as 轉(zhuǎn)換為 Any 類型,但是不能使用 as 轉(zhuǎn)換回去.必須使用 as? , as! 轉(zhuǎn)換回去.
比如:
X.self. , X.Type , AnyClass(X
表示的是類)
如下所示代碼:
class Person{
}
var p = Person()
它的內(nèi)存分布如下:
全局變量p
存儲(chǔ)的是Person
實(shí)例對(duì)象的前8個(gè)字節(jié)的地址.這8個(gè)字節(jié)就是Person
元類型地址.
X.self
:是一個(gè)元類型(metadata)
的地址,metadata
中存放的是類型相關(guān)的信息.
也就是說X.self
返回的是Person
實(shí)例對(duì)象的前8個(gè)字節(jié)中存放的內(nèi)存地址,我們將通過匯編證明這一點(diǎn).
實(shí)例代碼:
class Person{
}
var p = Person()
var personType = Person.self
以上代碼的匯編如下:
從上圖可以看到,personType
中存放的內(nèi)容和Person
對(duì)象的前8個(gè)字節(jié)的內(nèi)容相同.
X.self
是X.Type
類型的:
所以,也可以這樣定義:
元類型的繼承
元類型同樣也支持繼承,父類metatype
可以指向子類metatype
:
AnyObject.Type
: 上面講過AnyObject
是指任意類類型,所以AnyObject.Type
表示任意類類型的metadata
.
在 Swift 有這樣的定義:
public typealias AnyClass = AnyObject.Type
即AnyClass
也表示任意類類型的metaclass
type(of: <T>)
:傳入一個(gè)實(shí)例對(duì)象,返回這個(gè)實(shí)例對(duì)象的元類型.
type(of:)
表面上看起來很像一個(gè)函數(shù),但是我們窺探它的匯編發(fā)現(xiàn)它的本質(zhì)就是直接取出實(shí)例對(duì)象的前8個(gè)字節(jié),并沒有函數(shù)調(diào)用:
元類型的應(yīng)用
一: 通過類名創(chuàng)建控制器實(shí)例,類似于 OC 中的 xx.class
override func viewDidLoad() {
super.viewDidLoad()
var vcs = [UIViewController.Type]()
vcs.append(HomeViewController.self)
vcs.append(MineViewController.self)
for vc in vcs{
//通過元類型創(chuàng)建視圖控制器
let childVC = vc.init()
self.addChild(childVC)
}
}
二: 通過父類元類型,創(chuàng)建子類實(shí)例.注意:父類中的 init() 方法必須加上 required ,因?yàn)楸仨氁_保子類都有 init() 方法的實(shí)現(xiàn).
class Person{
required init(){
}
}
class Student: Person {}
class Teacher: Person {}
class Driver: Person {}
func createPerson(pTypes: [Person.Type]) -> [Person]{
var persons = [Person]()
for pType in pTypes{
persons.append(pType.init())
}
return persons
}
Self:一般用作方法返回值,用來限定方法返回值和方法調(diào)用者必須是同一類型,類似OC
中的instancetype
如上圖所示,test
是Person
類中的實(shí)例方法,返回值是Self
.即方法調(diào)用者和返回值必須是同一類型
.為什么返回Person
實(shí)例對(duì)象報(bào)錯(cuò)了呢?因?yàn)?Person 可能也會(huì)有子類,所以不能寫死
所以要這樣寫:
但是通過元類型創(chuàng)建實(shí)例對(duì)象必須要加上required
:
Self
也可表示當(dāng)前類型:
比如說下面代碼:
class Person{
var age = 0
static var name = "佚名"
func makeOneSelf(){
print("my name is \(Self.name) , age is \(self.age)")
}
}
Self
代表當(dāng)前類型,也就是Person
類型,所以能調(diào)用類型屬性;
self
表示調(diào)用方法的當(dāng)前實(shí)例對(duì)象.