Swift基础学习——难点和易忘的知识点总结

用来记录学习Swift中,与其他语法(java、kotlin)的差异以及难点和易忘点总结。

1.运算符

空合并运算符

var q:Int?=8
var value:Int
value = q ?? 0

如果q为nil,value = 0,否则,value = q。相当于

var q:Int?=8
var value:Int
if q==nil{
	value = q
}else{
	value = 0
}

区间运算

//range表示范围[0,10]区间
var range1 = 0...10

//range2表示范围[0,10)区间
var range2 = 0..<10

//8是否在range 区间,输出rue
print(range1 ~= 8)
# 2.


2.元组

元组允许一些并不相关的类型进行自由组合成为新的集合类型。

	//指定元组中的参数名称
	let student : (name:String ,age:Int ,grade:Double) = ("zhangsan",23,98.3)
	print(student.name)
	//不指定元组中的参数名称,使用下标调用
	let student : (String ,Int ,Double, Int) = ("zhangsan",23,98.3)
	print(student.0)
	//进行元组分解
	let  (Strname, age, Strscore, stuNumber) = student
	print(stuNumber)
	

3.集合类型

Array数组

var array1:[Int] = []
var array2:Array<Int> = Array()

Set集合类型

var set:Set<Int> = []

Dictionary字典类型

var lookup: [String: Int] = [:]

4.字符串

//字符串分割数组 -- 基于空格
let fullName = "First Last"
let fullNameArr = fullName.characters.split{$0 == " "}.map(String.init)
// or simply:// let fullNameArr = fullName.characters.split{" "}.map(String.init)
fullNameArr[0] // First
fullNameArr[1] // Last

5.循环

//不包含10
for i in stride(from: 0, to: 10 ,by: 2) {
	print(i)
}

//包含10
for i in stride(from: 0, through: 10, by: 2) {
	print(i)
}

6.switch-case分支选择

//同一个case中可以包含多分支
switch charac {
case "a","b","c":
	print("chara is word")
case "1","2":
	print("chara is num")
}
//在case中可以使用一个范围
var num = 3
switch num {
case 1...3:
	print("1<=num<=3")
case 4:
	print("chara is num")
}
//使用switch语句进行元组的匹配
var tuple= (0,0)
switch tuple{
//进行完全匹配
case (0,1):
	print("sure")
//进行选择性匹配
case (_,1):
	print("sim")
//进行元组元素的范围匹配
case (0...3,0...3):
	print("SIM")
}
var tuple= (0,0)
switch tuple{
//对元组中的第一个元素进行捕获
case (let a,1):
	print(a)
//捕获元组中的两个元素,相当于(let a,let b)
case let(a,b):
	print(a,b)
//当元组中的两个元素都等于0时候才匹配成功,并且捕获第一个元素的值
case let(b,0) where b ==0:
	print(b)
//当元组中的两个元素相同时,才会进入下面的case
case let(a,b):
	print(a,b)
}
var index = 10
switch index {
   case 100  :
      print( "index 的值为 100")
      fallthrough
   case 10,15  :
      print( "index 的值为 10 或 15")
      fallthrough
   case 5  :
      print( "index 的值为 5")
   default :
      print( "默认 case")
}

结果:

index 的值为 1015
index 的值为 5

“_”符号表示匿名参数

注意:在大多数语言中,switch 语句块中,case 要紧跟 break,否则 case 之后的语句会顺序运行,而在 Swift 语言中,默认是不会执行下去的,switch 也会终止。如果你想在 Swift 中让 case 之后的语句会按顺序继续运行,则需要使用 fallthrough 语句。

7.闭包

标准函数

func div(val1: Int, val2: Int) -> Int{
	return val1/val2
}

let result = div(200, 20)
print (result)

闭包实现

let result = {(val1: Int, val2: Int) -> Int in 
   return val1 / val2 
}
print (result(200, 20))

闭包表达式

