Swift语法2

目录

继承   构造过程   析构过程  可选链  类型转换  协议   泛型  访问控制   相等与相同  内存管理

继承

继承我们可以理解为一个类获取了另外一个类的方法和属性。
当一个类继承其它类时,继承类叫子类,被继承类叫超类(或父类)
在 Swift 中,类可以调用和访问超类的方法,属性和下标脚本,并且可以重写它们。
也可以为类中继承来的属性添加属性观察器。
1.基类
没有继承其它类的类,称之为基类(Base Class)。
以下实例中我们定义了基类 StudDetails ,描述了学生(stname)及其各科成绩的分数(mark1、mark2、mark3):

class StudDetails {
    var stname: String!
    var mark1: Int!
    var mark2: Int!
    var mark3: Int!
    init(stname: String, mark1: Int, mark2: Int, mark3: Int) {
        self.stname = stname
        self.mark1 = mark1
        self.mark2 = mark2
        self.mark3 = mark3
    }
}
let stname = "swift"
let mark1 = 98
let mark2 = 89
let mark3 = 76
let sds = StudDetails(stname:stname, mark1:mark1, mark2:mark2, mark3:mark3);
print(sds.stname);print(sds.mark1);print(sds.mark2);print(sds.mark3)

2.子类
子类指的是在一个已有类的基础上创建一个新的类。
为了指明某个类的超类,将超类名写在子类名的后面,用冒号(:)分隔,语法格式如下

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

3.重写(Overriding)
子类可以通过继承来的实例方法,类方法,实例属性,或下标脚本来实现自己的定制功能,我们把这种行为叫重写(overriding)。
我们可以使用 override 关键字来实现重写。

重写    访问方法,属性,下标脚本
方法    super.somemethod()
属性    super.someProperty()
下标脚本      super[someIndex]
  • 重写方法
    以下实例中我们重写了 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:
class Circle {
    var radius = 12.5
    var 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)")
// 重写属性观察器
class Square: Rectangle {
    override var radius: Double {
        didSet {
            print = Int(radius/5.0)+1
        }
    }
}
let sq = Square()
sq.radius = 100.0
print("半径: \(sq.area)")
以上程序执行输出结果为:
Radius 矩形半径 25.0  ,但现在被重写为 3
半径: 矩形半径为 100.0  ,但现在被重写为 21
  • 重写属性观察器
    你可以在属性重写中为一个继承来的属性添加属性观察器。这样一来,当继承来的属性值发生改变时,你就会监测到。
    注意:你不可以为继承来的常量存储型属性或继承来的只读计算型属性添加属性观察器。

4.防止重写
我们可以使用 final 关键字防止它们被重写。
如果你重写了final方法,属性或下标脚本,在编译时会报错。
你可以通过在关键字class前添加final特性(final class)来将整个类标记为 final 的,这样的类是不可被继承的,否则会报编译错误。

final class Circle {
    final var radius = 12.5
    var area: String {
        return "矩形半径为 \(radius) "
    }
}

构造过程

构造过程是为了使用某个类、结构体或枚举类型的实例而进行的准备过程。这个过程包含了为实例中的每个属性设置初始值和为其执行必要的准备和初始化任务。
Swift 构造函数使用 init() 方法。
与 Objective-C 中的构造器不同,Swift 的构造器无需返回值,它们的主要任务是保证新实例在第一次使用前完成正确的初始化。
类实例也可以通过定义析构器(deinitializer)在类实例释放之前执行清理内存的工作。

1.存储型属性的初始赋值
类和结构体在实例创建时,必须为所有存储型属性设置合适的初始值。
存储属性在构造器中赋值时,它们的值是被直接设置的,不会触发任何属性观测器。
存储属性在构造器中赋值流程:

  • 创建初始值。
  • 在属性定义中指定默认属性值。
  • 初始化实例,并调用 init() 方法。

2.构造器
构造器在创建某特定类型的新实例时调用。它的最简形式类似于一个不带任何参数的实例方法,以关键字init命名。

  • 语法
init()
{
    // 实例化后执行的代码
}

实例
以下结构体定义了一个不带参数的构造器 init,并在里面将存储型属性 length 和 breadth 的值初始化为 6 和 12:

