Swift知识点(三)

11. init、deinit、可选链、协议、元类型

构造和析构

构造方法

构造方法是一种特殊的方法
一个对象创建完毕后,都需要调用构造方法进行初始化(比如属性的初始化)
在这里插入图片描述
验证:init方法是在对象创建完毕的时候调用

回到存储属性
在对象创建的时候,存储属性必须有初始值
两种设置初始值的方法:
在定义属性时为其设置默认值
在构造方法中为属性赋初值

不管在哪赋初值,都是为了保证:对象创建后,存储属性有值

除了没有参数的构造方法,还有自定义的有参数构造方法

构造方法类型

构造方法可以分为两类:
指定构造方法(Designated Constructor)
便利构造方法(Convenience Constructor)
被convenicence修饰的构造方法,被称为便利构造方法
在这里插入图片描述

  • 默认情况下,每一个类都有一个隐私的无参的指定构造方法:init(){}
  • 如果一个类定义了一个有参的指定构造方法,就不会自动生成无参的指定构造方法
  • 指定构造方法必须调用其直接父类的指定构造方法(除非没有父类)
  • 便利构造方法必须调用同一类中定义的其他构造方法(不一定是指定构造)
  • 便利构造方法最终必须以调用一个指定构造方法结束

在这里插入图片描述
只有便利构造方法才能直接调用当前类中的其他构造方法
也就是说,指定构造方法在当前类中不能直接调用其他指定构造方法

只有指定构造方法才能直接调用其父类的构造方法
也就是说,便利构造方法不能直接调用其父类的构造方法

  • 如果父类中只有一个指定构造方法且是无参的,那么,子类的指定构造方法默认会自动调用父类中无参的指定构造方法
  • 如果父类中存在有参的指定构造方法,那么,子类的指定构造方法不会自动调用父类的无参的指定构造方法
  • 常量只能在父类中初始化

自动继承

  • 如果子类没有自定义任何指定初始化器,它会自动继承父类所有的指定初始化器

required

  • 用required修饰指定初始化器,表明其所有子类都必须实现该初始化器(通过继承或重写实现)
  • 如果子类重写了required初始化器,也必须加上required,不用加override

可失败初始化器

类、结构体、枚举都可以使用init?定义可失败初始化器
就是,通过这种初始化器初始化的对象,可能是nil,也就是初始化完后的对象,是可选项对象

反初始化器(deinit)

deinit叫做反初始化器,类似C++的析构函数、OC中的dealloc方法
当类的实例对象被释放内存时,就会调用实例对象的deinit方法

deinit{}

  • deinit不接受任何参数,不能写小括号,不能自行调用
  • 父类的deinit被子类继承
  • 子类的deinit实现执行完后,会调用父类的deinit(先子后父)

可选链(Optional Chaining)

  • 如果可选项为nil,调用方法、下标、属性失败,结果为nil
  • 如果可选项不为nil,调用方法、下标、属性成功,结果会被包装成可选项
  • 结果本来就是可选项的,不会进行再次包装

在这里插入图片描述


协议(Protocol)

协议可以用来定义方法、属性、下标声明,协议可以被枚举、结构体、类遵守(多个协议之间用逗号隔开)

一个简单的协议:

protocol Drawable {
	//方法
    func draw()
    //属性
    var x: Int {get set}//可读可写
    var y: Int {get}//只读
    //下标
    subscript(index: Int) -> Int {get set}//可读可写
}
  • 协议中定义方法时,不能有默认参数值
  • 默认情况下,协议中定义的内容必须全部实现(也有办法做到只实现一部分协议)

协议中的属性

  • 协议中定义属性时,必须使用var关键字

如果是只读属性,在实现的时候,可能返回的是函数,所以返回值是不确定的)

  • 实现协议时的属性权限 >= 协议定义的属性权限
  • 协议定义get、set,用var存储属性或者get、set计算属性去实现
  • 协议定义get,用任何属性都可以实现

协议中static、class使用

为了保证通用,协议中必须使用static定义类型方法、类型属性、类型下标

class关键字只能用在类里面,而结构体也可以遵守协议,结构体没有class,所以,
涉及到类的,协议中只能使用static

在协议声明的时候,声明类型的时候,必须用static,然而,在实现类型的时候,可以用class也可以用static
区别是:
class允许被子类重写;
static不允许被子类重写

protocol Drawable {
    //声明类方法
    static func draw()
}

class Size: Drawable {
    //使用class修饰类方法
    class func draw() {}
}