func mySort(array:inout Array<Any> , sortClosure:(Int,Int)->Bool) -> Array<Any>{
	//冒泡排序算法
	for indexI in array.indices{
		//最后一个元素直接返回
		if indexI == array.count-1{
		break;
		}
		//冒泡排序
		for indexJ in 0...((array.count - 1)-indexI - 1){
			//调用传递进来的闭包算法
			if sortClosure(indexJ,indexJ+1){
			}else{
				//进行元素交换
				swap(&array[indexJ],&array[indexJ+1])
			}
		}
	}
	return array
}

var arr:Array<Any> = [1,4,3,8,7,9]
mySort(array:&arr,sortClosure:{(index:Int,nextIndex:Int)->Bool in
	return (arr[index] as! Int)>(arr[nextIndex]) as! Int
})
print(arr)

写法优化:

  • 省略返回值类型
  • 省略return
  • 参数名称缩写
var arr:Array<Any> = [1,4,3,8,7,9]
mySort(array:&arr,sortClosure:{
	//$0和$1表示闭包中第一个和第二个String类型的参数。
	(arr[$0] as! Int)>(arr[$1]) as! Int
})
print(arr)

后置闭包(尾随闭包)

当函数中的最后一个参数为闭包参数时,可以将闭包放出参数列表,追加在函数尾部

var arr:Array<Any> = [1,4,3,8,7,9]
mySort(array:&arr){
	//$0和$1表示闭包中第一个和第二个String类型的参数。
	(arr[$0] as! Int)>(arr[$1]) as! Int
}
print(arr)

如果函数只需要闭包表达式一个参数,当您使用尾随闭包时,您甚至可以把()省略掉。

let names = ["AT", "AE", "D", "S", "BE"]

var reversed = names.sorted(by: (s1: String, s2: String) -> Bool {
    return s1 > s2
})
var reversed = names.sorted { $0 > $1 }

8.溢出运算符

  • 溢出加法 &+
  • 溢出减法 &-
  • 溢出乘法 &*
  • 溢出除法 &/
  • 溢出求余 &%
var willOverflow = UInt8.max
// willOverflow 等于UInt8的最大整数 255
willOverflow = willOverflow &+ 1
// 这时候 willOverflow 等于 0
var willUnderflow = UInt8.min
// willUnderflow 等于UInt8的最小值0
willUnderflow = willUnderflow &- 1
// 此时 willUnderflow 等于 255
var willUnderflow = UInt8.min
// willUnderflow 等于UInt8的最小值0
willUnderflow = willUnderflow &* 2
// 此时 willUnderflow 等于 254

个数除于0 i / 0,或者对0求余数 i % 0,就会产生一个错误。
使用它们对应的可溢出的版本的运算符&/和&%进行除0操作时就会得到0值。

    let x = 1
    let y = x &/ 0
    // y 等于 0

提示:对二进制数据进行乘2运算,实质就是对二进制数据左移一位的运算。例如,二进制数据 111111112=11111110*

9.枚举

// 定义枚举
enum DaysofaWeek {
    case Sunday
    case Monday
    case TUESDAY
    case WEDNESDAY
    case THURSDAY
    case FRIDAY
    case Saturday
}

var weekDay = DaysofaWeek.THURSDAY
weekDay = .THURSDAY
switch weekDay
{
case .Sunday:
    print("星期天")
case .Monday:
    print("星期一")
case .TUESDAY:
    print("星期二")
case .WEDNESDAY:
    print("星期三")
case .THURSDAY:
    print("星期四")
case .FRIDAY:
    print("星期五")
case .Saturday:
    print("星期六")
}

注意:Swift 的枚举成员在被创建时不会被赋予一个默认的整型值。在上面的DaysofaWeek例子中,Sunday,Monday,……和Saturday不会隐式地赋值为0,1,……和6。相反,这些枚举成员本身就有完备的值,这些值是已经明确定义好的DaysofaWeek类型。

原始值 相关值
相同数据类型 不同数据类型
实例: enum {10,35,50} 实例: enum {10,0.8,“Hello”}
预先填充的值 值的创建基于常量或变量
原始值始终是相同的 相关值是当你在创建一个基于枚举成员的新常量或变量时才会被设置,并且每次当你这么做得时候,它的值可以是不同的。

原始值

