作者刘文涛
转载请注明出处
方法(Methods)
方法是与某些特定类型相关联的函数。类、结构体、枚举都可以定义实例方法:实例方法为给定类型的实例封装了具体的任务与功能。类、结构体、枚举也可以定义类型方法:类型方法与类型本身相关联。类型方法与 OC 中的类方法相似。
结构体和枚举能够定义方法是 Swift 与 C/Objective-C 的主要区别之一。在 OC 中,类是唯一能定义方法的类型。但是在 Swift 中,你不仅能选择是否要定义一个类/结构体/枚举,还能灵活地在你创建的类型(类/结构体/枚举)上定义方法。
实例方法
实例方法是属于某个特定类、结构体或者枚举类型实例的方法。实例方法提供访问和修改实例属性的方法或提供与实例目的相关的功能,并以此来支撑实例的功能。实例方法的语法与函数完全一致。
实例方法要写在它所属的类型的前后大括号之间。实例方法能够隐式访问它所属类型的所有的其他实例方法和属性。实例方法只能被它所属的类的某个特定实例调用。实例方法不能脱离于现存的实例而被调用。
下面的例子,定义一个很简单的 Counter
类, Counter
能被用来对一个动作发生的次数进行计数:
|
|
Counter
类定义了三个实例方法: - increment
让计数器按1递增;- increment(by: Int)
让计数器按一个指定的整数递增; - reset
将计数器重置为0。
Counter
这个类还声明了一个可变属性 count
,用它来保持对当前计数器值的追踪。
和调用属性一样,用点语法调用实例方法:
|
|
函数参数可以同时有一个局部名称(在函数体内部使用)和一个外部名称(在调用函数时使用)。方法参数也一样,因为方法就是函数,只是这个函数与某个类型相关联了。
self属性
类型的每一个实例都有一个隐含属性叫做 self
, self
完全等同于该实例本身。你可以再一个实例的实例方法中使用这个隐含的 self
属性来引用当前实例。
上面例子中的 increment
方法还可以这样写:
|
|
实际上,你不必在你的代码里面经常写 self 。不论何时,只要在一个方法中使用一个已知的属性或者方法名称,如果你没有明确地写 self ,Swift 假定你是指当前实例的属性或者方法。这种假定在上面的 Counter
中已经示范了: Counter
中的三个实例方法中都使用的是 count
(而不是 self.count
)。
使用这条规则的主要场景是实例方法的某个参数名称与实例的某个属性名称相同的时候。在这种情况下,参数名称享有优先权,并且在引用属性时必须使用一种更严格的方式。这时你可以使用 self 属性来区分参数名称和属性名称。
下面的例子中,self 消除方法参数 x 和实例属性 x 之间的歧义:
|
|
如果不使用 self 前缀, Swift 就认为两次使用的 x 都指的是名称为 x 的函数参数。
在实例方法中修改值类型
结构体和枚举是值类型。默认情况下,值类型的属性不能在它的实例方法中被修改。
但是,如果你确实需要在某个特定的方法中修改结构体或者枚举的属性,你可以为这个方法选择 可变(mutating)
行为,然后就可以从其它方法内部改变它的属性;并且这个方法做的任何改变都会在方法执行结束时写回到原始结构中。方法还可以给它隐含的 self 属性赋予一个全新的实例,这个新实例在方法结束时会替换现存实例。
要使用可变方法,将关键字 mutating
放到方法的 func
关键字之前就可以了:
|
|
上面的 Point2
结构体定义了一个可变方法 moveByX(_:y:)
来移动 Point2
实例到给定的位置。该方法被调用时修改了这个点,而不是返回一个新的点。方法定义时加上了 mutating
关键字,从而允许修改属性。
注意,不能在结构体类型的常量(a constant of structure type)上调用可变方法,因为其属性不能被改 变,即使属性是变量属性。
|
|
在可变方法中给self赋值
可变方法能够赋给隐含属性 self 一个全新的实例。上面 Point 的例子可以用下面的方式改写:
|
|
新版的可变方法 moveBy(x: y:)
创建了一个新的结构体实例,它的 x 和 y 的值都被设定为目标值。调用这个版本的方法和调用上个版本的最终结果是一样的。
枚举的可变方法可以把 self 设置为同一枚举类型中不同的成员:
|
|
上面的例子定义了一个三态开关的枚举。每次调用 next()
方法时,开关在不同的电源状态(Off,Low,High)之间循环切换。
类型方法
实例方法是被某个类型的实例调用的方法。你也可以定义在类型本身上调用的方法,这种方法就叫做类型方法。在方法的 func
关键字之前加上关键字 static
,来指定类型方法。类型还可以使用关键字 class
来允许子类重写父类的方法实现。
注意:
在 OC 中,你只能为 OC 的类类型定义类型方法。在Swift中,你可以为所有的类、结构体和枚举定义类型方法。每一个类型方法都被它所支持的类型显示包含。
类型方法和实例方法一样用点语法调用。但是,你是在类型上调用这个方法,而不是在实例上调用。下面是如何在 SomeClass
类上调用类型方法的例子:
|
|
在类型方法的方法体(body)中, self 指向这个类型本身,而不是类型的某个实例。这意味着你可以用 self 来消除类型属性和类型方法参数之间的歧义(类似于我们在前面处理实例属性和实例方法参数时做的那样)。
一般来说,在类型方法的方法体中,任何未限定的方法和属性名称,可以被本类中其他的类型方法和类型属性引用。一个类型方法可以直接通过类型方法的名称调用本类中的其它类型方法,而无需在方法名称前加上类型名称。类似的,在结构体和枚举中,也能够直接通过类型属性的名称访问本类中的类型属性,而不需要前面加上类型名称。
下标
下标可以定义在类、结构体和枚举中,是访问集合,列表或序列中元素的快捷方式。可以使用下标的索引,设置和获取值,而不需要再调用对应的存取方法。举例来说,用下标访问一个 Array 实例中的元素可以写作 someArray[index] ,访问 Dictionary 实例中的元素可以写作 someDictionary[key] 。
一个类型可以定义多个下标,通过不同索引类型进行重载。下标不限于一维,你可以定义具有多个入参的下标满足自定义类型的需求。
下标语法
下标允许你通过在实例名称后面的方括号中传入一个或多个索引值来对实例进行存取。语法类似于实例方法语法和计算型语法的混合。与定义实例方法类似,定义下标使用 subscript
关键字,指定一个或多个输入参数和返回类型;与实例方法不同的是,下标可以设定为读写或只读。这种行为由 getter 和 setter 实现,有点类似计算型属性:
|
|
newValue
的类型和下标的返回类型相同。如同计算型属性,可以不指定 setter 的参数 (newValue
)。如果不指定参数,setter 会提供一个名为 newValue
的默认参数。
如同只读计算型属性,可以省略只读下标的 get 关键字:
|
|
下面代码演示了只读下标的实现,这里定义了一个 TimesTable
结构体,用来表示传入整数的乘法表:
|
|
在上例中,创建了一个 TimesTable
实例,用来表示整数 3 的乘法表。数值 3 被传递给结构体的构造函数,作为实例成员 multiplier
的值。
你可以通过下标访问 threeTimesTable
实例,例如上面演示的 threeTimesTable[6]
。这条语句查询了 3 的乘法表中的第六个元素,返回3的6倍即18。
下标用法
下标的确切含义取决于使用场景。下标通常作为访问集合,列表或序列中元素的快捷方式。你可以针对自己特定的类或结构体的功能来自由地以最恰当的方式实现下标。
例如,Swift 的 Dictionary 类型实现下标用于对其实例中储存的值进行存取操作。为字典设值时,在下标中使用和字典的键类型相同的键,并把一个和字典的值类型相同的值赋给这个下标:
|
|
上例定义一个名为 numberOfLegs
的变量,并用一个包含三对键值的字典字面量初始化它。 numberOfLegs
字典的类型被推断为 [String: Int]
。字典创建完成后,该例子通过下标将 String
类型的键 bird
和 Int
类型的值 2 添加到字典中。
注意
Swift 的 Dictionary
类型的下标接受并返回可选类型的值。上例中的 numberOfLegs
字典通过下标返回的是一个 Int?
或者说“可选的int”。 Dictionary
类型之所以如此实现下标,是因为不是每个键都有个对应的值,同时这也提供了一种通过键删除对应值的方式,只需将键对应的值赋值为 nil
即可
下标选项
下标可以接受任意数量的入参,并且这些入参可以是任意类型。下标的返回值也可以是任意类型。下标可以使用变量参数和可变参数,但是不能使用输入输出参数,也不能给参数设置默认值。
一个类或结构体可以根据自身需要提供多个下标实现,使用下标时将通过入参的数量和类型进行区分,自动匹配合适下标,这就是下标的重载。
继承
一个类可以继承另一个类的方法,属性和其它特性。当一个类继承其它类时,继承类叫子类,被继承类叫超类或父类。在Swift中,继承是区分[类]与其它类型的一个基本特征。
在Swift中,类可以调用和访问超类的方法,属性和下标,并且可以重写这些方法,属性和下标来优化或修改它们的行为。Swift会检查你的重写定义在超类中是否有匹配的定义,以此确保你的重写行为是正确的。
可以为类中继承来的属性添加属性观察器,这样一来,当属性值改变时,类就会被通知到。可以为任何属性添加属性观察器,无论它原本被定义为存储型属性还是计算型属性。
定义一个基类
不继承与其它类的类,称之为基类。
注意
Swift 中的类并不是从一个通用的基类继承而来。如果你不为你定义的类指定一个超类的话,这个类就自动成为基类。
下面的例子定义了一个叫 Vehicle
的基类。这个基类声明了一个名为 currentSpeed
,默认值是 0.0 的存储属性(属性类型推断为 Double
)。 currentSpeed
属性的值被一个 String
类型的只读计算型属性 description
使用,用来创建车辆的描述。
Vehicle
基类也定义了一个名为 makeNoise
的方法。这个方法实际上不为 Vehicle
实例做任何事,但之后将会被 Vehicle
的子类定制:
|
|
您可以用初始化语法创建一个 Vehicle
的新实例,即类名后面跟一个空括号:
|
|
现在已经创建了一个
print(“Vehicle: (someVehicle.description)”)
// 打印 “Vehicle: traveling at 0.0 miles per hour”
class SomeClass: SomeSuperclass {
//这里是子类的定义
}
class Bicycle: Vehicle {
var hasBasket = false
}
let bicycle = Bicycle()
bicycle.hasBasket = true
bicycle.currentSpeed = 15.0
print(“Bicycle: (bicycle.description)”)
//打印 Bicycle: traveling at 15.0 miles per hour
class Tandem: Bicycle {
var currentNumberOfPassengers = 0
}
class Train: Vehicle {
override func makeNoise() {
print(“Choo Choo”)
}
}
let train = Train()
train.makeNoise()
//打印 “Choo Choo”
class Car: Vehicle {
var gear = 1
override var description: String {
return super.description + “in gear (gear)”
}
}
let car = Car()
car.currentSpeed = 25.0
car.gear = 3
print(“car: (car.description)”)
// 打印 car: traveling at 25.0 miles per hourin gear 3
class AutomaticCar:Car {
override var currentSpeed: Double{
didSet{
gear = Int(currentSpeed / 10.0) + 1
}
}
}
let automatic = AutomaticCar()
automatic.currentSpeed = 35.0
print(“AutomaticCar: (automatic.description)”)
// 打印 AutomaticCar: traveling at 35.0 miles per hourin gear 4
````
防止重写
你可以通过把方法,属性或下标标记为 final
来防止它们被重写,只需要在声明关键字前加 final
修饰符即可(例如:final var
, final func
, final class func
)。
如果你重写了带有 final
标记的方法,属性或下标,在编译时会报错。 在类扩展中的方法,属性或下标也可以在扩展的定义里标记为 final
的。
你可以通过在关键字 class
前添加 final
修饰符( final class
)来将整个类标记为 final
的。这样的类是不可被继承的,试图继承这样的类会导致编译报错。