struct rectangle {
    var length: Double
    var breadth: Double
    init() {
        length = 6
        breadth = 12
    }
}
var area = rectangle()
print("矩形面积为 \(area.length*area.breadth)")
以上程序执行输出结果为:
矩形面积为 72.0
  • 默认属性值
    我们可以在构造器中为存储型属性设置初始值;同样,也可以在属性声明时为其设置默认值。
    使用默认值能让你的构造器更简洁、更清晰,且能通过默认值自动推导出属性的类型。
    以下实例我们在属性声明时为其设置默认值:
struct rectangle {
    // 设置默认值
    var length = 6
    var breadth = 12
}
var area = rectangle()
print("矩形的面积为 \(area.length*area.breadth)")
  • 构造参数
    你可以在定义构造器 init() 时提供构造参数,如下所示:
struct Rectangle {
    var length: Double
    var breadth: Double
    var area: Double
    init(fromLength length: Double, fromBreadth breadth: Double) {
        self.length = length
        self.breadth = breadth
        area = length * breadth
    }
    init(fromLeng leng: Double, fromBread bread: Double) {
        self.length = leng
        self.breadth = bread
        area = 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 Rectangle {
    var 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)")
以上程序执行输出结果为:
面积为: 180.0
  • 可选属性类型
    如果你定制的类型包含一个逻辑上允许取值为空的存储型属性,你都需要将它定义为可选类型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
    }
}
let rectarea = Rectangle(180.0)
print("面积为:\(rectarea.length)")
以上程序执行输出结果为:
面积为:Optional(180.0)
  • 构造过程中修改常量属性
    只要在构造过程结束前常量的值能确定,你可以在构造过程中的任意时间点修改常量属性的值。
    对某个类实例来说,它的常量属性只能在定义它的类的构造过程中修改;不能在子类中修改。
    尽管 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)
  • 默认构造器
    默认构造器将简单的创建一个所有属性值都设置为默认值的实例;
    类中的所有属性都有默认值,且它是没有父类的基类,它将自动获得一个可以为所有属性设置默认值的默认构造器

  • 值类型的构造器代理
    构造器可以通过调用其它构造器来完成实例的部分构造过程。这一过程称为构造器代理,它能减少多个构造器间的代码重复。
    以下实例中,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 = origin
        self.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) 
  • 构造器代理规则

    • 值类型
      不支持继承,所以构造器代理的过程相对简单,因为它们只能代理给本身提供的其它构造器。 你可以使用self.init在自定义的构造器中引用其它的属于相同值类型的构造器。
    • 类类型
      它可以继承自其它类,这意味着类有责任保证其所有继承的存储型属性在构造时也能正确的初始化。
  • 类的继承和构造过程
    Swift 提供了两种类型的类构造器来确保所有类实例中存储型属性都能获得初始值,它们分别是指定构造器和便利构造器。

    • 指定构造器
      类中最主要的构造器
      初始化类中提供的所有属性,并根据父类链往上调用父类的构造器来实现父类的初始化。
      每一个类都必须拥有至少一个指定构造器
      Init(parameters) {
      statements
      }
    • 便利构造器
      类中比较次要的、辅助型的构造器
      可以定义便利构造器来调用同一个类中的指定构造器,并为其参数提供默认值。你也可以定义便利构造器来创建一个特殊用途或特定输入的实例。
      只在必要的时候为类提供便利构造器
      convenience init(parameters) {
      statements
      }
  • 构造器的继承和重载
    Swift 中的子类不会默认继承父类的构造器。
    父类的构造器仅在确定和安全的情况下被继承。
    当你重写一个父类指定构造器时,你需要写override修饰符。

