swift学习第5天

构造方法和构析方法

构造方法的设计与使用

  • swift 要求结构体和类必须在构造方法结束前完成对存储实行的构造(延时存储属性除外)
    往往在以下两种方式来处理存储属性:
  • 在类和结构体中声明存储属性时直接为其设置初始默认值
  • 在类和结构体的构造方法中对存储属性进行构造或者设置默认值
swift 复制代码
class MyClass {
    var cout : Int = 0 {
        willSet {
            print("willSet")
        }
    }
    var name : String {
        didSet {
            print("didSet")
        }
    }
    //optional属性,在不赋值的时候,自动取nil
    var opt : Int?
    init () {
        name = "HS"
    }
}
  • optional属性,在不赋值的时候,自动取nil

  • 如果类或者结构体中的所有存储属性都有初始默认值,那么开发者不显式的提供任何构造方法,编译器也会默认自动生成一个无参的构造方法init()

  • 结构体可以不去实现构造方法,会自动生成

  • 值类型提供了自定义的构造方法,则系统默认生成的构造方法将失效

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

  • 指定构造方法是类的基础构造方法,任何类都要至少一个指定构造方法;便利构造方法则是为了方便开发者使用,为类额外添加的构造方法;便利构造方法最终要调用指定构造方法
  • 指定构造方法不需要关键字修饰;便利构造方法需要用Convenience关键字修饰
    原则:
  • 子类的指定构造方法中必须调用父类的指定构造方法
  • 便利构造方法中必须调用当前类的其他构造方法
  • 便利构造方法归根结底要调用某个指定构造方法
swift 复制代码
class BaseClass {
    init () {
        print("baseclass Designted")
    }
    convenience init (param : String) {
        print("baseclass Convenience")
        self.init()
    }
}

class SubClass : BaseClass {
    override init() {
        print("SubClass Designted")
    }
    convenience init (param : String) {
        print("SubClass Convenience")
        self.init()
    }
    convenience init (param : Int) {
        self.init(param: "HS")
    }
}

构造方法的继承关系

  • 如果子类没有覆写或重写任何指定构造方法,则默认子类会继承父类所有的指定构造方法(重载也不可以)
  • 如果子类中提供了父类所有的指定构造方法,则子类会默认继承父类所有的便利构造方法
swift 复制代码
// 基类:提供 1 个指定初始化器 + 1 个便利初始化器
class BaseCls {
    
    // 指定初始化器①:无参
    init() {
        print("basecls init")
    }
    
    // 指定初始化器②:Int 参数
    init(param: Int) {
        print("basecls init param")
    }
    
    // 便利初始化器:String 参数
    // 规则:必须横向调用"自己类"里的某个指定初始化器(这里调的是 init())
    convenience init(param: String) {
        self.init()          // 横向调用
    }
}

// 子类一:完全继承基类所有初始化器,不写任何 init
class SubClsOne: BaseCls {
    // 编译器会自动继承:
    // 1. 如果子类没有定义任何**指定**初始化器,则自动继承父类**所有**指定+便利初始化器
    // 因此 SubClsOne 拥有 init()、init(param: Int)、init(param: String) 三个入口
}

// 子类二:只重写了无参指定初始化器
class SubClsTwo: BaseCls {
    // 重写了父类的指定初始化器①
    override init() {
        super.init()   // 必须先向上调用父类对应初始化器
    }
    // 由于子类现在有了自己的指定初始化器,规则发生变化:
    // 不再自动继承父类的其他初始化器(init(param:) 和 init(param:) 的便利版都不会自动过来)
}

// 子类三:新增了一个带 Bool 参数的指定初始化器
class SubClsThree: BaseCls {
    // 新的指定初始化器
    init(param: Bool) {
        super.init()   // 向上调用父类指定初始化器(这里选了 init())
    }
    // 同样因为"子类定义了指定初始化器",父类所有初始化器都不会自动继承
}

// 子类四:把父类两个指定初始化器全部重写一遍
class SubClsFour: BaseCls {
    // 重写父类指定初始化器②
    override init(param: Int) {
        super.init(param: param)   // 向上调用父类对应版本
    }
    
    // 重写父类指定初始化器①
    override init() {
        super.init()
    }
    // 由于两个指定初始化器都被显式重写,父类便利初始化器 init(param: String)
    // 仍然不会自动继承(便利初始化器只能被"自动继承"当子类没写任何指定初始化器时)
}

构造方法的安全性检查

