在面向過程的語言中,要想實(shí)現(xiàn)類似類的功能只能借助結(jié)構(gòu)體,其實(shí)從OC源碼也能看出來,類的組成本就是復(fù)雜的結(jié)構(gòu)體實(shí)現(xiàn)的罪既。
而在Swift中結(jié)構(gòu)體的功能被擴(kuò)大化了,基本擁有了和類差不多的功能:
- 定義屬性
- 定義方法
- 定義getter和setter
- 可以定義初始化器來設(shè)置初始狀態(tài)
- 實(shí)現(xiàn)擴(kuò)展的功能
- 遵循協(xié)議,并實(shí)現(xiàn)協(xié)議的方法
- 結(jié)構(gòu)總是被復(fù)制窗看,并且不使用引用計(jì)數(shù)。
類具有結(jié)構(gòu)不具備的附加功能:
- 繼承允許一個類繼承另一個類的特征。
- 類型轉(zhuǎn)換使您能夠在運(yùn)行時檢查和解釋類實(shí)例的類型惰爬。
- 初始化器使一個類的實(shí)例能夠釋放它所分配的任何資源。
- 引用計(jì)數(shù)允許多個引用到一個類實(shí)例辜王。
定義結(jié)構(gòu)體和類
class className {
}
struct structName {
}
我們?yōu)轭惡徒Y(jié)構(gòu)體添加一些變量和方法:
struct Resolution {
var width = 0
var height = 0
func resolutionFun() {
print("method of struct Resolution")
}
}
class VideoMode {
var resolution = Resolution()
var interlaced = false
var frameRate = 0.0
var name: String?
func VideoMode() {
print("method of class VideoMode")
}
}
類和結(jié)構(gòu)體的初始化
定義是一回事,初始化又是另一回事劈狐。我們來看如何初始化它們:
let object Name
= Class Name
()
let variable Name
= Struct Name
()
let someResolution = Resolution()
let someVideoMode = VideoMode()
訪問屬性
Swift中訪問屬性使用的是鏈?zhǔn)浇Y(jié)構(gòu),訪問屬性的時候使用 .
object name
.propertyName
print("The width of someResolution is \(someResolution.width)")
print("The width of someVideoMode is \(someVideoMode.resolution.width)")
類或者結(jié)構(gòu)中的屬性默認(rèn)情況是可寫可讀的:
someVideoMode.resolution.width = 1280
print("The width of someVideoMode is now \(someVideoMode.resolution.width)")
關(guān)于直接修改類中結(jié)構(gòu)的數(shù)據(jù),這一點(diǎn)與OC是不同的,OC中并沒有處理好這一層級關(guān)系,導(dǎo)致類的中結(jié)構(gòu)體是只讀的呐馆,無法去修改肥缔。而Swift允許您直接設(shè)置結(jié)構(gòu)屬性的子屬性。
結(jié)構(gòu)體的初始化
所有結(jié)構(gòu)都有一個自動生成的成員智能初始化器汹来,您可以使用它來初始化新結(jié)構(gòu)實(shí)例的成員屬性:
struct Resolution {
var width = 0
var height = 0
}
let resolution = Resolution(width:100,height:120)
print("width:\(resolution.width),height:\(resolution.height)")
結(jié)果:
width:100,height:120
而類并不具備這樣的初始化器,如果需要特定的初始化需要去自定義,那么如何創(chuàng)建類似這樣的初始化器呢?
首先要知道初始化器是系統(tǒng)本身的方法,我們只能去重寫或者重載它
class VideoMode {
var _name: String
init(){
_name = "Jin"
}
}
let videoModeNormal = VideoMode()
print("name:\(videoModeNormal._name)")
結(jié)果:
name:Jin
我們重寫了系統(tǒng)默認(rèn)的init()初始化方法,并且設(shè)定我們想定義的功能续膳。
那么我們?nèi)绾稳?shí)現(xiàn)像結(jié)構(gòu)體那樣的初始功能呢?
這時候我們需要用到重載功能.
重載就是將父類已有的方法通過擴(kuò)充參數(shù)的形式重新定義一遍。
我們可以看一下對比:
class VideoMode {
var _name: String
init(){
_name = "Jin"
}
init(name:String){
_name = name;
}
}
let videoModeNormal = VideoMode()
print("name:\(videoModeNormal._name)")
let videoModeCustom = VideoMode(name: "Jack")
print("name:\(videoModeCustom._name)")
結(jié)果:
name:Jin
name:Jack
我們類中申明了兩個初始化方法,通過我們在實(shí)例化的時候使用不同的初始化方式,系統(tǒng)會自動識別使用哪個方法收班。
例如調(diào)用VideoMode()
的時候系統(tǒng)會調(diào)用init()方法,調(diào)用VideoMode(name: "Jack")
的時候坟岔,系統(tǒng)會調(diào)用 init(name:String)
方法。
當(dāng)然,struct的初始化方法也是可以這樣實(shí)現(xiàn)的:
struct Resolution {
var width: Double
init(){
width = 100
}
init(width:Double){
self.width = width;
}
}
let resolutionNormal = Resolution()
print("width:\(resolutionNormal.width)")
let resolutionCustom = Resolution(width: 120)
print("width:\(resolutionCustom.width)")
結(jié)果:
width:100.0
width:120.0
調(diào)用init初始化方法,必須初始化結(jié)構(gòu)體或者類中所有的屬性摔桦。當(dāng)然,如果該屬性定義的時候已經(jīng)初始化可以不用炮车。
初始化可以參數(shù)下面的設(shè)定
var width: Double = 0
var height: Double = 0
init(){
width = 100
}
如果你像下面這樣寫,會報出:Return from initializer without initializing all stored properties(提示你返回的時候,并沒有將所有屬性初始化)的錯誤
var width: Double = 0
var height: Double
init(){
width = 100
}
類的初始化
上面大致了解了結(jié)構(gòu)體的初始化,還有一些類的初始化.雖然兩者很多地方是相同.但總有一些不同的地方.
class 的設(shè)定中,初始化分為了指定初始化(Designated initializers)和便利初始化(Convenience initializers).
指定初始化就是我們上面講的:
init(
parameters
) {
statements
}
而便利初始化是無法獨(dú)立運(yùn)作的。它設(shè)定是這樣的:
convenience init(
parameters
) {
statements
}
我們可以是實(shí)驗(yàn)一下:
class Resolution {
var width: Double = 0
var height: Double = 0
init(){
width = 100
}
convenience init(height:Double){
self.height = height;
}
}
會報錯:
Use of 'self' in property access 'height' before self.init initializes self
Self.init isn't called on all paths before returning from initializer
這兩個錯誤簡單來說反應(yīng)的就是這個init目前來說并不具備系統(tǒng)的init功能,它其實(shí)只是我們自定義的方法,但因?yàn)槭褂昧薸nit關(guān)鍵字,系統(tǒng)就無法理解這種行為了酣溃。 而要想讓它發(fā)生作用,我們需要它本身加入init方法的功能瘦穆。
convenience init(height:Double){
self.init()
self.height = height;
}
這樣就可以了。
而Swift本生也指定了這樣的規(guī)則:
- Rule 1
A designated initializer must call a designated initializer from its immediate superclass.
指定的初始化器必須從其直接的超類調(diào)用指定的初始化器赊豌。- Rule 2
A convenience initializer must call another initializer from the same class.
一個convenience修飾的初始化器必須從同一個類調(diào)用另一個初始化器扛或。- Rule 3
A convenience initializer must ultimately call a designated initializer.
一個convenience修飾的初始化器必須最終調(diào)用一個指定的初始化器。
這些規(guī)則如下圖所示:
在上圖中碘饼,超類有一個指定的初始化器和兩個方便的初始化器熙兔。一個方便初始化器調(diào)用另一個方便初始化器,它依次調(diào)用單個指定的初始化器艾恼。這滿足了上面的規(guī)則2和3住涉。超類本身沒有進(jìn)一步的超類,因此規(guī)則1不適用钠绍。
這個圖中的子類有兩個指定的初始化器和一個方便的初始化器舆声。方便初始化器必須調(diào)用兩個指定的初始化器中的一個,因?yàn)樗荒軓耐粋€類調(diào)用另一個初始化器柳爽。這滿足了上面的規(guī)則2和3媳握。兩個指定的初始化器都必須從超類調(diào)用單個指定的初始化器,以滿足上面的規(guī)則1磷脯。
這些規(guī)則不會影響您的類的用戶如何創(chuàng)建每個類的實(shí)例蛾找。上圖中的任何初始化器都可以用來創(chuàng)建它們所屬的類的完全初始化實(shí)例。這些規(guī)則只會影響您如何編寫類的初始化器的實(shí)現(xiàn)赵誓。
下圖顯示了四個類的更復(fù)雜的類層次結(jié)構(gòu)打毛。它演示了這個層次結(jié)構(gòu)中指定的初始化器如何充當(dāng)類初始化的“漏斗”點(diǎn)柿赊,簡化了鏈中的類之間的相互關(guān)系:
看似復(fù)雜的關(guān)系,其實(shí)也很簡單。
父類的init方法是會被繼承的,而它的子類如果選擇繼承父類的初始化方法,而想創(chuàng)建convenience修飾的初始化器幻枉,就必然需要使用各自類中的init方法闹瞧。
兩階段初始化
Class initialization in Swift is a two-phase process. In the first phase, each stored property is assigned an initial value by the class that introduced it. Once the initial state for every stored property has been determined, the second phase begins, and each class is given the opportunity to customize its stored properties further before the new instance is considered ready for use.
Swift中的類初始化是一個兩階段過程。在第一階段中展辞,每個存儲的屬性都由引入它的類分配一個初始值。一旦確定了每個存儲屬性的初始狀態(tài)万牺,第二個階段就開始了罗珍,并且每個類都有機(jī)會在新實(shí)例準(zhǔn)備好使用之前進(jìn)一步定制它的存儲屬性。
The use of a two-phase initialization process makes initialization safe, while still giving complete flexibility to each class in a class hierarchy. Two-phase initialization prevents property values from being accessed before they are initialized, and prevents property values from being set to a different value by another initializer unexpectedly.
使用兩階段的初始化過程使初始化更加安全脚粟,同時仍然為類層次結(jié)構(gòu)中的每個類提供完全的靈活性覆旱。兩階段初始化防止在初始化前訪問屬性值,并防止屬性值被另一個初始化器設(shè)置為不同的值核无。
Swift’s two-phase initialization process is similar to initialization in Objective-C. The main difference is that during phase 1, Objective-C assigns zero or null values (such as 0 or nil) to every property. Swift’s initialization flow is more flexible in that it lets you set custom initial values, and can cope with types for which 0 or nil is not a valid default value.
Swift的兩階段初始化過程類似于在Objective-C中初始化扣唱。主要的區(qū)別在于,在第1階段团南,Objective-C中對每個屬性都賦值0或null值(例如0或nil)噪沙。Swift的初始化流更靈活,它允許您設(shè)置自定義的初始值吐根,并且可以處理0或nil不是有效的默認(rèn)值的類型正歼。
Swift的編譯器執(zhí)行四個有用的安全檢查,以確保兩階段的初始化是沒有錯誤的完成的:
- Safety check 1
A designated initializer must ensure that all of the properties introduced by its class are initialized before it delegates up to a superclass initializer.
一個指定的初始化器必須確保它的類所引入的所有屬性在它委托給一個超類初始化器之前被初始化拷橘。
- As mentioned above, the memory for an object is only considered fully initialized once the initial state of all of its stored properties is known. In order for this rule to be satisfied, a designated initializer must make sure that all of its own properties are initialized before it hands off up the chain.
正如上面所提到的局义,只有當(dāng)所有存儲屬性的初始狀態(tài)被知道時,對象的內(nèi)存才會被完全初始化冗疮。為了讓這個規(guī)則得到滿足萄唇,一個指定的初始化器必須確保所有的屬性都在它被連接到鏈之前被初始化。
- Safety check 2
A designated initializer must delegate up to a superclass initializer before assigning a value to an inherited property. If it doesn’t, the new value the designated initializer assigns will be overwritten by the superclass as part of its own initialization.
指定的初始化器必須在將一個值賦值給繼承屬性之前將其委托給一個超類初始化器术幔。如果沒有另萤,指定初始化器分配的新值將被超類作為其自身初始化的一部分重寫。
- Safety check 3
A convenience initializer must delegate to another initializer before assigning a value to any property (including properties defined by the same class). If it doesn’t, the new value the convenience initializer assigns will be overwritten by its own class’s designated initializer.
一個便利的初始化器必須委托給另一個初始化器诅挑,然后將一個值賦給任何屬性(包括由同一個類定義的屬性)仲墨。如果它不這樣做,那么方便初始化器分配的新值將被它自己的類的指定初始化器重寫揍障。
- Safety check 4
An initializer cannot call any instance methods, read the values of any instance properties, or refer to self as a value until after the first phase of initialization is complete.
一個初始化器不能調(diào)用任何實(shí)例方法目养,讀取任何實(shí)例屬性的值,或者將self作為一個值毒嫡,直到初始化的第一階段完成癌蚁。
- The class instance is not fully valid until the first phase ends. Properties can only be accessed, and methods can only be called, once the class instance is known to be valid at the end of the first phase.
類實(shí)例在第一個階段結(jié)束之前是不完全有效的幻梯。只有當(dāng)類實(shí)例在第一階段的末尾是有效的時,才可以訪問屬性努释,并且只能調(diào)用方法碘梢。
下面是第一階段如何查找一個假設(shè)的子類和超類的初始化調(diào)用:
下面是兩階段的初始化是如何進(jìn)行的,基于上面的四個安全檢查:
階段 1
- A designated or convenience initializer is called on a class.
在類上調(diào)用指定的或方便的初始化器伐蒂。
- Memory for a new instance of that class is allocated. The memory is not yet initialized.
為該類的一個新實(shí)例的內(nèi)存分配煞躬。內(nèi)存還沒有初始化。
- A designated initializer for that class confirms that all stored properties introduced by that class have a value. Thememory for these stored properties is now initialized.
這個類的指定初始化器確認(rèn)了該類所引入的所有存儲屬性都有一個值逸邦。這些存儲屬性的內(nèi)存現(xiàn)在已經(jīng)被初始化了恩沛。
- The designated initializer hands off to a superclass initializer to perform the same task for its own stored properties.
指定的初始化器交給一個超類初始化器,為它自己的存儲屬性執(zhí)行相同的任務(wù)缕减。
- This continues up the class inheritance chain until the top of the chain is reached.
這將繼續(xù)繼承類繼承鏈雷客,直到到達(dá)鏈的頂端。
- Once the top of the chain is reached, and the final class in the chain has ensured that all of its stored properties have a value, the instance’s memory is considered to be fully initialized, and phase 1 is complete.
一旦到達(dá)鏈的頂端桥狡,并且鏈中的最后一個類確保其所有的存儲屬性都有一個值搅裙,實(shí)例的內(nèi)存就被認(rèn)為是完全初始化的,而階段1是完整的裹芝。
這里的鏈就是
class.classProperty
通過.
連接起來的結(jié)構(gòu)
階段2
- Working back down from the top of the chain, each designated initializer in the chain has the option to customize the instance further. Initializers are now able to access self and can modify its properties, call its instance methods, and so on.
從鏈的頂部向下工作部逮,鏈中的每個指定初始化器都可以選擇進(jìn)一步自定義實(shí)例。初始化器現(xiàn)在能夠訪問self并可以修改它的屬性嫂易,調(diào)用它的實(shí)例方法甥啄,等等。
- Finally, any convenience initializers in the chain have the option to customize the instance and to work with self.
最后炬搭,鏈中的任何方便的初始化器都可以選擇自定義實(shí)例并與self一起工作蜈漓。
下面是第2階段如何查找相同的初始化調(diào)用:
官網(wǎng)給出的解釋很是詳盡的解釋了初始化的工作步驟,雖然枯燥,但確實(shí)必須的。從這層工作中也看到了Swift相比于OC的安全和穩(wěn)定性更加出色,比起OC更多時候因?yàn)橐馔鈱?dǎo)致的崩潰情況,Swift在這方面的檢查機(jī)制要更加全面一點(diǎn)宫盔。
初始化器繼承和重寫
與Objective-C中的子類不同融虽,Swift子類在默認(rèn)情況下不會繼承父類初始化器。
Swift’s approach prevents a situation in which a simple initializer from a superclass is inherited by a more specialized subclass and is used to create a new instance of the subclass that is not fully or correctly initialized.
Swift的方法防止了一種情況灼芭,即一個超類的簡單初始化器是由一個更專門化的子類繼承而來的有额,它被用來創(chuàng)建一個未完全或正確初始化的子類的新實(shí)例。
當(dāng)您編寫一個與超類指定初始化器相匹配的子類初始化器時彼绷,您實(shí)際上對指定的初始化器進(jìn)行重寫巍佑。因此,您必須在子類的初始化器定義之前編寫override
修飾符寄悯。
class Vehicle {
var numberOfWheels = 0
}
class Bicycle: Vehicle {
override init() {
super.init()
numberOfWheels = 2
}
}
自動初始化繼承
As mentioned above, subclasses do not inherit their superclass initializers by default. However, superclass initializers are automatically inherited if certain conditions are met. In practice, this means that you do not need to write initializer overrides in many common scenarios, and can inherit your superclass initializers with minimal effort whenever it is safe to do so.
正如上面提到的萤衰,子類在默認(rèn)情況下不會繼承父類初始化器。但是猜旬,如果滿足某些條件脆栋,超類初始化器就會自動繼承倦卖。在實(shí)踐中,這意味著您不需要在許多常見的場景中重寫初始化器椿争,并且可以在安全的情況下以最小的工作量來繼承您的超類初始化器怕膛。
假設(shè)您為您在子類中引入的任何新屬性提供了默認(rèn)值,下面的兩個規(guī)則適用:
- 規(guī)則1
如果你的子類沒有定義任何指定的初始化器秦踪,它會自動繼承所有父類指定的初始化器褐捻。
- 規(guī)則2
如果您的子類提供了所有超類指定的初始化器的實(shí)現(xiàn),或者通過它們實(shí)現(xiàn)繼承并遵守規(guī)則1椅邓,或者通過提供自定義實(shí)現(xiàn)作為其定義的一部分柠逞,那么它就會自動繼承所有超類的方便初始化器。
總結(jié)
- You can use both classes and structures to define custom data types to use as the building blocks of your program’s code.
您可以使用類和結(jié)構(gòu)來定義定制的數(shù)據(jù)類型希坚,以作為程序代碼的構(gòu)建塊。
- However, structure instances are always passed by value, and class instances are always passed by reference. This means that they are suited to different kinds of tasks. As you consider the data constructs and functionality that you need for a project, decide whether each data construct should be defined as a class or as a structure.
但是陵且,結(jié)構(gòu)實(shí)例總是通過值傳遞裁僧,而類實(shí)例總是通過引用傳遞。這意味著它們適用于不同類型的任務(wù)慕购。當(dāng)您考慮一個項(xiàng)目需要的數(shù)據(jù)結(jié)構(gòu)和功能時聊疲,決定每個數(shù)據(jù)結(jié)構(gòu)是否應(yīng)該被定義為一個類或一個結(jié)構(gòu)。
As a general guideline, consider creating a structure when one or more of these conditions apply:
指導(dǎo)方針沪悲,考慮在一個或多個條件適用的情況下創(chuàng)建一個結(jié)構(gòu):
- The structure’s primary purpose is to encapsulate a few relatively simple data values.
結(jié)構(gòu)體的主要目的是封裝一些相對簡單的數(shù)據(jù)值获洲。
- It is reasonable to expect that the encapsulated values will be copied rather than referenced when you assign or pass around an instance of that structure.
這是當(dāng)您合理的分配或傳遞該結(jié)構(gòu)的實(shí)例時,封裝的值將被復(fù)制殿如,而不是被引用贡珊。
- Any properties stored by the structure are themselves value types, which would also be expected to be copied rather than referenced.
結(jié)構(gòu)中存儲的任何屬性都是值類型,它也可以被復(fù)制涉馁,而不是被引用门岔。
- The structure does not need to inherit properties or behavior from another existing type.
結(jié)構(gòu)體不需要從另一個現(xiàn)有類型繼承屬性或行為。
優(yōu)秀的結(jié)構(gòu)體例子包括:
- The size of a geometric shape, perhaps encapsulating a width property and a height property, both of type Double.
幾何形狀的大小烤送,可能是封裝了寬度屬性和高度屬性寒随,兩者都是Double類型。
- A way to refer to ranges within a series, perhaps encapsulating a start property and a length property, both of type Int.
一種表示范圍內(nèi)的范圍的方法帮坚,可能是封裝一個起始屬性和一個長度屬性妻往,這兩種類型都是Int類型。
- A point in a 3D coordinate system, perhaps encapsulating x, y and z properties, each of type Double.
In all other cases, define a class, and create instances of that class to be managed and passed by reference. In practice, this means that most custom data constructs should be classes, not structures.
三維坐標(biāo)系中的一個點(diǎn)试和,可能是封裝x讯泣、y和z的屬性,每一種類型都是雙精度的阅悍。
在所有其他情況下判帮,定義一個類局嘁,并創(chuàng)建該類的實(shí)例,以便通過引用來管理和傳遞晦墙。在實(shí)踐中悦昵,這意味著大多數(shù)自定義數(shù)據(jù)結(jié)構(gòu)應(yīng)該是類,而不是結(jié)構(gòu)晌畅。
In Swift, many basic data types such as String, Array, and Dictionary are implemented as structures. This means that data such as strings, arrays, and dictionaries are copied when they are assigned to a new constant or variable, or when they are passed to a function or method.
在Swift中但指,許多基本的數(shù)據(jù)類型,如字符串抗楔、數(shù)組和字典都是作為結(jié)構(gòu)實(shí)現(xiàn)的棋凳。這意味著當(dāng)將字符串、數(shù)組和字典等數(shù)據(jù)分配給新的常量或變量時连躏,或者當(dāng)它們被傳遞給函數(shù)或方法時滑废,就會復(fù)制這些數(shù)據(jù)。
This behavior is different from Foundation: NSString, NSArray, and NSDictionary are implemented as classes, not structures. Strings, arrays, and dictionaries in Foundation are always assigned and passed around as a reference to an existing instance, rather than as a copy.
這種行為與Foundation庫不同:NSString坎吻、NSArray和NSDictionary都是作為類實(shí)現(xiàn)的解总,而不是結(jié)構(gòu)。Foundation中的字符串勺良、數(shù)組和字典總是被分配和傳遞绰播,作為對現(xiàn)有實(shí)例的引用,而不是作為副本尚困。