iOS面試題 - Swift語(yǔ)言

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

由上面代碼可知宣蔚,Personclass類型即引用類型,所以當(dāng)修改anotherPerson對(duì)象的name屬性時(shí)候认境,原有person對(duì)象的name值也發(fā)生了改變胚委,而SomeStructstruct,是值類型叉信,所以structB對(duì)象的name屬性改變并不會(huì)影響structAname值亩冬。

在內(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ì)于classstruct的詳細(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)來看,SwiftJava這類純面向?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雹仿,internalfileprivate邑商,private

Swift中有五個(gè)級(jí)別的訪問控制權(quán)限,從高到低依次為openpublic暇仲,internalfileprivateprivate

它們遵循的基本原則是:高級(jí)別的變量不允許被定義為低級(jí)別變量的成員變量中跌。例如:一個(gè)private的class中不能包含public的String值。反之,低級(jí)別的變量卻可以定義在高級(jí)別的變量中闷沥。比如,public的class中可以含有private的Int值

open:具備最高的訪問權(quán)限虫啥。其修飾的類和方法可以在任意的module中被訪問和重寫;它是Swift3中新添加的訪問權(quán)限

publicpublic的權(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病往、extensionstruct共同使用

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ì)奔潰

引入weakunowned是為了解決由strong帶來的循環(huán)引用問題。簡(jiǎn)單來說沮明,就是當(dāng)兩個(gè)對(duì)象互相有一個(gè)強(qiáng)引用指向?qū)Ψ綍r(shí)辕坝,就會(huì)導(dǎo)致兩個(gè)對(duì)象在內(nèi)存中無(wú)法釋放荐健。

weakunwoned的使用場(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的特性,具有兩種:willSetdidSet.

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è)定,以及在willSetdidSet中對(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可以被classstruct以及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ì)算推遲到判定leftfalse時(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為什么將StringArrayDictionary設(shè)計(jì)成為值類型剃幌,就要和OC中相同的數(shù)據(jù)結(jié)構(gòu)設(shè)計(jì)進(jìn)行比較聋涨。在OC中,String锥忿,ArrayDictionary是皆為引用類型牛郑。

值類型相比引用類型,最大的優(yōu)勢(shì)在于可以高效地使用內(nèi)存敬鬓。值類型在棧上進(jìn)行操作淹朋,引用類型在堆上操作。棧上的操作僅僅是單個(gè)指針的上下移動(dòng)钉答,而堆上的操作則牽涉合并础芍、移位、重鏈接等数尿。也就說仑性,Swift這樣設(shè)計(jì)大幅度減少了堆上的內(nèi)存分配和回收的次數(shù)。同時(shí)右蹦,copy-on-write又將值傳遞和復(fù)制的開銷降到最低诊杆。

SwiftString歼捐,ArrayDictionary設(shè)計(jì)成為值類型也是為了線程安全。通過Swiftlet設(shè)置晨汹,使得這些數(shù)據(jù)達(dá)到真正意義上的“不變”豹储,也從根本上解決了多線程中內(nèi)存訪問和操作順序的問題。

SwiftString淘这,ArrayDictionary設(shè)計(jì)成為值類型還可以提升API 的靈活度剥扣。例如,通過實(shí)現(xiàn)Collection這樣的協(xié)議铝穷,可以遍歷String,使得整個(gè)開發(fā)更加靈活钠怯、高效。

14曙聂、如何使用Swift將協(xié)議(protocol)中的部分方法設(shè)計(jì)成為可選

@optional@requiredOC中特有的關(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),也可以被structenum這樣的值類型實(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í),新增conveniencerequired兩個(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則是判斷objectSomeClass(非子類)的實(shí)例對(duì)象時(shí)颈畸,才返回真。

注意:這兩個(gè)方法都有一個(gè)前提没讲,即object必須是NSObject或其子類

Swift中眯娱,由于很多class并非繼承自NSObject,故而Swiftis函數(shù)來進(jìn)行判斷爬凑,它相當(dāng)于isKindOfClass徙缴。這樣做的優(yōu)點(diǎn)是is函數(shù)不僅可以用于任何class類型上,也可以用來判斷enumstruct類型

自省經(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ò),提示找不到gettersetter方法啄刹,這是因?yàn)?code>Category不會(huì)自動(dòng)生成這兩個(gè)方法涮坐。解決的辦法是引入運(yùn)行時(shí)頭文件,并配合關(guān)聯(lián)對(duì)象的方法來實(shí)現(xiàn)誓军。其中主要涉及的兩個(gè)函數(shù)是objc_getAssociatedObjectobjc_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)settergetter溯职。

