屬性
存儲(chǔ)屬性
簡(jiǎn)單來(lái)說(shuō),一個(gè)存儲(chǔ)屬性就是存儲(chǔ)在特定類(lèi)或結(jié)構(gòu)體實(shí)例里的一個(gè)常量或變量。存儲(chǔ)屬性可以是變量存儲(chǔ)屬性var,也可以是常量存儲(chǔ)屬性let。
struct FixedLengthRange {
var firstValue: Int
let length: Int
}
因?yàn)榻Y(jié)構(gòu)體有成員逐一構(gòu)造器,所以不需要屬性必須要有初始值或者必須需要構(gòu)造器。但在類(lèi)中卻不行。
// 報(bào)錯(cuò) Class 'Men' has no initializers
class Men {
var name: String
let sex = "man"
}
常量的存儲(chǔ)屬性
結(jié)構(gòu)體struct屬于值類(lèi)型。當(dāng)值類(lèi)型的實(shí)例被聲明為常量的時(shí)候,它的所有屬性也就成了常量。
let rangeOfFourItems = FixedLengthRange(firstValue: 0, length: 4)
// 該區(qū)間表示整數(shù)0,1,2,3
rangeOfFourItems.firstValue = 6
// 盡管 firstValue 是個(gè)變量屬性,這里還是會(huì)報(bào)錯(cuò)
類(lèi)class屬于引用類(lèi)型。把一個(gè)引用類(lèi)型的實(shí)例賦給一個(gè)常量后,仍然可以修改該實(shí)例的變量屬性。
class Men {
var name: String = ""
let sex = "man"
}
let someMan = Men()
someMan.name = "Jay"
延遲存儲(chǔ)屬性
延遲存儲(chǔ)屬性是指當(dāng)?shù)谝淮伪徽{(diào)用的時(shí)候才會(huì)計(jì)算其初始值的屬性。在屬性聲明前使用lazy來(lái)標(biāo)示一個(gè)延遲存儲(chǔ)屬性。
必須將延遲存儲(chǔ)屬性聲明成變量var,因?yàn)閷傩缘某跏贾悼赡茉趯?shí)例構(gòu)造完成之后才會(huì)得到。而常量屬性在構(gòu)造過(guò)程完成之前必須要有初始值,因此無(wú)法聲明成延遲屬性。
class DataImporter {
/* DataImporter 是一個(gè)負(fù)責(zé)將外部文件中的數(shù)據(jù)導(dǎo)入的類(lèi)。 這個(gè)類(lèi)的初始化會(huì)消耗不少時(shí)間。 */
var fileName = "data.txt"
// 這里會(huì)提供數(shù)據(jù)導(dǎo)入功能
}
class DataManager {
lazy var importer = DataImporter()
var data = [String]()
// 這里會(huì)提供數(shù)據(jù)管理功能
}
let manager = DataManager()
manager.data.append("Some data")
manager.data.append("Some more data")
// DataImporter 實(shí)例的 importer 屬性還沒(méi)有被創(chuàng)建
importer屬性只有在第一次被訪問(wèn)的時(shí)候才被創(chuàng)建。比如訪問(wèn)它的屬性fileName時(shí):
print(manager.importer.fileName)
// DataImporter 實(shí)例的 importer 屬性現(xiàn)在被創(chuàng)建了
注意:
如果一個(gè)被標(biāo)記為lazy的屬性在沒(méi)有初始化時(shí)就同時(shí)被多個(gè)線程訪問(wèn),則無(wú)法保證該屬性只會(huì)被初始化一次。
計(jì)算屬性
除存儲(chǔ)屬性外,類(lèi)、結(jié)構(gòu)體和枚舉可以定義計(jì)算屬性。計(jì)算屬性不直接存儲(chǔ)值,而是提供一個(gè)getter和一個(gè)可選的setter,來(lái)間接獲取和設(shè)置其他屬性或變量的值。
struct Point {
var x = 0.0, y = 0.0
}
struct Size {
var width = 0.0, height = 0.0
}
struct Rect {
var origin = Point()
var size = Size()
var center: Point {
get {
let centerX = origin.x + (size.width / 2)
let centerY = origin.y + (size.height / 2)
return Point(x: centerX, y: centerY)
}
set(newCenter) {
origin.x = newCenter.x - (size.width / 2)
origin.y = newCenter.y - (size.height / 2)
}
}
}
var square = Rect(origin: Point(x: 0.0, y: 0.0), size: Size(width: 10.0, height: 10.0))
let initialSquareCenter = square.center
square.center = Point(x: 15.0, y: 15.0)
print("square.origin is now at (\(square.origin.x), \(square.origin.y))")
// 打印 "square.origin is now at (10.0, 10.0)”
這個(gè)例子定義了 3 個(gè)結(jié)構(gòu)體來(lái)描述幾何形狀:
-
Point封裝了一個(gè)(x, y)的坐標(biāo) -
Size封裝了一個(gè)width和一個(gè)height -
Rect表示一個(gè)有原點(diǎn)和尺寸的矩形
Rect也提供了一個(gè)名為center的計(jì)算屬性。一個(gè)矩形的中心點(diǎn)可以從原點(diǎn)origin和大小size算出,所以不需要將它以顯式聲明的 Point來(lái)保存。Rect的計(jì)算屬性center提供了自定義的getter和setter來(lái)獲取和設(shè)置矩形的中心點(diǎn),就像它有一個(gè)存儲(chǔ)屬性一樣。
簡(jiǎn)化 setter 聲明
如果計(jì)算屬性的setter沒(méi)有定義表示新值的參數(shù)名,則可以使用默認(rèn)名稱(chēng)newValue。
struct Rect {
var origin = Point()
var size = Size()
var center: Point {
get {
let centerX = origin.x + (size.width / 2)
let centerY = origin.y + (size.height / 2)
return Point(x: centerX, y: centerY)
}
set {
origin.x = newValue.x - (size.width / 2)
origin.y = newValue.y - (size.height / 2)
}
}
}
只讀計(jì)算屬性
只有getter沒(méi)有setter的計(jì)算屬性就是只讀計(jì)算屬性。只讀計(jì)算屬性總是返回一個(gè)值,可以通過(guò)點(diǎn)運(yùn)算符訪問(wèn),但不能設(shè)置新的值。
必須使用var關(guān)鍵字定義計(jì)算屬性,包括只讀計(jì)算屬性,因?yàn)樗鼈兊闹挡皇枪潭ǖ摹?code>let關(guān)鍵字只用來(lái)聲明常量屬性,表示初始化后再也無(wú)法修改的值。
struct Rect {
var origin = Point()
var size = Size()
var center: Point {
get {
let centerX = origin.x + (size.width / 2)
let centerY = origin.y + (size.height / 2)
return Point(x: centerX, y: centerY)
}
}
}
只讀計(jì)算屬性的聲明可以去掉get關(guān)鍵字和花括號(hào):
struct Rect {
var origin = Point()
var size = Size()
var center: Point {
let centerX = origin.x + (size.width / 2)
let centerY = origin.y + (size.height / 2)
return Point(x: centerX, y: centerY)
}
}
屬性觀察器
屬性觀察器監(jiān)控和響應(yīng)屬性值的變化,每次屬性被設(shè)置值的時(shí)候都會(huì)調(diào)用屬性觀察器,即使新值和當(dāng)前值相同的時(shí)候也不例外。
可以為除了延遲存儲(chǔ)屬性之外的其他存儲(chǔ)屬性添加屬性觀察器,也可以通過(guò)重寫(xiě)屬性的方式為繼承的屬性(包括存儲(chǔ)屬性和計(jì)算屬性)添加屬性觀察器。你不必為非重寫(xiě)的計(jì)算屬性添加屬性觀察器,因?yàn)榭梢酝ㄟ^(guò)它的setter直接監(jiān)控和響應(yīng)值的變化。
-
willSet在新的值被設(shè)置之前調(diào)用 -
didSet在新的值被設(shè)置之后立即調(diào)用
class StepCounter {
var totalSteps: Int = 0 {
willSet(newTotalSteps) {
print("About to set totalSteps to \(newTotalSteps)")
}
didSet {
if totalSteps > oldValue {
print("Added \(totalSteps - oldValue) steps")
}
}
}
}
let stepCounter = StepCounter()
stepCounter.totalSteps = 200
// About to set totalSteps to 200
// Added 200 steps
注意:
父類(lèi)的屬性在子類(lèi)的構(gòu)造器中被賦值時(shí),它在父類(lèi)中的 willSet 和 didSet 觀察器會(huì)被調(diào)用,隨后才會(huì)調(diào)用子類(lèi)的觀察器。在父類(lèi)初始化方法調(diào)用之前,子類(lèi)給屬性賦值時(shí),觀察器不會(huì)被調(diào)用。如果將屬性通過(guò) in-out 方式傳入函數(shù),willSet 和 didSet 也會(huì)調(diào)用。這是因?yàn)?in-out 參數(shù)采用了拷入拷出模式:即在函數(shù)內(nèi)部使用的是參數(shù)的 copy,函數(shù)結(jié)束后,又對(duì)參數(shù)重新賦值。
全局變量和局部變量
計(jì)算屬性和屬性觀察器所描述的功能也可以用于全局變量和局部變量。全局變量是在函數(shù)、方法、閉包或任何類(lèi)型之外定義的變量。局部變量是在函數(shù)、方法或閉包內(nèi)部定義的變量。
另外,在全局或局部范圍都可以定義計(jì)算型變量和為存儲(chǔ)型變量定義觀察器。計(jì)算型變量跟計(jì)算屬性一樣,返回一個(gè)計(jì)算結(jié)果而不是存儲(chǔ)值,聲明格式也完全一樣
全局的常量或變量都是延遲計(jì)算的,跟延遲存儲(chǔ)屬性相似,不同的地方在于,全局的常量或變量不需要標(biāo)記
lazy修飾符。局部范圍的常量或變量從不延遲計(jì)算。
類(lèi)型屬性
實(shí)例屬性屬于一個(gè)特定類(lèi)型的實(shí)例,每創(chuàng)建一個(gè)實(shí)例,實(shí)例都擁有屬于自己的一套屬性值,實(shí)例之間的屬性相互獨(dú)立。
類(lèi)型本身定義屬性,無(wú)論創(chuàng)建了多少個(gè)該類(lèi)型的實(shí)例,這些屬性都只有唯一一份。這種屬性就是類(lèi)型屬性。
類(lèi)型屬性注意點(diǎn)
- 跟實(shí)例的存儲(chǔ)型屬性不同,必須給存儲(chǔ)型類(lèi)型屬性指定默認(rèn)值,因?yàn)轭?lèi)型本身沒(méi)有構(gòu)造器,也就無(wú)法在初始化過(guò)程中使用構(gòu)造器給類(lèi)型屬性賦值。
- 存儲(chǔ)型類(lèi)型屬性是延遲初始化的,它們只有在第一次被訪問(wèn)的時(shí)候才會(huì)被初始化。即使它們被多個(gè)線程同時(shí)訪問(wèn),系統(tǒng)也保證只會(huì)對(duì)其進(jìn)行一次初始化,并且不需要對(duì)其使用
lazy修飾符。
類(lèi)型屬性語(yǔ)法
使用關(guān)鍵字static來(lái)定義類(lèi)型屬性。在為類(lèi)定義計(jì)算型類(lèi)型屬性時(shí),可以改用關(guān)鍵字class來(lái)支持子類(lèi)對(duì)父類(lèi)的實(shí)現(xiàn)進(jìn)行重寫(xiě)。
struct SomeStruct {
static var storedTypeProperty = "Some value."
static var computedTypeProperty: Int {
return 1
}
}
enum SomeEnum {
static var storedTypeProperty = "Some value."
static var computedTypeProperty: Int {
return 6
}
}
class SomeClass {
static var storedTypeProperty = "Some value."
static var computedTypeProperty: Int {
return 27
}
class var overrideableComputedTypeProperty: Int {
return 107
}
}
跟實(shí)例屬性一樣,類(lèi)型屬性也是通過(guò)點(diǎn)運(yùn)算符來(lái)訪問(wèn)。但是,類(lèi)型屬性是通過(guò)類(lèi)型本身來(lái)訪問(wèn),而不是通過(guò)實(shí)例。
print(SomeStruct.storedTypeProperty) // 打印 "Some value."
SomeStruct.storedTypeProperty = "Another value."
print(SomeStruct.storedTypeProperty) // 打印 "Another value.”
print(SomeEnum.computedTypeProperty) // 打印 "6"
print(SomeClass.computedTypeProperty) // 打印 "27"
方法
實(shí)例方法
實(shí)例方法是屬于某個(gè)特定類(lèi)、結(jié)構(gòu)體或者枚舉類(lèi)型實(shí)例的方法。實(shí)例方法提供訪問(wèn)和修改實(shí)例屬性的方法或提供與實(shí)例目的相關(guān)的功能,并以此來(lái)支撐實(shí)例的功能。實(shí)例方法的語(yǔ)法與函數(shù)完全一致,詳情參見(jiàn)函數(shù)。
class Counter {
var count = 0
func increment() {
count += 1
}
func increment(by amount: Int) {
count += amount
}
func reset() {
count = 0
}
}
self 屬性
類(lèi)型的每一個(gè)實(shí)例都有一個(gè)隱含屬性叫做self,self完全等同于該實(shí)例本身。你可以在一個(gè)實(shí)例的實(shí)例方法中使用這個(gè)隱含的self屬性來(lái)引用當(dāng)前實(shí)例。
func increment() {
self.count += 1
}
實(shí)際上,你不必在你的代碼里面經(jīng)常寫(xiě)self。只要在一個(gè)方法中使用一個(gè)已知的屬性或者方法名稱(chēng),如果你沒(méi)有明確地寫(xiě)self,Swift假定你是指當(dāng)前實(shí)例的屬性或者方法。
使用這條規(guī)則的主要場(chǎng)景是實(shí)例方法的某個(gè)參數(shù)名稱(chēng)與實(shí)例的某個(gè)屬性名稱(chēng)相同的時(shí)候。在這種情況下,參數(shù)名稱(chēng)享有優(yōu)先權(quán),并且在引用屬性時(shí)必須使用一種更嚴(yán)格的方式。這時(shí)你可以使用self屬性來(lái)區(qū)分參數(shù)名稱(chēng)和屬性名稱(chēng)。
struct Point {
var x = 0.0, y = 0.0
func isToTheRightOfX(x: Double) -> Bool {
return self.x > x
}
}
在實(shí)例方法中修改值類(lèi)型
結(jié)構(gòu)體和枚舉是值類(lèi)型。默認(rèn)情況下,值類(lèi)型的屬性不能在它的實(shí)例方法中被修改。
但是,如果你確實(shí)需要在某個(gè)特定的方法中修改結(jié)構(gòu)體或者枚舉的屬性,你可以為這個(gè)方法選擇可變mutating行為,然后就可以從其方法內(nèi)部改變它的屬性;并且這個(gè)方法做的任何改變都會(huì)在方法執(zhí)行結(jié)束時(shí)寫(xiě)回到原始結(jié)構(gòu)中。方法還可以給它隱含的self屬性賦予一個(gè)全新的實(shí)例,這個(gè)新實(shí)例在方法結(jié)束時(shí)會(huì)替換現(xiàn)存實(shí)例。
struct Point {
var x = 0.0, y = 0.0
mutating func moveBy(x deltaX: Double, y deltaY: Double) {
x += deltaX
y += deltaY
}
}
var somePoint = Point(x: 1.0, y: 1.0)
somePoint.moveBy(x: 2.0, y: 3.0)
print("The point is now at (\(somePoint.x), \(somePoint.y))")
// 打印 "The point is now at (3.0, 4.0)"
不能在結(jié)構(gòu)體類(lèi)型的常量上調(diào)用可變方法,因?yàn)槠鋵傩圆荒鼙桓淖?,即使屬性是變量屬性?/p>
let fixedPoint = Point(x: 3.0, y: 3.0)
fixedPoint.moveBy(x: 2.0, y: 3.0)
// 這里將會(huì)報(bào)告一個(gè)錯(cuò)誤
在可變方法中給 self 賦值
可變方法能夠賦給隱含屬性self一個(gè)全新的實(shí)例。上面Point的例子可以用下面的方式改寫(xiě):
struct Point {
var x = 0.0, y = 0.0
mutating func moveBy(x deltaX: Double, y deltaY: Double) {
self = Point(x: x + deltaX, y: y + deltaY)
}
}
類(lèi)型方法
實(shí)例方法是被某個(gè)類(lèi)型的實(shí)例調(diào)用的方法。你也可以定義在類(lèi)型本身上調(diào)用的方法,這種方法就叫做類(lèi)型方法。在方法的func關(guān)鍵字之前加上關(guān)鍵字static,來(lái)指定類(lèi)型方法。類(lèi)還可以用關(guān)鍵字class來(lái)允許子類(lèi)重寫(xiě)父類(lèi)的方法實(shí)現(xiàn)。
class SomeClass {
static func someMethod() {
// 在這里實(shí)現(xiàn)類(lèi)型方法
}
class func overrideableMethod() {
// 在這里實(shí)現(xiàn)類(lèi)型方法
}
}
SomeClass.someMethod()
SomeClass.overrideableMethod()
下標(biāo)
一個(gè)類(lèi)型可以定義多個(gè)下標(biāo),通過(guò)不同索引類(lèi)型進(jìn)行重載。下標(biāo)不限于一維,你可以定義具有多個(gè)入?yún)⒌南聵?biāo)滿足自定義類(lèi)型的需求。
下標(biāo)語(yǔ)法
下標(biāo)允許你通過(guò)在實(shí)例名稱(chēng)后面的方括號(hào)中傳入一個(gè)或者多個(gè)索引值來(lái)對(duì)實(shí)例進(jìn)行存取。語(yǔ)法類(lèi)似于實(shí)例方法語(yǔ)法和計(jì)算型屬性語(yǔ)法的混合。與定義實(shí)例方法類(lèi)似,定義下標(biāo)使用subscript關(guān)鍵字,指定一個(gè)或多個(gè)輸入?yún)?shù)和返回類(lèi)型;與實(shí)例方法不同的是,下標(biāo)可以設(shè)定為讀寫(xiě)或只讀。這種行為由getter和setter實(shí)現(xiàn),有點(diǎn)類(lèi)似計(jì)算型屬性:
struct TimesTable {
let multiplier: Int
subscript(index: Int) -> Int {
return multiplier * index
}
}
let threeTimesTable = TimesTable(multiplier: 3)
print("six times three is \(threeTimesTable[6])")
// 打印 "six times three is 18"
下標(biāo)可以接受任意數(shù)量的入?yún)ⅲ⑶疫@些入?yún)⒖梢允侨我忸?lèi)型。下標(biāo)的返回值也可以是任意類(lèi)型。下標(biāo)可以使用變量參數(shù)和可變參數(shù),但不能使用輸入輸出參數(shù),也不能給參數(shù)設(shè)置默認(rèn)值。
一個(gè)類(lèi)或結(jié)構(gòu)體可以根據(jù)自身需要提供多個(gè)下標(biāo)實(shí)現(xiàn),使用下標(biāo)時(shí)將通過(guò)入?yún)⒌臄?shù)量和類(lèi)型進(jìn)行區(qū)分,自動(dòng)匹配合適的下標(biāo),這就是下標(biāo)的重載。