class SuperClass {
    var corners = 4
    var 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 边
  • 类的可失败构造器
    如果一个类,结构体或枚举类型的对象,在构造自身的过程中有可能失败,则为其定义一个可失败构造器。
    变量初始化失败可能的原因有:
   * 传入无效的参数值。
   * 缺少某种所需的外部资源。
   * 没有满足特定条件。

为了妥善处理这种构造过程中可能会失败的情况。
你可以在一个类,结构体或是枚举类型的定义中,添加一个或多个可失败构造器。其语法为在init关键字后面加添问号(init?)。

下例中,定义了一个名为Animal的结构体,其中有一个名为species的,String类型的常量属性。
同时该结构体还定义了一个,带一个String类型参数species的,可失败构造器。这个可失败构造器,被用来检查传入的参数是否为一个空字符串,如果为空字符串,则该可失败构造器,构建对象失败,否则成功。

struct Animal {
    let species: String
    init?(species: String) {
        if species.isEmpty { return nil }
        self.species = species
    }
}
//通过该可失败构造器来构建一个Animal的对象,并检查其构建过程是否成功
// someCreature 的类型是 Animal? 而不是 Animal
let someCreature = Animal(species: "长颈鹿")
// 打印 "动物初始化为长颈鹿"
if let giraffe = someCreature {
    print("动物初始化为\(giraffe.species)")
}
以上程序执行输出结果为:
动物初始化为长颈鹿

类的可失败构造器
值类型(如结构体或枚举类型)的可失败构造器,对何时何地触发构造失败这个行为没有任何的限制。
但是,类的可失败构造器只能在所有的类属性被初始化后和所有类之间的构造器之间的代理调用发生完后触发失败行为。

覆盖一个可失败构造器
就如同其它构造器一样,你也可以用子类的可失败构造器覆盖基类的可失败构造器。
者你也可以用子类的非可失败构造器覆盖一个基类的可失败构造器。
你可以用一个非可失败构造器覆盖一个可失败构造器,但反过来却行不通。
一个非可失败的构造器永远也不能代理调用一个可失败构造器。

可失败构造器 init!
通常来说我们通过在init关键字后添加问号的方式(init?)来定义一个可失败构造器,但你也可以使用通过在init后面添加惊叹号的方式来定义一个可失败构造器(init!)。

析构过程

在一个类的实例被释放之前,析构函数被立即调用。用关键字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

可选链

可选链(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是非可选Int(Int?)时这一点也成立。只要是通过可选链的请求就意味着最后numberOfRooms总是返回一个Int?而不是Int。

  • 为可选链定义模型类
    你可以使用可选链来多层调用属性,方法,和下标脚本。这让你可以利用它们之间的复杂模型来获取更底层的属性,并检查是否可以成功获取此类底层属性。

  • 通过可选链调用方法
    你可以使用可选链的来调用可选值的方法并检查方法调用是否成功。即使这个方法没有返回值,你依然可以使用可选链来达成这一目的。

if ((john.residence?.printNumberOfRooms()) != nil) 
  • 使用可选链调用下标脚本
    你可以使用可选链来尝试从下标脚本获取值并检查下标脚本的调用是否成功,然而,你不能通过可选链来设置下标脚本。
if let firstRoomName = john.residence?[0].name
  • 通过可选链接调用来访问下标
    通过可选链接调用,我们可以用下标来对可选值进行读取或写入,并且判断下标调用是否成功。
if let firstRoomName = john.residence?[0].name
  • 连接多层链接
    你可以将多层可选链连接在一起,可以掘取模型内更下层的属性方法和下标脚本。然而多层可选链不能再添加比已经返回的可选值更多的层。
    如果你试图通过可选链获得Int值,不论使用了多少层链接返回的总是Int?。 相似的,如果你试图通过可选链获得Int?值,不论使用了多少层链接返回的总是Int?。
if let johnsStreet = john.residence?.address?.street
  • 对返回可选值的函数进行链接
    我们还可以通过可选链接来调用返回可空值的方法,并且可以继续对可选值进行链接。

类型转换

类型转换
Swift 语言类型转换可以判断实例的类型。也可以用于检测实例类型是否属于其父类或者子类的实例。
Swift 中类型转换使用 is 和 as 操作符实现,is 用于检测值的类型,as 用于转换类型。
类型转换也可以用来检查一个类是否实现了某个协议。

  • 检查类型
    类型转换用于检测实例类型是否属于特定的实例类型。
    你可以将它用在类和子类的层次结构上,检查特定类实例的类型并且转换这个类实例的类型成为这个层次结构中的其他类型。
    类型检查使用 is 关键字。
    操作符 is 来检查一个实例是否属于特定子类型。若实例属于那个子类型,类型检查操作符返回 true,否则返回 false。

  • 向下转型
    向下转型,用类型转换操作符(as? 或 as!)
    当你不确定向下转型可以成功时,用类型转换的条件形式(as?)。条件形式的类型转换总是返回一个可选值(optional value),并且若下转是不可能的,可选值将是 nil。
    只有你可以确定向下转型一定会成功时,才使用强制形式(as!)。当你试图向下转型为一个不正确的类型时,强制形式的类型转换会触发一个运行时错误。

  • Any和AnyObject的类型转换
    Swift为不确定类型提供了两种特殊类型别名:
    AnyObject可以代表任何class类型的实例。
    Any可以表示任何类型,包括方法类型(function types)。
    注意:
    只有当你明确的需要它的行为和功能时才使用Any和AnyObject。在你的代码里使用你期望的明确的类型总是更好的。

协议

指定了类和结构需要实现的方法和变量,optional关键字可以定义协议中选择实现的方法和变量,一个结构体或者类可以实现多个协议,值得注意的是协议之间是可以继承的。

  • 语法
协议的语法格式如下:
protocol SomeProtocol {
    // 协议内容
}
要使类遵循某个协议,需要在类型名称后加上协议名称,中间以冒号:分隔,作为类型定义的一部分。遵循多个协议时,各协议之间用逗号,分隔。
struct SomeStructure: FirstProtocol, AnotherProtocol {
    // 结构体内容
}
如果类在遵循协议的同时拥有父类,应该将父类名放在协议名之前,以逗号分隔。
class SomeClass: SomeSuperClass, FirstProtocol, AnotherProtocol {
    // 类的内容
}
  • 对属性的规定
    协议用于指定特定的实例属性或类属性,而不用指定是存储型属性或计算型属性。此外还必须指明是只读的还是可读可写的。
    协议中的通常用var来声明变量属性,在类型声明后加上{ set get }来表示属性是可读可写的,只读属性则用{ get }来表示。

  • 对 Mutating 方法的规定
    有时需要在方法中改变它的实例。
    例如,值类型(结构体,枚举)的实例方法中,将mutating关键字作为函数的前缀,写在func之前,表示可以在该方法中修改它所属的实例及其实例属性的值。

  • 对构造器的规定
    协议可以要求它的遵循者实现指定的构造器。
    你可以像书写普通的构造器那样,在协议的定义里写下构造器的声明,但不需要写花括号和构造器的实体,语法如下:

protocol SomeProtocol {
   init(someParameter: Int)
}

实例

protocol tcpprotocol {
   init(aprot: Int)
}
  • 在扩展中添加协议成员
    我们可以可以通过扩展来扩充已存在类型( 类,结构体,枚举等)。
    扩展可以为已存在的类型添加属性,方法,下标脚本,协议等成员。
protocol AgeClasificationProtocol {
   var age: Int { get }
   func agetype() -> String
}
class Person {
   let firstname: String
   let lastname: String
   var age: Int
   init(firstname: String, lastname: String) {
      self.firstname = firstname
      self.lastname = lastname
      self.age = 10
   }
}
extension Person : AgeClasificationProtocol {
   func fullname() -> String {
      var c: String
      c = firstname + " " + lastname
      return c
   }
   
   func agetype() -> String {
      switch age {
      case 0...2:
         return "Baby"
      case 2...12:
         return "Child"
      case 13...19:
         return "Teenager"
      case let x where x > 65:
         return "Elderly"
      default:
         return "Normal"
      }
   }
}
  • 协议的继承
    协议能够继承一个或多个其他协议,可以在继承的协议基础上增加新的内容要求。
    协议的继承语法与类的继承相似,多个被继承的协议间用逗号分隔:
protocol InheritingProtocol: SomeProtocol, AnotherProtocol {
    // 协议定义
}
  • 类专属协议
    你可以在协议的继承列表中,通过添加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: Int
    init(no1: Int, no2 : Int) {
        self.no2 = no2
        super.init(no1:no1)
    }
    // 因为遵循协议,需要加上"required"; 因为继承自父类,需要加上"override"
    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)")
  • 协议合成
    Swift 支持合成多个协议,这在我们需要同时遵循多个协议时非常有用。
    语法格式如下:
protocol Stname {
    var name: String { get }
}
protocol Stage {
    var age: Int { get }
}
struct Person: Stname, Stage {
    var name: String
    var age: Int
}
func show(celebrator: Stname & Stage) {
    print("\(celebrator.name) is \(celebrator.age) years old")
}
let studname = Person(name: "Priya", age: 21)
print(studname)
let stud = Person(name: "Rehan", age: 29)
print(stud)
let student = Person(name: "Roshan", age: 19)
print(student)
以上程序执行输出结果为:
Person(name: "Priya", age: 21)
Person(name: "Rehan", age: 29)
Person(name: "Roshan", age: 19)
  • 检验协议的一致性
    你可以使用is和as操作符来检查是否遵循某一协议或强制转化为某一类型。
    is操作符用来检查实例是否遵循了某个协议。
    as?返回一个可选值,当实例遵循协议时,返回该协议类型;否则返回nil。
    as用以强制向下转型,如果强转失败,会引起运行时错误。
    实例
protocol HasArea {
    var area: Double { get }
}
for object in objects {
    // 对迭代出的每一个元素进行检查,看它是否遵循了HasArea协议
    if let objectWithArea = object as? HasArea {
        print("面积为 \(objectWithArea.area)")
    } else {
        print("没有面积")
    }
}
  • 多协议
protocol AreaComputetionProtocol {
    func computeArea() -> Double
}
protocol PerimeterComputetionProtocol {
    func computePerimeter() -> Double
}
struct RectAngle: AreaComputetionProtocol, PerimeterComputetionProtocol {
    var width, height: Double