class Size1: Size {
    //使用class修饰类方法,允许子类重写
    override class func draw() {}
}

class Point: Drawable {
    //使用static修饰类方法
    static func draw() {
        print("Point-draw")
    }
}

class Point1: Point {
    //使用static修饰类方法,不允许子类重写
    //Method does not override any method from its superclass
    //override func draw() {}
}

协议中mutating

只有将协议中的实例方法标记为mutating

  • 才允许结构体、枚举的具体实现修改自身内存
  • 类在实现方法时不用加mutating,枚举、结构体才需要加mutating

在这里插入图片描述

协议中init

协议中还可以定义初始化器init
非final类实现时,必须加上required

目的是强制要求遵守协议的类,必须实现init方法

protocol Drawable {
    init(x: Int, y: Int)
}

class Point: Drawable {
    //必须加上required,不然报错
    required init(x: Int, y: Int) {}
}

//使用final修饰,子类不能继承,所以自己实现就可以,不需要加required
final class Size: Drawable {
    init(x: Int, y: Int) {}
}

struct struct1: Drawable{
    //struct没有继承,也就不需要加required
    init(x: Int, y: Int) {}
}

协议可以继承(遵守)

一个协议,可以继承(遵守)其他协议

protocol Drawable {
    func fun1()
}

protocol Drawable2: Drawable
{
    func fun2()
}

class person: Drawable2{
    func fun1() {
        
    }
    func fun2() {
        
    }
}

协议组合

在这里插入图片描述

CaseIterable协议

Iterable: 可迭代对象,可迭代的

让枚举遵守CaseIterable协议,可以实现遍历枚举值

//创建一个枚举,遵守CaseIterable协议
enum Season: CaseIterable{
    case spring, summer, autumn, winter
}

//就可以使用CaseIterable里面的allCases方法,获取所有的元素,组成一个数组
let seasons = Season.allCases
print(seasons);
//打印:[swiftTest.Season.spring, swiftTest.Season.summer, swiftTest.Season.autumn, swiftTest.Season.winter]

CustomStringConvertible协议

convertible:可以转换的, 可转化的

遵守CustomStringConvertible协议,可以自定义实例的打印字符串

//遵守CustomStringConvertible协议
class Person: CustomStringConvertible{
    var age: Int
    var weight: Int
    init(age: Int, weight: Int) {
        self.age = age
        self.weight = weight
    }

    //实现协议方法:你想怎样自定义打印
    var description: String{
        "age = \(age), weight = \(weight)"
    }
}

var p1 = Person(age: 11, weight: 80)
print(p1)//age = 11, weight = 80

其他关键字:

mutating

默认情况下,结构体、枚举,不允许内部方法修改其实例属性值:
在这里插入图片描述
添加mutating后,不报错:

struct Point {
    var x = 0.0
    var y = 0.0
    mutating func moveBy(deltaX: Double, deltaY: Double){
        x += deltaX
        y += deltaY
    }
}

类里面,在实现方法时,可以直接修改存储属性,不需要加mutating

@discardableResult

在func前加上@discardableResult,可以消除:函数有返回值,但没有使用的警告⚠️

Any、AnyObject

Swift提供了2种特殊类型:Any、AnyObject

  • Any:可以代表任意类型(枚举、结构体、类,也包括函数类型)
  • AnyObject:可以代表任何类型(在协议后面写上 :AnyObject代表只有类能遵守这个协议)

is、as?、as!、as

is是用来判断是否为某种类型
as用来做强制类型转换

is后面可以放本类,也可以放父类,也可以放遵守的协议,都会返回true

as?一般用于类型转换,转换后可能为nil,因此用as?,转换后是一个可选类型
as!带有强制解包的意思,转换后不是可选类型,但转换失败就会崩溃,因此,慎用

X.self、X.Type、AnyClass

此处X,指的是类(非结构体),且是类对象(非对象)

X.self是一个元类型(metadata)的指针,metadata存放着类型相关的信息

Person.self,即元类型指针,里面存放着元类型的地址,即person对象的前八个字节的地址

X.self属于X.Type类型

//p是Person类型
var p: Person = Person(age: 11, weight: 80)
//pType是Persin.Type类型,即X.Type类型
var pType: Person.Type = Person.self

public typealise AnyClass = Anyobject.Type

即,AnyClass是一个X.Type类型,是一个存储元类地址的指针类型

Self

Self一般用作返回值类型,限定返回值跟方法调用者必须是同一类型
Self也可以作为参数类型