感觉要记的话比较麻烦,碰到了再看

可失败构造方法与必要构造方法

  • 当传递的参数不符合要求时,开发者需要让这次构造失败;简单说,就是允许构造方法返回nil;这是可以使用init?的可失败构造方法
  • 如果一个类中的构造方法被require修饰,定义为必要构造方法,则其子类必须实现这个构造方法(继承和覆写也算)
swift 复制代码
class Check {
    var property : Int
    required init(property: Int) {
        self.property = property
    }
    init?(param : Bool) {
        guard param else {
            return nil
        }
        self.property = 1
    }
    
    var name : Int = {return 9 + 6}()
}

var check = Check(param: false)

构析方法

实例要销毁时调用;应该对应oc中的decalloc();

swift中调用deinit ()

swift 复制代码
class Temp {
    deinit {
        print("deinited ")
    }
}
var temp : Temp? = Temp ()
temp = nil

内存管理和异常处理

自动引用计数

  • 在swift中普通变量和常量的作用域往往只在离其最近的大括号内;属性和实例的生命周期保持一致
swift 复制代码
func Test () {
    var a = 10
    while a > 0 {
        a -= 1
        var b = 2
    }
    if a < 0 {
        var c = 3
    }
}

class TestClass {
    var name: String = "HS"
}

var obj:TestClass? = TestClass()
obj = nil
  • 引用计数这一块和oc差不多,没有写例子了

循环引用及其解决方法

循环引用时ARC下最常见的内存泄漏场景

swift 复制代码
class ClassOne {
    var cls :ClassTwo?
    init(cls: ClassTwo? = nil) {
        self.cls = cls
    }
    deinit {
        print("ClassOne deinit")
    }
}

class ClassTwo {
    var cls : ClassOne?
    init(cls: ClassOne? = nil) {
        self.cls = cls
    }
    deinit {
        print("ClassTwo deinit")
    }
}
var obj: ClassOne? = ClassOne()
var obj2 :ClassTwo? = ClassTwo(cls: obj)
obj?.cls = obj2 //可选链语法,如果obj不为nil,则赋值;否则不执行任何操作
obj = nil
obj2 = nil

上面的代码执行完后,不会执行实例对象的deinit方法;因为obj引用了obj2,obj2又引用了obj,构成了循环引用

解决方法和oc一样,使用weak关键字;使用weak关键字修饰的引用类型数据在传递时不会使引用计数加1

swift 复制代码
class ClassOne {
    weak var cls :ClassTwo?
    init(cls: ClassTwo? = nil) {
        self.cls = cls
    }
    deinit {
        print("ClassOne deinit")
    }
}

class ClassTwo {
    var cls : ClassOne?
    init(cls: ClassOne? = nil) {
        self.cls = cls
    }
    deinit {
        print("ClassTwo deinit")
    }
}
var obj: ClassOne? = ClassOne()
var obj2 :ClassTwo? = ClassTwo(cls: obj)
obj?.cls = obj2 //可选链语法,如果obj不为nil,则赋值;否则不执行任何操作
obj = nil
obj2 = nil

原理参考oc中的weak

  • swift中的weak关键字只能修饰Optional类型的属性,被弱引用的实例释放后,这个属性会被自动设置为nil
  • 如果属性的类不是optional类型的,我们就不能使用weak;为了解决循环引用,就需要用到另一个关键字unowned(无主引用)
swift 复制代码
class ClassOne {
    unowned var cls :ClassTwo
    init(cls: ClassTwo) {
        self.cls = cls
    }
    deinit {
        print("ClassOne deinit")
    }
}

class ClassTwo {
    var cls : ClassOne?
    init(cls: ClassOne? = nil) {
        self.cls = cls
    }
    deinit {
        print("ClassTwo deinit")
    }
}
var obj2: ClassTwo? = ClassTwo()
var obj :ClassOne? = ClassOne(cls: obj2!)
obj2!.cls = obj!
obj = nil
obj2 = nil
  • 无主引用与弱引用最大的区别是unowned修饰的属性所引用的实例被销毁时,再次使用这个实例,程序会直接崩溃;
  • 与weak相比,unowned的安全性更强,weak的兼容性更好

无主引用最佳的引用场景:无主引用与隐式拆包语法相结合可以使两个类中互相引用的属性都是非Optional值(虽然不知道具体是哪来干嘛用的)如下:

swift 复制代码
class ClassThree {
    unowned var cls : ClassFour
    init(cls: ClassFour) {
        self.cls = cls
    }
    deinit {
        print("ClassThree deinit")
    }
}

