建站快车产品介绍,深圳东门解封了吗,wordpress怎么搬家,最新发布的手机2022Swift 类是构建代码所用的一种通用且灵活的构造体。
我们可以为类定义属性#xff08;常量、变量#xff09;和方法。
与其他编程语言所不同的是#xff0c;Swift 并不要求你为自定义类去创建独立的接口和实现文件。你所要做的是在一个单一文件中定义一个类#xff0c;系…Swift 类是构建代码所用的一种通用且灵活的构造体。
我们可以为类定义属性常量、变量和方法。
与其他编程语言所不同的是Swift 并不要求你为自定义类去创建独立的接口和实现文件。你所要做的是在一个单一文件中定义一个类系统会自动生成面向其它代码的外部接口。
类和结构体对比
Swift 中类和结构体有很多共同点。共同处在于
定义属性用于存储值定义方法用于提供功能定义附属脚本用于访问值定义构造器用于生成初始化值通过扩展以增加默认实现的功能符合协议以对某类提供标准功能
与结构体相比类还有如下的附加功能
继承允许一个类继承另一个类的特征类型转换允许在运行时检查和解释一个类实例的类型解构器允许一个类实例释放任何其所被分配的资源引用计数允许对一个类的多次引用
语法:
class classname {Definition 1Definition 2……Definition N
}
类定义
class student{var studname: Stringvar mark: Int var mark2: Int
}
实例化类
let studrecord student() 作为引用类型访问类属性
类的属性可以通过 . 来访问。格式为实例化类名.属性名
恒等运算符
因为类是引用类型有可能有多个常量和变量在后台同时引用某一个类实例。
为了能够判定两个常量或者变量是否引用同一个类实例Swift 内建了两个恒等运算符
恒等运算符不恒等运算符运算符为运算符为!如果两个常量或者变量引用同一个类实例则返回 true如果两个常量或者变量引用不同一个类实例则返回 true
实例
import Cocoaclass SampleClass: Equatable {let myProperty: Stringinit(s: String) {myProperty s}
}
func (lhs: SampleClass, rhs: SampleClass) - Bool {return lhs.myProperty rhs.myProperty
}let spClass1 SampleClass(s: Hello)
let spClass2 SampleClass(s: Hello)if spClass1 spClass2 {// falseprint(引用相同的类实例 \(spClass1))
}if spClass1 ! spClass2 {// trueprint(引用不相同的类实例 \(spClass2))
}
以上程序执行输出结果为
引用不相同的类实例 SampleClass
Swift 属性
Swift 属性将值跟特定的类、结构或枚举关联。
属性可分为存储属性和计算属性:
存储属性计算属性存储常量或变量作为实例的一部分计算而不是存储一个值 用于类和结构体用于类、结构体和枚举
存储属性和计算属性通常用于特定类型的实例。
属性也可以直接用于类型本身这种属性称为类型属性。
另外还可以定义属性观察器来监控属性值的变化以此来触发一个自定义的操作。属性观察器可以添加到自己写的存储属性上也可以添加到从父类继承的属性上。 存储属性
简单来说一个存储属性就是存储在特定类或结构体的实例里的一个常量或变量。
存储属性可以是变量存储属性用关键字var定义也可以是常量存储属性用关键字let定义。 可以在定义存储属性的时候指定默认值 也可以在构造过程中设置或修改存储属性的值甚至修改常量存储属性的值
延迟存储属性
延迟存储属性是指当第一次被调用的时候才会计算其初始值的属性。
在属性声明前使用 lazy 来标示一个延迟存储属性。 注意必须将延迟存储属性声明成变量使用var关键字因为属性的值在实例构造完成之前可能无法得到。而常量属性在构造过程完成之前必须要有初始值因此无法声明成延迟属性。 延迟存储属性一般用于 延迟对象的创建。 当属性的值依赖于其他未知类
import Cocoaclass sample {lazy var no number() // var 关键字是必须的
}class number {var name Runoob Swift 教程
}var firstsample sample()
print(firstsample.no.name)
以上程序执行输出结果为
Runoob Swift 教程
实例化变量
如果您有过 Objective-C 经验应该知道Objective-C 为类实例存储值和引用提供两种方法。对于属性来说也可以使用实例变量作为属性值的后端存储。
Swift 编程语言中把这些理论统一用属性来实现。Swift 中的属性没有对应的实例变量属性的后端存储也无法直接访问。这就避免了不同场景下访问方式的困扰同时也将属性的定义简化成一个语句。
一个类型中属性的全部信息——包括命名、类型和内存管理特征——都在唯一一个地方类型定义中定义。 计算属性
除存储属性外类、结构体和枚举可以定义计算属性计算属性不直接存储值而是提供一个 getter 来获取值一个可选的 setter 来间接设置其他属性或变量的值。
import Cocoaclass sample {var no1 0.0, no2 0.0var length 300.0, breadth 150.0var middle: (Double, Double) {get{return (length / 2, breadth / 2)}set(axis){no1 axis.0 - (length / 2)no2 axis.1 - (breadth / 2)}}
}var result sample()
print(result.middle)
result.middle (0.0, 10.0)print(result.no1)
print(result.no2)
以上程序执行输出结果为
(150.0, 75.0)
-150.0
-65.0
如果计算属性的 setter 没有定义表示新值的参数名则可以使用默认名称 newValue。 只读计算属性
只有 getter 没有 setter 的计算属性就是只读计算属性。
只读计算属性总是返回一个值可以通过点(.)运算符访问但不能设置新的值。
import Cocoaclass film {var head var duration 0.0var metaInfo: [String:String] {return [head: self.head,duration:\(self.duration)]}
}var movie film()
movie.head Swift 属性
movie.duration 3.09print(movie.metaInfo[head]!)
print(movie.metaInfo[duration]!)
以上程序执行输出结果为
Swift 属性
3.09 注意 必须使用var关键字定义计算属性包括只读计算属性因为它们的值不是固定的。let关键字只用来声明常量属性表示初始化后再也无法修改的值。 属性观察器
属性观察器监控和响应属性值的变化每次属性被设置值的时候都会调用属性观察器甚至新的值和现在的值相同的时候也不例外。
可以为除了延迟存储属性之外的其他存储属性添加属性观察器也可以通过重载属性的方式为继承的属性包括存储属性和计算属性添加属性观察器。 注意 不需要为无法重载的计算属性添加属性观察器因为可以通过 setter 直接监控和响应值的变化。 可以为属性添加如下的一个或全部观察器
willSet在设置新的值之前调用didSet在新的值被设置之后立即调用willSet和didSet观察器在属性初始化过程中不会被调用
import Cocoaclass Samplepgm {var counter: Int 0{willSet(newTotal){print(计数器: \(newTotal))}didSet{if counter oldValue {print(新增数 \(counter - oldValue))}}}
}
let NewCounter Samplepgm()
NewCounter.counter 100
NewCounter.counter 800
以上程序执行输出结果为
计数器: 100
新增数 100
计数器: 800
新增数 700
全局变量和局部变量
计算属性和属性观察器所描述的模式也可以用于全局变量和局部变量。
局部变量全局变量在函数、方法或闭包内部定义的变量。函数、方法、闭包或任何类型之外定义的变量。用于存储和检索值。用于存储和检索值。存储属性用于获取和设置值。存储属性用于获取和设置值。也用于计算属性。也用于计算属性。 类型属性
类型属性是作为类型定义的一部分写在类型最外层的花括号{}内。
使用关键字 static 来定义值类型的类型属性关键字 class 来为类定义类型属性。 获取和设置类型属性的值
类似于实例的属性类型属性的访问也是通过点运算符(.)来进行。但是类型属性是通过类型本身来获取和设置而不是通过实例。实例如下
Swift 方法
Swift 方法是与某些特定类型相关联的函数
在 Objective-C 中类是唯一能定义方法的类型。但在 Swift 中你不仅能选择是否要定义一个类/结构体/枚举还能灵活的在你创建的类型类/结构体/枚举上定义方法。 实例方法
在 Swift 语言中实例方法是属于某个特定类、结构体或者枚举类型实例的方法。
实例方法提供以下方法 可以访问和修改实例属性 提供与实例目的相关的功能
实例方法要写在它所属的类型的前后大括号({})之间。
实例方法能够隐式访问它所属类型的所有的其他实例方法和属性。
实例方法只能被它所属的类的某个特定实例调用。
实例方法不能脱离于现存的实例而被调用。
语法
func funcname(Parameters) - returntype
{Statement1Statement2……Statement Nreturn parameters
} 方法的局部参数名称和外部参数名称
Swift 函数参数可以同时有一个局部名称在函数体内部使用和一个外部名称在调用函数时使用
Swift 中的方法和 Objective-C 中的方法极其相似。像在 Objective-C 中一样Swift 中方法的名称通常用一个介词指向方法的第一个参数比如withforby等等。
Swift 默认仅给方法的第一个参数名称一个局部参数名称;默认同时给第二个和后续的参数名称为全局参数名称。
以下实例中 no1 在swift中声明为局部参数名称。no2 用于全局的声明并通过外部程序访问。
是否提供外部名称设置
我们强制在第一个参数添加外部名称把这个局部名称当作外部名称使用Swift 2.0前是使用 # 号。
相反我们呢也可以使用下划线_设置第二个及后续的参数不提供一个外部名称。 self 属性
类型的每一个实例都有一个隐含属性叫做selfself 完全等同于该实例本身。
你可以在一个实例的实例方法中使用这个隐含的self属性来引用当前实例。
在实例方法中修改值类型
Swift 语言中结构体和枚举是值类型。一般情况下值类型的属性不能在它的实例方法中被修改。
但是如果你确实需要在某个具体的方法中修改结构体或者枚举的属性你可以选择变异(mutating)这个方法然后方法就可以从方法内部改变它的属性并且它做的任何改变在方法结束时还会保留在原始结构中。
方法还可以给它隐含的self属性赋值一个全新的实例这个新实例在方法结束后将替换原来的实例。 import Cocoastruct area {var length 1var breadth 1func area() - Int {return length * breadth} mutating func scaleBy(res: Int) {length * resbreadth * resprint(length)print(breadth)}
}var val area(length: 3, breadth: 5)
val.scaleBy(res: 3)
val.scaleBy(res: 30)
val.scaleBy(res: 300)
以上程序执行输出结果为
9
15
270
450
81000
135000 在可变方法中给 self 赋值
可变方法能够赋给隐含属性 self 一个全新的实例。 mutating func scaleBy(res: Int) {self.length * resself.breadth * resprint(length)print(breadth)} 类型方法
实例方法是被类型的某个实例调用的方法你也可以定义类型本身调用的方法这种方法就叫做类型方法。
声明结构体和枚举的类型方法在方法的func关键字之前加上关键字static。类可能会用关键字class来允许子类重写父类的实现方法。
类型方法和实例方法一样用点号(.)语法调用。
import Cocoaclass Math
{class func abs(number: Int) - Int{if number 0{return (-number)}else{return number}}
}struct absno
{static func abs(number: Int) - Int{if number 0{return (-number)}else{return number}}
}let no Math.abs(number: -35)
let num absno.abs(number: -5)print(no)
print(num)
以上程序执行输出结果为
35
5 Swift 下标脚本
下标脚本 可以定义在类Class、结构体structure和枚举enumeration这些目标中可以认为是访问对象、集合或序列的快捷方式不需要再调用实例的特定的赋值和访问方法。
举例来说用下标脚本访问一个数组(Array)实例中的元素可以这样写 someArray[index] 访问字典(Dictionary)实例中的元素可以这样写 someDictionary[key]。
对于同一个目标可以定义多个下标脚本通过索引值类型的不同来进行重载而且索引值的个数可以是多个。 下标脚本语法及应用
语法
下标脚本允许你通过在实例后面的方括号中传入一个或者多个的索引值来对实例进行访问和赋值。
语法类似于实例方法和计算型属性的混合。
与定义实例方法类似定义下标脚本使用subscript关键字显式声明入参一个或多个和返回类型。
与实例方法不同的是下标脚本可以设定为读写或只读。这种方式又有点像计算型属性的getter和setter
subscript(index: Int) - Int {get {// 用于下标脚本值的声明}set(newValue) {// 执行赋值操作}
}
实例 1
import Cocoastruct subexample {let decrementer: Intsubscript(index: Int) - Int {return decrementer / index}
}
let division subexample(decrementer: 100)print(100 除以 9 等于 \(division[9]))
print(100 除以 2 等于 \(division[2]))
print(100 除以 3 等于 \(division[3]))
print(100 除以 5 等于 \(division[5]))
print(100 除以 7 等于 \(division[7]))
以上程序执行输出结果为
100 除以 9 等于 11
100 除以 2 等于 50
100 除以 3 等于 33
100 除以 5 等于 20
100 除以 7 等于 14
在上例中通过 subexample 结构体创建了一个除法运算的实例。数值 100 作为结构体构造函数传入参数初始化实例成员 decrementer。
你可以通过下标脚本来得到结果比如 division[2] 即为 100 除以 2。 下标脚本选项
下标脚本允许任意数量的入参索引并且每个入参类型也没有限制。
下标脚本的返回值也可以是任何类型。
下标脚本可以使用变量参数和可变参数。
一个类或结构体可以根据自身需要提供多个下标脚本实现在定义下标脚本时通过传入参数的类型进行区分使用下标脚本时会自动匹配合适的下标脚本实现运行这就是下标脚本的重载。
Swift 继承
继承我们可以理解为一个类获取了另外一个类的方法和属性。
当一个类继承其它类时继承类叫子类被继承类叫超类或父类)
在 Swift 中类可以调用和访问超类的方法属性和下标脚本并且可以重写它们。
我们也可以为类中继承来的属性添加属性观察器。
基类
没有继承其它类的类称之为基类Base Class。
子类
子类指的是在一个已有类的基础上创建一个新的类。
为了指明某个类的超类将超类名写在子类名的后面用冒号(:)分隔,语法格式如下
class SomeClass: SomeSuperclass {// 类的定义
}
实例
以下实例中我们定义了超类 StudDetails然后使用子类 Tom 继承它
class StudDetails
{var mark1: Int;var mark2: Int;init(stm1:Int, results stm2:Int){mark1 stm1;mark2 stm2;}func show(){print(Mark1:\(self.mark1), Mark2:\(self.mark2))}
}class Tom : StudDetails
{init(){super.init(stm1: 93, results: 89)}
}let tom Tom()
tom.show()
以上程序执行输出结果为
Mark1:93, Mark2:89 重写Overriding
子类可以通过继承来的实例方法类方法实例属性或下标脚本来实现自己的定制功能我们把这种行为叫重写overriding。
我们可以使用 override 关键字来实现重写。
访问超类的方法、属性及下标脚本
你可以通过使用super前缀来访问超类的方法属性或下标脚本。
重写访问方法属性下标脚本方法super.somemethod()属性super.someProperty()下标脚本super[someIndex] 重写方法和属性
重写方法
在我们的子类中我们可以使用 override 关键字来重写超类的方法。
以下实例中我们重写了 show() 方法
class SuperClass {func show() {print(这是超类 SuperClass)}
}class SubClass: SuperClass {override func show() {print(这是子类 SubClass)}
}let superClass SuperClass()
superClass.show()let subClass SubClass()
subClass.show()
以上程序执行输出结果为
这是超类 SuperClass
这是子类 SubClass 重写属性
你可以提供定制的 getter或 setter来重写任意继承来的属性无论继承来的属性是存储型的还是计算型的属性。
子类并不知道继承来的属性是存储型的还是计算型的它只知道继承来的属性会有一个名字和类型。所以你在重写一个属性时必需将它的名字和类型都写出来。
注意点 如果你在重写属性中提供了 setter那么你也一定要提供 getter。 如果你不想在重写版本中的 getter 里修改继承来的属性值你可以直接通过super.someProperty来返回继承来的值其中someProperty是你要重写的属性的名字。
以下实例我们定义了超类 Circle 及子类 Rectangle, 在 Rectangle 类中我们重写属性 area lass Circle {var radius 12.5var area: String {return 矩形半径 \(radius) }
}// 继承超类 Circle
class Rectangle: Circle {var print 7 override var area: String {return super.area 但现在被重写为 \(print)}
}let rect Rectangle()
rect.radius 25.0
rect.print 3
print(Radius \(rect.area))
以上程序执行输出结果为
Radius 矩形半径 25.0 但现在被重写为 3
重写属性观察器
你可以在属性重写中为一个继承来的属性添加属性观察器。这样一来当继承来的属性值发生改变时你就会监测到。
注意你不可以为继承来的常量存储型属性或继承来的只读计算型属性添加属性观察器。
class Square: Rectangle {override var radius: Double {didSet {print Int(radius/5.0)1}}
}防止重写
我们可以使用 final 关键字防止它们被重写。
如果你重写了final方法属性或下标脚本在编译时会报错。
你可以通过在关键字class前添加final特性final class来将整个类标记为 final 的这样的类是不可被继承的否则会报编译错误。
final class Circle {final var radius 12.5var area: String {return 矩形半径为 \(radius) }
}
class Rectangle: Circle {var print 7override var area: String {return super.area 但现在被重写为 \(print)}
} 由于以上实例使用了 final 关键字不允许重写所以执行会报错
error: var overrides a final varoverride var area: String {^
note: overridden declaration is herevar area: String {^
error: var overrides a final varoverride var radius: Double {^
note: overridden declaration is herefinal var radius 12.5^
error: inheritance from a final class Circle
class Rectangle: Circle {^ Swift 构造过程
构造过程是为了使用某个类、结构体或枚举类型的实例而进行的准备过程。这个过程包含了为实例中的每个属性设置初始值和为其执行必要的准备和初始化任务。
Swift 构造函数使用 init() 方法。
与 Objective-C 中的构造器不同Swift 的构造器无需返回值它们的主要任务是保证新实例在第一次使用前完成正确的初始化。
类实例也可以通过定义析构器deinitializer在类实例释放之前执行清理内存的工作。 存储型属性的初始赋值
类和结构体在实例创建时必须为所有存储型属性设置合适的初始值。
存储属性在构造器中赋值时它们的值是被直接设置的不会触发任何属性观测器。
存储属性在构造器中赋值流程 创建初始值。 在属性定义中指定默认属性值。 初始化实例并调用 init() 方法。 构造器
构造器在创建某特定类型的新实例时调用。它的最简形式类似于一个不带任何参数的实例方法以关键字init命名。
语法
init()
{// 实例化后执行的代码
}
实例
以下结构体定义了一个不带参数的构造器 init并在里面将存储型属性 length 和 breadth 的值初始化为 6 和 12
struct rectangle {var length: Doublevar breadth: Doubleinit() {length 6breadth 12}
}
var area rectangle()
print(矩形面积为 \(area.length*area.breadth))
以上程序执行输出结果为
矩形面积为 72.0 默认属性值
我们可以在构造器中为存储型属性设置初始值同样也可以在属性声明时为其设置默认值。 使用默认值能让你的构造器更简洁、更清晰且能通过默认值自动推导出属性的类型。 以下实例我们在属性声明时为其设置默认值
struct rectangle {// 设置默认值var length 6var breadth 12
}
var area rectangle()
print(矩形的面积为 \(area.length*area.breadth))
以上程序执行输出结果为
矩形面积为 72
构造参数
你可以在定义构造器 init() 时提供构造参数如下所示
struct Rectangle {var length: Doublevar breadth: Doublevar area: Doubleinit(fromLength length: Double, fromBreadth breadth: Double) {self.length lengthself.breadth breadtharea length * breadth}init(fromLeng leng: Double, fromBread bread: Double) {self.length lengself.breadth breadarea leng * bread}
}let ar Rectangle(fromLength: 6, fromBreadth: 12)
print(面积为: \(ar.area))let are Rectangle(fromLeng: 36, fromBread: 12)
print(面积为: \(are.area))
以上程序执行输出结果为
面积为: 72.0
面积为: 432.0 内部和外部参数名
跟函数和方法参数相同构造参数也存在一个在构造器内部使用的参数名字和一个在调用构造器时使用的外部参数名字。
然而构造器并不像函数和方法那样在括号前有一个可辨别的名字。所以在调用构造器时主要通过构造器中的参数名和类型来确定需要调用的构造器。
如果你在定义构造器时没有提供参数的外部名字Swift 会为每个构造器的参数自动生成一个跟内部名字相同的外部名。
struct Color {let red, green, blue: Doubleinit(red: Double, green: Double, blue: Double) {self.red redself.green greenself.blue blue}init(white: Double) {red whitegreen whiteblue white}
} 没有外部名称参数
如果你不希望为构造器的某个参数提供外部名字你可以使用下划线_来显示描述它的外部名。
struct Rectangle {var length: Doubleinit(frombreadth breadth: Double) {length breadth * 10}init(frombre bre: Double) {length bre * 30}//不提供外部名字init(_ area: Double) {length area}
/ 调用不提供外部名字
let rectarea Rectangle(180.0)
print(面积为: \(rectarea.length))可选属性类型
如果你定制的类型包含一个逻辑上允许取值为空的存储型属性你都需要将它定义为可选类型optional type可选属性类型。
当存储属性声明为可选时将自动初始化为空 nil。 struct Rectangle {var length: Double?init(frombreadth breadth: Double) {length breadth * 10}init(frombre bre: Double) {length bre * 30}init(_ area: Double) {length area}
}
构造过程中修改常量属性
只要在构造过程结束前常量的值能确定你可以在构造过程中的任意时间点修改常量属性的值。
对某个类实例来说它的常量属性只能在定义它的类的构造过程中修改不能在子类中修改。
尽管 length 属性现在是常量我们仍然可以在其类的构造器中设置它的值
struct Rectangle {let length: Double?init(frombreadth breadth: Double) {length breadth * 10}init(frombre bre: Double) {length bre * 30}init(_ area: Double) {length area}
}let rectarea Rectangle(180.0)
print(面积为\(rectarea.length))
以上程序执行输出结果为
面积为Optional(180.0) 默认构造器
默认构造器将简单的创建一个所有属性值都设置为默认值的实例:
以下实例中ShoppingListItem类中的所有属性都有默认值且它是没有父类的基类它将自动获得一个可以为所有属性设置默认值的默认构造器
class ShoppingListItem {var name: String?var quantity 1var purchased false
}
var item ShoppingListItem()print(名字为: \(item.name))
print(数理为: \(item.quantity))
print(是否付款: \(item.purchased))
以上程序执行输出结果为
名字为: nil
数理为: 1
是否付款: false 结构体的逐一成员构造器
如果结构体对所有存储型属性提供了默认值且自身没有提供定制的构造器它们能自动获得一个逐一成员构造器。
我们在调用逐一成员构造器时通过与成员属性名相同的参数名进行传值来完成对成员属性的初始赋值。
下面例子中定义了一个结构体 Rectangle它包含两个属性 length 和 breadth。Swift 可以根据这两个属性的初始赋值100.0 、200.0自动推导出它们的类型Double。
struct Rectangle {var length 100.0, breadth 200.0
}
let area Rectangle(length: 24.0, breadth: 32.0)print(矩形的面积: \(area.length))
print(矩形的面积: \(area.breadth))
由于这两个存储型属性都有默认值结构体 Rectangle 自动获得了一个逐一成员构造器 init(width:height:)。 你可以用它来为 Rectangle 创建新的实例。
以上程序执行输出结果为
矩形的面积: 24.0
矩形的面积: 32.0 值类型的构造器代理
构造器可以通过调用其它构造器来完成实例的部分构造过程。这一过程称为构造器代理它能减少多个构造器间的代码重复。
以下实例中Rect 结构体调用了 Size 和 Point 的构造过程
struct Size {var width 0.0, height 0.0
}
struct Point {var x 0.0, y 0.0
}struct Rect {var origin Point()var size Size()init() {}init(origin: Point, size: Size) {self.origin originself.size size}init(center: Point, size: Size) {let originX center.x - (size.width / 2)let originY center.y - (size.height / 2)self.init(origin: Point(x: originX, y: originY), size: size)}
}// origin和size属性都使用定义时的默认值Point(x: 0.0, y: 0.0)和Size(width: 0.0, height: 0.0)
let basicRect Rect()
print(Size 结构体初始值: \(basicRect.size.width, basicRect.size.height) )
print(Rect 结构体初始值: \(basicRect.origin.x, basicRect.origin.y) )// 将origin和size的参数值赋给对应的存储型属性
let originRect Rect(origin: Point(x: 2.0, y: 2.0),size: Size(width: 5.0, height: 5.0))print(Size 结构体初始值: \(originRect.size.width, originRect.size.height) )
print(Rect 结构体初始值: \(originRect.origin.x, originRect.origin.y) )//先通过center和size的值计算出origin的坐标。
//然后再调用或代理给init(origin:size:)构造器来将新的origin和size值赋值到对应的属性中
let centerRect Rect(center: Point(x: 4.0, y: 4.0),size: Size(width: 3.0, height: 3.0))print(Size 结构体初始值: \(centerRect.size.width, centerRect.size.height) )
print(Rect 结构体初始值: \(centerRect.origin.x, centerRect.origin.y) )
以上程序执行输出结果为
Size 结构体初始值: (0.0, 0.0)
Rect 结构体初始值: (0.0, 0.0)
Size 结构体初始值: (5.0, 5.0)
Rect 结构体初始值: (2.0, 2.0)
Size 结构体初始值: (3.0, 3.0)
Rect 结构体初始值: (2.5, 2.5)
类的继承和构造过程
Swift 提供了两种类型的类构造器来确保所有类实例中存储型属性都能获得初始值它们分别是指定构造器和便利构造器。
指定构造器便利构造器类中最主要的构造器类中比较次要的、辅助型的构造器初始化类中提供的所有属性并根据父类链往上调用父类的构造器来实现父类的初始化。可以定义便利构造器来调用同一个类中的指定构造器并为其参数提供默认值。你也可以定义便利构造器来创建一个特殊用途或特定输入的实例。每一个类都必须拥有至少一个指定构造器只在必要的时候为类提供便利构造器 Init(parameters) {statements
} convenience init(parameters) {statements
}
指定构造器实例
class mainClass {var no1 : Int // 局部存储变量init(no1 : Int) {self.no1 no1 // 初始化}
}
class subClass : mainClass {var no2 : Int // 新的子类存储变量init(no1 : Int, no2 : Int) {self.no2 no2 // 初始化super.init(no1:no1) // 初始化超类}
}let res mainClass(no1: 10)
let res2 subClass(no1: 10, no2: 20)print(res 为: \(res.no1))
print(res2 为: \(res2.no1))
print(res2 为: \(res2.no2))
以上程序执行输出结果为
res 为: 10
res 为: 10
res 为: 20
便利构造器实例
class mainClass {var no1 : Int // 局部存储变量init(no1 : Int) {self.no1 no1 // 初始化}
}class subClass : mainClass {var no2 : Intinit(no1 : Int, no2 : Int) {self.no2 no2super.init(no1:no1)}// 便利方法只需要一个参数override convenience init(no1: Int) {self.init(no1:no1, no2:0)}
}
let res mainClass(no1: 20)
let res2 subClass(no1: 30, no2: 50)print(res 为: \(res.no1))
print(res2 为: \(res2.no1))
print(res2 为: \(res2.no2))
以上程序执行输出结果为
res 为: 20
res2 为: 30
res2 为: 50
构造器的继承和重载
Swift 中的子类不会默认继承父类的构造器。
父类的构造器仅在确定和安全的情况下被继承。
当你重写一个父类指定构造器时你需要写override修饰符。
class SuperClass {var corners 4var description: String {return \(corners) 边}
}
let rectangle SuperClass()
print(矩形: \(rectangle.description))class SubClass: SuperClass {override init() { //重载构造器super.init()corners 5}
}let subClass SubClass()
print(五角型: \(subClass.description))
以上程序执行输出结果为
矩形: 4 边
五角型: 5 边 指定构造器和便利构造器实例
接下来的例子将在操作中展示指定构造器、便利构造器和自动构造器的继承。
它定义了包含两个个类MainClass、SubClass的类层次结构并将演示它们的构造器是如何相互作用的。
class MainClass {var name: Stringinit(name: String) {self.name name}convenience init() {self.init(name: [匿名])}
}
let main MainClass(name: Runoob)
print(MainClass 名字为: \(main.name))let main2 MainClass()
print(没有对应名字: \(main2.name))class SubClass: MainClass {var count: Intinit(name: String, count: Int) {self.count countsuper.init(name: name)}override convenience init(name: String) {self.init(name: name, count: 1)}
}let sub SubClass(name: Runoob)
print(MainClass 名字为: \(sub.name))let sub2 SubClass(name: Runoob, count: 3)
print(count 变量: \(sub2.count))
以上程序执行输出结果为
MainClass 名字为: Runoob
没有对应名字: [匿名]
MainClass 名字为: Runoob
count 变量: 3
类的可失败构造器
如果一个类结构体或枚举类型的对象在构造自身的过程中有可能失败则为其定义一个可失败构造器。
变量初始化失败可能的原因有 传入无效的参数值。 缺少某种所需的外部资源。 没有满足特定条件。
为了妥善处理这种构造过程中可能会失败的情况。
你可以在一个类结构体或是枚举类型的定义中添加一个或多个可失败构造器。其语法为在init关键字后面加添问号(init?)。 枚举类型的可失败构造器
你可以通过构造一个带一个或多个参数的可失败构造器来获取枚举类型中特定的枚举成员。
实例
下例中定义了一个名为TemperatureUnit的枚举类型。其中包含了三个可能的枚举成员(KelvinCelsius和 Fahrenheit)和一个被用来找到Character值所对应的枚举成员的可失败构造器
enum TemperatureUnit {// 开尔文摄氏华氏case Kelvin, Celsius, Fahrenheit init?(symbol: Character) {switch symbol {case K:self .Kelvincase C:self .Celsiuscase F:self .Fahrenheitdefault:return nil}}
}let fahrenheitUnit TemperatureUnit(symbol: F)
if fahrenheitUnit ! nil {print(这是一个已定义的温度单位所以初始化成功。)
}let unknownUnit TemperatureUnit(symbol: X)
if unknownUnit nil {print(这不是一个已定义的温度单位所以初始化失败。)
}
以上程序执行输出结果为
这是一个已定义的温度单位所以初始化成功。
这不是一个已定义的温度单位所以初始化失败。 类的可失败构造器
值类型如结构体或枚举类型的可失败构造器对何时何地触发构造失败这个行为没有任何的限制。
但是类的可失败构造器只能在所有的类属性被初始化后和所有类之间的构造器之间的代理调用发生完后触发失败行为。
实例
下例子中定义了一个名为 StudRecord 的类因为 studname 属性是一个常量所以一旦 StudRecord 类构造成功studname 属性肯定有一个非nil的值。
class StudRecord {let studname: String!init?(studname: String) {self.studname studnameif studname.isEmpty { return nil }}
}
if let stname StudRecord(studname: 失败构造器) {print(模块为 \(stname.studname))
}
以上程序执行输出结果为
模块为 失败构造器 覆盖一个可失败构造器
就如同其它构造器一样你也可以用子类的可失败构造器覆盖基类的可失败构造器。
者你也可以用子类的非可失败构造器覆盖一个基类的可失败构造器。
你可以用一个非可失败构造器覆盖一个可失败构造器但反过来却行不通。
一个非可失败的构造器永远也不能代理调用一个可失败构造器。
实例
以下实例描述了可失败与非可失败构造器
class Planet {var name: Stringinit(name: String) {self.name name}convenience init() {self.init(name: [No Planets])}
}
let plName Planet(name: Mercury)
print(行星的名字是: \(plName.name))let noplName Planet()
print(没有这个名字的行星: \(noplName.name))class planets: Planet {var count: Intinit(name: String, count: Int) {self.count countsuper.init(name: name)}override convenience init(name: String) {self.init(name: name, count: 1)}
}
以上程序执行输出结果为
行星的名字是: Mercury
没有这个名字的行星: [No Planets]
可失败构造器 init!
通常来说我们通过在init关键字后添加问号的方式init?来定义一个可失败构造器但你也可以使用通过在init后面添加惊叹号的方式来定义一个可失败构造器(init!)。实例如下
struct StudRecord {let stname: Stringinit!(stname: String) {if stname.isEmpty {return nil }self.stname stname}
}let stmark StudRecord(stname: Runoob)
if let name stmark {print(指定了学生名)
}let blankname StudRecord(stname: )
if blankname nil {print(学生名为空)
}
以上程序执行输出结果为
指定了学生名
学生名为空
Swift 析构过程
在一个类的实例被释放之前析构函数被立即调用。用关键字deinit来标示析构函数类似于初始化函数用init来标示。析构函数只适用于类类型。 析构过程原理
Swift 会自动释放不再需要的实例以释放资源。
Swift 通过自动引用计数ARC处理实例的内存管理。
通常当你的实例被释放时不需要手动地去清理。但是当使用自己的资源时你可能需要进行一些额外的清理。
例如如果创建了一个自定义的类来打开一个文件并写入一些数据你可能需要在类实例被释放之前关闭该文件。
语法
在类的定义中每个类最多只能有一个析构函数。析构函数不带任何参数在写法上不带括号
deinit {// 执行析构过程
}
实例
var counter 0; // 引用计数器
class BaseClass {init() {counter 1;}deinit {counter - 1;}
}var show: BaseClass? BaseClass()
print(counter)
show nil
print(counter)
以上程序执行输出结果为
1
0
当 show nil 语句执行后计算器减去 1show 占用的内存就会释放。
var counter 0; // 引用计数器class BaseClass {init() {counter 1;}deinit {counter - 1;}
}var show: BaseClass? BaseClass()print(counter)
print(counter)
以上程序执行输出结果为
1
1 Swift 可选链
可选链Optional Chaining是一种可以请求和调用属性、方法和子脚本的过程用于请求或调用的目标可能为nil。
可选链返回两个值 如果目标有值调用就会成功返回该值 如果目标为nil调用将返回nil
多次请求或调用可以被链接成一个链如果任意一个节点为nil将导致整条链失效。 可选链可替代强制解析
通过在属性、方法、或下标脚本的可选值后面放一个问号(?)即可定义一个可选链。
可选链 ?感叹号!强制展开方法属性下标脚本可选链? 放置于可选值后来调用方法属性下标脚本! 放置于可选值后来调用方法属性下标脚本来强制展开值当可选为 nil 输出比较友好的错误信息当可选为 nil 时强制展开执行错误
使用感叹号(!)可选链实例
class Person {var residence: Residence?
}class Residence {var numberOfRooms 1
}let john Person()//将导致运行时错误
let roomCount john.residence!.numberOfRooms以上程序执行输出结果为
fatal error: unexpectedly found nil while unwrapping an Optional value想使用感叹号!强制解析获得这个人residence属性numberOfRooms属性值将会引发运行时错误因为这时没有可以供解析的residence值。
使用问号(?)可选链实例
class Person {var residence: Residence?
}class Residence {var numberOfRooms 1
}let john Person()// 链接可选residence?属性如果residence存在则取回numberOfRooms的值
if let roomCount john.residence?.numberOfRooms {print(John 的房间号为 \(roomCount)。)
} else {print(不能查看房间号)
}
以上程序执行输出结果为
不能查看房间号 因为这种尝试获得numberOfRooms的操作有可能失败可选链会返回Int?类型值或者称作可选Int。当residence是空的时候上例选择Int将会为空因此会出现无法访问numberOfRooms的情况。
要注意的是即使numberOfRooms是非可选IntInt?时这一点也成立。只要是通过可选链的请求就意味着最后numberOfRooms总是返回一个Int?而不是Int。
为可选链定义模型类
你可以使用可选链来多层调用属性方法和下标脚本。这让你可以利用它们之间的复杂模型来获取更底层的属性并检查是否可以成功获取此类底层属性。
实例
定义了四个模型类其中包括多层可选链
class Person {var residence: Residence?
}// 定义了一个变量 rooms它被初始化为一个Room[]类型的空数组
class Residence {var rooms [Room]()var numberOfRooms: Int {return rooms.count}subscript(i: Int) - Room {return rooms[i]}func printNumberOfRooms() {print(房间号为 \(numberOfRooms))}var address: Address?
}// Room 定义一个name属性和一个设定room名的初始化器
class Room {let name: Stringinit(name: String) { self.name name }
}// 模型中的最终类叫做Address
class Address {var buildingName: String?var buildingNumber: String?var street: String?func buildingIdentifier() - String? {if (buildingName ! nil) {return buildingName} else if (buildingNumber ! nil) {return buildingNumber} else {return nil}}
} 通过可选链调用方法
你可以使用可选链的来调用可选值的方法并检查方法调用是否成功。即使这个方法没有返回值你依然可以使用可选链来达成这一目的。
class Person {var residence: Residence?
}// 定义了一个变量 rooms它被初始化为一个Room[]类型的空数组
class Residence {var rooms [Room]()var numberOfRooms: Int {return rooms.count}subscript(i: Int) - Room {return rooms[i]}func printNumberOfRooms() {print(房间号为 \(numberOfRooms))}var address: Address?
}// Room 定义一个name属性和一个设定room名的初始化器
class Room {let name: Stringinit(name: String) { self.name name }
}// 模型中的最终类叫做Address
class Address {var buildingName: String?var buildingNumber: String?var street: String?func buildingIdentifier() - String? {if (buildingName ! nil) {return buildingName} else if (buildingNumber ! nil) {return buildingNumber} else {return nil}}
}let john Person()if ((john.residence?.printNumberOfRooms()) ! nil) {print(输出房间号)
} else {print(无法输出房间号)
}
以上程序执行输出结果为
无法输出房间号
使用if语句来检查是否能成功调用printNumberOfRooms方法如果方法通过可选链调用成功printNumberOfRooms的隐式返回值将会是Void如果没有成功将返回nil。 使用可选链调用下标脚本
你可以使用可选链来尝试从下标脚本获取值并检查下标脚本的调用是否成功然而你不能通过可选链来设置下标脚本。
实例1
class Person {var residence: Residence?
}// 定义了一个变量 rooms它被初始化为一个Room[]类型的空数组
class Residence {var rooms [Room]()var numberOfRooms: Int {return rooms.count}subscript(i: Int) - Room {return rooms[i]}func printNumberOfRooms() {print(房间号为 \(numberOfRooms))}var address: Address?
}// Room 定义一个name属性和一个设定room名的初始化器
class Room {let name: Stringinit(name: String) { self.name name }
}// 模型中的最终类叫做Address
class Address {var buildingName: String?var buildingNumber: String?var street: String?func buildingIdentifier() - String? {if (buildingName ! nil) {return buildingName} else if (buildingNumber ! nil) {return buildingNumber} else {return nil}}
}let john Person()
if let firstRoomName john.residence?[0].name {print(第一个房间名 \(firstRoomName).)
} else {print(无法检索到房间)
}
以上程序执行输出结果为
无法检索到房间
在下标脚本调用中可选链的问号直接跟在 john.residence 的后面在下标脚本括号的前面因为 john.residence 是可选链试图获得的可选值。
实例2
实例中创建一个 Residence 实例给 john.residence且在他的 rooms 数组中有一个或多个 Room 实例那么你可以使用可选链通过 Residence 下标脚本来获取在 rooms 数组中的实例了
class Person {var residence: Residence?
}// 定义了一个变量 rooms它被初始化为一个Room[]类型的空数组
class Residence {var rooms [Room]()var numberOfRooms: Int {return rooms.count}subscript(i: Int) - Room {return rooms[i]}func printNumberOfRooms() {print(房间号为 \(numberOfRooms))}var address: Address?
}// Room 定义一个name属性和一个设定room名的初始化器
class Room {let name: Stringinit(name: String) { self.name name }
}// 模型中的最终类叫做Address
class Address {var buildingName: String?var buildingNumber: String?var street: String?func buildingIdentifier() - String? {if (buildingName ! nil) {return buildingName} else if (buildingNumber ! nil) {return buildingNumber} else {return nil}}
}let john Person()
let johnsHouse Residence()
johnsHouse.rooms.append(Room(name: 客厅))
johnsHouse.rooms.append(Room(name: 厨房))
john.residence johnsHouselet johnsAddress Address()
johnsAddress.buildingName The Larches
johnsAddress.street Laurel Street
john.residence!.address johnsAddressif let johnsStreet john.residence?.address?.street {print(John 所在的街道是 \(johnsStreet)。)
} else {print(无法检索到地址。 )
}
以上程序执行输出结果为
John 所在的街道是 Laurel Street。 通过可选链接调用来访问下标
通过可选链接调用我们可以用下标来对可选值进行读取或写入并且判断下标调用是否成功。
实例
class Person {var residence: Residence?
}// 定义了一个变量 rooms它被初始化为一个Room[]类型的空数组
class Residence {var rooms [Room]()var numberOfRooms: Int {return rooms.count}subscript(i: Int) - Room {return rooms[i]}func printNumberOfRooms() {print(房间号为 \(numberOfRooms))}var address: Address?
}// Room 定义一个name属性和一个设定room名的初始化器
class Room {let name: Stringinit(name: String) { self.name name }
}// 模型中的最终类叫做Address
class Address {var buildingName: String?var buildingNumber: String?var street: String?func buildingIdentifier() - String? {if (buildingName ! nil) {return buildingName} else if (buildingNumber ! nil) {return buildingNumber} else {return nil}}
}let john Person()let johnsHouse Residence()
johnsHouse.rooms.append(Room(name: 客厅))
johnsHouse.rooms.append(Room(name: 厨房))
john.residence johnsHouseif let firstRoomName john.residence?[0].name {print(第一个房间名为\(firstRoomName))
} else {print(无法检索到房间)
}
以上程序执行输出结果为
第一个房间名为客厅 访问可选类型的下标
如果下标返回可空类型值比如Swift中Dictionary的key下标。可以在下标的闭合括号后面放一个问号来链接下标的可空返回值
var testScores [Dave: [86, 82, 84], Bev: [79, 94, 81]]
testScores[Dave]?[0] 91
testScores[Bev]?[0]
testScores[Brian]?[0] 72
// the Dave array is now [91, 82, 84] and the Bev array is now [80, 94, 81]
上面的例子中定义了一个testScores数组包含了两个键值对 把String类型的key映射到一个整形数组。
这个例子用可选链接调用把Dave数组中第一个元素设为91把Bev数组的第一个元素1然后尝试把Brian数组中的第一个元素设为72。
前两个调用是成功的因为这两个key存在。但是keyBrian在字典中不存在所以第三个调用失败。 连接多层链接
你可以将多层可选链连接在一起可以掘取模型内更下层的属性方法和下标脚本。然而多层可选链不能再添加比已经返回的可选值更多的层。
如果你试图通过可选链获得Int值不论使用了多少层链接返回的总是Int?。 相似的如果你试图通过可选链获得Int?值不论使用了多少层链接返回的总是Int?。
实例1
下面的例子试图获取john的residence属性里的address的street属性。这里使用了两层可选链来联系residence和address属性它们两者都是可选类型
class Person {var residence: Residence?
}// 定义了一个变量 rooms它被初始化为一个Room[]类型的空数组
class Residence {var rooms [Room]()var numberOfRooms: Int {return rooms.count}subscript(i: Int) - Room {return rooms[i]}func printNumberOfRooms() {print(房间号为 \(numberOfRooms))}var address: Address?
}// Room 定义一个name属性和一个设定room名的初始化器
class Room {let name: Stringinit(name: String) { self.name name }
}// 模型中的最终类叫做Address
class Address {var buildingName: String?var buildingNumber: String?var street: String?func buildingIdentifier() - String? {if (buildingName ! nil) {return buildingName} else if (buildingNumber ! nil) {return buildingNumber} else {return nil}}
}let john Person()if let johnsStreet john.residence?.address?.street {print(John 的地址为 \(johnsStreet).)
} else {print(不能检索地址)
}
以上程序执行输出结果为
不能检索地址
实例2
如果你为Address设定一个实例来作为john.residence.address的值并为address的street属性设定一个实际值你可以通过多层可选链来得到这个属性值。
class Person {var residence: Residence?
}class Residence {var rooms [Room]()var numberOfRooms: Int {return rooms.count}subscript(i: Int) - Room {get{return rooms[i]}set {rooms[i] newValue}}func printNumberOfRooms() {print(房间号为 \(numberOfRooms))}var address: Address?
}class Room {let name: Stringinit(name: String) { self.name name }
}class Address {var buildingName: String?var buildingNumber: String?var street: String?func buildingIdentifier() - String? {if (buildingName ! nil) {return buildingName} else if (buildingNumber ! nil) {return buildingNumber} else {return nil}}
}
let john Person()
john.residence?[0] Room(name: 浴室)let johnsHouse Residence()
johnsHouse.rooms.append(Room(name: 客厅))
johnsHouse.rooms.append(Room(name: 厨房))
john.residence johnsHouseif let firstRoomName john.residence?[0].name {print(第一个房间是\(firstRoomName))
} else {print(无法检索房间)
}
以上实例输出结果为
第一个房间是客厅 对返回可选值的函数进行链接
我们还可以通过可选链接来调用返回可空值的方法并且可以继续对可选值进行链接。
实例
class Person {var residence: Residence?
}// 定义了一个变量 rooms它被初始化为一个Room[]类型的空数组
class Residence {var rooms [Room]()var numberOfRooms: Int {return rooms.count}subscript(i: Int) - Room {return rooms[i]}func printNumberOfRooms() {print(房间号为 \(numberOfRooms))}var address: Address?
}// Room 定义一个name属性和一个设定room名的初始化器
class Room {let name: Stringinit(name: String) { self.name name }
}// 模型中的最终类叫做Address
class Address {var buildingName: String?var buildingNumber: String?var street: String?func buildingIdentifier() - String? {if (buildingName ! nil) {return buildingName} else if (buildingNumber ! nil) {return buildingNumber} else {return nil}}
}let john Person()if john.residence?.printNumberOfRooms() ! nil {print(指定了房间号))
} else {print(未指定房间号)
}
以上程序执行输出结果为
未指定房间号 Swift 自动引用计数ARC
Swift 使用自动引用计数ARC这一机制来跟踪和管理应用程序的内存
通常情况下我们不需要去手动释放内存因为 ARC 会在类的实例不再被使用时自动释放其占用的内存。
但在有些时候我们还是需要在代码中实现内存管理。
ARC 功能 当每次使用 init() 方法创建一个类的新的实例的时候ARC 会分配一大块内存用来储存实例的信息。 内存中会包含实例的类型信息以及这个实例所有相关属性的值。 当实例不再被使用时ARC 释放实例所占用的内存并让释放的内存能挪作他用。 为了确保使用中的实例不会被销毁ARC 会跟踪和计算每一个实例正在被多少属性常量和变量所引用。 实例赋值给属性、常量或变量它们都会创建此实例的强引用只要强引用还在实例是不允许被销毁的。 类实例之间的循环强引用
在上面的例子中ARC 会跟踪你所新创建的 Person 实例的引用数量并且会在 Person 实例不再被需要时销毁它。
然而我们可能会写出这样的代码一个类永远不会有0个强引用。这种情况发生在两个类实例互相保持对方的强引用并让对方不被销毁。这就是所谓的循环强引用。
解决实例之间的循环强引用
Swift 提供了两种办法用来解决你在使用类的属性时所遇到的循环强引用问题
弱引用无主引用
弱引用和无主引用允许循环引用中的一个实例引用另外一个实例而不保持强引用。这样实例能够互相引用而不产生循环强引用。
对于生命周期中会变为nil的实例使用弱引用。相反的对于初始化赋值后再也不会被赋值为nil的实例使用无主引用。
弱引用实例
class Module {let name: Stringinit(name: String) { self.name name }var sub: SubModule?deinit { print(\(name) 主模块) }
}class SubModule {let number: Intinit(number: Int) { self.number number } weak var topic: Module?deinit { print(子模块 topic 数为 \(number)) }
}var toc: Module?
var list: SubModule?
toc Module(name: ARC)
list SubModule(number: 4)
toc!.sub list
list!.topic toctoc nil
list nil
以上程序执行输出结果为
ARC 主模块
子模块 topic 数为 4
无主引用实例
class Student {let name: Stringvar section: Marks?init(name: String) {self.name name}deinit { print(\(name)) }
}
class Marks {let marks: Int unowned let stname: Studentinit(marks: Int, stname: Student) {self.marks marksself.stname stname}deinit { print(学生的分数为 \(marks)) }
}var module: Student?
module Student(name: ARC)
module!.section Marks(marks: 98, stname: module!)
module nil
以上程序执行输出结果为
ARC
学生的分数为 98
闭包引起的循环强引用
循环强引用还会发生在当你将一个闭包赋值给类实例的某个属性并且这个闭包体中又使用了实例。这个闭包体中可能访问了实例的某个属性例如self.someProperty或者闭包中调用了实例的某个方法例如self.someMethod。这两种情况都导致了闭包 捕获 self从而产生了循环强引用。
实例
下面的例子为你展示了当一个闭包引用了self后是如何产生一个循环强引用的。例子中定义了一个叫HTMLElement的类用一种简单的模型表示 HTML 中的一个单独的元素
class HTMLElement {let name: Stringlet text: String?lazy var asHTML: () - String {if let text self.text {return \(self.name)\(text)/\(self.name)} else {return \(self.name) /}}init(name: String, text: String? nil) {self.name nameself.text text}deinit {print(\(name) is being deinitialized)}}// 创建实例并打印信息
var paragraph: HTMLElement? HTMLElement(name: p, text: hello, world)
print(paragraph!.asHTML())
HTMLElement 类产生了类实例和 asHTML 默认值的闭包之间的循环强引用。
实例的 asHTML 属性持有闭包的强引用。但是闭包在其闭包体内使用了self引用了self.name和self.text因此闭包捕获了self这意味着闭包又反过来持有了HTMLElement实例的强引用。这样两个对象就产生了循环强引用。
解决闭包引起的循环强引用:在定义闭包时同时定义捕获列表作为闭包的一部分通过这种方式可以解决闭包和类实例之间的循环强引用。 弱引用和无主引用
当闭包和捕获的实例总是互相引用时并且总是同时销毁时将闭包内的捕获定义为无主引用。
相反的当捕获引用有时可能会是nil时将闭包内的捕获定义为弱引用。
如果捕获的引用绝对不会置为nil应该用无主引用而不是弱引用。
实例
前面的HTMLElement例子中无主引用是正确的解决循环强引用的方法。这样编写HTMLElement类来避免循环强引用
class HTMLElement {let name: Stringlet text: String?lazy var asHTML: () - String { [unowned self] inif let text self.text {return \(self.name)\(text)/\(self.name)} else {return \(self.name) /}}init(name: String, text: String? nil) {self.name nameself.text text}deinit {print(\(name) 被析构)}}//创建并打印HTMLElement实例
var paragraph: HTMLElement? HTMLElement(name: p, text: hello, world)
print(paragraph!.asHTML())// HTMLElement实例将会被销毁并能看到它的析构函数打印出的消息
paragraph nil
以上程序执行输出结果为
phello, world/p
p 被析构 Swift 类型转换
Swift 语言类型转换可以判断实例的类型。也可以用于检测实例类型是否属于其父类或者子类的实例。
Swift 中类型转换使用 is 和 as 操作符实现is 用于检测值的类型as 用于转换类型。
类型转换也可以用来检查一个类是否实现了某个协议。 检查类型
类型转换用于检测实例类型是否属于特定的实例类型。
你可以将它用在类和子类的层次结构上检查特定类实例的类型并且转换这个类实例的类型成为这个层次结构中的其他类型。
类型检查使用 is 关键字。
操作符 is 来检查一个实例是否属于特定子类型。若实例属于那个子类型类型检查操作符返回 true否则返回 false。
class Subjects {var physics: Stringinit(physics: String) {self.physics physics}
}class Chemistry: Subjects {var equations: Stringinit(physics: String, equations: String) {self.equations equationssuper.init(physics: physics)}
}class Maths: Subjects {var formulae: Stringinit(physics: String, formulae: String) {self.formulae formulaesuper.init(physics: physics)}
}let sa [Chemistry(physics: 固体物理, equations: 赫兹),Maths(physics: 流体动力学, formulae: 千兆赫),Chemistry(physics: 热物理学, equations: 分贝),Maths(physics: 天体物理学, formulae: 兆赫),Maths(physics: 微分方程, formulae: 余弦级数)]let samplechem Chemistry(physics: 固体物理, equations: 赫兹)
print(实例物理学是: \(samplechem.physics))
print(实例方程式: \(samplechem.equations))let samplemaths Maths(physics: 流体动力学, formulae: 千兆赫)
print(实例物理学是: \(samplemaths.physics))
print(实例公式是: \(samplemaths.formulae))var chemCount 0
var mathsCount 0
for item in sa {// 如果是一个 Chemistry 类型的实例返回 true相反返回 false。if item is Chemistry {chemCount} else if item is Maths {mathsCount}
}print(化学科目包含 \(chemCount) 个主题数学包含 \(mathsCount) 个主题)
以上程序执行输出结果为
实例物理学是: 固体物理
实例方程式: 赫兹
实例物理学是: 流体动力学
实例公式是: 千兆赫
化学科目包含 2 个主题数学包含 3 个主题
向下转型
向下转型用类型转换操作符(as? 或 as!)
当你不确定向下转型可以成功时用类型转换的条件形式(as?)。条件形式的类型转换总是返回一个可选值optional value并且若下转是不可能的可选值将是 nil。
只有你可以确定向下转型一定会成功时才使用强制形式(as!)。当你试图向下转型为一个不正确的类型时强制形式的类型转换会触发一个运行时错误。 class Subjects {var physics: Stringinit(physics: String) {self.physics physics}
}class Chemistry: Subjects {var equations: Stringinit(physics: String, equations: String) {self.equations equationssuper.init(physics: physics)}
}class Maths: Subjects {var formulae: Stringinit(physics: String, formulae: String) {self.formulae formulaesuper.init(physics: physics)}
}let sa [Chemistry(physics: 固体物理, equations: 赫兹),Maths(physics: 流体动力学, formulae: 千兆赫),Chemistry(physics: 热物理学, equations: 分贝),Maths(physics: 天体物理学, formulae: 兆赫),Maths(physics: 微分方程, formulae: 余弦级数)]let samplechem Chemistry(physics: 固体物理, equations: 赫兹)
print(实例物理学是: \(samplechem.physics))
print(实例方程式: \(samplechem.equations))let samplemaths Maths(physics: 流体动力学, formulae: 千兆赫)
print(实例物理学是: \(samplemaths.physics))
print(实例公式是: \(samplemaths.formulae))var chemCount 0
var mathsCount 0for item in sa {// 类型转换的条件形式if let show item as? Chemistry {print(化学主题是: \(show.physics), \(show.equations))// 强制形式} else if let example item as? Maths {print(数学主题是: \(example.physics), \(example.formulae))}
} 以上程序执行输出结果为
实例物理学是: 固体物理
实例方程式: 赫兹
实例物理学是: 流体动力学
实例公式是: 千兆赫
化学主题是: 固体物理, 赫兹
数学主题是: 流体动力学, 千兆赫
化学主题是: 热物理学, 分贝
数学主题是: 天体物理学, 兆赫
数学主题是: 微分方程, 余弦级数 Any和AnyObject的类型转换
Swift为不确定类型提供了两种特殊类型别名
AnyObject可以代表任何class类型的实例。(类似于Objective-C里的id)Any可以表示任何类型包括方法类型function types。 注意只有当你明确的需要它的行为和功能时才使用Any和AnyObject。在你的代码里使用你期望的明确的类型总是更好的。 Any 实例
class Subjects {var physics: Stringinit(physics: String) {self.physics physics}
}class Chemistry: Subjects {var equations: Stringinit(physics: String, equations: String) {self.equations equationssuper.init(physics: physics)}
}class Maths: Subjects {var formulae: Stringinit(physics: String, formulae: String) {self.formulae formulaesuper.init(physics: physics)}
}let sa [Chemistry(physics: 固体物理, equations: 赫兹),Maths(physics: 流体动力学, formulae: 千兆赫),Chemistry(physics: 热物理学, equations: 分贝),Maths(physics: 天体物理学, formulae: 兆赫),Maths(physics: 微分方程, formulae: 余弦级数)]let samplechem Chemistry(physics: 固体物理, equations: 赫兹)
print(实例物理学是: \(samplechem.physics))
print(实例方程式: \(samplechem.equations))let samplemaths Maths(physics: 流体动力学, formulae: 千兆赫)
print(实例物理学是: \(samplemaths.physics))
print(实例公式是: \(samplemaths.formulae))var chemCount 0
var mathsCount 0for item in sa {// 类型转换的条件形式if let show item as? Chemistry {print(化学主题是: \(show.physics), \(show.equations))// 强制形式} else if let example item as? Maths {print(数学主题是: \(example.physics), \(example.formulae))}
}// 可以存储Any类型的数组 exampleany
var exampleany [Any]()exampleany.append(12)
exampleany.append(3.14159)
exampleany.append(Any 实例)
exampleany.append(Chemistry(physics: 固体物理, equations: 兆赫))for item2 in exampleany {switch item2 {case let someInt as Int:print(整型值为 \(someInt))case let someDouble as Double where someDouble 0:print(Pi 值为 \(someDouble))case let someString as String:print(\(someString))case let phy as Chemistry:print(主题 \(phy.physics), \(phy.equations))default:print(None)}
}
以上程序执行输出结果为
实例物理学是: 固体物理
实例方程式: 赫兹
实例物理学是: 流体动力学
实例公式是: 千兆赫
化学主题是: 固体物理, 赫兹
数学主题是: 流体动力学, 千兆赫
化学主题是: 热物理学, 分贝
数学主题是: 天体物理学, 兆赫
数学主题是: 微分方程, 余弦级数
整型值为 12
Pi 值为 3.14159
Any 实例
主题 固体物理, 兆赫
AnyObject 实例
class Subjects {var physics: Stringinit(physics: String) {self.physics physics}
}class Chemistry: Subjects {var equations: Stringinit(physics: String, equations: String) {self.equations equationssuper.init(physics: physics)}
}class Maths: Subjects {var formulae: Stringinit(physics: String, formulae: String) {self.formulae formulaesuper.init(physics: physics)}
}// [AnyObject] 类型的数组
let saprint: [AnyObject] [Chemistry(physics: 固体物理, equations: 赫兹),Maths(physics: 流体动力学, formulae: 千兆赫),Chemistry(physics: 热物理学, equations: 分贝),Maths(physics: 天体物理学, formulae: 兆赫),Maths(physics: 微分方程, formulae: 余弦级数)]let samplechem Chemistry(physics: 固体物理, equations: 赫兹)
print(实例物理学是: \(samplechem.physics))
print(实例方程式: \(samplechem.equations))let samplemaths Maths(physics: 流体动力学, formulae: 千兆赫)
print(实例物理学是: \(samplemaths.physics))
print(实例公式是: \(samplemaths.formulae))var chemCount 0
var mathsCount 0for item in saprint {// 类型转换的条件形式if let show item as? Chemistry {print(化学主题是: \(show.physics), \(show.equations))// 强制形式} else if let example item as? Maths {print(数学主题是: \(example.physics), \(example.formulae))}
}var exampleany [Any]()
exampleany.append(12)
exampleany.append(3.14159)
exampleany.append(Any 实例)
exampleany.append(Chemistry(physics: 固体物理, equations: 兆赫))for item2 in exampleany {switch item2 {case let someInt as Int:print(整型值为 \(someInt))case let someDouble as Double where someDouble 0:print(Pi 值为 \(someDouble))case let someString as String:print(\(someString))case let phy as Chemistry:print(主题 \(phy.physics), \(phy.equations))default:print(None)}
}
以上程序执行输出结果为
实例物理学是: 固体物理
实例方程式: 赫兹
实例物理学是: 流体动力学
实例公式是: 千兆赫
化学主题是: 固体物理, 赫兹
数学主题是: 流体动力学, 千兆赫
化学主题是: 热物理学, 分贝
数学主题是: 天体物理学, 兆赫
数学主题是: 微分方程, 余弦级数
整型值为 12
Pi 值为 3.14159
Any 实例
主题 固体物理, 兆赫
在一个switch语句的case中使用强制形式的类型转换操作符as, 而不是 as?来检查和转换到一个明确的类型。
Swift 扩展
扩展就是向一个已有的类、结构体或枚举类型添加新功能。
扩展可以对一个类型添加新的功能但是不能重写已有的功能。
Swift 中的扩展可以
添加计算型属性和计算型静态属性定义实例方法和类型方法提供新的构造器定义下标定义和使用新的嵌套类型使一个已有类型符合某个协议
语法
扩展声明使用关键字 extension
extension SomeType {// 加到SomeType的新功能写到这里
}
一个扩展可以扩展一个已有类型使其能够适配一个或多个协议语法格式如下
extension SomeType: SomeProtocol, AnotherProctocol {// 协议实现写到这里
} 计算型属性
扩展可以向已有类型添加计算型实例属性和计算型类型属性。
实例
下面的例子向 Int 类型添加了 5 个计算型实例属性并扩展其功能
extension Int {var add: Int {return self 100 }var sub: Int { return self - 10 }var mul: Int { return self * 10 }var div: Int { return self / 5 }
}let addition 3.add
print(加法运算后的值\(addition))let subtraction 120.sub
print(减法运算后的值\(subtraction))let multiplication 39.mul
print(乘法运算后的值\(multiplication))let division 55.div
print(除法运算后的值: \(division))let mix 30.add 34.sub
print(混合运算结果\(mix))
以上程序执行输出结果为
加法运算后的值103
减法运算后的值110
乘法运算后的值390
除法运算后的值: 11
混合运算结果154
构造器
扩展可以向已有类型添加新的构造器。
这可以让你扩展其它类型将你自己的定制类型作为构造器参数或者提供该类型的原始实现中没有包含的额外初始化选项。
扩展可以向类中添加新的便利构造器 init()但是它们不能向类中添加新的指定构造器或析构函数 deinit() 。 struct sum {var num1 100, num2 200
}struct diff {var no1 200, no2 100
}struct mult {var a sum()var b diff()
}extension mult {init(x: sum, y: diff) {_ x.num1 x.num2_ y.no1 y.no2}
}let a sum(num1: 100, num2: 200)
let b diff(no1: 200, no2: 100)let getMult mult(x: a, y: b)
print(getMult sum\(getMult.a.num1, getMult.a.num2))
print(getMult diff\(getMult.b.no1, getMult.b.no2))
以上程序执行输出结果为
getMult sum(100, 200)
getMult diff(200, 100) 方法
扩展可以向已有类型添加新的实例方法和类型方法。
下面的例子向Int类型添加一个名为 topics 的新实例方法
extension Int {func topics(summation: () - ()) {for _ in 0..self {summation() }}
} 4.topics({print(扩展模块内)
}) 3.topics({print(内型转换模块内)
})
以上程序执行输出结果为
扩展模块内
扩展模块内
扩展模块内
扩展模块内
内型转换模块内
内型转换模块内
内型转换模块内
这个topics方法使用了一个() - ()类型的单参数表明函数没有参数而且没有返回值。
定义该扩展之后你就可以对任意整数调用 topics 方法,实现的功能则是多次执行某任务
可变实例方法
通过扩展添加的实例方法也可以修改该实例本身。
结构体和枚举类型中修改self或其属性的方法必须将该实例方法标注为mutating正如来自原始实现的修改方法一样。
实例
下面的例子向 Swift 的 Double 类型添加了一个新的名为 square 的修改方法来实现一个原始值的平方计算
extension Double {mutating func square() {let pi 3.1415self pi * self * self}
}var Trial1 3.3
Trial1.square()
print(圆的面积为: \(Trial1))var Trial2 5.8
Trial2.square()
print(圆的面积为: \(Trial2))var Trial3 120.3
Trial3.square()
print(圆的面积为: \(Trial3))
以上程序执行输出结果为
圆的面积为: 34.210935
圆的面积为: 105.68006
圆的面积为: 45464.070735
下标
扩展可以向一个已有类型添加新下标。
实例
以下例子向 Swift 内建类型Int添加了一个整型下标。该下标[n]返回十进制数字
extension Int {subscript(var multtable: Int) - Int {var no1 1while multtable 0 {no1 * 10--multtable}return (self / no1) % 10}
}print(12[0])
print(7869[1])
print(786543[2])
以上程序执行输出结果为
2
6
5 嵌套类型
扩展可以向已有的类、结构体和枚举添加新的嵌套类型 extension Int {enum calc{case addcase subcase multcase divcase anything}var print: calc {switch self{case 0:return .addcase 1:return .subcase 2:return .multcase 3:return .divdefault:return .anything}}
}func result(numb: [Int]) {for i in numb {switch i.print {case .add:print( 10 )case .sub:print( 20 )case .mult:print( 30 )case .div:print( 40 )default:print( 50 )}}
}result([0, 1, 2, 3, 4, 7])
以上程序执行输出结果为 10 20 30 40 50 50
笔记
扩展下标文中的代码对于较高版本的swift可能会报错
var in this position is interpreted as an argument label
Left side of mutating operator isnt mutable: multtable is immutable
验证了写法这样写可以避免问题
extension Int{subscript(digitIndex:Int)-Int{var decimalBase 1var digit digitIndex// 不能直接使用digitIndex会报错while digit 0 {decimalBase * 10digit digit - 1}return (self/decimalBase) % 10}
}print(12[0])
print(7869[1])
print(786543[2])
参考了网上的写法还可以这样写
extension Int{subscript(digitIndex:Int)-Int{var decimalBase 1for _ in 0 .. digitIndex{decimalBase * 10}return (self/decimalBase) % 10}
}
print(12[0])
print(7869[1])
print(786543[2]) Swift 协议
协议规定了用来实现某一特定功能所必需的方法和属性。
任意能够满足协议要求的类型被称为遵循(conform)这个协议。
类结构体或枚举类型都可以遵循协议并提供具体实现来完成协议定义的方法和功能。
语法
协议的语法格式如下
protocol SomeProtocol {// 协议内容
}要使类遵循某个协议需要在类型名称后加上协议名称中间以冒号:分隔作为类型定义的一部分。遵循多个协议时各协议之间用逗号,分隔。
struct SomeStructure: FirstProtocol, AnotherProtocol {// 结构体内容
}如果类在遵循协议的同时拥有父类应该将父类名放在协议名之前以逗号分隔。
class SomeClass: SomeSuperClass, FirstProtocol, AnotherProtocol {// 类的内容
}对属性的规定
协议用于指定特定的实例属性或类属性而不用指定是存储型属性或计算型属性。此外还必须指明是只读的还是可读可写的。
协议中的通常用var来声明变量属性在类型声明后加上{ set get }来表示属性是可读可写的只读属性则用{ get }来表示。 protocol classa {var marks: Int { get set }var result: Bool { get }func attendance() - Stringfunc markssecured() - String}protocol classb: classa {var present: Bool { get set }var subject: String { get set }var stname: String { get set }}class classc: classb {var marks 96let result truevar present falsevar subject Swift 协议var stname Protocolsfunc attendance() - String {return The \(stname) has secured 99% attendance}func markssecured() - String {return \(stname) has scored \(marks)}
}let studdet classc()
studdet.stname Swift
studdet.marks 98
studdet.markssecured()print(studdet.marks)
print(studdet.result)
print(studdet.present)
print(studdet.subject)
print(studdet.stname)以上程序执行输出结果为
98
true
false
Swift 协议
Swift对 Mutating 方法的规定
有时需要在方法中改变它的实例。
例如值类型(结构体枚举)的实例方法中将mutating关键字作为函数的前缀写在func之前表示可以在该方法中修改它所属的实例及其实例属性的值。
protocol daysofaweek {mutating func show()
}enum days: daysofaweek {case sun, mon, tue, wed, thurs, fri, satmutating func show() {switch self {case .sun:self .sunprint(Sunday)case .mon:self .monprint(Monday)case .tue:self .tueprint(Tuesday)case .wed:self .wedprint(Wednesday)case .thurs:self .thursprint(Wednesday)case .fri:self .friprint(Firday)case .sat:self .satprint(Saturday)default:print(NO Such Day)}}
}var res days.wed
res.show()以上程序执行输出结果为
Wednesday 对构造器的规定
协议可以要求它的遵循者实现指定的构造器。
你可以像书写普通的构造器那样在协议的定义里写下构造器的声明但不需要写花括号和构造器的实体语法如下
protocol SomeProtocol {init(someParameter: Int)
}实例
protocol tcpprotocol {init(aprot: Int)
} 协议构造器规定在类中的实现
你可以在遵循该协议的类中实现构造器并指定其为类的指定构造器或者便利构造器。在这两种情况下你都必须给构造器实现标上required修饰符
class SomeClass: SomeProtocol {required init(someParameter: Int) {// 构造器实现}
}protocol tcpprotocol {init(aprot: Int)
}class tcpClass: tcpprotocol {required init(aprot: Int) {}
}使用required修饰符可以保证所有的遵循该协议的子类同样能为构造器规定提供一个显式的实现或继承实现。
如果一个子类重写了父类的指定构造器并且该构造器遵循了某个协议的规定那么该构造器的实现需要被同时标示required和override修饰符
protocol tcpprotocol {init(no1: Int)
}class mainClass {var no1: Int // 局部变量init(no1: Int) {self.no1 no1 // 初始化}
}class subClass: mainClass, tcpprotocol {var no2: Intinit(no1: Int, no2 : Int) {self.no2 no2super.init(no1:no1)}// 因为遵循协议需要加上required; 因为继承自父类需要加上overriderequired override convenience init(no1: Int) {self.init(no1:no1, no2:0)}
}
let res mainClass(no1: 20)
let show subClass(no1: 30, no2: 50)print(res is: \(res.no1))
print(res is: \(show.no1))
print(res is: \(show.no2))以上程序执行输出结果为
res is: 20
res is: 30
res is: 50协议类型
尽管协议本身并不实现任何功能但是协议可以被当做类型来使用。
协议可以像其他普通类型一样使用使用场景:
作为函数、方法或构造器中的参数类型或返回值类型作为常量、变量或属性的类型作为数组、字典或其他容器中的元素类型
实例
protocol Generator {associatedtype membersfunc next() - members?
}var items [10,20,30].makeIterator()
while let x items.next() {print(x)
}for lists in [1,2,3].map( {i in i*5}) {print(lists)
}print([100,200,300])
print([1,2,3].map({i in i*10}))以上程序执行输出结果为
10
20
30
5
10
15
[100, 200, 300]
[10, 20, 30]在扩展中添加协议成员
我们可以可以通过扩展来扩充已存在类型( 类结构体枚举等)。
扩展可以为已存在的类型添加属性方法下标脚本协议等成员。
protocol AgeClasificationProtocol {var age: Int { get }func agetype() - String
}class Person {let firstname: Stringlet lastname: Stringvar age: Intinit(firstname: String, lastname: String) {self.firstname firstnameself.lastname lastnameself.age 10}
}extension Person : AgeClasificationProtocol {func fullname() - String {var c: Stringc firstname lastnamereturn c}func agetype() - String {switch age {case 0...2:return Babycase 2...12:return Childcase 13...19:return Teenagercase let x where x 65:return Elderlydefault:return Normal}}
} 协议的继承
协议能够继承一个或多个其他协议可以在继承的协议基础上增加新的内容要求。
协议的继承语法与类的继承相似多个被继承的协议间用逗号分隔 protocol InheritingProtocol: SomeProtocol, AnotherProtocol {// 协议定义
}实例
protocol Classa {var no1: Int { get set }func calc(sum: Int)
}protocol Result {func print(target: Classa)
}class Student2: Result {func print(target: Classa) {target.calc(1)}
}class Classb: Result {func print(target: Classa) {target.calc(5)}
}class Student: Classa {var no1: Int 10func calc(sum: Int) {no1 - sumprint(学生尝试 \(sum) 次通过)if no1 0 {print(学生缺席考试)}}
}class Player {var stmark: Result!init(stmark: Result) {self.stmark stmark}func print(target: Classa) {stmark.print(target)}
}var marks Player(stmark: Student2())
var marksec Student()marks.print(marksec)
marks.print(marksec)
marks.print(marksec)
marks.stmark Classb()
marks.print(marksec)
marks.print(marksec)
marks.print(marksec)以上程序执行输出结果为
学生尝试 1 次通过
学生尝试 1 次通过
学生尝试 1 次通过
学生尝试 5 次通过
学生尝试 5 次通过
学生缺席考试
学生尝试 5 次通过
学生缺席考试 类专属协议
你可以在协议的继承列表中,通过添加class关键字,限制协议只能适配到类class类型。
该class关键字必须是第一个出现在协议的继承列表中其后才是其他继承协议。格式如下
protocol SomeClassOnlyProtocol: class, SomeInheritedProtocol {// 协议定义
}实例
protocol TcpProtocol {init(no1: Int)
}class MainClass {var no1: Int // 局部变量init(no1: Int) {self.no1 no1 // 初始化}
}class SubClass: MainClass, TcpProtocol {var no2: Intinit(no1: Int, no2 : Int) {self.no2 no2super.init(no1:no1)}// 因为遵循协议需要加上required; 因为继承自父类需要加上overriderequired override convenience init(no1: Int) {self.init(no1:no1, no2:0)}
}let res MainClass(no1: 20)
let show SubClass(no1: 30, no2: 50)print(res is: \(res.no1))
print(res is: \(show.no1))
print(res is: \(show.no2))以上程序执行输出结果为
res is: 20
res is: 30
res is: 50 协议合成
Swift 支持合成多个协议这在我们需要同时遵循多个协议时非常有用。
语法格式如下
protocol Stname {var name: String { get }
}protocol Stage {var age: Int { get }
}struct Person: Stname, Stage {var name: Stringvar age: Int
}func show(celebrator: Stname Stage) {print(\(celebrator.name) is \(celebrator.age) years old)
}let studname Person(name: Priya, age: 21)
show(studname)let stud Person(name: Rehan, age: 29)
print(stud)let student Person(name: Roshan, age: 19)
print(student)以上程序执行输出结果为
Priya is 21 years old
Person(name: Rehan, age: 29)
Person(name: Roshan, age: 19)检验协议的一致性
你可以使用is和as操作符来检查是否遵循某一协议或强制转化为某一类型。
is操作符用来检查实例是否遵循了某个协议。as?返回一个可选值当实例遵循协议时返回该协议类型;否则返回nil。as用以强制向下转型如果强转失败会引起运行时错误。
实例
下面的例子定义了一个 HasArea 的协议要求有一个Double类型可读的 area
protocol HasArea {var area: Double { get }
}// 定义了Circle类都遵循了HasArea协议
class Circle: HasArea {let pi 3.1415927var radius: Doublevar area: Double { return pi * radius * radius }init(radius: Double) { self.radius radius }
}// 定义了Country类都遵循了HasArea协议
class Country: HasArea {var area: Doubleinit(area: Double) { self.area area }
}// Animal是一个没有实现HasArea协议的类
class Animal {var legs: Intinit(legs: Int) { self.legs legs }
}let objects: [AnyObject] [Circle(radius: 2.0),Country(area: 243_610),Animal(legs: 4)
]for object in objects {// 对迭代出的每一个元素进行检查看它是否遵循了HasArea协议if let objectWithArea object as? HasArea {print(面积为 \(objectWithArea.area))} else {print(没有面积)}
}以上程序执行输出结果为
面积为 12.5663708
面积为 243610.0
没有面积
Swift 泛型
Swift 提供了泛型让你写出灵活且可重用的函数和类型。
Swift 标准库是通过泛型代码构建出来的。
Swift 的数组和字典类型都是泛型集。
你可以创建一个Int数组也可创建一个String数组或者甚至于可以是任何其他 Swift 的类型数据数组。
以下实例是一个非泛型函数 exchange 用来交换两个 Int 值
实例
// 定义一个交换两个变量的函数 func swapTwoInts(_ a: inout Int, _ b: inout Int) { let temporaryA a a b b temporaryA } var numb1 100 var numb2 200 print(交换前数据: \(numb1) 和 \(numb2)) swapTwoInts(numb1, numb2) print(交换后数据: \(numb1) 和 \(numb2))
以上程序执行输出结果为
交换前数据: 100 和 200
交换后数据: 200 和 100
以上实例只试用与交换整数 Int 类型的变量。如果你想要交换两个 String 值或者 Double 值就得重新写个对应的函数例如 swapTwoStrings(_:_:) 和 swapTwoDoubles(_:_:)如下所示
String 和 Double 值交换函数
func swapTwoStrings(_ a: inout String, _ b: inout String) { let temporaryA a a b b temporaryA } func swapTwoDoubles(_ a: inout Double, _ b: inout Double) { let temporaryA a a b b temporaryA }
从以上代码来看它们功能代码是相同的只是类型上不一样这时我们可以使用泛型从而避免重复编写代码。
泛型使用了占位类型名在这里用字母 T 来表示来代替实际类型名例如 Int、String 或 Double。
func swapTwoValuesT(_ a: inout T, _ b: inout T)
swapTwoValues 后面跟着占位类型名T并用尖括号括起来T。这个尖括号告诉 Swift 那个 T 是 swapTwoValues(_:_:) 函数定义内的一个占位类型名因此 Swift 不会去查找名为 T 的实际类型。
以下实例是一个泛型函数 exchange 用来交换两个 Int 和 String 值
实例
// 定义一个交换两个变量的函数func swapTwoValuesT(_ a: inout T, _ b: inout T) { let temporaryA a a b b temporaryA } var numb1 100 var numb2 200 print(交换前数据: \(numb1) 和 \(numb2)) swapTwoValues(numb1, numb2) print(交换后数据: \(numb1) 和 \(numb2)) var str1 A var str2 B print(交换前数据: \(str1) 和 \(str2)) swapTwoValues(str1, str2) print(交换后数据: \(str1) 和 \(str2))
以上程序执行输出结果为
交换前数据: 100 和 200
交换后数据: 200 和 100
交换前数据: A 和 B
交换后数据: B 和 A 泛型类型
Swift 允许你定义你自己的泛型类型。
自定义类、结构体和枚举作用于任何类型如同 Array 和 Dictionary 的用法。
接下来我们来编写一个名为 Stack 栈的泛型集合类型栈只允许在集合的末端添加新的元素称之为入栈且也只能从末端移除元素称之为出栈。
接下来我们来编写一个名为 Stack 栈的泛型集合类型栈只允许在集合的末端添加新的元素称之为入栈且也只能从末端移除元素称之为出栈。 图片中从左到右解析如下
三个值在栈中。第四个值被压入到栈的顶部。现在有四个值在栈中最近入栈的那个值在顶部。栈中最顶部的那个值被移除或称之为出栈。移除掉一个值后现在栈又只有三个值了。
以下实例是一个非泛型版本的栈以 Int 型的栈为例
Int 型的栈
struct IntStack { var items [Int]() mutating func push(_ item: Int) { items.append(item) } mutating func pop() - Int { return items.removeLast() } }
这个结构体在栈中使用一个名为 items 的 Array 属性来存储值。Stack 提供了两个方法push(_:) 和 pop()用来向栈中压入值以及从栈中移除值。这些方法被标记为 mutating因为它们需要修改结构体的 items 数组。
上面的 IntStack 结构体只能用于 Int 类型。不过可以定义一个泛型 Stack 结构体从而能够处理任意类型的值。
下面是相同代码的泛型版本
泛型的栈
struct StackElement {
var items [Element]()
mutating func push(_ item: Element) { items.append(item)
} mutating func pop() - Element { return items.removeLast() }
}
var stackOfStrings StackString()
print(字符串元素入栈: )
stackOfStrings.push(google)
stackOfStrings.push(runoob)
print(stackOfStrings.items);
let deletetos stackOfStrings.pop()
print(出栈元素: deletetos)
var stackOfInts StackInt()
print(整数元素入栈: )
stackOfInts.push(1)
stackOfInts.push(2)
print(stackOfInts.items);
实例执行结果为
字符串元素入栈:
[google, runoob]
出栈元素: runoob
整数元素入栈:
[1, 2]
Stack 基本上和 IntStack 相同占位类型参数 Element 代替了实际的 Int 类型。
以上实例中 Element 在如下三个地方被用作占位符
创建 items 属性使用 Element 类型的空数组对其进行初始化。指定 push(_:) 方法的唯一参数 item 的类型必须是 Element 类型。指定 pop() 方法的返回值类型必须是 Element 类型。 扩展泛型类型
当你扩展一个泛型类型的时候使用 extension 关键字你并不需要在扩展的定义中提供类型参数列表。更加方便的是原始类型定义中声明的类型参数列表在扩展里是可以使用的并且这些来自原始类型中的参数名称会被用作原始定义中类型参数的引用。
下面的例子扩展了泛型类型 Stack为其添加了一个名为 topItem 的只读计算型属性它将会返回当前栈顶端的元素而不会将其从栈中移除
泛型
struct StackElement { var items [Element]() mutating func push(_ item: Element) { items.append(item) } mutating func pop() - Element { return items.removeLast() } } extension Stack { var topItem: Element? { return items.isEmpty ? nil : items[items.count - 1] } } var stackOfStrings StackString() print(字符串元素入栈: ) stackOfStrings.push(google) stackOfStrings.push(runoob) if let topItem stackOfStrings.topItem { print(栈中的顶部元素是\(topItem).) } print(stackOfStrings.items) 实例中 topItem 属性会返回一个 Element 类型的可选值。当栈为空的时候topItem 会返回 nil当栈不为空的时候topItem 会返回 items 数组中的最后一个元素。
以上程序执行输出结果为
字符串元素入栈:
栈中的顶部元素是runoob.
[google, runoob]
我们也可以通过扩展一个存在的类型来指定关联类型。
例如 Swift 的 Array 类型已经提供 append(_:) 方法一个 count 属性以及一个接受 Int 类型索引值的下标用以检索其元素。这三个功能都符合 Container 协议的要求所以你只需简单地声明 Array 采纳该协议就可以扩展 Array。
以下实例创建一个空扩展即可:
extension Array: Container {}
类型约束
类型约束指定了一个必须继承自指定类的类型参数或者遵循一个特定的协议或协议构成。
类型约束语法
你可以写一个在一个类型参数名后面的类型约束通过冒号分割来作为类型参数链的一部分。这种作用于泛型函数的类型约束的基础语法如下所示和泛型类型的语法相同
func someFunctionT: SomeClass, U: SomeProtocol(someT: T, someU: U) {// 这里是泛型函数的函数体部分
}
上面这个函数有两个类型参数。第一个类型参数 T有一个要求 T 必须是 SomeClass 子类的类型约束第二个类型参数 U有一个要求 U 必须符合 SomeProtocol 协议的类型约束。
实例
泛型
// 非泛型函数查找指定字符串在数组中的索引 func findIndex(ofString valueToFind: String, in array: [String]) - Int? { for (index, value) in array.enumerated() { if value valueToFind { // 找到返回索引值 return index } } return nil } let strings [google, weibo, taobao, runoob, facebook] if let foundIndex findIndex(ofString: runoob, in: strings) { print(runoob 的索引为 \(foundIndex)) } 索引下标从 0 开始。
以上程序执行输出结果为
runoob 的索引为 3 关联类
Swift 中使用 associatedtype 关键字来设置关联类型实例。
下面例子定义了一个 Container 协议该协议定义了一个关联类型 ItemType。
Container 协议只指定了三个任何遵从 Container 协议的类型必须提供的功能。遵从协议的类型在满足这三个条件的情况下也可以提供其他额外的功能。
// Container 协议
protocol Container {associatedtype ItemType// 添加一个新元素到容器里mutating func append(_ item: ItemType)// 获取容器中元素的数var count: Int { get }// 通过索引值类型为 Int 的下标检索到容器中的每一个元素subscript(i: Int) - ItemType { get }
}// Stack 结构体遵从 Container 协议
struct StackElement: Container {// StackElement 的原始实现部分var items [Element]()mutating func push(_ item: Element) {items.append(item)}mutating func pop() - Element {return items.removeLast()}// Container 协议的实现部分mutating func append(_ item: Element) {self.push(item)}var count: Int {return items.count}subscript(i: Int) - Element {return items[i]}
}var tos StackString()
tos.push(google)
tos.push(runoob)
tos.push(taobao)
// 元素列表
print(tos.items)
// 元素个数
print( tos.count)
以上程序执行输出结果为
[google, runoob, taobao]
3 Where 语句
类型约束能够确保类型符合泛型函数或类的定义约束。
你可以在参数列表中通过where语句定义参数的约束。
你可以写一个where语句紧跟在在类型参数列表后面where语句后跟一个或者多个针对关联类型的约束以及或一个或多个类型和关联类型间的等价(equality)关系。
实例
下面的例子定义了一个名为allItemsMatch的泛型函数用来检查两个Container实例是否包含相同顺序的相同元素。
如果所有的元素能够匹配那么返回 true反之则返回 false。
泛型
// Container 协议 protocol Container { associatedtype ItemType // 添加一个新元素到容器里 mutating func append(_ item: ItemType) // 获取容器中元素的数 var count: Int { get } // 通过索引值类型为 Int 的下标检索到容器中的每一个元素 subscript(i: Int) - ItemType { get } } // // 遵循Container协议的泛型TOS类型 struct StackElement: Container { // StackElement 的原始实现部分 var items [Element]() mutating func push(_ item: Element) { items.append(item) } mutating func pop() - Element { return items.removeLast() } // Container 协议的实现部分 mutating func append(_ item: Element) { self.push(item) } var count: Int { return items.count } subscript(i: Int) - Element { return items[i] } } // 扩展将 Array 当作 Container 来使用 extension Array: Container {} func allItemsMatchC1: Container, C2: Container (_ someContainer: C1, _ anotherContainer: C2) - Bool where C1.ItemType C2.ItemType, C1.ItemType: Equatable { // 检查两个容器含有相同数量的元素 if someContainer.count ! anotherContainer.count { return false } // 检查每一对元素是否相等 for i in 0..someContainer.count { if someContainer[i] ! anotherContainer[i] { return false } } // 所有元素都匹配返回 true return true } var tos StackString() tos.push(google) tos.push(runoob) tos.push(taobao) var aos [google, runoob, taobao] if allItemsMatch(tos, aos) { print(匹配所有元素) } else { print(元素不匹配) }
以上程序执行输出结果为
匹配所有元素 Swift 访问控制
访问控制可以限定其他源文件或模块中代码对你代码的访问级别。
你可以明确地给单个类型类、结构体、枚举设置访问级别也可以给这些类型的属性、函数、初始化方法、基本类型、下标索引等设置访问级别。
协议也可以被限定在一定的范围内使用包括协议里的全局常量、变量和函数。
访问控制基于模块与源文件。
模块指的是以独立单元构建和发布的 Framework 或 Application。在 Swift 中的一个模块可以使用 import 关键字引入另外一个模块。
源文件是单个源码文件它通常属于一个模块 源文件可以包含多个类和函数 的定义。
Swift 为代码中的实体提供了四种不同的访问级别public、internal、fileprivate、private。
访问级别定义public可以访问自己模块中源文件里的任何实体别人也可以通过引入该模块来访问源文件里的所有实体。internal可以访问自己模块中源文件里的任何实体但是别人不能访问该模块中源文件里的实体。fileprivate文件内私有只能在当前源文件中使用。private只能在类中访问离开了这个类或者结构体的作用域外面就无法访问。
public 为最高级访问级别private 为最低级访问级别。
实例
public class SomePublicClass {} internal class SomeInternalClass {} fileprivate class SomeFilePrivateClass {} private class SomePrivateClass {} public var somePublicVariable 0 internal let someInternalConstant 0 fileprivate func someFilePrivateFunction() {} private func somePrivateFunction() {}
除非有特殊的说明否则实体都使用默认的访问级别 internal。
未指定访问级别默认为 internal
class SomeInternalClass {} // 访问级别为 internal let someInternalConstant 0 // 访问级别为 internal
函数类型访问权限
函数的访问级别需要根据该函数的参数类型和返回类型的访问级别得出。
下面的例子定义了一个名为someFunction全局函数并且没有明确地申明其访问级别。
func someFunction() - (SomeInternalClass, SomePrivateClass) {// 函数实现
}
函数中其中一个类 SomeInternalClass 的访问级别是 internal另一个 SomePrivateClass 的访问级别是 private。所以根据元组访问级别的原则该元组的访问级别是 private元组的访问级别与元组中访问级别最低的类型一致。
因为该函数返回类型的访问级别是 private所以你必须使用 private 修饰符明确的声明该函数
private func someFunction() - (SomeInternalClass, SomePrivateClass) {// 函数实现
}
将该函数申明为 public 或 internal或者使用默认的访问级别 internal 都是错误的因为如果这样你就无法访问 private 级别的返回值。 枚举类型访问权限
枚举中成员的访问级别继承自该枚举你不能为枚举中的成员单独申明不同的访问级别。
实例
比如下面的例子枚举 Student 被明确的申明为 public 级别那么它的成员 NameMark 的访问级别同样也是 public
实例
public enum Student { case Name(String) case Mark(Int,Int,Int) } var studDetails Student.Name(Swift) var studMarks Student.Mark(98,97,95) switch studMarks { case .Name(let studName): print(学生名: \(studName).) case .Mark(let Mark1, let Mark2, let Mark3): print(学生成绩: \(Mark1),\(Mark2),\(Mark3)) }
以上程序执行输出结果为
学生成绩: 98,97,95
子类访问权限
子类的访问级别不得高于父类的访问级别。比如说父类的访问级别是 internal子类的访问级别就不能申明为 public。
实例
public class SuperClass { fileprivate func show() { print(超类) } } // 访问级别不能高于超类 public internal internal class SubClass: SuperClass { override internal func show() { print(子类) } } let sup SuperClass() sup.show() let sub SubClass() sub.show()
以上程序执行输出结果为
超类
子类
常量、变量、属性、下标访问权限
常量、变量、属性不能拥有比它们的类型更高的访问级别。
比如说你定义一个public级别的属性但是它的类型是private级别的这是编译器所不允许的。
同样下标也不能拥有比索引类型或返回类型更高的访问级别。
如果常量、变量、属性、下标索引的定义类型是private级别的那么它们必须要明确的申明访问级别为private:
private var privateInstance SomePrivateClass()
Getter 和 Setter访问权限
常量、变量、属性、下标索引的Getters和Setters的访问级别继承自它们所属成员的访问级别。
Setter的访问级别可以低于对应的Getter的访问级别这样就可以控制变量、属性或下标索引的读写权限。
实例
class Samplepgm { fileprivate var counter: Int 0{ willSet(newTotal){ print(计数器: \(newTotal)) } didSet{ if counter oldValue { print(新增加数量 \(counter - oldValue)) } } } } let NewCounter Samplepgm() NewCounter.counter 100 NewCounter.counter 800
counter 的访问级别为 fileprivate在文件内可以访问。
以上程序执行输出结果为
计数器: 100
新增加数量 100
计数器: 800
新增加数量 700
构造器和默认构造器访问权限
初始化
我们可以给自定义的初始化方法申明访问级别但是要不高于它所属类的访问级别。但必要构造器例外它的访问级别必须和所属类的访问级别相同。
如同函数或方法参数初始化方法参数的访问级别也不能低于初始化方法的访问级别。
默认初始化方法
Swift为结构体、类都提供了一个默认的无参初始化方法用于给它们的所有属性提供赋值操作但不会给出具体值。
默认初始化方法的访问级别与所属类型的访问级别相同。 实例
在每个子类的 init() 方法前使用 required 关键字声明访问权限。
实例
class classA { required init() { var a 10 print(a) } } class classB: classA { required init() { var b 30 print(b) } } let res classA() let show classB()
以上程序执行输出结果为
10
30
10
协议访问权限
如果想为一个协议明确的申明访问级别那么需要注意一点就是你要确保该协议只在你申明的访问级别作用域中使用。
如果你定义了一个public访问级别的协议那么实现该协议提供的必要函数也会是public的访问级别。这一点不同于其他类型比如public访问级别的其他类型他们成员的访问级别为internal。
实例
public protocol TcpProtocol { init(no1: Int) } public class MainClass { var no1: Int // local storage init(no1: Int) { self.no1 no1 // initialization } } class SubClass: MainClass, TcpProtocol { var no2: Int init(no1: Int, no2 : Int) { self.no2 no2 super.init(no1:no1) } // Requires only one parameter for convenient method required override convenience init(no1: Int) { self.init(no1:no1, no2:0) } } let res MainClass(no1: 20) let show SubClass(no1: 30, no2: 50) print(res is: \(res.no1)) print(res is: \(show.no1)) print(res is: \(show.no2))
以上程序执行输出结果为
res is: 20
res is: 30
res is: 50
扩展访问权限
你可以在条件允许的情况下对类、结构体、枚举进行扩展。扩展成员应该具有和原始类成员一致的访问级别。比如你扩展了一个公共类型那么你新加的成员应该具有和原始成员一样的默认的internal访问级别。 或者你可以明确申明扩展的访问级别比如使用private extension给该扩展内所有成员申明一个新的默认访问级别。这个新的默认访问级别仍然可以被单独成员所申明的访问级别所覆盖。 泛型访问权限
泛型类型或泛型函数的访问级别取泛型类型、函数本身、泛型类型参数三者中的最低访问级别。
实例
public struct TOST { var items [T]() private mutating func push(item: T) { items.append(item) } mutating func pop() - T { return items.removeLast() } } var tos TOSString() tos.push(Swift) print(tos.items) tos.push(泛型) print(tos.items) tos.push(类型参数) print(tos.items) tos.push(类型参数名) print(tos.items) let deletetos tos.pop()
以上程序执行输出结果为
[Swift]
[Swift, 泛型]
[Swift, 泛型, 类型参数]
[Swift, 泛型, 类型参数, 类型参数名]
类型别名
任何你定义的类型别名都会被当作不同的类型以便于进行访问控制。一个类型别名的访问级别不可高于原类型的访问级别。
比如说一个private级别的类型别名可以设定给一个public、internal、private的类型但是一个public级别的类型别名只能设定给一个public级别的类型不能设定给internal或private 级别的类型。
注意这条规则也适用于为满足协议一致性而给相关类型命名别名的情况。
实例
public protocol Container { typealias ItemType mutating func append(item: ItemType) var count: Int { get } subscript(i: Int) - ItemType { get } } struct StackT: Container { // original StackT implementation var items [T]() mutating func push(item: T) { items.append(item) } mutating func pop() - T { return items.removeLast() } // conformance to the Container protocol mutating func append(item: T) { self.push(item) } var count: Int { return items.count } subscript(i: Int) - T { return items[i] } } func allItemsMatch C1: Container, C2: Container where C1.ItemType C2.ItemType, C1.ItemType: Equatable (someContainer: C1, anotherContainer: C2) - Bool { // check that both containers contain the same number of items if someContainer.count ! anotherContainer.count { return false } // check each pair of items to see if they are equivalent for i in 0..someContainer.count { if someContainer[i] ! anotherContainer[i] { return false } } // all items match, so return true return true } var tos StackString() tos.push(Swift) print(tos.items) tos.push(泛型) print(tos.items) tos.push(Where 语句) print(tos.items) var eos [Swift, 泛型, Where 语句] print(eos)
以上程序执行输出结果为
[Swift]
[Swift, 泛型]
[Swift, 泛型, Where 语句]
[Swift, 泛型, Where 语句]