class Person{
    var age = 1
    static var count = 2
    func run(){
        //self是对象
        print(self.age)//1
        //Self代表类对象
        print(Self.count);//2
    }
}

let p = Person()
p.run()

12. Error处理、泛型

错误处理

错误类型

开发过程中常见的错误类型:

  • 语法错误(编译报错)
  • 逻辑错误
  • 运行时错误(可能会导致闪退,一般也叫做异常)

处理Error的两种方式:

  1. 使用do-try-catch
do {
	try 可能错误的代码
}catch {

}
  1. 不捕捉Error,在当前函数前增加throws声明,Error将自动抛给上层函数
    如果上层也没用处理,则还是崩溃,因此,少用或者用的严谨些

try?、try!

可以使用try?、try!调用可能会抛出Error的函数,这样就不用去处理Error
try?,如果调用不成功,则结果是nil;如果调用成功,则会包装一下成Option
try!,如果调用成功,则不会包装

defer

defer语句:用来定义以任何方式(抛错误、return等)离开代码块前必须要执行的代码
也就是,错误发生了,也得执行defer

defer语句将延迟至当前作用域结束前执行

fun funcName(参数) throws {
	//正常代码
	print("123")
	defer{
		//必须执行的代码,且在}结束的时候才执行
	}
	//可能会出错的代码
	try xxx
}

defer语句的执行顺序与定义顺序相反

assert(断言)

不符合指定条件就抛出运行时错误,常用于调试(debug)阶段的条件判断
默认情况下,Swift的断言只会在Debug模式下生效,Release模式下会忽略

do-catch一般用于编写正式代码,万一有错,去处理错误,不至于崩溃
assert是在编译开发阶段,有错误就崩溃,不进行处理

-assert-config Release强制关闭断言
-assert-config Debug强制开启断言

fatalError

如果遇到严重问题,希望结束程序运行时,可以直接使用fatalError函数抛出错误

泛型(Generics)

泛型可以将类型参数化,提高代码复用率,减少代码量

可以在函数名后面加上<T>,T代表任意类型

协议中如何使用泛型?

协议中,不能直接使用<T>
而是使用:

关联类型(Associated Type)

//定义一个栈,是一个协议
protocol Stackable{
    //使用关联对象,定义Element
    associatedtype Element
    
    //push一个元素
    mutating func push(_ element: Element)
    //出栈一个元素
    mutating func pop() -> Element
    //获取顶部元素
    func top() -> Element
    //获取栈还有几个元素
    func size() -> Int
}

关联类型的作用:给协议中用到的类型定义一个占位名称
协议中可以拥有多个关联类型

类型约束

泛型是可以被约束的:<T: Person & Runnable>

//定义一个协议
protocol Runnable {}
//定义一个类
class Person{}
//函数里面的参数是一个泛型,且泛型是Person类或其子类,其遵守Runnable协议
func swapValues<T: Person & Runnable>(_ a: inout T, _ b: inout T){
    (a, b) = (b, a)
}

13. String、Array底层


14. 可选项本质、运算符重载、扩展

//定义一个可选项
var age: Int? = 10

switch age {
    case let v://此处不会解包
        print("1", v)
    case nil:
    print("2")
}
打印:
1 Optional(10)

v是一个可选项,也就是在switch-case中,并不会像if let那样,解包操作


switch age {
    case let v?://v后面加一个?
        print("1", v)
    case nil:
    print("2")
}
打印:
1 10

age如果是非nil,则赋值给v
age如果是nil,则走case nil

高级运算符

溢出运算符(Overflow Operator)

swift的算数运算符出现溢出的时候,会抛出运行时错误

print(Int8.min)
print(Int8.max)

print(UInt8.min)
print(UInt8.max)

打印:
-128
127
0
255

在知道UInt8的取值范围后,做超出值的赋值操作:

var a: UInt8 = UInt8.max
a += 1

此时,会报错:Thread 1: Swift runtime failure: arithmetic overflow

  • swift有溢出运算符( &+、&-、&*),用来支持运算
var a1: UInt8 = UInt8.max
var a2 = a1 &+ 1
print(a1, a2)//255, 0

这三个运算符,是一个循环
也就是255后面没有值了,再加1,就回到起点,成了0
其他两个减和乘类似

运算符重载(Operator Overload)

类、结构体、枚举可以为现有的运算符提供自定义的实现,这个操作叫做:运算符重载
比如,a + b = 10
可以做到p(1, 2) + p(2, 5) = (3, 7)