class ClassFour {
    var cls : ClassThree!
    init() {
        cls = ClassThree(cls: self)
    }
    deinit {
        print("ClassFour deinit")
    }
}
var obj5 : ClassFour? = ClassFour()
obj5 = nil

闭包中的循环引用

逻辑可以参考oc中block的循环引用,一样的

swift 复制代码
class MyClassSix {
    var name :String = "HS"
    lazy var closure : () -> Void = {
        print(self.name)
    }
    deinit {
        print("MyClassSix deinit")
    }
}
var obj6 : MyClassSix? = MyClassSix()
obj6?.closure
obj6 = nil
  • 和oc不一样的是,swift专门为闭包结构提供了捕获列表,来对闭包内使用到的变量或者实例进行弱引用或无主引用的转换
  • 捕获列表在结构上需要紧跟在闭包的起始大括号后,使用中括号包围,如下:
swift 复制代码
class MyClassSix {
    var name :String = "HS"
    lazy var closure : () -> Void = {
        [unowned self] () -> Void in
        print(self.name)
    }
    deinit {
        print("MyClassSix deinit")
    }
}
var obj6 : MyClassSix? = MyClassSix()
obj6?.closure
obj6 = nil

异常的抛出和传递

开发者可以编写自定义的枚举类型,使其遵守Error协议来描述所需求的异常类型

  • 在代码中通过throw关键字进行了异常的抛出,抛出的异常如果不进行捕获解决,程序就会断在抛出异常的地方(断点)
swift 复制代码
enum MyError: Error {
    case DesTroyError
    case NormalError
    case SimpleError
}

print("should error")
throw MyError.DesTroyError
print("finish")
  • 函数中产生的异常只能在函数内部解决,开发者可以使用throws关键字将此函数声明为可抛异常函数,这样就允许开发者在函数外部解决函数内部抛出的异常
  • 执行可抛异常函数需要添加try关键字
    如下:
swift 复制代码
func MyFunc (param : Bool) throws->Void {
    if param {
        print("success")
    } else {
        throw MyError.NormalError
    }
}

try MyFunc(param: false)

异常的捕获和处理

swift提供了3种异常处理方法:

  • 使用do-catch结构来捕获处理异常
  • 将异常映射为optional值
  • 终止异常传递
    do-catch结构:
swift 复制代码
do {
    try MyFunc(param: false)
} catch MyError.DesTroyError {
    print("摧毁错误")
} catch MyError.NormalError {
    print("普通错误")
} catch MyError.SimpleError {
    print("简单错误")
}

将异常映射为optional值:

swift 复制代码
var temp  = try? MyFunc(param: false)
if temp == nil {
    print("失败了")
} else {
    print("成功了")
}

if let _ = try? MyFunc(param: false) {
    print("成功了")
} else {
    print("失败了")
}

终止异常传递:

swift 复制代码
try! MyFunc(param: true)

延时执行结构

使用延时执行语句可以保证无论函数因为何种原因结束,在结束前都会执行延时结构块种的代码 (比如说抛出异常中断)

swift 复制代码
func TemFunc () throws-> Void {
    defer {
        print("finish")
    }
    print("handle")
    throw MyError.DesTroyError
}

try TemFunc()
  • 所以延时执行语法通常引用于代码块结束前必须执行某段操作的场景中

类型转换,泛式,扩展与协议

类型检查和转换

swift语言中的类型检查

类型检查使用is关键字

swift 复制代码
var str = "HS"
if str is String {
    print("str is string")
}

对于有继承关系的类,类型检查有如下原则 :

  • 子类实例进行父类类型的检查可以检查成功
  • 父类实例进行子类类型的检查不可以检查成功
swift 复制代码
class BaseClass {
    
}

class MyClass : BaseClass {
    
}

var cls1 = BaseClass()
var cls2 = MyClass()
if cls1 is MyClass {
    print("cls1 is MyClass")
}

if cls2 is BaseClass {
    print("cls2 is BaseClass")
}

swift语言中的类型转换

swift类型转换的关键字是as关键字

swift语言中的类型转换有着向上兼容与向下转换的原则 :

  • 一个父类类型的集合可以接收子类类型的实例
  • 在使用第1条原则中父类集合中的实例时,可以将其转换为子类类型

如下:

swift 复制代码
class MyClass : BaseClass {
    var name : String?
}