    internal func computeArea() -> Double {
        return width*height
    }

    internal func computePerimeter() -> Double {
        return 2*(width + height)
    }
}
let rect: RectAngle = RectAngle(width: 3.0, height: 4.0)
print(rect.computeArea())
print(rect.computePerimeter())

//协议继承
protocol TriangeleProtocol: AreaComputetionProtocol, PerimeterComputetionProtocol {
    var a: Double {get set}
    var b: Double {get set}
    var c: Double {get set}
    var base: Double {get set}
    var height: Double {get set}
}
struct Triangle: TriangeleProtocol {
    var a: Double = 0.0
    var b: Double = 0.0
    var c: Double = 0.0
    var base: Double = 0.0
    var height: Double = 0.0
    func computeArea() -> Double {
        return base*height/2.0
    }
    func computePerimeter() -> Double {
        return a + b + c
    }
}
let triangle: Triangle = Triangle(a: 3, b: 4, c: 5, base: 3, height: 4)
print(triangle.computeArea())
print(triangle.computePerimeter())

说到协议就不得不说委托,委托是为了让一个类或结构体能够将工作和决策交给另一个类或结构去完成。

//委托:让一个类或结构能够将工作和决策交给另一个类或结构去完成

/// 售货机协议
protocol VendingMathineProtocol {
    /// 是否投币
    var coinInserted: Bool {get set}

