原文鏈接:如何自己實(shí)現(xiàn)一個(gè)Swift數(shù)組
本文中徙鱼,我們將會(huì)探索Swift原生Array數(shù)組的實(shí)現(xiàn)方式缀遍,并且自定義實(shí)現(xiàn)一個(gè)數(shù)組類型,能夠字面量來(lái)創(chuàng)建數(shù)組弥奸,通過(guò)下標(biāo)來(lái)獲取元素盛泡。
查看文檔我們發(fā)現(xiàn),Swift的數(shù)組是一個(gè)結(jié)構(gòu)體類型夷蚊,它遵守了CollectionType
构挤、MutableCollectionType
、_DstructorSafeContainer
協(xié)議惕鼓,其中最重要的就是CollectionType
協(xié)議筋现,數(shù)組的一些主要功能都是通過(guò)這個(gè)協(xié)議實(shí)現(xiàn)的。
而CollectionType
協(xié)議又遵守Indexable
和SequenceType
這兩個(gè)協(xié)議箱歧。而在這兩個(gè)協(xié)議中矾飞,SequenceType
協(xié)議是數(shù)組、字典等集合類型最重要的協(xié)議叫胁,在文檔中解釋了SequenceType
是一個(gè)可以通過(guò)for
...in
循環(huán)迭代的類型凰慈,實(shí)現(xiàn)了這個(gè)協(xié)議汞幢,就可以for
...in
循環(huán)了驼鹅。
A type that can be iterated with a
for
...in
loop.
而SequenceType
是建立在GeneratorType
基礎(chǔ)上的,sequence需要GeneratorType
來(lái)告訴它如何生成元素森篷。
GeneratorType
GeneratorType
協(xié)議有兩部分組成:
- 它需要有一個(gè)
Element
關(guān)聯(lián)類型输钩,這也是它產(chǎn)生的值的類型。 - 它需要有一個(gè)
next
方法仲智。這個(gè)方法返回Element
的可選對(duì)象买乃。通過(guò)這個(gè)方法就可以一直獲取下一個(gè)元素,直到返回nil钓辆,就意味著已經(jīng)獲取到了所有元素剪验。
/// Encapsulates iteration state and interface for iteration over a
/// sequence.
///
/// - Note: While it is safe to copy a generator, advancing one
/// copy may invalidate the others.
///
/// Any code that uses multiple generators (or `for`...`in` loops)
/// over a single sequence should have static knowledge that the
/// specific sequence is multi-pass, either because its concrete
/// type is known or because it is constrained to `CollectionType`.
/// Also, the generators must be obtained by distinct calls to the
/// sequence's `generate()` method, rather than by copying.
public protocol GeneratorType {
/// The type of element generated by `self`.
associatedtype Element
/// Advance to the next element and return it, or `nil` if no next
/// element exists.
///
/// - Requires: `next()` has not been applied to a copy of `self`
/// since the copy was made, and no preceding call to `self.next()`
/// has returned `nil`. Specific implementations of this protocol
/// are encouraged to respond to violations of this requirement by
/// calling `preconditionFailure("...")`.
@warn_unused_result
public mutating func next() -> Self.Element?
}
我把自己實(shí)現(xiàn)的數(shù)組命名為MYArray
,generator為MYArrayGenerator
前联,為了簡(jiǎn)單功戚,這里通過(guò)字典來(lái)存儲(chǔ)數(shù)據(jù),并約定字典的key為從0開始的連續(xù)數(shù)字似嗤。就可以這樣來(lái)實(shí)現(xiàn)GeneratorType
:
/// 需保準(zhǔn)dic的key是從0開始的連續(xù)數(shù)字
struct MYArrayGenerator<T>: GeneratorType {
private let dic: [Int: T]
private var index = 0
init(dic: [Int: T]) {
self.dic = dic
}
mutating func next() -> T? {
let element = dic[index]
index += 1
return element
}
}
這里通過(guò)next
方法的返回值啸臀,隱式地為Element賦值。顯式地賦值可以這樣寫typealias Element = T
烁落。要使用這個(gè)生成器就非常簡(jiǎn)單了:
let dic = [0: "XiaoHong", 1: "XiaoMing"]
var generator = MYArrayGenerator(dic: dic)
while let elment = generator.next() {
print(elment)
}
// 打印的結(jié)果:
// XiaoHong
// XiaoMing
SequenceType
有了generator乘粒,接下來(lái)就可以實(shí)現(xiàn)SequenceType
協(xié)議了豌注。SequenceType
協(xié)議也是主要有兩部分:
- 需要有一個(gè)Generator關(guān)聯(lián)類型,它要遵守
GeneratorType
灯萍。 - 要實(shí)現(xiàn)一個(gè)
generate
方法轧铁,返回一個(gè)Generator。
同樣的旦棉,我們可以通過(guò)制定generate
方法的方法類型來(lái)隱式地設(shè)置Generator:
struct MYArray<T>: SequenceType {
private let dic: [Int: T]
func generate() -> MYArrayGenerator<T> {
return MYArrayGenerator(dic: dic)
}
}
這樣我們就可以創(chuàng)建一個(gè)MYArray
實(shí)例属桦,并通過(guò)for循環(huán)來(lái)迭代:
let dic = [0: "XiaoHong", 1: "XiaoMing", 2: "XiaoWang", 3: "XiaoHuang", 4: "XiaoLi"]
let array = MYArray(dic: dic)
for value in array {
print(value)
}
let names = array.map { $0 }
當(dāng)然,目前這個(gè)實(shí)現(xiàn)還存在很大的隱患他爸,因?yàn)閭魅氲淖值涞膋ey是不可知的聂宾,雖然我們限定了必須是Int類型,但無(wú)法保證它一定是從0開始诊笤,并且是連續(xù)系谐,因此我們可以通過(guò)修改初始化方法來(lái)改進(jìn):
init(elements: T...) {
dic = [Int: T]()
elements.forEach { dic[dic.count] = $0 }
}
然后我們就可以通過(guò)傳入多參數(shù)來(lái)創(chuàng)建實(shí)例了:
let array = MYArray(elements: "XiaoHong", "XiaoMing", "XiaoWang", "XiaoHuang", "XiaoLi")
再進(jìn)一步,通過(guò)實(shí)現(xiàn)ArrayLiteralConvertible
協(xié)議讨跟,我們可以像系統(tǒng)的Array數(shù)組一樣纪他,通過(guò)字面量來(lái)創(chuàng)建實(shí)例:
let array = ["XiaoHong", "XiaoMing", "XiaoWang", "XiaoHuang", "XiaoLi"]
最后還有一個(gè)數(shù)組的重要特性,就是通過(guò)下標(biāo)來(lái)取值晾匠,這個(gè)特性我們可以通過(guò)實(shí)現(xiàn)subscript
方法來(lái)實(shí)現(xiàn):
extension MYArray {
subscript(idx: Int) -> Element {
precondition(idx < dic.count, "Index out of bounds")
return dic[idx]!
}
}
print(array[3]) // XiaoHuang
至此茶袒,一個(gè)自定義的數(shù)組就基本實(shí)現(xiàn)了,我們可以通過(guò)字面量來(lái)創(chuàng)建一個(gè)數(shù)組凉馆,可以通過(guò)下標(biāo)來(lái)取值薪寓,可以通過(guò)for循環(huán)來(lái)遍歷數(shù)組,可以使用map澜共、forEach等高階函數(shù)向叉。
小結(jié)
要實(shí)現(xiàn)一個(gè)數(shù)組的功能,主要是通過(guò)實(shí)現(xiàn)
SequenceType
協(xié)議嗦董。SequenceType
協(xié)議有一個(gè)Generator實(shí)現(xiàn)GeneratorType
協(xié)議母谎,并通過(guò)Generator的next
方法來(lái)取值,這樣就可以通過(guò)連續(xù)取值京革,來(lái)實(shí)現(xiàn)for循環(huán)遍歷了奇唤。同時(shí)通過(guò)實(shí)現(xiàn)ArrayLiteralConvertible
協(xié)議和subscript
,就可以通過(guò)字面量來(lái)創(chuàng)建數(shù)組匹摇,并通過(guò)下標(biāo)來(lái)取值咬扇。
CollectionType
上面我們?yōu)榱伺宄?code>SequenceType的實(shí)現(xiàn)原理,通過(guò)實(shí)現(xiàn)SequenceType
和GeneratorType
來(lái)實(shí)現(xiàn)數(shù)組来惧,但實(shí)際上Swift系統(tǒng)的Array類型是通過(guò)實(shí)現(xiàn)CollectionType
來(lái)獲得這些特性的冗栗,而CollectionType
協(xié)議又遵守Indexable
和SequenceType
這兩個(gè)協(xié)議。并擴(kuò)展了兩個(gè)關(guān)聯(lián)類型Generator
和SubSequence
,以及9個(gè)方法隅居,但這兩個(gè)關(guān)聯(lián)類型都是默認(rèn)值钠至,而且9個(gè)方法也都在協(xié)議擴(kuò)展中有默認(rèn)實(shí)現(xiàn)。
因此胎源,我們只需要為Indexable
協(xié)議中要求的 startIndex
和 endIndex
提供實(shí)現(xiàn)棉钧,并且實(shí)現(xiàn)一個(gè)通過(guò)下標(biāo)索引來(lái)獲取對(duì)應(yīng)索引的元素的方法。只要我們實(shí)現(xiàn)了這三個(gè)需求涕蚤,我們就能讓一個(gè)類型遵守 CollectionType
了宪卿。因此這個(gè)自定義的數(shù)組可以這樣實(shí)現(xiàn):
struct MYArray<Element>: CollectionType {
private var dic: [Int: Element]
init(elements: Element...) {
dic = [Int: Element]()
elements.forEach { dic[dic.count] = $0 }
}
var startIndex: Int { return 0 }
var endIndex: Int { return dic.count }
subscript(idx: Int) -> Element {
precondition(idx < endIndex, "Index out of bounds")
return dic[idx]!
}
}
extension MYArray: ArrayLiteralConvertible {
init(arrayLiteral elements: Element...) {
dic = [Int: Element]()
elements.forEach { dic[dic.count] = $0 }
}
}