class MySubClassOne : MyClass {
    var cout : Int?
}

class MySubClassTwo : MyClass {
    var isBiger : Bool?
}

var obj1 = MyClass()
obj1.name = "HS"
var obj2 = MySubClassOne()
obj2.cout = 100
var obj3 = MySubClassTwo()
obj3.isBiger = true

var array: [MyClass] = [obj1, obj2, obj3]
for var i in 0..<array.count {
    var obj = array[i]
    if obj is MySubClassOne {
        print((obj as! MySubClassOne).cout)
        continue
    }
    if obj is MySubClassTwo {
        print((obj as! MySubClassTwo).isBiger)
        continue
    }
    if obj is MyClass {
        print(obj.name)
    }
}

简单说就是:子类可以放入父类的容器类,取出后仍可以识别出子类的类名

  • as?较安全,转换失败会返回nil;as!不可以

Any与AnyObject类型

可以使用AnyObject作为引用类型的通用类型;

Any可以作为任意类型的通用类型

swift 复制代码
class MyClassOne {
    
}
class MyClassTwo {
    
}
class MyClassThree {
    
}
var clsOne = MyClassOne ()
var clsTwo = MyClassTwo()
var clsThree = MyClassThree()
var clsArray : Array<AnyObject> = [clsOne,clsTwo,clsThree]
for obj in clsArray {
    if obj is MyClassOne {
        print("MyClassOne")
    } else if obj is MyClassTwo {
        print("MyClassTwo")
    } else if obj is MyClassThree {
        print("MyClassThree")
    }
}

泛型

泛型通常表示一种未定的数据类型;

泛型可以作为函数的参数,也可以在声明集合类型时设置元素类型

swift 复制代码
func exchange<T> (param01 : inout T, param02 : inout T) {
    let temp = param01
    param01 = param02
    param02 = temp
}

var p1 = "15"
var p2 = "40"
exchange(param01: &p1, param02: &p2)
print(p1,p2)

对泛型进行约束

前面使用的都是完全泛型,开发者并没有对其进行任何约束,其可以表示任何类型

swift可以通过两种方法进行约束:

  • 通过继承基类或者遵守协议进行约束
  • 通过where子句来进行约束

使用继承方式约束:

swift 复制代码
class MyClass {
    
}

struct Stack<ItemType: MyClass> {
    var item : [ItemType] = []
        mutating func push(param : ItemType) {
            self.item.append(param)
        }
        mutating func pop () -> ItemType {
            return self.item.removeLast()
        }
}

使用遵守协议的方式约束 :

swift 复制代码
protocol MyProtocol {
    
}

struct Stack<ItemType: MyProtocol> {
    var item : [ItemType] = []
        mutating func push(param : ItemType) {
            self.item.append(param)
        }
        mutating func pop () -> ItemType {
            return self.item.removeLast()
        }
}

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

swift 复制代码
protocol MyProtocol {
    associatedtype ItemType
    var param : ItemType { get set }
    func printParam (param : ItemType) -> Void
}

class MyClass : MyProtocol {
    var param: Int {
        get {
            return 100
        }
        set(newVal) {
            print("set")
        }
    }
    func printParam(param: Int) {
        print(param)
    }
}

使用where子句与泛型结合,可以添加更严格的约束

swift 复制代码
// 定义一个泛型类 MyClassTC,它有两个泛型参数 T 和 C,这两个参数都遵循 BinaryInteger 协议
class MyClassTC<T, C> where T : BinaryInteger, C :BinaryInteger {
    // 定义一个类型为 T 的属性 param01
    var param01 : T
    // 定义一个类型为 C 的属性 param02
    var param02 : C
    
    // 类的初始化方法,接收两个参数 param01 和 param02,分别用于初始化类的两个属性
    init(param01: T, param02: C) {
        // 将传入的 param01 赋值给类的属性 param01
        self.param01 = param01
        // 将传入的 param02 赋值给类的属性 param02
        self.param02 = param02
    }
}

// 创建一个 MyClassTC 类的实例 obj9,传入的泛型参数为 Int 类型(因为 1 是 Int 类型的字面量)
// 这里 T 和 C 都推断为 Int 类型,因为传入的两个参数都是 1
var obj9 = MyClassTC(param01: 1, param02: 1)

扩展与协议

使用扩展进行补充

扩展可以为已经存在的数据类型进行新功能的追加,协议时对新功能属性与方法的声明的添加