原始值可以是字符串,字符,或者任何整型值或浮点型值。每个原始值在它的枚举声明中必须是唯一的。
在原始值为整数的枚举时,不需要显式的为每一个成员赋值,Swift会自动为你赋值。
例如,当使用整数作为原始值时,隐式赋值的值依次递增1。如果第一个值没有被赋初值,将会被自动置为0。

import Cocoa

enum Month: Int {
    case January = 1, February, March, April, May, June, July, August, September, October, November, December
}

let yearMonth = Month.May.rawValue
print("数字月份为: \(yearMonth)。")

相关值

以下实例中我们定义一个名为 Student 的枚举类型,它可以是 Name 的一个字符串(String),或者是 Mark 的一个相关值(Int,Int,Int)。

import Cocoa

enum Student{
    case Name(String)
    case Mark(Int,Int,Int)
}
var studDetails = Student.Name("Runoob")
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)。")
}

10.结构体

我们可以为结构体定义属性(常量、变量)和添加方法,从而扩展结构体的功能。
结构体总是通过被复制的方式在代码中传递,因此它的值是不可修改的。

按照通用的准则,当符合一条或多条以下条件时,请考虑构建结构体:

  • 结构体的主要目的是用来封装少量相关简单数据值。
  • 有理由预计一个结构体实例在赋值或传递时,封装的数据将会被拷贝而不是被引用。
  • 任何在结构体中储存的值类型属性,也将会被拷贝,而不是被引用。
  • 结构体不需要去继承另一个已存在类型的属性或者行为。

11.属性

属性可分为存储属性和计算属性:

存储属性 计算属性
存储常量或变量作为实例的一部分 计算(而不是存储)一个值
用于类和结构体 用于类、结构体和枚举

存储属性

简单来说,一个存储属性就是存储在特定类或结构体的实例里的一个常量或变量。
存储属性可以是变量存储属性(用关键字var定义),也可以是常量存储属性(用关键字let定义)。

计算属性

除存储属性外,类、结构体和枚举可以定义计算属性,计算属性不直接存储值,而是提供一个 getter 来获取值,一个可选的 setter 来间接设置其他属性或变量的值。
举个例子,创建一个圆形类。类中定义圆心与半径两个存储属性。为了方便使用,可以将圆的周长与面积也定于为属性,但圆的周长和面积可以通过半径求得。所以可以将周长和面积定义为计算属性。

class Circle{
	var r:Double
	var center:(Double,Double)
	//周长的计算属性
	var l:Double{
		get{
		return 2*r*M_PI
		}
		set(value){
			r = value/2/M_PI
		}
	}
	//面积的计算属性
	var s:Double{
		get{
		return r*r*M_PI
		}
		//计算属性的setter没有定义表示新值的参数名,则可以使用默认名称newValue。 
		set{
			r = sqrt(newValue/M_PI)
		}
	}
	//构造方法
	init(r:Double,center(Double,Double)){
		self.r = r
		self.center = center
	}
	//创建圆类实例
	var circle = Circle(r:3,center(3,3))
	//通过计算属性获取周长与面积
	print("周长是:\(cicle.l);面积是:\(cicle.s)")
	//通过周长来设置圆的半径
	circle.l = 12*M_PI
	print(当周长是12*M_PI时,半径为\(circle.r))
	//通过面积来设置圆的半径
	circle.s = 25*M_PI
	print(当面积是25*M_PI时,半径为\(circle.r))
}

只读计算属性

只有 getter 没有 setter 的计算属性就是只读计算属性。
只读计算属性总是返回一个值,可以通过点(.)运算符访问,但不能设置新的值。

属性观察器

属性观察器监控和响应属性值的变化,每次属性被设置值的时候都会调用属性观察器,甚至新的值和现在的值相同的时候也不例外。
可以为除了延迟存储属性之外的其他存储属性添加属性观察器,也可以通过重载属性的方式为继承的属性(包括存储属性和计算属性)添加属性观察器。

注意:不需要为无法重载的计算属性添加属性观察器,因为可以通过 setter 直接监控和响应值的变化。

可以为属性添加如下的一个或全部观察器:

  • willSet在设置新的值之前调用
  • didSet在新的值被设置之后立即调用
  • willSet和didSet观察器在属性初始化过程中不会被调用