其中精盅,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)類似功能敢会。另外曾沈,在SwiftOC中,還可以通過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)體即可

多繼承

SwiftOC是不支持多繼承的,因?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可以用于structenum這樣的值類型赞弥。

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ū)別

Swiftstaticclass都表示“類型范圍作用域”的關(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ò):


20180301190454894.png
  • class修飾方法和計(jì)算屬性

我們同樣可以使用class修飾方法和計(jì)算屬性粱玲,但是不能夠修飾存儲(chǔ)屬性躬柬。

20180301184734222.png

對(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
    }
}
  • staticprotocol

Swift中的class抽减,struct允青,enum都可以實(shí)現(xiàn)某個(gè)指定的協(xié)議。如果我們想在protocol中定義一個(gè)類型作用域上的方法或者計(jì)算屬性胯甩,應(yīng)該使用哪個(gè)關(guān)鍵字昧廷?答案顯而易見,肯定是static偎箫,因?yàn)?code>static是通用的木柬。

注意:在使用protocol的時(shí)候,在enumstruct中仍然使用static進(jìn)行修飾淹办,在class中眉枕,classstatic都可以使用。

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è)彎的猛。

協(xié)議中的 Self

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-Cprotocol都只有類能實(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兼容口锭,可以說是一種更好的解決方式朦前。

delegate

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面試之道》

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末沧竟,一起剝皮案震驚了整個(gè)濱河市窃蹋,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖挽拔,帶你破解...
    沈念sama閱讀 206,839評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異癣防,居然都是意外死亡绍豁,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門驶悟,熙熙樓的掌柜王于貴愁眉苦臉地迎上來胡野,“玉大人,你說我怎么就攤上這事痕鳍×蚨梗” “怎么了龙巨?”我有些...
    開封第一講書人閱讀 153,116評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)熊响。 經(jīng)常有香客問我旨别,道長(zhǎng),這世上最難降的妖魔是什么汗茄? 我笑而不...
    開封第一講書人閱讀 55,371評(píng)論 1 279
  • 正文 為了忘掉前任秸弛,我火速辦了婚禮,結(jié)果婚禮上剔难,老公的妹妹穿的比我還像新娘胆屿。我一直安慰自己,他們只是感情好偶宫,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,384評(píng)論 5 374
  • 文/花漫 我一把揭開白布非迹。 她就那樣靜靜地躺著,像睡著了一般纯趋。 火紅的嫁衣襯著肌膚如雪憎兽。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,111評(píng)論 1 285
  • 那天吵冒,我揣著相機(jī)與錄音纯命,去河邊找鬼。 笑死痹栖,一個(gè)胖子當(dāng)著我的面吹牛亿汞,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播揪阿,決...
    沈念sama閱讀 38,416評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼疗我,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了南捂?” 一聲冷哼從身側(cè)響起吴裤,我...
    開封第一講書人閱讀 37,053評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎溺健,沒想到半個(gè)月后麦牺,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,558評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡鞭缭,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,007評(píng)論 2 325
  • 正文 我和宋清朗相戀三年剖膳,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片岭辣。...
    茶點(diǎn)故事閱讀 38,117評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡潮秘,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出易结,到底是詐尸還是另有隱情枕荞,我是刑警寧澤柜候,帶...
    沈念sama閱讀 33,756評(píng)論 4 324
  • 正文 年R本政府宣布,位于F島的核電站躏精,受9級(jí)特大地震影響渣刷,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜矗烛,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,324評(píng)論 3 307
  • 文/蒙蒙 一辅柴、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧瞭吃,春花似錦碌嘀、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至和蚪,卻和暖如春止状,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背攒霹。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評(píng)論 1 262
  • 我被黑心中介騙來泰國(guó)打工怯疤, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人催束。 一個(gè)月前我還...
    沈念sama閱讀 45,578評(píng)論 2 355
  • 正文 我出身青樓集峦,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親抠刺。 傳聞我的和親對(duì)象是個(gè)殘疾皇子塔淤,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,877評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容