只需要做一个函数名为+的函数即可

Equatable

想要得知2个实例是否等价,一般做法是遵守Equatable协议,重载 == 运算符

判断 基本数据类型,可以直接比较,而对于实例(对象),直接用==会报错

class Person{
    var age: Int
    var weight: Int
    init(age: Int, weight: Int) {
        self.age = age
        self.weight = weight
    }
}

var p1 = Person(age: 10, weight: 80)
var p2 = Person(age: 10, weight: 90)

print(p1 == p2)//Binary operator '==' cannot be applied to two 'Person' operands

此时,我们约定,只要age相等,则就代表两个对象相等
这时,就可以用到上面提到的Equatable

class Person: Equatable{
    var age: Int
    var weight: Int
    init(age: Int, weight: Int) {
        self.age = age
        self.weight = weight
    }
    
    //协议方法
    //其实,这个就是运算符重载,使用了一个函数名为 == 的函数
    static func == (lhs: Person, rhs: Person) -> Bool {
        return lhs.age == rhs.age
    }
    
}

var p1 = Person(age: 10, weight: 80)
var p2 = Person(age: 10, weight: 90)

print(p1 == p2)//true
  • 引用类型比较存储的地址值是否相等(是否引用着同一个对象),使用===!==符号
    ===只适用于引用类型

Comparable

想比较两个实例的大小,一般可以:

  • 遵守Comparable协议
  • 重载相应的运算符
//遵守Comparable协议
class Person: Comparable{
    
    var age: Int
    var weight: Int
    init(age: Int, weight: Int) {
        self.age = age
        self.weight = weight
    }
    
    //重载相应的运算符
    static func < (lhs: Person, rhs: Person) -> Bool {
        lhs.age < rhs.age
    }
    //重载相应的运算符
    static func == (lhs: Person, rhs: Person) -> Bool {
        lhs.age == rhs.age
    }
}

var p1 = Person(age: 11, weight: 80)
var p2 = Person(age: 10, weight: 90)

print(p1 > p2)//true

自定义运算符(Custom Operator)

可以自定义新的运算符:在全局作用域使用operator进行声明

比如:
prefix operator 前缀运算符
prefix operator +++
就是定义了一个自定义运算符+++,实现前缀运算

扩展(Extension)

Swift中的扩展,类似于OC中的分类

扩展可以为:枚举、结构体、类、协议添加新功能
可以添加:方法、计算属性、下标、(便捷)初始化器、嵌套类型、协议等

扩展不能做的事:

  • 不能覆盖原有的功能
  • 不能添加存储属性,不能向已有的属性添加属性观察器
  • 不能添加父类
  • 不能添加指定初始化器,不能添加反初始化器

主要是,不能改变原来的结构
存储属性相当于添加新的属性,影响了原来的结构
父类,如果对父类扩展,就是继承父类,那么父类里面的东西就被扩展拿到,影响了原来的结构

如果希望自定义初始化器的同时,编译器也能够生成默认初始化器
可以在扩展中编写自定义初始化器

required初始化器不能写在扩展中

也就是,扩展对象遵守的协议,在扩展中不能使用required初始化器

如果一个类,已经实现了某个协议的方法,但是没有声明它遵守该协议
则,可以在扩展中遵守该协议

扩展可以给协议提供默认实现,也即间接实现:可选协议的效果

一般协议,都要求必须实现,使用扩展,在扩展中写上某些不用的方法,就间接实现了可选协议的效果


15. 访问控制、内存管理

访问控制(Access Control)

Swift提供了5个不同的访问级别,从高到低有:

  • open:允许在定义实体的模块、其他模块中访问,允许其他模块进行继承、重写(open只能用在类、类成员上)
  • public:允许在定义实体的模块、其他模块中访问,不允许其他模块进行继承、重写
  • internal:只允许定义实体的模块中访问,不允许在其他模块中访问(绝大部分模式是internal级别)
  • fileprivate:只允许在定义实体的源文件中访问
  • private:只允许在定义实体的封闭声明中访问

模块:大致就是多个target,或者多个动态库,不同框架
源文件:就是.swift文件

open、public都可以在其他库里面直接使用,区别是public不能继承、重写
internal只能在当前库使用,其他库不允许使用
fileprivate只有当前文件.swift里面使用
private,只有自己的作用区域使用

访问级别的使用准则

一个实体,不可以被更低访问级别的实体定义

元组