class Samplepgm {
    var counter: Int = 0{
        willSet(newTotal){
            print("计数器: \(newTotal)")
        }
        didSet{
            if counter > oldValue {
                print("新增数 \(counter - oldValue)")
            }
        }
    }
}

在willSet的实现代码中可以为这个参数指定一个名称,默认名称newValue。类似地,didSet观察器会将旧的属性值作为参数传入,可以为该参数命名或者使用默认参数名oldValue。

类属性

类属性使用static或者class关键字来声明,使用static关键字声明的属性也称为静态属性。如果允许子类对其计算属性进行覆写,则需要用class关键字来声明。

12.函数

函数参数都有一个外部参数名和一个局部参数名。

局部参数名

局部参数名在函数的实现内部使用。

外部参数名

你可以在局部参数名前指定外部参数名,中间以空格分隔,外部参数名用于在函数调用时传递给函数的参数。

func runoob(name: String, site: String) -> String {
    return name + site
    }
print(runoob(name: "菜鸟教程:", site: "www.runoob.com"))
print(runoob(name: "Google:", site: "www.google.com"))

结果为:

菜鸟教程:www.runoob.com
Google:www.google.com

如果只有参数的内部名称,没有外部名称,编译器会自动为参数创建一个和内部名称相同的外部名称

func pow(firstArg a: Int, secondArg b: Int) -> Int {
   var res = a
   for _ in 1..<b {
      res = res * a
   }
   print(res)
   return res}
pow(firstArg:5, secondArg:3)

注意
如果你提供了外部参数名,那么函数在被调用时,必须使用外部参数名。

在实例方法中修改值类型

Swift 语言中结构体和枚举是值类型。一般情况下,值类型的属性不能在它的实例方法中被修改。
但是,如果你确实需要在某个具体的方法中修改结构体或者枚举的属性,你可以选择变异(mutating)这个方法,然后方法就可以从方法内部改变它的属性;并且它做的任何改变在方法结束时还会保留在原始结构中。
方法还可以给它隐含的self属性赋值一个全新的实例,这个新实例在方法结束后将替换原来的实例。

import Cocoa

struct area {
    var length = 1
    var breadth = 1
    
    func area() -> Int {
        return length * breadth
    }
    