    /// 能否售货
    func shouldVend() -> Bool
}

/// 自动售货机类,遵守售货机协议
class Vendor: VendingMathineProtocol {
    var coinInserted: Bool = false

    /// 如果投币则售货否则不售货
    ///
    /// - Returns: 是否售货
    func shouldVend() -> Bool {
        if coinInserted {
            coinInserted = false
            return true
        }
        else
        {
            return false
        }
    }
}

/// 可乐机类
class ColaMethine {
    // 自动售货机类,遵守售货机协议
    var vendor: VendingMathineProtocol
    init(vendor: VendingMathineProtocol) {
        self.vendor = vendor
    }

    /// 投币
    func insertCoin() {
        vendor.coinInserted = true
    }

    /// 销售可乐按钮事件
    func pressColaButton() -> String {
        if vendor.shouldVend() {
            return "Here's a cola!"
        }
        else
        {
            return "You must insert coin!"
        }
    }
    /// 销售啤酒按钮事件
    func pressRootBeerButton() -> String {
        if vendor.shouldVend() {
            return "Here's a Root Beer!"
        }
        else
        {
            return "You must insert coin!"
        }
    }
}
let methine: ColaMethine = ColaMethine.init(vendor: Vendor.init())
print(methine.pressColaButton())
methine.insertCoin()
print(methine.pressColaButton())
methine.insertCoin()
print(methine.pressRootBeerButton())
print(methine.pressColaButton())

泛型

Swift 提供了泛型让你写出灵活且可重用的函数和类型。
Swift 标准库是通过泛型代码构建出来的。
Swift 的数组和字典类型都是泛型集。

不指定数据的类型,只是指定一个占位符。编译器负责对数据类型进行检查。使用方式如下:

func valueEqual<T: Equatable>(value1: T, value2: T) -> Bool {
    return value1 == value2
}

代码来看,它们功能代码是相同的,只是类型上不一样,这时我们可以使用泛型,从而避免重复编写代码。
泛型使用了占位类型名(在这里用字母 T 来表示)来代替实际类型名(例如 Int、String 或 Double)。

func swapTwoValues<T>(_ a: inout T, _ b: inout T)

swapTwoValues 后面跟着占位类型名(T),并用尖括号括起来(<T>)。这个尖括号告诉 Swift 那个 T 是 swapTwoValues(::) 函数定义内的一个占位类型名,因此 Swift 不会去查找名为 T 的实际类型。
以下实例是一个泛型函数 exchange 用来交换两个 Int 和 String 值:

// 定义一个交换两个变量的函数
func swapTwoValues<T>(_ 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 (栈)的泛型集合类型,栈只允许在集合的末端添加新的元素(称之为入栈),且也只能从末端移除元素(称之为出栈)。

  • 类型约束
    类型约束指定了一个必须继承自指定类的类型参数,或者遵循一个特定的协议或协议构成。
    类型约束语法
    你可以写一个在一个类型参数名后面的类型约束,通过冒号分割,来作为类型参数链的一部分。这种作用于泛型函数的类型约束的基础语法如下所示(和泛型类型的语法相同):

func someFunction<T: 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 Stack<Element>: Container {
    // Stack<Element> 的原始实现部分
    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 = Stack<String>()
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 Stack<Element>: Container {
    // Stack<Element> 的原始实现部分
    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 allItemsMatch<C1: 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 = Stack<String>()
tos.push("google")
tos.push("runoob")
tos.push("taobao")
var aos = ["google", "runoob", "taobao"]
if allItemsMatch(tos, aos) {
    print("匹配所有元素")
} else {
    print("元素不匹配")
}
以上程序执行输出结果为:
匹配所有元素
  • 运算符重载:只需定义一个运算符命名的函数就可以对运算符进行重载。

访问控制

  • public 可以访问自己模块中源文件里的任何实体,别人也可以通过引入该模块来访问源文件里的所有实体。
  • internal 可以访问自己模块中源文件里的任何实体,但是别人不能访问该模块中源文件里的实体。
  • fileprivate 文件内私有,只能在当前源文件中使用。
  • private 只能在类中访问,离开了这个类或者结构体的作用域外面就无法访问。

1.public 为最高级访问级别,private 为最低级访问级别。
2.元组的访问级别与元组中访问级别最低的类型一致
3.枚举中成员的访问级别继承自该枚举,你不能为枚举中的成员单独申明不同的访问级别。
4.函数的访问级别需要根据该函数的参数类型和返回类型的访问级别得出。
5.子类的访问级别不得高于父类的访问级别。比如说,父类的访问级别是internal,子类的访问级别就不能申明为public。
6.常量、变量、属性不能拥有比它们的类型更高的访问级别。
比如说,你定义一个public级别的属性,但是它的类型是private级别的,这是编译器所不允许的。
同样,下标也不能拥有比索引类型或返回类型更高的访问级别。
如果常量、变量、属性、下标索引的定义类型是private级别的,那么它们必须要明确的申明访问级别为private:
7.如果想为一个协议明确的申明访问级别,那么需要注意一点,就是你要确保该协议只在你申明的访问级别作用域中使用。
如果你定义了一个public访问级别的协议,那么实现该协议提供的必要函数也会是public的访问级别。这一点不同于其他类型,比如,public访问级别的其他类型,他们成员的访问级别为internal。
8.任何你定义的类型别名都会被当作不同的类型,以便于进行访问控制。一个类型别名的访问级别不可高于原类型的访问级别。
9.常量、变量、属性、下标索引的Getters和Setters的访问级别继承自它们所属成员的访问级别。

扩展

不继承而扩展类的功能,扩展能够以非侵入式的方式,增加类、结构、甚至基本类型的行为和功能,类似于Objective_C的类别,但是强大得多。基本使用如下:
扩展就是向一个已有的类、结构体或枚举类型添加新功能。
扩展可以对一个类型添加新的功能,但是不能重写已有的功能。

// MARK: - 可乐机的扩展

extension ColaMethine {
    /// 健怡可乐按钮事件
    func pressDietColaButton() -> String {
        if vendor.shouldVend() {
            return "Here's a Diet Cola!"
        }
        else
        {
            return "You must insert coin!"
        }
    }
}
methine.insertCoin()
print(methine.pressDietColaButton())

1.扩展基本数据类型:不能在扩展中添加常规存储属性,但可以添加计算属性:值是通过计算获得的属性

let limit: Double = 1024.0
// MARK: - 为Int64扩展属性,获取存储单位的换算结果
extension Int64 {
    var K: String {return String.init(format: "%fK", Double(self)/limit)}
    var M: String {return String.init(format: "%fM", Double(self)/limit/limit)}
    var G: String {return String.init(format: "%fG", Double(self)/limit/limit/limit)}
    var T: String {return String.init(format: "%fT", Double(self)/limit/limit/limit/limit)}
}
let bytes: Int64 = 2345346457
print(bytes.K)
print(bytes.M)
print(bytes.G)
print(bytes.T)

2.mutating关键字:如果要修改自身的值而不是返回计算的结果,那么就得使用mutating关键字

// MARK: - 为Double数据类型扩展方法
extension Double {
    /// 平方值
    mutating func squra() {
        self = self*self
    }

    /// 立方值
    mutating func cube() {
        self = self*self*self
    }
}
var num: Double = 1.5
num.squra()
print(num)
num.cube()
print(num)

相等与相同

相等是指值相等,相同是指指向的对象为同一个对象。使用==来判断值的相等,用===来判断是否为同一个对象。

let a: Int = 1
let b: Int = 1
class IntClass {
    var value: Int = 0
    init(value: Int) {
        self.value = value
    }
}
let intA: IntClass = IntClass.init(value: 1)
let intB: IntClass = IntClass.init(value: 1)
print(a == b)
print(a != b)
print(intA === intB)
print(intA !== intB)

Swift内存管理

Swift以ARC方式为引用类型数据管理内存,这种方式是由编译器提供支持的。每一个引用类型的数据都有一个引用计数的的属性,当这个对象被创建的时候它的引用计数就为1,当这个对象在应用程序运行过程中被传递,就可能被多个“所有者”持有并使用。当“所有者”获得所有权时引用计算就相应加1,而在放弃所有权时引用计数就相应减1,直到引用计数为0时(所有的“所有者”都放弃了所有权),对象就会被销毁,它所占用的内存归还给内存池,供其他对象使用。

1.循环引用:循环引用就是指不同对象之间相互持有对方,这样造成了对象永远被“所有者”所占有,而无法释放,从而形成内存泄漏。

  • 普通对象之间的循环引用:这种循环引用可以使用weak关键字来打破。
  • 闭包内的循环引用:闭包是引用类型的数据,类的实例变量强引用了闭包,而如果闭包中再引用了类对象的话,就会造成循环引用。在闭包定义中添加"[unowned self] in"可以解决闭包内的循环引用。

Swift 提供了两种办法用来解决你在使用类的属性时所遇到的循环强引用问题:
弱引用(weak)
无主引用(unowned)
弱引用和无主引用允许循环引用中的一个实例引用另外一个实例而不保持强引用。这样实例能够互相引用而不产生循环强引用。
对于生命周期中会变为nil的实例使用弱引用。相反的,对于初始化赋值后再也不会被赋值为nil的实例,使用无主引用。

说明:一个对象在销毁的时候会调用deinit,而一个变量赋值为nil时就会放弃对原有对象的持有。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,968评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,601评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,220评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,416评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,425评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,144评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,432评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,088评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,586评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,028评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,137评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,783评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,343评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,333评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,559评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,595评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,901评论 2 345

推荐阅读更多精彩内容