十 屬性ios
一、存儲屬性swift
最簡單的情形,做爲特定類或結構實例的一部分,存儲屬性存儲着常量或者變量的值。存儲屬性可分爲變量存儲屬性(關鍵字var描述)和常量存儲屬性(關鍵字let描述)。數組
當定義存儲屬性時,你能夠提供一個默認值,這些在「默認屬性值」描述。在初始化過程當中你也能夠設置或改變存儲屬性的初值。這個準則對常量存儲屬性也一樣適用(在「初始化過程當中改變常量屬性」描述)閉包
下面的例子定義了一個叫FixedLengthRange的結構,它描述了一個必定範圍內的整數值,當建立這個結構時,範圍長度是不能夠被改變的:app
struct FixedLengthRange {函數
var firstValue: Intthis
let length: Intspa
}繼承
var rangeOfThreeItems = FixedLengthRange(firstValue: 0, length: 3)接口
the range represents integer values 0, 1, and 2
rangeOfThreeItems.firstValue = 6
常量結構實例的存儲屬性
若是你建立一個結構實例,並將其賦給一個常量,這個實例中的屬性將不能夠被改變,即便他們被聲明爲變量屬性
let rangeOfFourItems = FixedLengthRange(firstValue: 0, length: 4)
this range represents integer values 0, 1, 2, and 3
rangeOfFourItems.firstValue = 6 這裏報錯,屬性不可變
由於rangeOfFourItems是一個常量(let),即使firstValue是一個變量屬性,它的值也是不能夠被改變的
這樣的特性是由於結構是值類型。當一個值類型實例做爲常量而存在,它的全部屬性也做爲常量而存在。
而這個特性對類並不適用,由於類是引用類型。若是你將引用類型的實例賦值給常量,依然可以改變實例的變量屬性。
Lazy Stored Properties(懶惰存儲屬性)
懶惰存儲屬性是當它第一次被使用時才進行初值計算。經過在屬性聲明前加上lazy來標識一個懶惰存儲屬性。
注意
必須聲明懶惰存儲屬性爲變量屬性(經過var),由於它的初始值直到實例初始化完成以後才被檢索。常量屬性在實例初始化完成以前就應該被賦值,所以常量屬性不可以被聲明爲懶惰存儲屬性。
當屬性初始值由於外部緣由,在實例初始化完成以前不可以肯定時,就要定義成懶惰存儲屬性。當屬性初始值須要複雜或高代價的設置,在它須要時才被賦值時,懶惰存儲屬性就派上用場了。
下面的例子使用懶惰存儲屬性來防止類中沒必要要的初始化操做。它定義了類DataImporter和類DataManager:
class DataImporter {
var fileName = "data.txt"
}
class DataManager {
lazy var importer = DataImporter()
var data = [String]()
}
let manager = DataManager()
manager.data.append("Some data")
manager.data.append("Some more data")
類DataManager有一個稱爲data的存儲屬性,它被初始化爲一個空的String數組。雖然DataManager定義的其它部分並無寫出來,但能夠看出DataManager的目的是管理String數據併爲其提供訪問接口。
DataManager類的部分功能是從文件中引用數據。這個功能是由DataImporter類提供的,這個類須要必定的時間來初始化,由於它的實例須要打開文件並見內容讀到內存中。
由於DataManager實例可能並不須要當即管理從文件中引用的數據,因此在DataManager實例被建立時,並不須要立刻就建立一個新的DataImporter實例。這就使得當DataImporter實例在須要時才被建立起來。
由於被聲明爲lazy屬性,DataImporter的實例importer只有在當它在第一次被訪問時才被建立。例如它的fileName屬性須要被訪問時:
print(manager.importer.fileName)
二、計算屬性
除了存儲屬性,類、結構和枚舉可以定義計算屬性。計算屬性並不存儲值,它提供getter和可選的setter來間接地獲取和設置其它的屬性和值。
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))")
這個例子定義了三個處理幾何圖形的結構:
Point包含一個(x,y)座標
Size包含寬度width和高度height
Rect定義了一個長方形,包含原點和大小size
Rect結構包含一個稱之爲center的計算屬性。Rect當前中心點的座標能夠經過origin和size屬性得來,因此並不須要顯式地存儲中心點的值。取而代之的是,Rect定義一個稱爲center的計算屬性,它包含一個get和一個set方法,經過它們來操做長方形的中心點,就像它是一個真正的存儲屬性同樣。
setter聲明的簡略寫法
若是計算屬性的setter方法沒有將被設置的值定義一個名稱,將會默認地使用newValue這個名稱來代替。下面的例子採用了這樣一種特性,定義了Rect結構的新版本:
struct AlternativeRect {
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)
}
}
}
只讀計算屬性
只讀計算屬性只帶有一個getter方法,經過點操做符,能夠放回屬性值,可是不能修改它的值。
注意
應該使用var關鍵字將計算屬性(包含只讀計算屬性)定義成變量屬性,由於它們的值並非固定的。let關鍵字只被常量屬性說使用,以代表一旦被設置它們的值就是不可改變的了
經過移除get關鍵字和它的大括號,能夠簡化只讀計算屬性的定義:
struct Cuboid {
var width = 0.0, height = 0.0, depth = 0.0
var volume: Double {
return width * height * depth
}
}
let fourByFiveByTwo = Cuboid(width: 4.0, height: 5.0, depth: 2.0)
print("the volume of fourByFiveByTwo is \(fourByFiveByTwo.volume)")
這個例子定義了一個三維長方體結構Cuboid,包含了長寬高三個屬性,和一個表示長方體容積的只讀計算屬性volume。volume值是不可被設置的,由於它直接由長寬高三個屬性計算而來。經過提供這樣一個只讀計算屬性,Cuboid使外部用戶可以訪問到其當前的容積值。
三、屬性觀察者
屬性觀察者觀察屬性值的改變並對此作出響應。當設置屬性的值時,屬性觀察者就被調用,即便當新值同原值相同時也會被調用。
除了懶惰存儲屬性,你能夠爲任何存儲屬性加上屬性觀察者定義。另外,經過重寫子類屬性,也能夠繼承屬性(存儲或計算)加上屬性觀察者定義。屬性重寫在「重寫」章節定義。
注意
沒必要爲未重寫的計算屬性定義屬性觀察者,由於能夠經過它的setter方法直接對值的改變作出響應
定義屬性的觀察者時,你能夠單獨或同時使用下面的方法:
willSet:設置值前被調用
didSet:設置值後馬上被調用
當實現willSet觀察者時,新的屬性值做爲常量參數被傳遞。你能夠爲這個參數起一個名字,若是不的話,這個參數就默認地被命名成newValue。
在實現didSet觀察者時也是同樣,只不過傳遞的產量參數表示的是舊的屬性值。
屬性初始化時,willset和didSet並不會被調用。只有在初始化上下文以外,當設置屬性值時才被調用
下面是一個willSet和didSet用法的實例。定義了一個類StepCounter,用來統計人走路時的步數。它能夠從計步器或其它計數器上獲取輸入數據,對平常聯繫鍛鍊的步數進行追蹤。
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
stepCounter.totalSteps = 360
About to set totalSteps to 360
Added 160 steps
stepCounter.totalSteps = 896
About to set totalSteps to 896
Added 536 steps
類StepCounter聲明瞭一個Int類型的、含有willSet和didSet觀察者的存儲屬性totalSteps。當這個屬性被賦予新值時,willSet和didSet將會被調用,即便新值和舊值是相同的。
例子中的willSet觀察者爲參數起了個新的名字newTotalSteps,它簡單地打印了即將被設置的值。
當totalSteps值被更新時,didSet觀察者被調用,它比較totalSteps的新值和舊值,若是新值比舊值大,就打印所增長的步數。didSet並無爲舊值參數命名,在本例中,將會使用默認的名字oldValue來表示舊的值。
注意
若是經過didSet來設置屬性的值,即便屬性值剛剛被設置過,起做用的也將會是didSet,即新值是didSet設置的值
四、全局和局部變量
以上所寫的關於計算與觀察屬性值的特性一樣適用於全局和局部變量。全局變量是在任何函數、方法、閉包、類型上下文外部定義的變量,而局部變量是在函數、方法、閉包中定義的變量。
前面章節所遇到過的全局、局部變量都是存儲變量。和存儲屬性同樣,存儲變量爲特定類型提供存儲空間而且能夠被訪問
可是,你能夠在全局或局部範圍定義計算變量和存儲變量觀察者。計算變量並不存儲值,只用來計算特定值,它的定義方式與計算屬性同樣。
注意
全局常量和變量一般是延遲計算的,跟懶惰存儲屬性同樣,可是不須要加上@lazy。而局部常量與變量不是延遲計算的。
五、類型屬性
實例屬性是特定類型實例的屬性。當建立一個類型的實例時,這個實例有本身的屬性值的集合,這將它與其它實例區分開來。
也能夠定義屬於類型自己的屬性,即便建立再多的這個類的實例,這個屬性也不屬於任何一個,它只屬於類型自己,這樣的屬性就稱爲類型屬性。
類型屬性適用於定義那些特定類型實例所通用的屬性,例如一個能夠被全部實例使用的常量屬性(就像c中的靜態常量),或者變量屬性(c中的靜態變量)。
能夠爲值類型(結構、枚舉)定義存儲類型屬性和計算類型屬性。對類而言,只可以定義計算類型屬性。
值類型的存儲類型屬性能夠是常量也能夠是變量。而計算類型屬性一般聲明成變量屬性,相似於計算實例屬性
注意
不想存儲實例屬性,你須要給存儲類型屬性一個初始值。由於類型自己在初始化時不能爲存儲類型屬性設置值
類型屬性句法
在C和Objective-C中,定義靜態常量、變量和全局靜態變量同樣。可是在swift中,類型屬性的定義要放在類型定義中進行,在類型定義的大括號中,顯示地聲明它在類型中的做用域。
對值類型而言,定義類型屬性使用static關鍵字,而定義類類型的類型屬性使用class關鍵字。下面的例子展現了存儲和計算類型屬性的用法:
struct SomeStructure {
static var storedTypeProperty = "Some value."
static var computedTypeProperty: Int {
return 3
}
}
enum SomeEnumeration {
static var storedTypeProperty = "Some value."
static var computedTypeProperty: Int {
return 4
}
}
class SomeClass {
class var computedTypeProperty: Int {
return 5
}
}
注意
上面的例子是針對只讀計算類型屬性而言的,不過你也能夠像計算實例屬性同樣定義可讀可寫的計算類型屬性
查詢與設置類型屬性
像實例屬性同樣,類型屬性經過點操做符來查詢與設置。可是類型屬性的查詢與設置是針對類型而言的,並非針對類型的實例。例如:
print(SomeClass.computedTypeProperty)
print(SomeStructure.storedTypeProperty)
SomeStructure.storedTypeProperty = "Another value."
print(SomeStructure.storedTypeProperty)
十一方法
方法是關聯到一個特定類型的函數。類、結構、枚舉全部能夠定義實例方法,封裝特定任務和功能處理給定類型的一個實例。類、結構、枚舉類型還能夠定義方法,相關的類型自己。類型方法相似於objective – c類方法。
結構和枚舉能夠定義方法swift與C和objective – C是一個重大的區別。在objective – c中,類是惟一類型能夠定義方法。在swift,你能夠選擇是否要定義一個類,結構,或枚舉,還有你定義方法類型的靈活性創造。
一、實例方法
實例方法是屬於一個特定的類,結構或枚舉實例的功能。他們支持這些實例的功能,不管是經過提供方法來訪問和修改實例屬性,或提供的功能與實例的目的。實例方法具備徹底相同的語法功能,如功能描述
你所屬的類型的打開和關閉括號內寫一個實例方法。一個實例方法具備隱式訪問全部其餘實例方法和該類型的屬性。一個實例方法只能在它所屬的類的特定實例調用,它不能訪問一個不存在的實例。
這裏,定義了一個簡單的計數器類,它能夠用來計數一個動做發生的次數的示例:
class Counter {
var count = 0
func increment() {
count++
}
func incrementBy(amount: Int) {
count += amount
}
func reset() {
count = 0
}
}
let counter = Counter()
the initial counter value is 0
counter.increment()
the counter's value is now 1
counter.incrementBy(5)
the counter's value is now 6
counter.reset()
the counter's value is now 0
本地和外部參數名稱的方法
函數參數能夠有一個本地名稱(在函數體內使用)和外部名稱(在調用函數時使用),所述外部參數名稱。方法參數也是如此,由於方法與類型相關的函數。然而,本地名稱和外部名稱的默認行爲是不一樣的函數和方法。
方法在Swift很是相似於objective – c的同行。在objective – c中,一個方法的名稱在Swift一般是指使用preposition等方法的第一個參數,,或者,就像在incrementBy方法從前面的counter類的例子。使用能夠被解讀爲一個判斷的方法叫作preposition。Swift使這個方法創建命名約定易於編寫經過使用一個不一樣的默認方法。
具體來講,Swift給第一個參數名稱方法默認本地參數名稱,並給出第二和後續的參數名稱默認本地和外部參數名稱。這個約定能夠在熟悉的objective – c中調用到,並使得表達方法調用而不須要符合你的參數名稱。
考慮這個替代版本的counter類,它定義了一個更復雜的形式的incrementBy方法:
class Counter1 {
var count: Int = 0
func incrementBy(amount: Int, numberOfTimes: Int) {
count += amount * numberOfTimes
}
}
let counter1 = Counter1()
counter1.incrementBy(3, numberOfTimes: 3)
print(counter1.count)
你不須要定義一個外部參數名稱爲第一個參數值,由於它是明確的函數名incrementBy。然而,第二個參數是由外部參數名稱進行限定。
Self屬性
一個類型的每一個實例都有所謂的一個隱含self屬性,它是徹底等同於該實例自己。您可使用這個隱含的self屬性來引用當前實例中它本身的實例方法。
在實踐中,你不須要寫self,這在你的代碼會很是頻繁。若是你沒有明確寫self,Swift假設你是指當前實例的屬性或方法,每當你使用一個方法中一個已知的屬性或方法名。這個假設是證實了裏邊三個實例方法的計數器使用count(rather than self.count)的。
主要的例外發生在一個實例方法的參數名稱相同的名稱做爲該實例的屬性。在這種狀況下,參數名稱的優先,有必要參考屬性更多合格的方式。您可使用隱式的自我屬性的參數名和屬性名來區分。
struct Point {
var x = 0.0, y = 0.0
func isToTheRightOfX(x: Double) -> Bool {
return self.x > x
}
}
let somePoint = Point(x: 4.0, y: 5.0)
if somePoint.isToTheRightOfX(1.0) {
print("This point is to the right of the line where x == 1.0")
}
修改值類型的實例方法
結構和枚舉值類型。默認狀況下,一個值類型的屬性不能修改它的實例方法
然而,若是您須要修改的屬性結構或枚舉在一個特定的方法,你能夠選擇該方法的變化行爲。但任何更改都會使它得編寫的方法結束時回到原來的結構。當該方法結束時還能夠分配一個徹底新的實例對其隱含的self屬性,而這個新的實例將取代現有的。
你能夠選擇這個行爲以前將變異的關鍵字mutating嵌入函數關鍵字的方法:
struct Point1 {
var x = 0.0, y = 0.0
mutating func moveByX(deltaX: Double, y deltaY: Double) {
x += deltaX
y += deltaY
}
}
var somePoint1 = Point1(x: 1.0, y: 1.0)
somePoint1.moveByX(2.0, y: 3.0)
print("The point is now at (\(somePoint1.x), \(somePoint1.y))")
Point結構上面定義了一個變異moveByX方法,它經過必定量移動一個Point實例。而不是返回一個新的起點,這種方法實際上會修改在其上調用點。該變異包含被添加到它的定義,使其可以修改其屬性。
請注意,您不能調用變異方法結構類型的常數,由於它的屬性不能改變
分配中的self變異方法
變異的方法能夠分配一個全新的實例隱含的self屬性。上面所示的點的例子也能夠寫成下面的方式來代替:
struct Point2 {
var x = 0.0, y = 0.0
mutating func moveByX(deltaX: Double, y deltaY: Double) {
self = Point2(x: x + deltaX, y: y + deltaY)
}
}
變異的方法枚舉能夠設置self參數是從同一個枚舉不一樣的成員
enum TriStateSwitch {
case Off, Low, High
mutating func next() {
switch self {
case Off:
self = Low
case Low:
self = High
case High:
self = Off
}
}
}
var ovenLight = TriStateSwitch.Low
ovenLight.next()
ovenLight.next()
這個例子定義了一個三態開關枚舉。三種不一樣的功率狀態之間的切換週期(關,低,高)
二、類型方法
如上所述,實例方法的方法要求一個特定類型的實例。您還能夠定義該類型自身的方法,這種方法被稱爲type方法,您顯示的type方法直接在類結構體裏面用class func開頭 ,對於枚舉和結構來講,類型方法是用static func開頭。
請注意;
在objective – c中,您能夠定義type-level方法僅爲objective – c類。在Swift能夠爲全部類定義type-level方法,結構,和枚舉。每種方法的顯示侷限於它所支持的類型。
在類型方法的主體,隱含的self屬性是指類型自己,而不是該類型的一個實例。對於結構體和枚舉,這意味着你可使用自助靜態屬性和靜態方法的參數消除歧義,就像你作的實例屬性和實例方法的參數。
更廣泛的是,你一個類型的方法體中使用任何不合格的方法和屬性名稱會參考其餘 type-level方法和屬性。 一種方法能夠調用另外一個類的方法與其餘方法的名稱,而不須要與類型名稱前綴了。一樣,結構和枚舉類型的方法可使用靜態屬性的名稱,沒有類型名稱前綴訪問靜態屬性。
下面的例子定義了一個名爲LevelTracker結構,它經過遊戲的不一樣層次或階段跟蹤球員的進步。這是一個單人遊戲,但能夠存儲的信息爲一個單一的設備上的多個玩家。
全部的遊戲的水平(除了一級)當遊戲第一次玩。每當玩家完成一個級別,該級別解鎖設備上的全部玩家。LevelTracker結構使用靜態屬性和方法來跟蹤哪些級別的比賽已經解鎖。它還跟蹤當前個別球員水平
struct LevelTracker {
static var highestUnlockedLevel = 1
static func unlockLevel(level: Int) {
if level > highestUnlockedLevel {
highestUnlockedLevel = level
}
}
static func levelIsUnlocked(level: Int) -> Bool {
return level <= highestUnlockedLevel
}
var currentLevel = 1
mutating func advanceToLevel(level: Int) -> Bool {
if LevelTracker.levelIsUnlocked(level) {
currentLevel = level
return true
} else {
return false
}
}
}
該LevelTracker結構跟蹤任何玩家解鎖的最高水平。這個值是存儲在一個名爲highestUnlockedLevel的靜態屬性。
LevelTracker還定義了兩種類型的功能與highestUnlockedLevel,首先是一種叫作unlockLevel功能,每當一個新的水平解鎖都會用來更新highestUnlockedLevel,第二個是levelIsUnlocked功能,若是一個特定的水平數已經解鎖,就會返回ture。注意,這些類型的方法能夠訪問highestUnlockedLevel靜態屬性可是你須要把它寫成LevelTracker.highestUnlockedLevel)。
除了它的靜態屬性和類型的方法,LevelTracker經過遊戲追蹤每一個玩家的進度。它使用被稱爲currentLevel實例屬性來跟蹤玩家級別。
爲了幫助管理urrentLevel屬性,advanceToLevel LevelTracker定義一個實例方法。這種方法更新currentLevel以前,用來檢查是否要求新的水平已經解除鎖定。該advanceToLevel方法返回一個布爾值來指示它是否可以設置currentLevel。
該LevelTracker結構使用Player類,以下所示,跟蹤和更新單個球員的進步:
class Player {
var tracker = LevelTracker()
let playerName: String
func completedLevel(level: Int) {
LevelTracker.unlockLevel(level + 1)
tracker.advanceToLevel(level + 1)
}
init(name: String) {
playerName = name
}
}
Player類建立LevelTracker的一個新實例來跟蹤球員的進步。它也提供了一個名爲completedLevel方法,每當玩家到達一個特定的級別,這種方法就會解鎖一個新的級別和進度並把玩家移到下一個級別。(advanceToLevel返回的布爾值將被忽略,由於已知被調用LevelTracker.unlockLevel。)
您能夠建立一個新球員Player 的實例,看看當玩家完成一個級別會發生什麼:
var player = Player(name: "Argyrios")
player.completedLevel(1)
print("highest unlocked level is now \(LevelTracker.highestUnlockedLevel)")
若是你建立第二個球員,你想嘗試移動到還沒有被遊戲解鎖的級別,就會出現當前級別失敗
player = Player(name: "Beto")
if player.tracker.advanceToLevel(6) {
print("player is now on level 6")
} else {
print("level 6 has not yet been unlocked")
}