Swift学习笔记(五)

十 属性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")

}

相关文章
相关标签/搜索