swift 复制代码
class MyClass {
    var name : String
    var age :Int
    init() {
        self.name = "HS"
        self.age = 24
    }
}

extension MyClass {
    var nameAndAge : String {
        return "\(self.name) \(self.age)"
    }
    convenience init (name : String, age : Int) {
        self.init()
        self.name = name
        self.age = age
    }
}

var obj = MyClass()
obj.nameAndAge

var obj2 = MyClass(name: "ZYH", age: 24)

extension MyClass {
    func logName () -> String {
        print(name)
        return name
    }
    
    class func logClassName () {
        print("MyClass")
    }
}
var obj3 = MyClass()
obj3.logName()
MyClass.logClassName()

如果对值类型进行扩展,可以使用mutating关键字修饰方法,使得在方法内部可以直接修改当前实例本身

swift 复制代码
extension Int {
    mutating func change () {
        self = self * self
    }
}
var cout = 3
cout.change()
print(cout)

可以使用扩展来使某个类遵守一个协议

如果使用扩展使某个数据类型遵守了一个协议,在此扩展中就需要实现协议中的方法

swift 复制代码
protocol MyProtrol {
    func myFunc() ;
}

extension MyClass : MyProtrol {
    func myFunc() {
        print("MyClass")
    }
}

var cls = MyClass()
cls.myFunc()

协议的特点,应用

协议约定了一系列的属性或方法,遵守协议的数据类型为其提供真正的实现

swift 复制代码
protocol ProtocolNew {
    var name : String { get }
    var age : Int { get set}
    var nameAndAge : String { get }
    static var className : String {get}
}

class ClassNew : ProtocolNew {
    var name: String
    var age: Int
    var nameAndAge: String {
        get {
            return "\(name) \(age)"
        }
    }
    static var className: String {
        get {
            return "MyClass"
        }
    }
    init () {
        name = "HS"
        age = 24
    }
}

protocol ProtocolNewTwo {
    func logName ()
    static func logClassName ()
}

class ClassNewTwo : ProtocolNewTwo {
    var name : String
    var age : Int
    init () {
        name = "HS"
        age = 24
    }
    func logName() {
        print(name)
    }
    static func logClassName() {
        print("ClassNewTwo")
    }
}
  • 协议虽然没有任何的属性和方式的实现,但其可以为作为函数中参数的类型 ,其意义是任意遵守了此协议的数据类型
  • 协议也可以最为某一个集合的元素类型,其意义是集合中所有元素都要遵守此协议
swift 复制代码
protocol MyProtocol {
    var name : String { get }
    var age : Int { get set}
    var nameAndAge : String { get }
    static var className : String {get}
    func logName ()
    static func logClassName ()
}

func test (param : MyProtocol){
    param.logName()
}

var array : Array <MyProtocol>

协议和类拥有相同的继承语法,一个协议继承了另一个协议,它就会拥有父协议中声明的属性和方法

swift 复制代码
protocol ProtocalNewTwo {
    func logName ()
    static func logClassName ()
}

protocol SubProtocal : ProtocalNewTwo {
    
}

如果希望某个协议只能被类遵守,可以使用class关键字来修饰

swift 复制代码
protocol ClassProtocal : class {
    
}

可以使用@obj将协议中约定的方法或属性声明为optional类型;可实现也可不实现

swift 复制代码
@objc protocol ObjcProtocal {
    @objc func logName ()
}

协议与扩展相结合

书上的好像过时了,等遇到再说

相关推荐
其美杰布-富贵-李16 小时前
TabNet: 注意力驱动的可解释表格学习架构
学习·表格数据·tabnet
im_AMBER16 小时前
Leetcode 98 从链表中移除在数组中存在的节点
c++·笔记·学习·算法·leetcode·链表
jamesge201016 小时前
kafka学习笔记
笔记·学习·kafka
_李小白16 小时前
【AlohaMini学习笔记】第一天:初见AlohaMini
笔记·学习
LaoZhangGong12316 小时前
学习TCP/IP的第1步:ARP数据包
网络·stm32·学习·tcp/ip·以太网·arp·uip
Hooray1117 小时前
前后端分离_案例学习_Python+Flask+VUE3
后端·python·学习·flask
小二·17 小时前
Python 学习教程(第2篇):用 Flask 开发你的第一个 Web 应用
python·学习·flask
Eternity∞17 小时前
基于Linux系统vim编译器情况下的C语言学习
linux·c语言·开发语言·学习·vim
qq_3597162317 小时前
Openvla的原理学习
学习