    mutating func scaleBy(res: Int) {
        length *= res
        breadth *= res
        
        print(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 赋值

import Cocoa

struct area {
    var length = 1
    var breadth = 1
    
    func area() -> Int {
        return length * breadth
    }
    
    mutating func scaleBy(res: Int) {
        self.length *= res
        self.breadth *= res
        print(length)
        print(breadth)
    }
}
var val = area(length: 3, breadth: 5)
val.scaleBy(res: 13)

给它隐含的self属性赋值一个全新的实例,这个新实例在方法结束后将替换原来的实例。

类方法

类方法使用static或者class关键字来声明,使用static关键字声明的类方法也称为静态方法,不能被子类覆写。用class关键字来声明的类方法可以被子类覆写。

元组作为函数返回值

import Cocoa

func minMax(array: [Int]) -> (min: Int, max: Int)? {
    if array.isEmpty { return nil }
    var currentMin = array[0]
    var currentMax = array[0]
    for value in array[1..<array.count] {
        if value < currentMin {
            currentMin = value
        } else if value > currentMax {
            currentMax = value
        }
    }
    return (currentMin, currentMax)}if let bounds = minMax(array: [8, -6, 2, 109, 3, 71]) {
print("最小值为 \(bounds.min),最大值为 \(bounds.max)")
}

注意
可选元组类型如(Int, Int)?与元组包含可选类型如(Int?, Int?)是不同的.可选的元组类型,整个元组是可选的,而不只是元组中的每个元素值。

常量,变量及 I/O 参数

一般默认在函数中定义的参数都是常量参数,也就是这个参数你只可以查询使用,不能改变它的值。
如果想要声明一个变量参数,可以在参数定义前加 inout 关键字,这样就可以改变这个参数的值了。

import Cocoa
func swapTwoInts(_ a: inout Int, _ b: inout Int) {
    let temporaryA = a
    a = b
    b = temporaryA}
var x = 1var y = 5
swapTwoInts(&x, &y)print("x 现在的值 \(x), y 现在的值 \(y)")

一般默认的参数传递都是传值调用的,而不是传引用。所以传入的参数在函数内改变,并不影响原来的那个参数。传入的只是这个参数的副本。
当传入的参数作为输入输出参数时,需要在参数名前加 & 符,表示这个值可以被函数修改。

函数类型及使用

import Cocoa

func sum(a: Int, b: Int) -> Int {
   return a + b
}
var addition: (Int, Int) -> Int = sum
print("输出结果: \(addition(40, 89))")

解析:
"定义一个叫做 addition 的变量,参数与返回值类型均是 Int ,并让这个新变量指向 sum 函数"。
sum 和 addition 有同样的类型,所以以上操作是合法的。

函数类型作为参数类型、函数类型作为返回类型

我们可以将函数作为参数传递给另外一个参数:

import Cocoa

func sum(a: Int, b: Int) -> Int {
    return a + b
}
var addition: (Int, Int) -> Int = sum
print("输出结果: \(addition(40, 89))")

func another(addition: (Int, Int) -> Int, a: Int, b: Int) {
    print("输出结果: \(addition(a, b))")
}
another(addition: sum, a: 10, b: 20)

以上程序执行输出结果为:

输出结果: 129
输出结果: 30

函数嵌套

函数嵌套指的是函数内定义一个新的函数,外部的函数可以调用函数内定义的函数。

import Cocoa

func calcDecrement(forDecrement total: Int) -> () -> Int {
   var overallDecrement = 0
   func decrementer() -> Int {
      overallDecrement -= total
      return overallDecrement
   }
   return decrementer
}
let decrem = calcDecrement(forDecrement: 30)
print(decrem())

calcDecrement函数返回的是一个() -> Int函数

以上程序执行输出结果为:

-30

下标方法

下标脚本允许你通过在实例后面的方括号中传入一个或者多个的索引值来对实例进行访问和赋值。
语法类似于实例方法和计算型属性的混合。
与定义实例方法类似,定义下标脚本使用subscript关键字,显式声明入参(一个或多个)和返回类型。
与实例方法不同的是下标脚本可以设定为读写或只读。这种方式又有点像计算型属性的getter和setter:

import Cocoa

class daysofaweek {
    private var days = ["Sunday", "Monday", "Tuesday", "Wednesday",
        "Thursday", "Friday", "saturday"]
    subscript(index: Int) -> String {
        get {
            return days[index]   // 声明下标脚本的值
        }
        set {
        	//默认外界设置的值会以newValue为名称传入,可以自定义
            self.days[index] = newValue   // 执行赋值操作
        }
    }
}
var p = daysofaweek()

print(p[0])
print(p[1])
print(p[2])
print(p[3])

以上程序执行输出结果为:

Sunday
Monday
Tuesday
Wednesday

13.重写(Overriding)

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

访问超类的方法、属性及下标脚本

你可以通过使用super前缀来访问超类的方法,属性或下标脚本。

重写 访问方法,属性,下标脚本
方法 super.somemethod()
属性 super.someProperty()
下标脚本 super[someIndex]

防止重写

我们可以使用 final 关键字防止它们被重写。
如果你重写了final方法,属性或下标脚本,在编译时会报错。

14.构造方法

Swift要求结构体和类必须构造方法结束前完成其中存储属性的构造(延时存储属性除外)。因此,开发者在设计类的时,往往采用两种方式来处理存储属性:

  • 在类和结构体中声明存储属性时直接为其设置初始默认值。
  • 在类和结构体的构造方法中对存储属性进行构造或者设置默认值。

构造过程中修改常量属性

只要在构造过程结束前常量的值能确定,你可以在构造过程中的任意时间点修改常量属性的值。
对某个类实例来说,它的常量属性只能在定义它的类的构造过程中修改;不能在子类中修改。

struct Rectangle {
    let length: Double?
    
    init(_ area: Double) {
        length = area
    }
}

let rectarea = Rectangle(180.0)
print("面积为:\(rectarea.length)")

let rearea = Rectangle(370.0)
print("面积为:\(rearea.length)")

let recarea = Rectangle(110.0)
print("面积为:\(recarea.length)")

以上程序执行输出结果为:

面积为:Optional(180.0)
面积为:Optional(370.0)
面积为:Optional(110.0)

结构体默认构造方法

和类不同的是,对于结构体来说,可以不实现其构造方法,编译器会默认会生成一个构造方法,将所有属性作为参数

struct Rectangle {
    var length = 100.0, breadth = 200.0
}
let area = Rectangle(length: 24.0, breadth: 32.0)

print("矩形的面积: \(area.length)")
print("矩形的面积: \(area.breadth)")

以上程序执行输出结果为:

矩形的面积: 24.0
矩形的面积: 32.0

指定构造方法和便利构造方法

  • 子类的指定构造方法中必须调用父类的指定构造方法
  • 便利构造方法中必须调用当前类的其他构造方法
  • 便利构造方法归根结底要调用到某个指定构造方法
  • convenience的初始化方法不能被子类重写或者是从子类中以super的方式被调用
指定构造器 便利构造器
类中最主要的构造器 类中比较次要的、辅助型的构造器
初始化类中提供的所有属性,并根据父类链往上调用父类的构造器来实现父类的初始化。 可以定义便利构造器来调用同一个类中的指定构造器,并为其参数提供默认值。你也可以定义便利构造器来创建一个特殊用途或特定输入的实例。
每一个类都必须拥有至少一个指定构造器 只在必要的时候为类提供便利构造器
Init(parameters) {} convenience init(parameters) {}
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)
    }
    // 便利方法只需要一个参数
    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

构造器的继承和重载