元组类型的访问级别,是所有成员类型最低的那个
比如,有一个元组(a, b)a的访问级别是public,b的访问级别是fileprivate
那么,该元组类型的访问级别是fileprivate

成员、嵌套类型

类型的访问级别会影响成员(属性、方法、初始化器、下标)、嵌套类型的默认访问级别

一般情况下,类型为privatefileprivate,那么成员\嵌套类型默认也是privatefileprivate
一般情况下,类型为internalpublic,那么成员\嵌套类型默认也是internal

直接在全局作用域下定义private,等价于fileprivate

初始化器

如果一个public类想在另外一个模块调用编译生成的默认无参初始化器,必须显式提供public的无参初始化器
因为public类的默认初始化器是internal级别的

required初始化器 >= 它的默认访问级别

如果结构体有private\ fileprivate的存储实例属性,那么它的成员初始化器也是private\ fileprivate
否则默认就是internal

枚举类型的case

不能给enum的每个case单独设置访问级别

每个case自动接收enum的访问级别
public enum定义的case也是public

协议

协议中定义的要求自动接收协议的访问级别,不能单独设置访问级别

即,协议的访问级别,不能在协议里面定义,而是在协议定义的时候写

扩展

如果有显式设置扩展的访问级别,扩展添加的成员自动接收扩展的访问级别

如果没有显式设置扩展的访问级别,扩展添加的成员的默认访问级别,跟直接在类型中定义的成员一样

可以单独给扩展添加的成员设置访问级别

不能给用于遵守协议的扩展显式设置扩展的访问级别

内存管理

跟OC一样,Swift也是采取基于引用计数的ARC内存管理方案(针对堆空间)

Swift的ARC有3种引用

  • 强引用:默认情况下,引用都是强引用,使用strong
  • 弱引用:通过weak定义弱引用
    必须是可选类型的var,因为实例销毁后,ARC会自动将弱引用设置为nil(会改变var,可以为nil,所以是可选类型)
    ARC自动给弱引用设置nil时,不会触发属性观察器
  • 无主引用:通过unowned定义无主引用
    不会产生强引用,实例销毁后仍然存储着实例的内存地址(类似于OC的unsafe_unretained
    试图在实例销毁后访问无主引用,会产生运行时错误(野指针)

闭包的循环引用

闭包表达式默认会对用到的外层对象产生额外的强引用(对外层对象进行了retain操作)
在闭包表达式的捕获列表声明weak或unowned引用,解决循环引用问题

闭包里面如果用到了self,必须在闭包前加上lazy
因为在实例初始化完毕之后,才能引用self

加强点:初始化、继承、协议、扩展

  • 11
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Swift 的闭包是一个自包含的函数代码块,可以在代码被传递和使用。闭包可以捕获和存储其所在上下文任意常量和变量的引用。Swift 的闭包类似于 C 和 Objective-C 的 blocks、以及其他一些编程语言的 lambdas。 闭包有以下种形式: 1. 全局函数,有名字但不能捕获任何值。 2. 嵌套函数,有名字,也能捕获其封闭函数内的值。 3. 闭包表达式,没有名字,使用轻量级语法,可以捕获上下文的值。 闭包表达式的基本语法如下: ``` { (parameters) -> return type in statements } ``` 其 `parameters` 为参数列表,可以为空;`return type` 为返回类型,也可以为空;`statements` 为闭包体,包含了要执行的代码。 例如,下面的代码定义了一个接受两个整数参数并返回它们之和的闭包: ``` let sum = { (a: Int, b: Int) -> Int in return a + b } ``` 可以像函数一样调用这个闭包: ``` let result = sum(1, 2) print(result) // 输出 3 ``` 闭包可以作为函数的参数或返回值。例如,下面的代码定义了一个接受一个整型数组和一个闭包参数的函数 `apply`: ``` func apply(_ array: [Int], _ transform: (Int) -> Int) -> [Int] { var result = [Int]() for element in array { result.append(transform(element)) } return result } ``` 可以使用闭包表达式作为 `transform` 参数传递: ``` let numbers = [1, 2, 3, 4, 5] let squared = apply(numbers, { (number) -> Int in return number * number }) print(squared) // 输出 [1, 4, 9, 16, 25] ``` 闭包还支持尾随闭包语法,可以将闭包表达式作为函数的最后一个参数传递,并将其放在圆括号之外。例如,上面的代码也可以写成: ``` let squared = apply(numbers) { (number) -> Int in return number * number } ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值