Swift5.1读书笔记
2021-11-25 本文已影响0人
midfisher
Swift5.1(参考Swift 编程网站5.1教程)
基础部分(如果你有c或者oc开发经验,你会发现swift 很多是你熟悉的内容,我不知道,因为我没有)
swift包含了c和oc上所有的基本类型:Int,Double,Float,Bool,String
三个集合类型:Array,Set,Dictionary
相比oc增加:元祖类型(Tuple),可选类型(Optional)用于处理数值缺失的情况,可选表示那儿有一个值,并且它等于x,活着那儿没有值。有点类似nil,但是它可以用在任何类型上,不仅仅是类。
swift是一门安全性语言,swift可以让你清楚的知道值传递的类型,如果你的代码需要一个string
常量和变量:如果你的代码中有不需要改变的值,请用let声明为常量
let changliang = 1 //常量
var bianliang = 2,age = 3 //变量,可以同时声明多个
类型注解:
var age:Int //如果你想为你定义的变量赋予类型可以在变量名后加类型
var age,color:Double //可以定义多个变量类型
一般来说很少会需要用到类型注解,如果你在定义变量初始化的时候赋予初始值,swift就可以推断出这个变量的类型
注释://单行注释 /**/多行注释
分号: swift并不强制要求鞋分号,可写可不写,有一种情况必写,既在一行内写多个语句
类型安全和类型判断:
swift是一个类型安全的语言,语言可以让你清楚的知道代码要处理的值的范围,你的代码需要一个String,是绝对无法放入一个int的/
它会做编译代码的时候进行类型检查,根据你的初始化值来进行判定
数值类型转换:
通常来说,即使你的变量已知非负,也请用Int类型,
整数转换:不同整数类型的变量可以存储不同范围的数字,int8可存储为-128-127,而Uint8是0-255:
let toobig:int8 = 129 //编译报错
let a = 3
let b = 3.14
let result = Double(a) + b //不同类型间转换请用这种方式,做运算时符号俩边的类型必须相等,否则无法相加,由于b的值为3.14,所以推断他为double
元祖:把多个值组成一个符合值
let tuple = (404,'nofoud') //可以将不同类型的值放入一起,
有俩种方法获取元祖的值,一种下标:tuple.0,
let tupel = (code:404,hint:'nofound') //这种可通过key,tuple.code获取
可选类型:使用可选类型(optionals)来处理值可能确实的问题,或许有值,或者根本没有值
nil:可以为变量设置nil来表示它没有值
var serverResponse:Int = 404
serverResponse = nil //现在没有值了
如果在初始化的时候不进行赋值操作,会默认给到nil
一:基本运算符
术语:一元运算符:-a(如果a为负数,就是负负得正),!a
二元运算符:a+b
三元运算符:a?b:c,换写就是如下形式:
if a {
b
}else{
c
}
赋值:var a = 10;
let (x,y) = (1,2) //注意,这是一个元祖赋值,这时候x=1,y=2
比较运算符:所有符合c语言标准的运算符都可使用(==,!=,>,<.>=,<=)
元祖可以进行比较,(1,'apple') < (2,'orange') // 从左到右比较,以第一个比较结果为最终结果,如果相等,到第二个下标继续比较,需要注意的是bool不能用于大于小于比较
空合运算符:a??b,对a进行空判断,如果a包含值就进行解包,否则就返回一个默认值b(默认值b的类型必须和a保持一致)
区间运算符:
闭区间:1..5 表示1-5可用于迭代中
半开区间:1..<5 表示1-4 ,不包含最后一个值
单侧区间:2... ...2 ..<2
逻辑运算符:! && || 同时存在按照顺序比较,可以用括号来确定优先级
二:字符串
let str = "dosomething" //字符串能否修改取决于你定义的是常量还是变量
let strLong = """
你好\
hello
""" //多行需要三个双引号标记,反斜杠作为换行符
初始化空串:
var emptyString = ""
var emptyString = String()
判断非空:
if emptyString.isEmpty {
}
拼接字符串可以用+号或者append拼接
字符串插值:
let num = 3
let msg = "\(num) tims 2.5" //放在\开头的()内就可以,当然你内部可以做计算,例如\(num*2)也是可以的
获取字符串长度:msg.count
字符串索引:msg.index 获取对应下标的char字符
插入和删除
插入俩种方式:
var welcome = "hello"
welcome.insert("!", at: welcome.endIndex)
// welcome 变量现在等于 "hello!"
welcome.insert(contentsOf:" there", at: welcome.index(before: welcome.endIndex))
// welcome 变量现在等于 "hello there!"
删除:
welcome.remove(at: welcome.index(before: welcome.endIndex))
// welcome 现在等于 "hello there"
let range = welcome.index(welcome.endIndex, offsetBy: -6)..<welcome.endIndex
welcome.removeSubrange(range)
// welcome 现在等于 "hello"
字符串比较可用俩个=号处理
三:集合
swift提供Array,Set,Dict(kotlin也是这样得)
数组(Arrays):数组使用有序序列存储同一类型的多个值,相同的值可以多次出现在一个数组的不同位置中
var someInts = [Int]() //通过构造类型,推断为Int数组
var someArrays = Array(repreting:0.0,count:3)//创建带默认数据的数组等价于[0.0,0.0,0.0]
可以通过俩个数组相加得到另外一个数组(俩个相同数据类型的数组)
var results = someInts + someArrays //会从俩个数组的数据类型推断当前的类型
用数组字面亮构建数组,俩中方式:
var results:String = ["",""]
var resutls []
获取数组长度:results.count
追加数据:results.append("你好"),results +=["追加"] //俩种都可以
results[1] = "33" //通过下标修改对应数据,但是results[results.count]这种方式是不行,必须是有效值
results.insert(str,at: index) //指定位置添加数据
results.remove(at: index) //删除指定位置数据
var age = results[1] //通过下标获取数据
判断数组是否为长度为0:
if results.isEmpty{
}
数组遍历
for item in results{
print(item)
}
for (index,value) in results{
print(index,value)
}
集合(set):用来存储无序且数据类型相同的值,当集合顺序不重要时或者希望每个元素只出现一次请使用集合
一个类型存储在集合中,它一定是可哈希化的,相等的对象哈希值必须相同,比如a = b,因此a.hashvalue = b .hashvalue
创建集合 var results = Set<数据类型>() //创建了一个空集合
var results:Set = ['','',''] //创建默认数据集合
新增数据 results.insert("")
删除单个数据
if let removeData = results.remove("a"){
成功删除
}else{
没有该数据
}
删除全部,results.removeAll()
是否包含某个数据:
if results.contains("fuck"){
包含
}else{
不包含
}
字典:存储的是键值对类型
var air = ["key":"value","":""]
if air.isEmpty{
为空
}else{
不为空
}
air[key] = value // 如果 key不存在则是新增,如果存在则是覆盖赋值
或者
if air.updateValue("新的value",forKey:"key"){
}
是否存在某个key
if let name = air["key"]{
}else{
}
if let removeValue = air.removeValue(forKey:"key"){
}
上述带逻辑都为可选类型
遍历:
for(key,value) in airs{
key,value
}
也可单独遍历key
for name in airs.keys或values{
}
控制流:
基本带在上面带例子已经有给出来了。
for-in同时也支持范围:
for index in 1...5{
}
循环条件是支持区间运算符得,例如:in 0..<5
while循环:
while true {
}
repeat-while:类似do-while,先执行一次,到条件为false停止
repear{
}while true
条件语句:if,switch
switch由多个case组成,当if分支过多可以使用switch
switch type {
case '1':
print()
case '2':
print()
//不需要显式的编写break,当执行完case后会自动结束switch
case '3':
case '4':
//上面的写法是无效的,每条case下必须执行语句
case '3','4':
//多条件可以使用这种形式
case 1..<4:
//可使用这种区间匹配方式
}
//可以使用临时变量赋值,并在case中使用
let anotherPoint = (2, 0)
switch anotherPoint {
case (let x, 0)://匹配一个y轴坐标为0的位置,并且将这个坐标的x轴赋值给x,
print("on the x-axis with an x value of \(x)")
case (0, let y):
print("on the y-axis with a y value of \(y)")
case let (x, y):
print("somewhere else at (\(x), \(y))")
还可增加where条件
case let(x,y) where x==y:
print()
循环控制语句:
continue:中止当前循环进入下一循环
break:结束整个循环
fallthrough:如果想支持c的贯穿语法:
例如:
case '2'
print(1)
fallthrough//想俩个case都执行则需要加入这个
case '3'
print(3)
标签:通过标签给循环标记,使用continue或者break可以直接操作这个循环,尤其是对嵌套循环比较有效
outer:for..in values{
for..in secondes{
break outer //直接结束外层循环
}
}
函数:
定义和调用:person为入参变量名,尖括号后面的类型为返回类型,如果不需要返回则不用写
func greet(person:String) -> String{
}
greet(pseron:"Nick")调用
可以使用元祖返回多个数据
func greet(person:String,name:String)->(max:Int,min:Int){
return (1,2)
}
let number = greet(person:"Nick",name:"123")
number.max,number.min//获取值,当返回会空数组时,这个调用会报错,我们可以使用可选类型来解决这个问题,例如在返回类型元祖(max:Int,min:Int)?加入问号
参数标签:它可以使你的函数更具表现力,如下:这个人来自哪里
func greet(person:String,from hometown:String) ->String{
return "Hello \(person)! Glad you could visit from \(hometown)."
}
print(greet(person:"zhangfei",from :"tokyo"))
忽略参数标签:如果你不希望为某个参数添加一个标签,可以使用_来代替一个参数标签
func greet(_ person:String,homg:String) -> String{
}
print greet("Nick",homg:"tokyo")
可变参数:
func greet(_ numbers:Double...) -> Double{
}
greet(1,2,3,4,5)
输入输出参数:函数默认参数是不能修改的,例如上面的在函数内将person = zhangsan //则会报错 let不支持修改
func greet(_ person:inout String,_ hometown:inout String) ->String{
person = "dji"
return "Hello \(person)! Glad you could visit from \(hometown)."
}
var name = "zhangfei"
var home = "tokyo"
print(greet(&name, &home))//必须定义变量,不能传入字面数据,例如greet("",""),这是错误的写法
函数同样可以作为参数及返回值使用
函数作为参数:
func multiplyTwoInts(_ a: Int, _ b: Int) -> Int {
return a * b
}
func printMathResult(_ multiplyTwoInts: (Int, Int) -> Int, _ a: Int, _ b: Int) {
print("Result: \(multiplyTwoInts(a, b))")
}
函数作为返回类型:你需要做的是在返回箭头(->)后写一个完整的函数类型
func stepForward(_ input: Int) -> Int {
return input + 1
}
func stepBackward(_ input: Int) -> Int {
return input - 1
}
//参数为bool类型,它的返回类型是 (Int) -> Int 类型的函数。(Int)就是下列stepbackward的返回类型
func chooseStepFunction(backward: Bool) -> (Int) -> Int {
return backward ? stepBackward : stepForward
}
闭包:
闭包是自包含的函数块:全局函数及嵌套函数实际上也是特殊的闭包,
全局函数是一个有名字但不会捕获任何值的闭包
嵌套函数是一个有名字可以捕获其他封闭函数域内值的闭包
闭包表达式是一个利用轻量级语法所写的可以捕获其上下文变量或常量的匿名闭包
枚举:
enum Number{
case one
case two
case three,four
//与oc不一样的是枚举在创建时并不会被赋予任何默认值,它本身就是一个完整的值
}
var num = Number.one
num = .two //因为已经定义了num为获取枚举类型,所以我们可以用这种方式来重新获取枚举值。
使用switch如下
switch (num){
case .one:
case .two:
case .three:
}
遍历枚举获取所有类:
for num in Number.allCases{
print(num)
}
关联值:以条形码距离,条形码包含一个一纬码图
enum QrCode:{
case upc(int,int,int)
case qrcode(String)
}
var numcode = Qrcode.upc(1,2,3)
numUrl = . qrcode("dddd") //这样俩个值就是关联的
枚举类也能设置默认值,但是值的类型必须一致
enum Number:Int{
case one = 1
case two =2
或者下面这种形式
case one=1,two,three //后面类推+1操作
}
递归枚举 使用indirect关键字
indirect enum Number:{
case number(Int)
case addition(Number, Number)
case multiplication(Number, Number)
}
let first = Number.numeber(1)
let two = Number.number(2)
he = number.addition(first,two) //求和
结构体和类:
struct SomeStructure {
// 在这里定义结构体
}
class SomeClass {
// 在这里定义类
}
let someResolution = SomeStructure() //访问结构体
let someVideoMode = SomeClass()//访问类 ,俩者是很像的
结构体更多像一种工厂规范,它是值引用,在传递过程中会被copy,Swift 中所有的结构体和枚举类型都是值类型
struct Roulation{
var width = 1920
var height = 1080
}
var cimart = Roulation()
cimart.width = 2080 ;//这里对值进行改变,但是Roulation的width依然是1920
属性:
属性将值与特定的类、结构体或枚举关联。
计算属性可以用于类、结构体和枚举,而存储属性只能用于类和结构体,看一下上面对结构体和类就能知道区别
延迟加载:
class Test{
lazy var num = add() //必须声明为变量,因为延迟是在构造器后执行,而常量在构造器器之前就会有默认值,无法进行延时加载
存储属性和实例变量:
计算属性不直接存储值,而是提供一个getter和可选对setter属性
struct Point {
var x = 0.0, y = 0.0
}
struct CompactRect {
var center: Point {
get {
Point(x: origin.x + (size.width / 2),
y: origin.y + (size.height / 2))
}
set {
//newvalue就是你传进来的Point
origin.x = newValue.x - (size.width / 2)
origin.y = newValue.y - (size.height / 2)
}
}
}
}
属性观察期:就是监听属性的变化,通过回调拿结果
class StepCounter {
var totalSteps: Int = 0 {
willSet(newTotalSteps) {
print("将 totalSteps 的值设置为 \(newTotalSteps)")
}
didSet {
if totalSteps > oldValue {
print("增加了 \(totalSteps - oldValue) 步")
}
}
}
}
属性包装器:就是类似分装
private var number //声明为私有,保证只在类中使用
var wapperValue: Int {
get{return number}
set{number = 3}
}
方法:方法是与某些特定类型相关联的函数。类、结构体、枚举都可以定义实例方法
func addNumber(count:Int){
self.count = 1 //self是获取当前实例本身,没有必要每次都显式用,如果没写,默认是实力方法。如果说函数内带参数和实例属性一致,且需要赋值,则要显式调用
}
修改值类型数据:前面说到结构体是值类型,且实例方法并不会修改值类型的属性,如果要修改则需要在加一个mutating,同样可以适用于
struct Point{
var x = 0
mutating func moveBy(x dex:Double){
x = x + dex
}
可变方法能够赋给隐含属性一个self
mutating func moveBy(x deltaX: Double) {
self = Point(x: x + deltaX)
}
}
var somePoint = Point(x: 1.0, y: 1.0)
somePoint.moveBy(x: 2.0, y: 3.0)
类型方法:通过static关键字实现,或者class定义,实现允许子类重写该方法
继承:一个类可以继承另一个类的方法,属性和其它特性
class Parent:
var currentPosition = 0
func makeNotic(){
}
class Child:Parent{
}
let child = Child()
child.currentPosition = 1
重写:
子类可以为继承来的实例方法,类方法,实例属性,类属性,或下标提供自己定制的实现。我们把这种行为叫重写。
如果要重写某个属性需要加上override关键字,防止重写则需要在父类加上final定义
class child:Parent{
//重写父类函数
override func makeNotic(){
}
}
构造过程:构造过程是使用类、结构体或枚举类型的实例之前的准备过程
类和结构体在创建实例时,必须为所有存储型属性设置合适的初始值
构造器定义:
struct Fatherheit{
var num : Double //也可以直接
init(){
//在此执行构造器
num = 10
}
init(fromFather fromfather:Double){
num = fromfather - 1
}
}
let fatherheit = Fatherheit(fromFather:3) //fromFather为参数标签,如果没有定义参数标签,构造器会默认给一个名称
struct Color{
var height = 0.0 ;
var first = 0.0
init(white:Double){
}
}
let color = Color(white:1.0)
let color = Color(1.0) //抛错
构造方法中可以做很多事情,还可以给属性设置默认值,如果没有构造器,系统会自动生成一个默认不带参数的构造器,即使没有设置自定义构造器,也可以用如下方式
let color = Color(height:0.1) //first这时候取默认值
如果使用了自定义构造器,需要显式定义默认构造器,否则无法访问到
类到继承和构造过程:类里面的所有存储型属性——包括所有继承自父类的属性——都必须在构造过程中设置初始值。
构造器到自动继承:
1.如果子类没有定义任何指定构造器,它将自动继承父类所有到制定构造器
2.如果子类提供了所有父类指定构造器的实现——无论是通过规则 1 继承过来的,还是提供了自定义实现——它将自动继承父类所有的便利构造器。
class Food {
var name:String
init(name:String){
self.name = name
}
//当实例化不带参数到构造函数时,将调用带参构造函数为属性设置默认值
convenience init(){
self.init(name:"[unset]")
}
}
可失败构造器:语法在init 后面加?,并且返回nil,这样在初始化或者类型检查到时候可以起到作用,比如限制枚举
struct Animal {
let species: String
init?(species: String) {
if species.isEmpty {
return nil
}
self.species = species
}
}
析构过程:析构器只适用于类类型,当一个类的实例被释放之前,析构器会被立即调用。析构器用关键字 deinit,没有其他引用等 来标示,类似于构造器要用 init 来标示。
Swift 会自动释放不再需要的实例以释放资源
每个类最多直邮一个析构器:析构器是在实例释放发生前被自动调用的。你不能主动调用析构器
deinit{
}
可选链调用:可选链式调用是一种可以在当前值可能为 nil 的可选值上请求和调用属性、方法及下标的方法。如果可选值有值,那么调用就会成功;如果可选值是 nil,那么调用将返回 nil。多个调用可以连接在一起形成一个调用链
通过在想调用的属性、方法,或下标的可选值后面放一个问号(?),可以定义一个可选链
class Person {
var residence: Residence?
}
class Residence {
var numberOfRooms = 1
}
//在residence不为nil的情况下调用numberOfRoomms
if let roomCount = john.residence?.numberOfRooms {
print("John's residence has \(roomCount) room(s).")
} else {
print("Unable to retrieve the number of rooms.")
}
觉得就是在某个参数涉及到一系列业务逻辑时可以使用这种方式去处理,减少空型错误
错误处理:错误处理(Error handling) 是响应错误以及从错误中恢复的过程。Swift 在运行时提供了抛出、捕获、传递和操作可恢复错误(recoverable errors)的一等支持(first-class support)。
抛出错误:
enum VendingMachineError: Error {
case invalidSelection //选择无效
case insufficientFunds(coinsNeeded: Int) //金额不足
case outOfStock //缺货
}
throw VendingMachineError.insufficientFunds(coinsNeeded: 5) 抛出错误用throw,表示还需要5个硬币
处理错误:
1.用throwing处理错误
2. 使用do-catch捕获异常,类似try-catch
do {
try expression
statements
} catch pattern 1 {
statements
} catch pattern 2 where condition {
statements
} catch {
statements
}
将错误换成可选值:
//如果报错返回nil,不报错则返回函数值
if let data = try? fetchDataFromDisk() { return data }
禁用错误传递:有时你知道某个 throwing 函数实际上在运行时是不会抛出错误的,在这种情况下,你可以在表达式前面写 try! 来禁用错误传递
let photo = try! loadImage(atPath: "./Resources/John Appleseed.jpg")
指定清理操作:defer,使用defer可以在即将离开代码块前做一系列操作,能让我们清理一些必要等东西
if exists(filename) {
let file = open(filename)
defer {
close(file)
}
while let line = try file.readline() {
// 处理文件。
}
// close(file) 会在这里被调用,即作用域的最后。
}
类型转换:使用is as操作
Son子类 Fath父lei
if son is Fath: //类似这种操作
向下转型: as as?
//在不知道item等情况下,通过as?确认是否是这个类型
if son = item as ?Fath{
}
Any和AnyObject类型转换
switft为不确定类型提供了俩种特殊等类型别名
Any可以表示任何类型,包括函数类型
AnyObject可以表示任何类类型等实例
var things = [Any]()
things可以添加任何类型数据
嵌套类型:类中嵌套类,结构体,枚举类等,
扩展:扩展可以给一个现有的类,结构体,枚举,还有协议添加新的功能。它还拥有不需要访问被扩展类型源代码就能完成扩展的能力(即逆向建模
使用extension关键字声明扩展
extension SomeType {
// 在这里给 SomeType 添加新的功能
}
对一个现有的类型,如果你定义了一个扩展来添加新的功能,那么这个类型的所有实例都可以使用这个新功能,包括那些在扩展定义之前就存在的实例。Kotlin也同样支持,节省工具类等写法
例如Double:
extension Double{
var km: Double { return self * 1_000.0 }
}
let lkm = 25.5.km 会输出25000
也可以给现有类添加新等构造器,不过为什么不在原类中之间加,没搞懂
struct Number{
var width = 1.0
}
extension Number{
init(width:Number){
相关操作
}
}
扩展方法:如果需要修改结构体等原始属性等,加上我们之前说等mutating
extension Int {
func repetitions(task: () -> Void) {
for _ in 0..<self {
task()
}
}
}
协议:类似于接口
protocol SomeClass: SomeSuperClass, FirstProtocol, AnotherProtocol {
// 这里是协议的定义部分
}
protocol AnotherProtocol {
//get set用来声明读写
static var someTypeProperty: Int { get set }
}
protocol FullyNamed {
var fullName: String { get }
}
遵循 FullyNamed 协议的简单结构体:
struct Person: FullyNamed {
var fullName: String
func random() -> Double //不需要关心函数实现
}
class LinearCongruentialGenerator: RandomNumberGenerator {
//实现该方法
func random() -> Double {
return 1
}
}
异变方法要求,在协议中定义函数时前缀加上mutating
protocol Togglable {
mutating func toggle()
}
mutating func toggle()
//例如开关,
enum OnOffSwitch: Togglable {
case off, on
mutating func toggle() {
switch self {
case .off:
self = .on
case .on:
self = .off
}
}
}
构造器要求:
协议中定义:init(someP:Int)
实现的时候init前需要加上required 关键字,如果继承等父类和协议都有init,那么则需要加上required,overriede
协议可以作为参数使用,调用时传入协议势力,通过参数名.函数名调用
委托:委托是一种模式,它允许类或结构体将一些需要它们负责的功能委托给其他类型的实例,比如定义了协议,用另外一个实例类去实现它
扩展协议,extension关键字 Dir:扩展的协议内容
extension DirPro: 扩展协议本身。
有条件等遵循协议:extension Array :TextPro where Element : TextPro 只要在存储时是遵循TextPro协议的时候就遵循这个协议
在扩展里面采纳协议,如果某个结构体已经遵循某种协议,可以在扩展内采纳协议,extension Struct : TextPro{} 个人觉得就是单纯为了规范。
协议继承:一个协议可以继承自另外一个协议,任何遵循子协议等实例也必须遵循父实例
protocol SomeProtocol:AnyObject 通过该关键字声明协议只能类类型采纳
协议合成:要求一个类型同时遵循多个协议是很有用的,可以使用协议组合
例如 func learPerson(to caht:ProtocolA & ProtocolB)
泛型:泛型代码让你能根据自定义的需求,编写出适用于任意类型的、灵活可复用的函数及类型。你可避免编写重复的代码,而是用一种清晰抽象的方式来表达代码的意图。
帆型函数定义:
func test<T> (_ a: inout T)
泛性约束,例如T:ParentClass 传入的类型必须是parent的类型。
泛型总结来说就是给我们去定义一些基类或者工厂类的时候用处大
关联类型:
protocol One{
associatedtype Item //associatedtype关键字定义关联类型
associatedtype Item :Equatable //为Item约束,必须遵守Equatable协议
mutating func append(_ item: Item)//item不指定类型
}
实现大时候
struct Two : One{
typealias Item = Int //指定Item类型
func append(_ item:Int) {//相应的item类型会转换为int
}
}
extenions Array:One{}通过空扩展为Array指定关联类型,因为Array里含有append,符合One协议,所以可以关联
不透明类型:
具有不透明返回类型的函数或方法会隐藏返回值的类型信息。函数不再提供具体的类型作为返回类型,而是根据它支持的协议来描述返回值
跟协议有点像,看的概念比较模糊,应用上再来补充
自动引用计数:
Swift 使用自动引用计数(ARC)机制来跟踪和管理你的应用程序的内存,ARC 会在类的实例不再被使用时,自动释放其占用的内存
工作机制:
每当我们创建一个实例,ARC会自动给我们分配一块内存,当该实例不被任何引用时,就会回收这个实例,当回收后,如果试图访问改实例,可能会崩溃。ARC会跟踪和计算每一个实例被多少属性,变量引用。
当我们将实例赋值给属性,变量或常量,它们都会创建此实例的强引用。
例子:
Person 类
person = Person()
person2 = Person()
person = nil #当前有俩个person的强引用,此时剩下一个person2的强饮用,Person并不会回收
person2 = nil #此时俩个引用回收,实例也回收
类实例之间的循环强引用
例子:
A,B俩个类互相持有对方的引用,就会造成这个情况,都不能被释放。即使你分别致为nil。
如何解决:Swift提供了俩种方式:弱引用(Java也存在)和无主引用
弱引用和无主引用允许循环引用中的一个实例引用另一个实例而不保持强引用。这样实例能够互相引用而不产生循环强引用
使用weak标记,
class A{
weak var B b; //使用weak标记为弱引用,这样可以解决循环强引用。
//因为弱引用不会保持所引用的实例,即使引用存在,实例也有可能被销毁。因此,ARC 会在引用的实例被销毁后自动将其弱引用赋值为 nil。并且因为弱引用需要在运行时允许被赋值为 nil,所以它们会被定义为可选类型变量,而不是常量
在使用的时候可以设置检查引用值是否存在,可以避免访问已被销毁的实例引用
}
无主引用:和弱引用类似,无主引用不会牢牢保持住引用的实例。和弱引用不同的是,无主引用在其他实例有相同或者更长的生命周期时使用。你可以在声明属性或者变量时,在前面加上关键字 unowned 表示这是一个无主引用。
无主引用通常都被期望拥有值。不过 ARC 无法在实例被销毁后将无主引用设为 nil,因为非可选类型的变量不允许被赋值为 nil。
注:使用无主引用,你必须确保引用始终指向一个未销毁的实例。
在使用俩种引用注意检查值,安全的运行代码。
如果俩个值都可为nil,使用弱引用
如果一个为nil,一个不可为nil,使用无主引用
两个属性都必须有值,并且初始化完成后永远不会为 nil。在这种场景中,需要一个类使用无主属性,而另外一个类使用隐式解包可选值属性。
闭包的循环强饮用:
类中有属性调用内部函数,内部函数又用了类中实例。
解决:
Swift 有如下要求:只要在闭包内使用 self 的成员,就要用 self.someProperty 或者 self.someMethod()(而不只是 someProperty 或 someMethod())。
内存安全:
默认情况下,Swift 会阻止你代码里不安全的行为。例如,Swift 会保证变量在使用之前就完成初始化,在内存被回收之后就无法被访问,并且数组的索引会做越界检查。
然而,理解潜在的冲突也是很重要的,可以避免你写出访问冲突的代码。而如果你的代码确实存在冲突,那在编译时或者运行时就会得到错误。
同一个存储地址的多个访问同时发生会造成不可预计或不一致的行为。在 Swift 里,有很多修改值的行为都会持续好几行代码,在修改值的过程中进行访问是有可能发生的。
冲突会发生在当你有两个访问符合下列的情况:
至少有一个是写访问
它们访问的是同一个存储地址
它们的访问在时间线上部分重叠
访问控制:
访问控制可以限定其它源文件或模块对你的代码的访问。这个特性可以让你隐藏代码的实现细节,并且能提供一个接口来让别人访问和使用你的代码。
open,public 实体被同一模块源文件中的所有实体访问,
internal 被同一个模块源文件所有实体访问,它是默认访问级别
fileprivate 只能在其定义的作用域
private 只能在其定义的作用域
函数的访问级别不能高于它的参数类型和返回类型的访问级别。因为这样就会出现函数可以在任何地方被访问,但是它的参数类型和返回类型却不可以的情况
对外提供接口,可以定义为open和public