  • 在继承关系中,如果子类没有覆写或者重写任何指定构造方法,则默认子类会继承父类所有的指定构造方法。
  • 如果子类中提供了父类所有的指定构造方法(无论是通过继承方式还是覆写方式),则子类会默认继承父类的便利构造方法。

可失败构造器

如果一个类,结构体或枚举类型的对象,在构造自身的过程中有可能失败,则为其定义一个可失败构造器。
变量初始化失败可能的原因有:

  • 传入无效的参数值。
  • 缺少某种所需的外部资源。
  • 没有满足特定条件。

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

可以用子类的可失败构造器覆盖基类的可失败构造器。或者可以用子类的非可失败构造器覆盖一个基类的可失败构造器。不能用可失败构造器覆盖一个非可失败构造器。

struct Animal {
    let species: String
    init?(species: String) {
        if species.isEmpty { return nil }
        self.species = species
    }
}

必要构造方法

如果一个类中的某些构造方法被指定为必要构造方法。则其子类必须实现这个构造方法(可以通过继承或者覆写的方式),必要构造方法使用required关键字进行修饰

struct Animal {
    let species: String
    required init(species: String) {
        self.species = species
    }
}

15.析构过程

在一个类的实例被释放之前,析构函数被立即调用。用关键字deinit来标示析构函数,类似于初始化函数用init来标示。

class Temp{
	deinit{
	print("Temp实例被销毁")
	}
}
var temp:Temp? = Temp()
//当可选类型的类实例变量被赋予nil时,实例会被释放
temp=nil

16.自动引用计数(ARC)

Swift 使用自动引用计数(ARC)这一机制来跟踪和管理应用程序的内存。
一个实例只要有其他量对其进行引用,它的引用计数就会加1,当一个量值解除对它的引用时,它的计数就会减1。而对于这个实例所对应的内存数据,当引用计数变成0时,它就会被销毁释放。
通常情况下我们不需要去手动释放内存,因为 ARC 会在类的实例不再被使用时,自动释放其占用的内存。但在有些时候我们还是需要在代码中实现内存管理。

循环引用

class Person {
    let name: String
    init(name: String) { self.name = name }
    var apartment: Apartment?
    deinit { print("\(name) 被析构") }
}

class Apartment {
    let number: Int
    init(number: Int) { self.number = number }
    var tenant: Person?
    deinit { print("Apartment #\(number) 被析构") }
}

// 两个变量都被初始化为nil
var runoob: Person?
var number73: Apartment?

// 赋值
runoob = Person(name: "Runoob")
number73 = Apartment(number: 73)

// 意感叹号是用来展开和访问可选变量 runoob 和 number73 中的实例
// 循环强引用被创建
runoob!.apartment = number73
number73!.tenant = runoob

// 断开 runoob 和 number73 变量所持有的强引用时,引用计数并不会降为 0,实例也不会被 ARC 销毁
// 注意,当你把这两个变量设为nil时,没有任何一个析构函数被调用。
// 强引用循环阻止了Person和Apartment类实例的销毁,并在你的应用程序中造成了内存泄漏
runoob = nil
number73 = nil

解决实例之间的循环强引用
Swift 提供了两种办法用来解决你在使用类的属性时所遇到的循环强引用问题:

  • 弱引用 weak
  • 无主引用 unowned

弱引用

使用weak关键字修饰的引用类型数据在传递时不会使引用计数加1。

class Module {
    let name: String
    init(name: String) { self.name = name }
    var sub: SubModule?
    deinit { print("\(name) 主模块") }
}

class SubModule {
    let number: Int
    
    init(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 = toc

toc = nil
list = nil

无主引用

使用unowned关键字修饰的引用类型数据在传递时同样不会使引用计数加1。
但是弱引用只能修饰Optional类型的属性,被弱引用的实例释放后,这个属性会被自动设置为nil。而unowned可以修饰非Optional类型的属性。

class Student {
    let name: String
    var section: Marks?
    
    init(name: String) {
        self.name = name
    }
    
    deinit { print("\(name)") }
}
class Marks {
    let marks: Int
    unowned let stname: Student
    
    init(marks: Int, stname: Student) {
        self.marks = marks
        self.stname = stname
    }
    
    deinit { print("学生的分数为 \(marks)") }
}

var module: Student?
module = Student(name: "ARC")
module!.section = Marks(marks: 98, stname: module!)
module = nil

***区别:***无主引用与弱引用最大的区别在于,无主引用总是假定属性是不为nil的,如果属性引用的实例被销毁释放了,再次使用这个实例程序会直接崩溃。弱引用则允许属性为nil,如果属性引用的实例被销毁释放 了,此属性会当做nil处理,不会崩溃。

闭包中的循环引用

在闭包中使用self关键字,会对当前类实例本身进行引用计数加1,而此闭包又是当前类的属性,闭包无法销毁,则当前类实例也无法销毁。

class HTMLElement {
    
    let name: String
    let 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 = name
        self.text = text
    }
    
    deinit {
        print("\(name) is being deinitialized")
    }
    
}

// 创建实例并打印信息
var paragraph: HTMLElement? = HTMLElement(name: "p", text: "hello, world")
print(paragraph!.asHTML())

当闭包和捕获的实例总是互相引用时并且总是同时销毁时,将闭包内的捕获定义为无主引用。
相反的,当捕获引用有时可能会是nil时,将闭包内的捕获定义为弱引用。
如果捕获的引用绝对不会置为nil,应该用无主引用,而不是弱引用。

class HTMLElement {
    
    let name: String
    let text: String?
    
    lazy var asHTML: () -> String = {
        [unowned self] in
        if let text = self.text {
            return "<\(self.name)>\(text)</\(self.name)>"
        } else {
            return "<\(self.name) />"
        }
    }
    
    init(name: String, text: String? = nil) {
        self.name = name
        self.text = text
    }
    
    deinit {
        print("\(name) 被析构")
    }
    
}

//创建并打印HTMLElement实例
var paragraph: HTMLElement? = HTMLElement(name: "p", text: "hello, world")
print(paragraph!.asHTML())

// HTMLElement实例将会被销毁,并能看到它的析构函数打印出的消息
paragraph = nil

17.异常

在Swift语言中,所有的错误和异常都是有Error协议来指定。
可以通过throw抛出异常;
或者使用do-catch结构来捕获处理异常。

18.扩展

扩展就是向一个已有的类、结构体或枚举类型添加新功能。
扩展可以对一个类型添加新的功能,但是不能重写已有的功能。

Swift 中的扩展可以:

  • 添加计算型属性和计算型静态属性
  • 定义实例方法和类型方法
  • 提供新的构造器
  • 定义下标
  • 定义和使用新的嵌套类型
  • 使一个已有类型符合某个协议
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

19.协议

协议规定了用来实现某一特定功能所必需的方法和属性。

protocol classa {
    
    var marks: Int { get set }
    var result: Bool { get }
    
    func attendance() -> String
    func 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 = 96
    let result = true
    var present = false
    var subject = "Swift 协议"
    var stname = "Protocols"
    
    func 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)

注意: 当协议中约定的属性是可读时,在实现中既可以是只读也可以是可读可写。如果协议中约定属性为可读可写,则在实现时必须是可读可写。

对 Mutating 方法的规定

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

protocol daysofaweek {
    mutating func show()
}

enum days: daysofaweek {
    case sun, mon, tue, wed, thurs, fri, sat
    mutating func show() {
        switch self {
        case .sun:
            self = .sun
            print("Sunday")
        case .mon:
            self = .mon
            print("Monday")
        case .tue:
            self = .tue
            print("Tuesday")
        case .wed:
            self = .wed
            print("Wednesday")
        case .thurs:
            self = .thurs
            print("Wednesday")
        case .fri:
            self = .fri
            print("Wednesday")
        case .sat:
            self = .sat
            print("Saturday")
        default:
            print("NO Such Day")
        }
    }
}

var res = days.wed
res.show()

对构造器的规定

协议可以要求它的遵循者实现指定的构造器。
你可以在遵循该协议的类中实现构造器,并指定其为类的指定构造器或者便利构造器。在这两种情况下,你都必须给构造器实现标上"required"修饰符:

protocol tcpprotocol {
   init(aprot: Int)
}

class tcpClass: tcpprotocol {
   required init(aprot: Int) {
   }
}

如果一个子类重写了父类的指定构造器,并且该构造器遵循了某个协议的规定,那么该构造器的实现需要被同时标示required和override修饰符:

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)")

协议类型

尽管协议本身并不实现任何功能,但是协议可以被当做类型来使用。
协议可以像其他普通类型一样使用,使用场景:

  • 作为函数、方法或构造器中的参数类型或返回值类型
  • 作为常量、变量或属性的类型
  • 作为数组、字典或其他容器中的元素类型
protocol Myprotocol {
   var name:String(get)
   var age:Int(set get)
   func logName()
}

//将协议类型作为参数
func test(param:Myprotocol){
	param.logName()
}

//将协议类型作为元素类型
var array:Array<Myprotocol>

类专属协议

协议可以被类、结构体等数据类型遵守。如果希望某个协议只能被类准守,可以使用class关键字
该class关键字必须是第一个出现在协议的继承列表中,其后,才是其他继承协议。格式如下:

protocol TcpProtocol1 {
   
}

//类专属协议,并继承TcpProtocol1协议
protocol TcpProtocol2: class, TcpProtocol1 {
    // 协议定义
}

如果需要协议中约定的属性或者方法是可选实现的,则使用@objc关键字修饰。

@objc protocol TcpProtocol1 {
	var name:String(get)
	var age:Int(set get)
	//可选实现的属性
	@objc var sex:String(get)
   
}

20.泛型


// 定义一个交换两个变量的函数
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)")

关联类

在创建协议时,可以使用associatedtype 关键字进行泛型类型的关联。当有数据类型实现此协议的时,这个关联的泛型的具体类型才会被指定。

// 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)

21.访问控制

访问级别 定义
public 可以访问自己模块中源文件里的任何实体,别人也可以通过引入该模块来访问源文件里的所有实体。
internal 可以访问自己模块中源文件里的任何实体,但是别人不能访问该模块中源文件里的实体。
fileprivate 文件内私有,只能在当前源文件中使用。
private 只能在类中访问,离开了这个类或者结构体的作用域外面就无法访问。
发布了63 篇原创文章 · 获赞 1 · 访问量 2101

猜你喜欢

转载自blog.csdn.net/weixin_42046829/article/details/104675579
今日推荐