1播揪、類(class)和結(jié)構(gòu)體(struct)有什么區(qū)別晒旅?
在Swift
中富稻,class
是引用類型掷邦,struct
是值類型。值類型在傳遞和賦值的過程中將進(jìn)行復(fù)制椭赋,而引用類型則只會(huì)使用引用對(duì)象的一個(gè)指向抚岗,所以兩者之間的主要區(qū)別還是類型的區(qū)別。
看個(gè)簡(jiǎn)單例子哪怔,代碼如下:
class Person {
var name: String
init(name: String) {
self.name = name
}
}
struct SomeStruct {
var name: String
}
簡(jiǎn)單使用
let person = Person(name: "Jack")
let anotherPerson = person
anotherPerson.name = "Justin"
print(person.name, anotherPerson.name) //Justin Justin
let structA = SomeStruct(name: "Lisa")
var structB = structA
structB.name = "Judy"
print(structA.name, structB.name) //Lisa Judy
由上面代碼可知宣蔚,Person
是class
類型即引用類型,所以當(dāng)修改anotherPerson
對(duì)象的name
屬性時(shí)候认境,原有person
對(duì)象的name
值也發(fā)生了改變胚委,而SomeStruct
是struct
,是值類型叉信,所以structB
對(duì)象的name
屬性改變并不會(huì)影響structA
的name
值亩冬。
在內(nèi)存中,引用類型硼身,諸如類硅急,是在堆上進(jìn)行存儲(chǔ)和操作的,而值類型佳遂,諸如結(jié)構(gòu)體营袜,則是在棧上存儲(chǔ)和操作的。相比棧上的操作丑罪,堆上的操作更加復(fù)雜耗時(shí)荚板,所以蘋果公司官方推薦使用結(jié)構(gòu)體凤壁,這樣可以提高APP的運(yùn)作效率。
兩者的區(qū)別
- class的如下功能是struct沒有的:
1跪另、可以繼承客扎,這樣子類可以使用父類的特性和方法
2、類型轉(zhuǎn)換可以在運(yùn)行時(shí)檢查和解釋一個(gè)實(shí)例的類型
3罚斗、可以用deinit
來釋放資源
4徙鱼、一個(gè)類可以被多次引用
- struct也有如下優(yōu)勢(shì):
1味悄、結(jié)構(gòu)較小圾叼,適用于復(fù)制操作,相比一個(gè)class
的實(shí)例被多次引用辜纲,struct
更安全
2距淫、無(wú)須擔(dān)心內(nèi)存泄露或者多線程沖突問題
什么時(shí)候使用類(class)什么時(shí)候使用結(jié)構(gòu)體(struct)绞绒?
- 如果是封裝一些簡(jiǎn)單數(shù)據(jù)請(qǐng)使用
struct
- 如果希望封裝的值是被復(fù)制而不是引用請(qǐng)使用
struct
- 無(wú)須使用繼承來使用相關(guān)屬性和行為請(qǐng)使用
struct
除以上情況請(qǐng)使用class
對(duì)于class
和struct
的詳細(xì)內(nèi)容可以看這里
2、Swift是面向?qū)ο筮€是函數(shù)式的編程語(yǔ)言榕暇?
Swift
既是面向?qū)ο蟮木幊陶Z(yǔ)言,也是函數(shù)式的編程語(yǔ)言狰晚。
說Swift
是面向?qū)ο蟮木幊陶Z(yǔ)言缴啡,是因?yàn)?code>Swift支持類的封裝、繼承和多態(tài)秒咐,從這一點(diǎn)來看,Swift
與Java
這類純面向?qū)ο蟮木幊陶Z(yǔ)言幾乎沒什么差別碘裕。
說Swift
是函數(shù)式的編程語(yǔ)言雷滋,那是因?yàn)?code>Swift支持map
惊豺,reduce
尸昧,filter
爆侣,flatmap
這類去除中間狀態(tài)兔仰、數(shù)學(xué)函數(shù)式的方法,更加強(qiáng)調(diào)運(yùn)算結(jié)果而不是中間過程潮尝。
3勉失、在Swift中顽素,什么是可選類型(optionals)胁出?
在Swift
中,可選型是為處理值可能缺失的情況嫂用,當(dāng)一個(gè)變量值為空時(shí)嘱函,那么該值就是nil
疏唾。在Swift
中,無(wú)論變量是引用類型還是值類型顿天,都可以是可選類型變量
例如:
var value: Float? = 37.0 //值類型為Float咽白,value的默認(rèn)值為37.0
var key: String? = nil //值類型為string晶框,key的默認(rèn)值為nil
let image: UIImage? //引用類型為UIImage,image的默認(rèn)值為nil
在OC
中沒有明確提出可選型的概念,然而模燥,其引用類型卻可以為nil
,以此來標(biāo)志其變量值為空的情況辽旋,而在Swift
中這一理念擴(kuò)大到值類型,并且明確提出來可選型的概念
4溶其、在Swift中,什么是泛型厢绝?
在Swift中,泛型主要是為增加代碼的靈活性而生的钞速,它可以使對(duì)應(yīng)的代碼滿足任意類型的變量或者方法。
舉個(gè)簡(jiǎn)單例子:假如要寫一個(gè)方法可以交換兩個(gè)Int
類型變量的值驾凶,通常我們會(huì)這樣實(shí)現(xiàn):
func swap(_ a: inout Int, _ b: inout Int) {
(a, b) = (b, a)
}
上面的函數(shù)寫法正確但是并不高效和通用,因?yàn)榧技纾偃缧枰獙?shí)現(xiàn)一個(gè)方法交換兩個(gè)Float
值,那么又得重新寫一般。兩個(gè)方法的差別僅僅是輸入?yún)?shù)的類型不同剧浸。范型就是為來解決這類問題而來的,通過實(shí)現(xiàn)一個(gè)一般性的方法,可以交換任意類型的變量
func swap<T>(_ a: inout T, _ b: inout T) {
(a, b) = (b, a)
}
注意:Swift是類型安全的語(yǔ)言,所以這里交換兩個(gè)變量的類型必須一致
5、關(guān)鍵字:open
祸挪,public
雹仿,internal
,fileprivate
邑商,private
Swift
中有五個(gè)級(jí)別的訪問控制權(quán)限,從高到低依次為open
,public
暇仲,internal
,fileprivate
,private
它們遵循的基本原則是:高級(jí)別的變量不允許被定義為低級(jí)別變量的成員變量中跌。例如:一個(gè)private
的class中不能包含public
的String值。反之,低級(jí)別的變量卻可以定義在高級(jí)別的變量中闷沥。比如,public
的class中可以含有private
的Int值
open
:具備最高的訪問權(quán)限虫啥。其修飾的類和方法可以在任意的module中被訪問和重寫;它是Swift3中新添加的訪問權(quán)限
public
:public
的權(quán)限僅次于open
又活。與open
唯一的區(qū)別在于箕般,它修飾的對(duì)象可以在任意module
中被訪問曲初,但不能被重寫
internal
:默認(rèn)的權(quán)限。它表示只能在當(dāng)前定義的module
中訪問和重寫颁褂,它可以被一個(gè)module
中的多個(gè)文件訪問,但不可以被其他module
訪問
fileprivate
:也是Swift3
新添加的權(quán)限,其修飾的對(duì)象只能在當(dāng)前文件中使用。例如:它可以被一個(gè)文件中的class
病往、extension
、struct
共同使用
private
:最低級(jí)別的訪問權(quán)限。它的對(duì)象只能在定義的作用域內(nèi)使用,離開來這個(gè)作用域缸榛,即使是同一個(gè)文件中的其他作用域,也無(wú)法訪問。
6找前、比較關(guān)鍵詞:strong
形帮、weak
躯枢、unonwed
Swift
的內(nèi)存管理機(jī)制與OC
一樣,都是ARC(Automatic Reference Counting)
自動(dòng)引用計(jì)數(shù)。其基本原理是朝抖,一個(gè)對(duì)象在沒有強(qiáng)引用指向它時(shí),所占用的內(nèi)存就會(huì)被回收坏怪。反之,只要有任何一個(gè)強(qiáng)引用指向該對(duì)象,它就會(huì)一直存在于內(nèi)存中
strong
:強(qiáng)引用,是默認(rèn)屬性惜纸。當(dāng)一個(gè)對(duì)象被聲明為strong
時(shí)压汪,表示對(duì)該對(duì)象進(jìn)行強(qiáng)引用腺阳,此時(shí),該對(duì)象的引用計(jì)數(shù)會(huì)增加1
weak
:弱引用,會(huì)被定義為可選類型變量。當(dāng)一個(gè)對(duì)象被聲明為weak
時(shí),表示對(duì)該對(duì)象不會(huì)保持強(qiáng)引用比庄,該對(duì)象的引用計(jì)數(shù)不會(huì)增加1。在該對(duì)象被釋放后,弱引用也隨機(jī)消失。繼續(xù)訪問該對(duì)象,程序會(huì)得到nil
,并不會(huì)奔潰趴酣。另外注意:當(dāng) ARC
設(shè)置弱引用為 nil
時(shí)柜蜈,屬性觀察不會(huì)被觸發(fā)隶垮。
unowned
:無(wú)主引用缆娃,與弱引用的本質(zhì)一樣暖侨。唯一不同的是,對(duì)象被釋放后些举,依然有一個(gè)無(wú)效的引用指向?qū)ο笈布罚皇?code>optional鸠信,也不指向nil
葬凳。如果繼續(xù)訪問該對(duì)象绰垂,則程序就會(huì)奔潰
引入weak
和unowned
是為了解決由strong
帶來的循環(huán)引用問題。簡(jiǎn)單來說沮明,就是當(dāng)兩個(gè)對(duì)象互相有一個(gè)強(qiáng)引用指向?qū)Ψ綍r(shí)辕坝,就會(huì)導(dǎo)致兩個(gè)對(duì)象在內(nèi)存中無(wú)法釋放荐健。
weak
和unwoned
的使用場(chǎng)景有如下區(qū)別:
當(dāng)訪問對(duì)象可能已經(jīng)被釋放時(shí)酱畅,則使用weak
,比如:delegate
的修飾
當(dāng)訪問對(duì)象不可能被釋放時(shí)江场,則用unwoned
纺酸,比如:self
的引用
實(shí)際上,為了安全址否,很多情況下基本使用weak
7餐蔬、在Swift中,如何理解copy-on-write
當(dāng)值類型(比如:struct
)在復(fù)制時(shí)佑附,復(fù)制的對(duì)象和原對(duì)象實(shí)際上在內(nèi)存中指向同一個(gè)對(duì)象樊诺。當(dāng)前僅當(dāng)修改復(fù)制后的對(duì)象時(shí),才會(huì)在內(nèi)存中重新創(chuàng)建一個(gè)新的對(duì)象音同。
let arrayA = [1,2,3]
var arrayB = arrayA // 這時(shí)arrayB和arrayA在內(nèi)存中時(shí)同一個(gè)數(shù)組词爬,內(nèi)存中并沒有生成新的數(shù)組
print("before: \(arrayA), b: \(arrayB)") //before: [1, 2, 3], b: [1, 2, 3]
arrayB.append(4) // arrayB被修改了,此時(shí)arrayB在內(nèi)存中變成了一個(gè)新的數(shù)組权均,而不是原來的arrayA
print("after: \(arrayA), b: \(arrayB)") //after: [1, 2, 3], b: [1, 2, 3, 4]
從上面的代碼可以看出顿膨,復(fù)制的數(shù)組和原數(shù)組共享同一個(gè)地址锅锨,直到其中之一發(fā)生改變。這樣設(shè)計(jì)使得值類型可以被多次復(fù)制而無(wú)須耗費(fèi)多余的內(nèi)存恋沃,只有變化的時(shí)候才增加開銷必搞。因此內(nèi)存的使用更加高效
8、屬性觀察(Property Observer)
屬性觀察是指在當(dāng)前類型內(nèi)對(duì)特定屬性進(jìn)行監(jiān)聽囊咏,并作出響應(yīng)的行為恕洲。屬性觀察是Swift
的特性,具有兩種:willSet
和didSet
.
var title: String {
willSet {
print("將標(biāo)題從\(title)設(shè)置到\(newValue)")
}
didSet {
print("已將標(biāo)題從\(oldValue)設(shè)置到\(title)")
}
}
上面代碼都對(duì)title
進(jìn)行了監(jiān)聽匆笤,在title
發(fā)生改變之前研侣,willSet
對(duì)應(yīng)的作用域?qū)⒈粓?zhí)行,新的值是newValue
炮捧,在title
發(fā)生改變之后,didSet
對(duì)應(yīng)的作用域?qū)⒈粓?zhí)行惦银,原來的值為oldValue
咆课,這就是屬性觀察
注意:在初始化方法對(duì)屬性的設(shè)定,以及在
willSet
和didSet
中對(duì)屬性的再次設(shè)定扯俱,都不會(huì)觸發(fā)調(diào)用屬性觀察
9书蚪、在結(jié)構(gòu)體中如何修改成員變量的方法
下面代碼存在什么問題
protocol Pet {
var name: String { get set }
}
struct MyDog: Pet {
var name: String
func changeName(name: String) {
self.name = name
}
}
一旦我們編寫這樣的代碼,編譯器就會(huì)告訴我們當(dāng)前的self
是不可變的迅栅,我們不能為name
進(jìn)行復(fù)制殊校,如果想要修改name
屬性,那么必須在方法前加上mutating
關(guān)鍵詞读存,表示該方法會(huì)修改結(jié)構(gòu)體中自己的成員變量
注意:
1为流、類不存在這樣的問題,因?yàn)轭惪梢噪S意修改自己的成員變量
2让簿、在設(shè)計(jì)協(xié)議的時(shí)候敬察,由于protocol
可以被class
和struct
以及enum
實(shí)現(xiàn),所以需要考慮是否使用mutating
關(guān)鍵詞來修飾方法
10尔当、如何使用Swift實(shí)現(xiàn)或(||)操作
直接一點(diǎn)的解法:
func ||(left: Bool, right: Bool) -> Bool {
return left ? left: right
}
上面解法雖然正確莲祸,但是效率不高⊥钟或(||)操作的本質(zhì)是锐帜,當(dāng)表達(dá)式左邊的值為真的時(shí)候,無(wú)須計(jì)算表達(dá)式右邊的值畜号。而上面的解法是將表達(dá)式右邊的默認(rèn)值準(zhǔn)備好了缴阎,再傳入進(jìn)行操作。當(dāng)表達(dá)式右邊的值計(jì)算釋放復(fù)雜時(shí)弄兜,會(huì)造成性能上的浪費(fèi)药蜻,所以瓷式,上面這種做法違反了或(||)操作的本質(zhì)。正確的實(shí)現(xiàn)方法如下:
func ||(left: Bool, right: @autoclosure () -> Bool) -> Bool {
return left ? left: right()
}
自動(dòng)閉包(autoclosure
)可以將表達(dá)式右邊的值的計(jì)算推遲到判定left
為false
時(shí)语泽,這樣就可以避免第一種方法帶來的不必要的計(jì)算贸典。
11、實(shí)現(xiàn)一個(gè)函數(shù):輸入是任意一個(gè)整數(shù)踱卵,輸出為輸入的整數(shù)+2
題目看起來很簡(jiǎn)單廊驼,直接寫代碼如下所示:
func addTwo(_ num: Int) -> Int {
return num + 2
}
但是接下來要實(shí)現(xiàn)輸入的整數(shù)加4的功能,難道我們又寫如下代碼:
func addFour(_ num: Int) -> Int {
return num + 4
}
如果要是加8加10呢惋砂?能不能定義一個(gè)方法解決所有的問題呢妒挎?必須的,使用柯里化(currying)特性
func add(_ num: Int) -> (Int) -> Int {
return { num + $0 }
}
// 簡(jiǎn)單使用
let addTwo = add(2)
print(addTwo(10)) // 12
let addFour = add(4)
print(addFour(10)) // 14
let addSix = add(6)
print(addSix(10)) // 16
Swift
的柯里化特性是函數(shù)式編程思想的體現(xiàn)西饵,它將接受多個(gè)參數(shù)的方法進(jìn)行變形酝掩,并用高階函數(shù)的方式進(jìn)行處理,使整個(gè)代碼更加靈活
12眷柔、求0-100(包括0和100)中為偶數(shù)并且恰好是其他數(shù)字平方的數(shù)字
題目也很簡(jiǎn)單期虾,滿足兩個(gè)條件即可,實(shí)現(xiàn)如下:
func num(fromValue: Int, toValue: Int) -> [Int] {
var result = [Int]()
for num in fromValue...toValue where num % 2 == 0 {
if (fromValue...toValue).contains(num * num) {
result.append(num)
}
}
return result
}
上面的實(shí)現(xiàn)雖然正確驯嘱,但是不夠優(yōu)雅镶苞,其實(shí)直接使用函數(shù)式編程思路實(shí)現(xiàn)簡(jiǎn)潔,如下:
(0...10).map { $0 * $0 }.filter { $0 % 2 == 0 }
13鞠评、Swift為什么將String茂蚓,Array和Dictionary設(shè)計(jì)成為值類型
要了解Swift
為什么將String
,Array
和Dictionary
設(shè)計(jì)成為值類型剃幌,就要和OC
中相同的數(shù)據(jù)結(jié)構(gòu)設(shè)計(jì)進(jìn)行比較聋涨。在OC
中,String
锥忿,Array
和Dictionary
是皆為引用類型牛郑。
值類型相比引用類型,最大的優(yōu)勢(shì)在于可以高效地使用內(nèi)存敬鬓。值類型在棧上進(jìn)行操作淹朋,引用類型在堆上操作。棧上的操作僅僅是單個(gè)指針的上下移動(dòng)钉答,而堆上的操作則牽涉合并础芍、移位、重鏈接等数尿。也就說仑性,Swift
這樣設(shè)計(jì)大幅度減少了堆上的內(nèi)存分配和回收的次數(shù)。同時(shí)右蹦,copy-on-write
又將值傳遞和復(fù)制的開銷降到最低诊杆。
Swift
將String
歼捐,Array
和Dictionary
設(shè)計(jì)成為值類型也是為了線程安全。通過Swift
的let
設(shè)置晨汹,使得這些數(shù)據(jù)達(dá)到真正意義上的“不變”豹储,也從根本上解決了多線程中內(nèi)存訪問和操作順序的問題。
Swift
將String
淘这,Array
和Dictionary
設(shè)計(jì)成為值類型還可以提升API 的靈活度剥扣。例如,通過實(shí)現(xiàn)Collection
這樣的協(xié)議铝穷,可以遍歷String
,使得整個(gè)開發(fā)更加靈活钠怯、高效。
14曙聂、如何使用Swift將協(xié)議(protocol)中的部分方法設(shè)計(jì)成為可選
@optional
和@required
是OC
中特有的關(guān)鍵字晦炊。
在Swift
中,協(xié)議中所有的方法默認(rèn)都必須實(shí)現(xiàn)宁脊,而且在協(xié)議中刽锤,方法是不能直接被定義為optional
。有兩種解決方案:
1)在協(xié)議和方法前面都加@objc
關(guān)鍵字朦佩,然后再在方法前加上optional
關(guān)鍵字。該方法實(shí)際上把協(xié)議轉(zhuǎn)換為OC
的方式庐氮,然后就可以進(jìn)行可選定義了语稠。如下:
@objc protocol SomeProtocol {
func run() // 必須實(shí)現(xiàn)方法
@objc optional func jump() //可選方法
}
2)使用擴(kuò)展(extension
)來規(guī)定可選方法。在Swift中弄砍,協(xié)議擴(kuò)展可以定義部分方法的默認(rèn)實(shí)現(xiàn)仙畦,這樣,這些方法在實(shí)際調(diào)用中就是可選實(shí)現(xiàn)來音婶。如:
protocol SomeProtocol {
func run() // 必須實(shí)現(xiàn)方法
func jump() //可選方法
}
extension SomeProtocol {
func jump() {
print("jump")
}
}
class Person: SomeProtocol {
func run() { //只需要實(shí)現(xiàn)run方法
print("run")
}
}
15慨畸、協(xié)議的代碼實(shí)戰(zhàn)
下面代碼有什么問題?
protocol SomeProtocol {
func doSomething()
}
class Person {
weak var delegate: SomeProtocol?
}
聲明delegate
屬性的時(shí)候錯(cuò)誤衣式,編譯器會(huì)報(bào)錯(cuò)寸士。
Swift
中協(xié)議不僅可以被class
這樣的引用類型實(shí)現(xiàn),也可以被struct
和enum
這樣的值類型實(shí)現(xiàn)(這是和OC
的一大區(qū)別)碴卧。weak
關(guān)鍵詞是ARC
環(huán)境下弱卡,為引用類型提供引用計(jì)數(shù)這樣的內(nèi)存管理,它是不能被用來修飾值類型的住册。
有兩種方法解決這個(gè)問題:
1)在protocol
前面加上@objc
婶博。在OC
中,協(xié)議只能由class
來實(shí)現(xiàn)荧飞,這樣一來凡人,weak
修飾的對(duì)象與OC
一樣名党,只不過是class
類型。如下:
@objc protocol SomeProtocol {
func doSomething()
}
2)在SomeProtocol
之后添加class
關(guān)鍵詞挠轴。如此一來就聲明該協(xié)議只能由類(class
)來實(shí)現(xiàn)传睹。如下:
protocol SomeProtocol: class {
func doSomething()
}
16、在Swift和OC的混合項(xiàng)目中忠荞,如何在Swift文件中調(diào)用OC文件中定義的方法蒋歌?又如何在OC文件中調(diào)用Swift文件中定義的方法
在Swift
中,若要使用OC
代碼委煤,則可以在ProjectName-Bridging-Header.h
文件中添加OC
的頭文件名稱堂油,這樣在Swift
文件中即可調(diào)用相應(yīng)的OC
代碼。一般情況下碧绞,Xcode
會(huì)在Swift
項(xiàng)目中第一次創(chuàng)建OC
文件時(shí)府框,自動(dòng)創(chuàng)建ProjectName-Bridging-Header.h
文件
在OC
中如果想要調(diào)用Swift
代碼,則可以導(dǎo)入Swift
生成的頭文件ProjectName-Swift.h
文件
在Swift
文件中讥邻,若要將固定的方法或?qū)傩员┞督oOC
使用迫靖,則可以在方法或?qū)傩郧凹由?code>@objc。如果該類是NSObject
子類兴使,那么Swift
會(huì)在非private
的方法或?qū)傩郧白詣?dòng)加上@objc.
17系宜、比較Swift和OC的初始化方法的異同
簡(jiǎn)單來說:Swift
的初始化方法更加嚴(yán)格和準(zhǔn)確
在OC
中,初始化方法無(wú)法保證所有成員變量都完成初始化发魄;編譯器對(duì)屬性甚至并無(wú)警告盹牧,但是,在實(shí)際操作中會(huì)出現(xiàn)初始化不完全的問題励幼;初始化方法與普通方法并無(wú)實(shí)際差別汰寓,可以多次調(diào)用。
在Swift
中苹粟,初始化方法必須保證所有非optional
的成員變量都完成初始化有滑;同時(shí),新增convenience
和required
兩個(gè)修飾初始化方法的關(guān)鍵詞嵌削。
-
convenience
:只是提供一個(gè)方便的初始化方法毛好,必須通過調(diào)用同一個(gè)類中的designated
初始化方法來完成 -
required
:強(qiáng)制子類重寫父類中所修飾的初始化方法
18、比較Swift和OC中協(xié)議的不同
相同點(diǎn):兩者都可以被用作代理掷贾,OC
中的protocol
類似Java
中的Interface
睛榄,在實(shí)際開發(fā)中主要用于適配器模式
不同點(diǎn):Swift
中的protocol
還可以對(duì)接口進(jìn)行抽象,例如:sequence
想帅,配合擴(kuò)展(extension
)场靴,泛型、關(guān)聯(lián)類型等可以實(shí)現(xiàn)面向協(xié)議編程,從而提高整個(gè)代碼的靈活性旨剥。同時(shí)咧欣,Swift
中的protocol
還可以用于值類型,如:結(jié)構(gòu)體和枚舉
19轨帜、談?wù)剬?duì)OC和Swift動(dòng)態(tài)性理解
Runtime
其實(shí)就是OC
的動(dòng)態(tài)機(jī)制魄咕。Runtime
執(zhí)行的是編譯后的代碼,這時(shí)它可以動(dòng)態(tài)加載對(duì)象蚌父、添加方法哮兰、修改屬性、傳遞信息等苟弛。具體過程是喝滞,在OC
中,對(duì)象調(diào)用方法時(shí)膏秫,如[self.tableView reloadData]
右遭,經(jīng)歷了兩個(gè)階段:
編譯階段:編譯器會(huì)把這句話翻譯為
objc_msgSend(self.tableView, @selector(reloadData))
,把消息發(fā)送給tableView
運(yùn)行階段:接受者
self.tableView
會(huì)響應(yīng)這個(gè)消息缤削,期間可能會(huì)直接執(zhí)行窘哈、轉(zhuǎn)發(fā)消息,也可能會(huì)找不到方法導(dǎo)致程序奔潰亭敢。所以滚婉,整個(gè)流程是:編譯器編譯->給接收者發(fā)送消息->接收者響應(yīng)消息
例如:[self.tableView reloadData]
中,self.tableView
就是接收者帅刀,reloadData
就是消息满哪,所以,方法調(diào)用的格式在編譯器看來是[receiver message]
劝篷。其中,接收者響應(yīng)代碼民宿,就發(fā)生在運(yùn)行時(shí)(Runtime
)娇妓。Runtime
的運(yùn)行機(jī)制就是OC
的動(dòng)態(tài)特性
Swift
目前被公認(rèn)為是一門靜態(tài)的語(yǔ)言,它的動(dòng)態(tài)特性都是通過橋接OC
實(shí)現(xiàn)的活鹰。如果要把其動(dòng)態(tài)特性寫的更“Swift”一點(diǎn)哈恰,則可以用protocol
來處理,比如:可以將OC
中的reflection
這樣寫:
if ([someImage respondsToSelector: @selector(shake)]) {
[someImage performSelector: shake];
}
在Swift
中實(shí)現(xiàn)如下
if let shakeableImage = someImage as? Shakeable {
shakeableImage.shake()
}
20志群、語(yǔ)言特性代碼實(shí)戰(zhàn)
下面代碼輸出什么着绷?
protocol Chef {
func makeFood()
}
extension Chef {
func makeFood() {
print("make food")
}
}
struct SeafoodChef: Chef {
func makeFood() {
print("make seafood")
}
}
let chefOne: Chef = SeafoodChef()
let chefTwo: SeafoodChef = SeafoodChef()
chefOne.makeFood()
chefTwo.makeFood()
代碼運(yùn)行輸出:
make seafood
make seafood
在Swift
中,協(xié)議中是動(dòng)態(tài)派發(fā)锌云,擴(kuò)展中是靜態(tài)派發(fā)荠医,也就是說,協(xié)議中如果有方法聲明,那么會(huì)根據(jù)對(duì)象的實(shí)際類型進(jìn)行調(diào)用彬向。
上面makeFood()
方法在Chef
協(xié)議中已經(jīng)聲明了兼贡,而chefOne
雖然聲明為Chef
,但實(shí)際實(shí)現(xiàn)為SeafoodChef
娃胆。所以遍希,根據(jù)實(shí)際情況,makeFood()
會(huì)調(diào)用SeafoodChef
中的實(shí)現(xiàn)里烦。chefTwo
也是同樣的道理凿蒜。
如果protocol
中沒有聲明makeFood()
方法,代碼又會(huì)輸出什么胁黑?
運(yùn)行如下:
make food
make seafood
因?yàn)閰f(xié)議中沒有聲明makeFood()
方法废封,所以,此時(shí)只會(huì)按照擴(kuò)展中的聲明類型進(jìn)行靜態(tài)派發(fā)别厘。也就是說虱饿,會(huì)根據(jù)對(duì)象的聲明類型進(jìn)行調(diào)用平斩。chefone
被聲明為Chef
筷厘,所以會(huì)調(diào)用擴(kuò)展的實(shí)現(xiàn),chefTwo
被聲明為SeafoodChef
砸捏,則會(huì)調(diào)用SeafoodChef
中的實(shí)現(xiàn)冗懦。
21爽冕、Swift和OC的自省有什么不同
自省在OC中就是判斷一個(gè)對(duì)象是否屬于某個(gè)類。它有兩種形式:
[object isKindOfClass: [SomeClass class]];
[object isMemberOfClass: [SomeClass class]];
isKindOfClass
是用來判斷object
是否為SomeClass
或者其子類的實(shí)例對(duì)象披蕉。
isMemberOfClass
則是判斷object
是SomeClass
(非子類)的實(shí)例對(duì)象時(shí)颈畸,才返回真。
注意:這兩個(gè)方法都有一個(gè)前提没讲,即object
必須是NSObject
或其子類
在Swift
中眯娱,由于很多class
并非繼承自NSObject
,故而Swift
用is
函數(shù)來進(jìn)行判斷爬凑,它相當(dāng)于isKindOfClass
徙缴。這樣做的優(yōu)點(diǎn)是is
函數(shù)不僅可以用于任何class
類型上,也可以用來判斷enum
和struct
類型
自省經(jīng)常是與動(dòng)態(tài)一起使用嘁信,動(dòng)態(tài)類型就是id類型
于样。任何類型的對(duì)象都可以用id
來代替,這個(gè)時(shí)候常常需要自省來判斷對(duì)象的實(shí)際所屬類潘靖。如下:
id vehicle = someCarInstance;
if ([vehicle isKindOfClass: [Car Class]]) {
NSLog(@"vehicle is a car");
} else if ([vehicle isKindOfClass: [Trunk class]]) {
NSLog(@"vehicle is a truck");
}
22穿剖、能否通過Category給已有的類添加屬性
可以通過Category
給已有的類添加屬性,無(wú)論是OC
還是Swift
卦溢。
在OC
中糊余,正常情況下秀又,在Category
添加屬性會(huì)報(bào)錯(cuò),提示找不到getter
和setter
方法啄刹,這是因?yàn)?code>Category不會(huì)自動(dòng)生成這兩個(gè)方法涮坐。解決的辦法是引入運(yùn)行時(shí)頭文件,并配合關(guān)聯(lián)對(duì)象的方法來實(shí)現(xiàn)誓军。其中主要涉及的兩個(gè)函數(shù)是objc_getAssociatedObject
和objc_setAssociatedObject
袱讹。在Swift
中,解決辦法OC
是一樣的昵时,只是寫法上不一樣捷雕。
假如有一個(gè)class
叫做User
,我們?cè)谄?code>Category中添加name
屬性壹甥,如下:
// .h
#import "User.h"
@interface User (Add)
@property (nonatomic, copy) NSString *name;
@end
// .m
#import "User+Add.h"
#import <objc/runtime.h>
static const void *nameKey = @"nameKey";
- (void)setName:(NSString *)name {
objc_setAssociatedObject(self, nameKey, name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)name {
return objc_getAssociatedObject(self, nameKey);
}
@end
代碼分析:
1救巷、在.h文件中添加name
屬性,此屬性為私有
2句柠、在.m文件中引入運(yùn)行時(shí)頭文件 <objc/runtime.h>
浦译,接著設(shè)置關(guān)聯(lián)屬性的key,最后實(shí)現(xiàn)setter
和getter
溯职。
其中精盅,objc_setAssociatedObject
這個(gè)方法的四個(gè)參數(shù)分別為原對(duì)象、關(guān)聯(lián)屬性key
谜酒、關(guān)聯(lián)值叹俏、關(guān)聯(lián)策略。
Swift
中的實(shí)現(xiàn)如下:
private var nameKey: Void?
class User: NSObject {}
extension User {
var name: String? {
get {
return objc_getAssociatedObject(self, &nameKey) as? String
}
set {
objc_setAssociatedObject(self, &nameKey, newValue, .OBJC_ASSOCIATION_COPY_NONATOMIC)
}
}
}
23僻族、OC和Swift在單例模式的創(chuàng)建上有什么區(qū)別
單例模式在創(chuàng)建過程中粘驰,要保證實(shí)例變量只被創(chuàng)建一次,在整個(gè)開發(fā)中需要特別注意線程安全述么,即使在多線程情況下蝌数,依然只初始化一次變量
+(instancetype)sharedManager {
static Manager *sharedManager = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedManager = [[Manager alloc]init];
});
return sharedManager;
}
在Swift
中,let
關(guān)鍵詞已經(jīng)保證了實(shí)例變量不會(huì)被修改度秘,所以單例的創(chuàng)建就簡(jiǎn)單很多:
class Manager {
static let sharedManager = Manager()
private init() {}
}
24籽前、解釋Swift中的懶加載?
懶加載主要是為了延遲加載,保證數(shù)據(jù)在用到的時(shí)候才會(huì)被加載敷钾,這樣可以減少內(nèi)存消耗
如下:使用懶加載創(chuàng)建UITableView
,懶加載說白了就是使用閉包創(chuàng)建對(duì)象肄梨,當(dāng)使用實(shí)例對(duì)象的時(shí)候會(huì)執(zhí)行閉包
lazy var tableView: UITableView = {
let tableView = UITableView (frame: self.view.bounds,
style: UITableView.Style.plain)
tableView.delegate = self
tableView.dataSource = self
return tableView
}()
與OC中懶加載的區(qū)別:
Swift
中懶加載只會(huì)在第一次調(diào)動(dòng)的時(shí)候執(zhí)行閉包阻荒, 然后將閉包的結(jié)果存在屬性中,如果以后將屬性置空众羡,屬性不在會(huì)有值侨赡,懶加載也不再執(zhí)行OC
中第一次使用點(diǎn)語(yǔ)法調(diào)用屬性的時(shí)候,執(zhí)行懶加載,置空以后再使用點(diǎn)語(yǔ)法調(diào)用羊壹,會(huì)再次執(zhí)行懶加載
25蓖宦、什么是OOP,它在iOS開發(fā)中有哪些優(yōu)缺點(diǎn)油猫?
OOP(Object Oritented Programming)
稠茂,即面向?qū)ο缶幊蹋悄壳白钪髁鞯木幊谭绞角檠T趇OS中絕大多數(shù)運(yùn)用的都是OOP
在iOS
開發(fā)中睬关,OOP
有如下優(yōu)點(diǎn):
封裝和權(quán)限控制
在Swift
中,相關(guān)屬性和方法被放入一個(gè)類中毡证,OC
中.h
文件負(fù)責(zé)聲明公共變量和方法电爹,.m
文件負(fù)責(zé)聲明私有變量,并實(shí)現(xiàn)所有方法料睛。在Swift
中也有public/internal/fileprivate/private/open
等權(quán)限控制
命名空間
在Swift
中丐箩,由于可以使用命名空間了,即使是名字相同的類型恤煞,只要是來自不同的命名空間的話屎勘,都是可以和平共處的。Swift
的命名空間是基于module
而不是在代碼中顯式地指明阱州,每個(gè)module
代表了Swift
中的一個(gè)命名空間挑秉。也就是說,同一個(gè)target
里的類型名稱還是不能相同的苔货。在我們進(jìn)行app
開發(fā)時(shí)犀概,默認(rèn)添加到 app
的主 target
的內(nèi)容都是處于同一個(gè)命名空間中的。而OC
沒有命名空間夜惭,所以很多類在命名時(shí)都加入類“駝峰式”的前綴
擴(kuò)展性
在Swift
中姻灶,class
可以通過extension
來增加新方法,通過動(dòng)態(tài)特性可以增加新變量诈茧。這樣可以保證在不破壞原來代碼的封裝的情況下實(shí)現(xiàn)新的功能产喉。而在OC
中,可以用category
來實(shí)現(xiàn)類似功能敢会。另外曾沈,在Swift
和OC
中,還可以通過protocol
和代理模式來實(shí)現(xiàn)更加靈活的擴(kuò)展
繼承和多態(tài)
同其他語(yǔ)言一樣鸥昏,在iOS
開發(fā)中塞俱,可以將共同的方法和變量定義在父類中,在子類繼承時(shí)再各自實(shí)現(xiàn)對(duì)應(yīng)的功能吏垮,高效率實(shí)現(xiàn)代碼復(fù)用障涯。同時(shí)罐旗,針對(duì)不同情況,可以調(diào)用不同子類唯蝶,從而大大增加代碼的靈活性九秀。
缺點(diǎn)
隱式共享
class
是引用類型,當(dāng)在代碼中的某處改變某個(gè)實(shí)例變量時(shí)粘我,另外一處調(diào)用此變量時(shí)就會(huì)受此修改的影響鼓蜒。例如:
class Person {
var name: String = ""
}
let person1 = Person()
person1.name = "person1"
let peron2 = person1
peron2.name = "person2"
print(person1.name, peron2.name) // person2 person2
這很容易造成異常。尤其是在多線程時(shí)涂滴,我們經(jīng)常遇到的資源競(jìng)爭(zhēng)就屬于這種情況友酱。解決的方案是在多線程時(shí)加鎖,當(dāng)前柔纵,這個(gè)方案會(huì)引入死鎖和代碼復(fù)雜度劇增的問題缔杉。解決這個(gè)問題的最好方案是盡可能地用struct
這樣的值類型替代class
冗雜的父類
想象一個(gè)場(chǎng)景:一個(gè)UIViewController
的子類和一個(gè)UITableViewController
中都需要加入handleSomething()
方法。OOP
的解決方案是直接在extension
中加入handleSomething()
方法搁料。但是隨著新方法越來越多或详,導(dǎo)致UIViewController
會(huì)越來越冗雜。當(dāng)然也可以引入一個(gè)專門的父類或工具類郭计,但是依然有職責(zé)不明確霸琴、依賴、冗雜等多種問題
另外一個(gè)方面昭伸,父類中的handleSomething()
方法必須有具體的實(shí)現(xiàn)梧乘,因?yàn)樗荒芨鶕?jù)子類作出靈活調(diào)整。子類如果要特定操作庐杨,則必須重寫方法來實(shí)現(xiàn)选调,那么父類中的實(shí)現(xiàn)又顯得多此一舉了。解決的方案是使用protocol
灵份,這樣它的方法就不需要具體的實(shí)現(xiàn)了仁堪,交給遵守它的類或結(jié)構(gòu)體即可
多繼承
Swift
和OC
是不支持多繼承的,因?yàn)檫@會(huì)造成“菱形”問題填渠,即多個(gè)父類實(shí)現(xiàn)了同一個(gè)方法弦聂,子類無(wú)法判斷繼承那個(gè)父類的情況。Swift
中又類似protocol
的解決方案
26氛什、Swift為什么要推出POP莺葫?
POP
(Protocol Oriented Programming)面向協(xié)議編程
1、OOP
有自身的缺點(diǎn)枪眉。在繼承捺檬、代碼復(fù)用等方面,其靈活度不高瑰谜。而POP
恰好解決來這些問題
2欺冀、POP
可以保證Swift作為靜態(tài)語(yǔ)言的安全性,而OC
時(shí)代的OOP
萨脑,其動(dòng)態(tài)性經(jīng)常會(huì)倒置異常
3隐轩、OOP
無(wú)法應(yīng)用于值類型,而POP
卻可以將其優(yōu)勢(shì)擴(kuò)展到結(jié)構(gòu)體渤早、枚舉類型中
27职车、POP相比OOP有哪些優(yōu)勢(shì)
優(yōu)勢(shì):
更加靈活
比如:可以使用協(xié)議來定義公共的方法,讓所有的服從類都可以有默認(rèn)的實(shí)現(xiàn)鹊杖,也可以自己實(shí)現(xiàn)
protocol SomethingProtocol {
func handleSomething()
}
extension SomethingProtocol {
func handleSomething() {
print("handleSomething")
}
}
class TableViewController: UITableViewController, SomethingProtocol {}
class ViewController: UIViewController, SomethingProtocol {}
減少依賴
相對(duì)于傳入具體的實(shí)例變量悴灵,我們可以傳入protocol
來實(shí)現(xiàn)多態(tài)。同時(shí)骂蓖,在測(cè)試時(shí)也可以利用protocol
來模擬真實(shí)的實(shí)例积瞒,減少對(duì)對(duì)象極其實(shí)現(xiàn)的依賴。
protocol Request {
func send(request: Info)
}
protocol Info {}
class UserInfo: Info {}
class UserRequest: Request {
// 這里傳入Info是protocol登下,不需要是具體的UserInfo茫孔,這方便了我們之后的測(cè)試
func send(request: Info) {
// 實(shí)際實(shí)現(xiàn),一般是吧info發(fā)送給server
}
}
如果需要測(cè)試被芳,實(shí)現(xiàn)protocol即可
class MockUserRequest: Request {
func send(request: Info) {
// 這里進(jìn)行測(cè)試缰贝,方便實(shí)現(xiàn)
}
}
func testUserRequest() {
let userRequest = MockUserRequest()
userRequest.send(request: UserInfo())
}
消除動(dòng)態(tài)分發(fā)的風(fēng)險(xiǎn)
對(duì)遵守protocol
的類或結(jié)構(gòu)體而言,它必須實(shí)現(xiàn)protocol
聲明的所有方法畔濒。否則編譯器報(bào)錯(cuò)剩晴,這杜絕了運(yùn)行時(shí)程序鎖具有的風(fēng)險(xiǎn)。
協(xié)議可用于值類型
相比OOP
只能用于class
侵状,POP
可以用于struct
和enum
這樣的值類型赞弥。
28、defer關(guān)鍵字的使用
工作原理:延遲執(zhí)行壹将,等當(dāng)前范圍內(nèi)的陳述語(yǔ)句執(zhí)行完成最后執(zhí)行
defer
1嗤攻、單個(gè)defer語(yǔ)句的執(zhí)行
func updateImage() {
defer { print("Did update image") }
print("Will update image")
imageView.image = updatedImage
}
// Will update Image
// Did update image
2、多個(gè)defer語(yǔ)句的執(zhí)行順序
在相同的范圍內(nèi)有多個(gè)defer
語(yǔ)句诽俯,執(zhí)行的順序跟顯示的順序相反妇菱,即從最后的defer
語(yǔ)句開始執(zhí)行
func printStringNumbers() {
defer { print("1") }
defer { print("2") }
defer { print("3") }
print("4")
}
/// Prints 4, 3, 2, 1
3、defer 和閉包
另一個(gè)比較有意思的事實(shí)是暴区,雖然defer
后面跟了一個(gè)閉包闯团,但是它更多地像是一個(gè)語(yǔ)法糖,和我們所熟知的閉包特性不一樣仙粱,并不會(huì)持有里面的值房交。比如:
func foo() {
var number = 1
defer { print("Statement 2: \(number)") }
number = 100
print("Statement 1: \(number)")
}
將會(huì)輸出:
Statement 1: 100
Statement 2: 100
在defer
中如果要依賴某個(gè)變量值時(shí),需要自行進(jìn)行復(fù)制:
func foo() {
var number = 1
var closureNumber = number
defer { print("Statement 2: \(closureNumber)") }
number = 100
print("Statement 1: \(number)")
}
// Statement 1: 100
// Statement 2: 1
使用場(chǎng)景
- 關(guān)閉文件句柄(FileHandle)
func writeFile() {
let file: FileHandle? = FileHandle(forReadingAtPath: filepath)
defer { file?.closeFile() }
// Write changes to the file
}
- 確保結(jié)果
確狈ジ睿回調(diào)閉包能夠被執(zhí)行
func getData(completion: (_ result: Result<String>) -> Void) {
var result: Result<String>?
defer {
guard let result = result else {
fatalError("We should always end with a result")
}
completion(result)
}
// Generate the result..
}
Defer usage in Swift
Swift defer 的正確使用
29候味、Static和Class的區(qū)別
在Swift
中static
和class
都表示“類型范圍作用域”的關(guān)鍵字刃唤。在所有類型中(class、static白群、enum
)中尚胞,我們可以使用static
來描述類型作用域。class
是專門用于修飾class
類型的帜慢。
-
static
可以修飾屬性和方法
class Person {
// 存儲(chǔ)屬性
static let age: Int = 20
// 計(jì)算屬性
static var workTime: Int {
return 8
}
// 類方法
static func sleep() {
print("sleep")
}
}
但是所修飾的屬性和方法不能夠被重寫笼裳,也就是說
static
修飾的類方法和屬性包含了final
關(guān)鍵字的特性。
重寫會(huì)報(bào)錯(cuò):
-
class
修飾方法和計(jì)算屬性
我們同樣可以使用
class
修飾方法和計(jì)算屬性粱玲,但是不能夠修飾存儲(chǔ)屬性躬柬。
對(duì)于 class
修飾的類方法和計(jì)算屬性是可以被重寫的,可以使用class
關(guān)鍵字也可以是static
關(guān)鍵字
class Person {
class var workTime: Int {
return 8
}
class func sleep() {
print("sleep")
}
}
class Student: Person {
// 使用class - ok
// override class func sleep() {
// }
// override class var workTime: Int {
// return 10
// }
// 使用static
override static func sleep() {
}
override static var workTime: Int {
return 10
}
}
-
static
和protocol
Swift
中的class抽减,struct允青,enum
都可以實(shí)現(xiàn)某個(gè)指定的協(xié)議。如果我們想在protocol
中定義一個(gè)類型作用域上的方法或者計(jì)算屬性胯甩,應(yīng)該使用哪個(gè)關(guān)鍵字昧廷?答案顯而易見,肯定是static
偎箫,因?yàn)?code>static是通用的木柬。
注意:在使用
protocol
的時(shí)候,在enum
和struct
中仍然使用static
進(jìn)行修飾淹办,在class
中眉枕,class
和static
都可以使用。
protocol MyProtocol {
static func foo() -> String
static func bar() -> String
}
struct MyStruct: MyProtocol {
static func foo() -> String {
return "MyStruct.foo()"
}
static func bar() -> String {
return "MyStruct.bar()"
}
}
enum MyEnum: MyProtocol {
static func foo() -> String {
return "MyEnum.foo()"
}
static func bar() -> String {
return "MyEnum.bar()"
}
}
class MyClass: MyProtocol {
// 在 class 中可以使用 class
class func foo() -> String {
return "MyClass.foo()"
}
// 也可以使用 static
static func bar() -> String {
return "MyClass.bar()"
}
}
總結(jié)
1怜森、用class
指定的類方法可以被子類重寫速挑,而static
指定的類方法是不能被子類重寫的,因?yàn)?code>static修飾的類方法和屬性包含了final
關(guān)鍵字的特性副硅。
2姥宝、class
不能修飾存儲(chǔ)屬性,而static
可以
3恐疲、對(duì)protocol
而言腊满,推薦使用static
關(guān)鍵字修飾類方法和屬性
30、講講deinit函數(shù)
當(dāng)一個(gè)類的實(shí)例被釋放之前培己,析構(gòu)器會(huì)被立即調(diào)用碳蛋。析構(gòu)器用關(guān)鍵字deinit
來標(biāo)示,類似于構(gòu)造器要用init
來標(biāo)示省咨。析構(gòu)器只適用于類類型肃弟。在類的定義中,每個(gè)類最多只能有一個(gè)析構(gòu)器,而且析構(gòu)器不帶任何參數(shù)笤受,如下所示:
deinit {
// perform the deinitialization
}
析構(gòu)器是在實(shí)例釋放發(fā)生前被自動(dòng)調(diào)用穷缤。你不能主動(dòng)調(diào)用析構(gòu)器。子類繼承了父類的析構(gòu)器箩兽,并且在子類析構(gòu)器實(shí)現(xiàn)的最后绅项,父類的析構(gòu)器會(huì)被自動(dòng)調(diào)用。即使子類沒有提供自己的析構(gòu)器比肄,父類的析構(gòu)器也同樣會(huì)被調(diào)用。
因?yàn)橹钡綄?shí)例的析構(gòu)器被調(diào)用后囊陡,實(shí)例才會(huì)被釋放芳绩,所以析構(gòu)器可以訪問實(shí)例的所有屬性,并且可以根據(jù)那些屬性可以修改它的行為撞反。開發(fā)中妥色,經(jīng)常在deinit
函數(shù)中進(jìn)行一些資源釋放,如:去除通知遏片、觀察者等嘹害。
31、元組
把多個(gè)值組合成一個(gè)復(fù)合值吮便。元組內(nèi)的值可以是任意類型笔呀,并不要求是相同類型。
例如:下面這個(gè)例子中髓需,(404, "Not Found") 是一個(gè)描述 HTTP 狀態(tài)碼(HTTP status code)的元組许师。HTTP 狀態(tài)碼是當(dāng)請(qǐng)求網(wǎng)頁(yè)的時(shí)候 web 服務(wù)器返回的一個(gè)特殊值。如果我們請(qǐng)求的網(wǎng)頁(yè)不存在就會(huì)返回一個(gè) 404 Not Found 狀態(tài)碼僚匆。
let http404Error = (404, "Not Found")
// http404Error 的類型是 (Int, String)微渠,值是 (404, "Not Found")
(404, "Not Found") 元組把一個(gè) Int 值和一個(gè) String 值組合起來表示 HTTP 狀態(tài)碼的兩個(gè)部分,這個(gè)元組可以被描述為“一個(gè)類型為 (Int, String) 的元組”咧擂。
元組的使用 (Tuple)
比如交換輸入逞盆,我們經(jīng)常會(huì)這么寫
func swap<T>(a: inout T, b: inout T) {
let temp = a
a = b
b = temp
}
但是要是使用多元組的話,我們可以不使用額外空間就完成交換松申,編寫簡(jiǎn)單:
func swap2<T>(a: inout T, b: inout T) {
(a,b) = (b,a)
}
32云芦、協(xié)議中的 Self
我們?cè)诳匆恍﹨f(xié)議的定義時(shí),可能會(huì)注意到出現(xiàn)了首字母大寫的Self
出現(xiàn)在類型的位置上:
protocol IntervalType {
func clamp(intervalClamp: Self) -> Self
}
比如上面這個(gè)
IntervalType
的協(xié)議定義了一個(gè)方法攻臀,接受實(shí)現(xiàn)該協(xié)議的自身的類型焕数,并返回一個(gè)同樣的類型
這么定義是因?yàn)閰f(xié)議其實(shí)本身是沒有自己的上下文類型信息的,在聲明協(xié)議的時(shí)候刨啸,我們并不知道最后究竟會(huì)是什么樣的類型來實(shí)現(xiàn)這個(gè)協(xié)議堡赔,Swift
中也不能在協(xié)議中定義泛型進(jìn)行限制。
而在聲明協(xié)議時(shí)设联,我們希望在協(xié)議中使用的類型就是實(shí)現(xiàn)這個(gè)協(xié)議本身的類型的話善已,就需要使用
Self
進(jìn)行指代灼捂。
但是在這種情況下,Self
不僅指代的是實(shí)現(xiàn)該協(xié)議的類型本身换团,也包括了這個(gè)類型的子類悉稠。從概念上來說,Self
十分簡(jiǎn)單艘包,但是實(shí)際實(shí)現(xiàn)一個(gè)這樣的方法卻稍微要轉(zhuǎn)個(gè)彎的猛。
33、final關(guān)鍵字
final
關(guān)鍵字可以用在 class 想虎,func
或者 var
前面進(jìn)行修飾卦尊,表示不允許對(duì)該內(nèi)容進(jìn)行繼承或者重寫操作。這樣的禁止繼承和重寫的做法是非常有益的舌厨,它可以更好地對(duì)代碼進(jìn)行版本控制岂却,得到更佳的性能,以及使代碼更安全裙椭。在寫 Swift 的時(shí)候可能會(huì)在什么情況下使用final
- 權(quán)限控制
給一段代碼加上final
就意味著編譯器向你作出保證躏哩,這段代碼不會(huì)再被修改;同時(shí),這也意味著你認(rèn)為這段代碼已經(jīng)完備并且沒有再被進(jìn)行繼承或重寫的必要
- 類或者方法的功能確實(shí)已經(jīng)完備了
對(duì)于很多的輔助性質(zhì)的工具類或者方法揉燃,可能我們會(huì)考慮加上final
扫尺。這樣的類有一個(gè)比較大的 特點(diǎn),是很可能只包含類方法而沒有實(shí)例方法炊汤。比如我們很難想到一種情況需要繼承或重寫一個(gè)負(fù)責(zé)計(jì)算一段字符串的MD5
或者AES
加密解密的工具類器联。這種工具類和方法的算法是經(jīng)過完備 驗(yàn)證和固定的,使用者只需要調(diào)用婿崭,而相對(duì)來說不可能有繼承和重寫的需求拨拓。
子類繼承和修改是一件危險(xiǎn)的事情
為了父類中某些代碼一定會(huì)被執(zhí)行
class Parent {
final func method() {
print("開始配置")
// ..必要的代碼
methodImpl()
// ..必要的代碼
print("結(jié)束配置")
}
func methodImpl() {
fatalError("子類必須實(shí)現(xiàn)這個(gè)方法")
// 或者也可以給出默認(rèn)實(shí)現(xiàn)
}
}
class Child: Parent {
override func methodImpl() {
//..子類的業(yè)務(wù)邏輯
}
}
這樣,無(wú)論如何我們?nèi)绾问褂?code>method 氓栈,都可以保證需要的代碼一定被運(yùn)行過渣磷,而同時(shí)又給了子類 繼承和重寫自定義具體實(shí)現(xiàn)的機(jī)會(huì)。
- 性能考慮
使用final
的另一個(gè)重要理由是可能帶來的性能改善授瘦。因?yàn)榫幾g器能夠從final
中獲取額外的信 息醋界,因此可以對(duì)類或者方法調(diào)用進(jìn)行額外的優(yōu)化處理。
34提完、Swift中delegate的使用形纺?
Cocoa
開發(fā)中接口-委托 (protocol-delegate) 模式是一種常用的設(shè)計(jì)模式,它貫穿于整個(gè)Cocoa 框架
中徒欣,為代碼之間的關(guān)系清理和解耦合做出了不可磨滅的貢獻(xiàn)逐样。
在ARC
中,對(duì)于一般的delegate
,我們會(huì)在聲明中將其指定為weak
脂新,在這個(gè)delegate
實(shí)際的對(duì)象被釋放的時(shí)候挪捕,會(huì)被重置回nil
。這可以保證即使delegate
已經(jīng)不存在時(shí)争便,我們也不會(huì)由于訪問到已被回收的內(nèi)存而導(dǎo)致崩潰级零。
在 Swift 中我們當(dāng)然也會(huì)希望這么做。但是當(dāng)我們嘗試書寫這樣的代碼的時(shí)候滞乙,編譯器不會(huì)讓我們通過:
protocol MyClassDelegate {
func method()
}
class MyClass {
weak var delegate: MyClassDelegate?
}
class ViewController: UIViewController, MyClassDelegate {
// ...
var someInstance: MyClass!
override func viewDidLoad() {
super.viewDidLoad()
someInstance = MyClass()
someInstance.delegate = self
}
func method() {
print("Do something")
}
//...
}
// weak var delegate: MyClassDelegate? 編譯錯(cuò)誤
// 'weak' cannot be applied to non-class type 'MyClassDelegate'
這是因?yàn)?code>Swift的protocol
是可以被除了class
以外的其他類型遵守的奏纪,而對(duì)于像struct
或是enum
這樣的類型,本身就不通過引用計(jì)數(shù)來管理內(nèi)存斩启,所以也不可能用weak
這樣的ARC
的概念來進(jìn)行修飾亥贸。
想要在Swift
中使用 weak delegate
,我們就需要將protocol
限制在class
內(nèi)浇垦。一種做法是將protocol
聲明為 Objective-C
的,這可以通過在protocol
前面加上 @objc
關(guān)鍵字來達(dá)到荣挨,Objective-C
的 protocol
都只有類能實(shí)現(xiàn)男韧,因此使用 weak 來修飾就合理了:
@objc protocol MyClassDelegate {
func method()
}
另一種可能更好的辦法是在protocol
聲明的名字后面加上class
,這可以為編譯器顯式地指明這個(gè)protocol
只能由 class
來實(shí)現(xiàn)默垄。
protocol MyClassDelegate: class {
func method()
}
相比起添加 @objc
此虑,后一種方法更能表現(xiàn)出問題的實(shí)質(zhì),同時(shí)也避免了過多的不必要的Objective-C
兼容口锭,可以說是一種更好的解決方式朦前。
35、Swift編程風(fēng)格指南
objc
出版的《Swift進(jìn)階》中鹃操,看到編程風(fēng)格習(xí)慣的內(nèi)容韭寸,覺得很不錯(cuò)。所以這里記錄下來荆隘,希望我們?cè)谧约旱捻?xiàng)目中使用Swift
代碼時(shí)恩伺,也應(yīng)該盡量遵循如下的原則:
1、對(duì)于命名椰拒,在使用時(shí)能清晰表意是最重要晶渠。因?yàn)?code>API被使用的次數(shù)要遠(yuǎn)遠(yuǎn)多于被聲明的次數(shù),所以我們應(yīng)當(dāng)從使用者的?度來考慮它們的名字燃观。盡快熟悉
Swift API
設(shè)計(jì)準(zhǔn)則褒脯,并且在你自己的代碼中堅(jiān)持使用這些準(zhǔn)則。2缆毁、簡(jiǎn)潔經(jīng)常有助于代碼清晰番川,但是簡(jiǎn)潔本身不應(yīng)該獨(dú)自成為我們編碼的目標(biāo)。
3、務(wù)必為函數(shù)添加文檔注釋—特別是泛型函數(shù)爽彤。
4养盗、類型使用大寫字母開頭,函數(shù)适篙、變量和枚舉成員使用小寫字母開頭往核,兩者都使用駝峰式命名法。
5嚷节、使用類型推斷聂儒,省略掉顯而易?的類型會(huì)有助于提高可讀性。
6硫痰、如果存在歧義或者在進(jìn)行定義的時(shí)候不要使用類型推斷衩婚。(比如func就需要顯式地指定
返回類型)7、優(yōu)先選擇結(jié)構(gòu)體效斑,只在確實(shí)需要使用到類特有的特性或者是引用語(yǔ)義時(shí)才使用類非春。
8、除非你的設(shè)計(jì)就是希望某個(gè)類被繼承使用缓屠,否則都應(yīng)該將它們標(biāo)記為
final
奇昙。9、除非一個(gè)閉包后面立即跟隨有左括號(hào)敌完,否則都應(yīng)該使用尾隨閉包(trailingclosure)的語(yǔ) 法储耐。
10、使用
guard
來提早退出方法滨溉。11什湘、避免對(duì)可選值進(jìn)行強(qiáng)制解包和隱式強(qiáng)制解包。它們偶爾有用晦攒,但是經(jīng)常需要使用它們的話往往意味著有其他不妥的地方闽撤。
12、不要寫重復(fù)的代碼脯颜。如果你發(fā)現(xiàn)你寫了好幾次類似的代碼片段的話腹尖,試著將它們提取到 一個(gè)函數(shù)里,并且考慮將這個(gè)函數(shù)轉(zhuǎn)化為協(xié)議擴(kuò)展的可能性伐脖。
13热幔、試著去使用
map和reduce
,但這不是強(qiáng)制的讼庇。當(dāng)合適的時(shí)候绎巨,使用for
循環(huán)也無(wú)可厚 非。高階函數(shù)的意義是讓代碼可讀性更高蠕啄。但是如果使用reduce
的場(chǎng)景難以理解的話场勤, 強(qiáng)行使用往往事與愿違戈锻,這種時(shí)候簡(jiǎn)單的for
循環(huán)可能會(huì)更清晰。14和媳、試著去使用不可變值:除非你需要改變某個(gè)值格遭,否則都應(yīng)該使用
let
來聲明變量。不過 如果能讓代碼更加清晰高效的話留瞳,也可以選擇使用可變的版本拒迅。用函數(shù)將可變的部分封 裝起來,可以把它帶來的副作用進(jìn)行隔離她倘。15璧微、
Swift
的泛型可能會(huì)導(dǎo)致非常?的函數(shù)簽名。壞消息是我們現(xiàn)在除了將函數(shù)聲明強(qiáng)制寫成幾行以外硬梁,對(duì)此并沒有什么好辦法前硫。我們會(huì)在示例代碼中在這點(diǎn)上保持一貫性,這樣 你能看到我們是如何處理這個(gè)問題的荧止。16屹电、除非你確實(shí)需要,否則不要使用
self.
跃巡。在閉包表達(dá)式中危号,使用self
是一個(gè)清晰的信號(hào), 表明閉包將會(huì)捕獲self
瓷炮。17、盡可能地對(duì)現(xiàn)有的類型和協(xié)議進(jìn)行擴(kuò)展递宅,而不是寫一些全局函數(shù)娘香。這有助于提高可讀性, 讓別人更容易發(fā)現(xiàn)你的代碼办龄。
參考
《iOS面試之道》