Swift-内存管理与异常处理

216 阅读7分钟

内存

在Swift语言中,也是使用自动引用计数来管理内存。任何变量和常量都有作用域,普通变量和常量的作用域往往只在离其最近的大括号内。属性则特殊一些,其和具体的实例关联,和实例的生命周期保持一致。

对类实例进行不当的引用会造成内存泄露,最常见的就是循环引用,对于两个相互引用的实例,一旦造成循环引用,则系统无法完成对其内存的释放和回收。Swift语言提供了关键字weak来解决这一问题。weak关键字的作用是在使用这个实例的时候并不会增加对实例的引用计数。

因此,弱引用会有一个特点,只能修饰optional类型的属性,被弱引用的实例释放后,这个属性会自动被置为nil。

除了weak,Swift语言还提供了一个关键字unowned来处理非optional类型的属性循环引用的问题。两个关键字的区别在于,unowned总是假定属性不为nil,如果属性所引用的实例被销毁了,再次使用这个属性会造成crash。而weak属性允许为nil,同样的场景再次访问就不会crash。

除了两个类实例之间会产生相互引用,在一个类中,如果有一个属性时闭包,也会产生相互引用。闭包语法结构特殊,在其中使用引用类型的实例都会使其引用计数加1.如果在闭包中使用self关键字,则会对类实例本身进行引用计数加1.由于闭包又是当前类的一个属性,闭包无法销毁则当前类也无法销毁,反之,当前类无法销毁,闭包就无法销毁,如此产生循环引用。

为了解决这类问题,Swift语言专门为闭包提供了捕获列表来对闭包内使用到的变量或者实例进行weak引用或者unowned引用的转换。示例代码如下:

class MyClass {

    var name:String = "jj"

    lazy var closure:() -> Void = {

        [unowned self]() -> Void in

            print(self.name)

    }

    deinit {

        print("MyClass deinit")

    }

}


var obj:MyClass? = MyClass()

obj!.closure()

obj = nil

异常

在Swift语言中,所有的错误和异常都由Error协议来指定,可自定义异常类型,通过throw关键字进行异常抛出。抛出的异常如果不进行捕获解决,程序会断在抛出异常的地方。

默认情况下,函数中的异常只能在函数内部解决,也可以使用throws关键字将函数声明为可抛异常函数,如此声明则允许在函数外部解决函数内部抛出的异常。

enum MyError:Error {

    case DesTroyError

    case NormalError

    case SimpleError

}

func MyFunc(param:Bool) throws -> Void {

    if param {

        print("success")

    } else {

        throw MyError.NormalError

    }

}

Swift提供了3种异常处理的方法,第一种是使用do-catch结构来捕获异常,第二种是将异常映射为optional值、第三种是终止异常传递。还需要使用到try关键字,作用是试图执行一个可能抛出异常的函数。

首先do-catch结构是比较常用的处理异常的方式,可以根据异常类型分别提供处理方案:

do {

    try MyFunc(param: false)

} catch MyError.SimpleError {

} catch MyError.NormalError {

    print("NormalError")

} catch MyError.DesTroyError {     

} catch {
}

第二种方式,将异常映射成optional值,如果函数正常执行无异常则正常返回,如果执行出错抛出异常则返回optional值nil。使用try?来调用函数将异常映射为optional值:

if let _ = try? MyErrorFunc(param: false) {

    print("success")

} else {

    print("fail")

}

第三种是比较极端的情况,如果开发者可以保证函数一定不会抛异常时,可以使用try!来强制终止异常的传递,然而这么做有一定风险,如果确实执行出错抛出异常,就会产生运行时出错误:

try! MyErrorFunc(param: false)//error: Execution was interrupted, reason: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0).

延时执行

Swift语言中还提供了一种延时执行的结构,不同于lazy,是在函数中使用,可以保证延时执行的代码块在函数要结束时执行。因为函数可能会提前break或return,所以可使用defer延时执行结构 ,保证在函数结束时一定会执行一些操作

func MyDeferFunc() throws -> Void {

    defer {

        print("finish")

    }
    throw MyError.NormalError
    print("handle")

}

类型检查与转换

Swift语言中要判断某个实例是否属于某个类型,可以使用is关键字,使用is关键字组成的判断语句将返回一个bool值。

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

  • 子类进行父类类型的检查可以成功
  • 父类进行子类类型的检查不可以成功
class BaseClass {    

}

class MyClass : BaseClass {

}

var cl1 = BaseClass()

var cl2 = MyClass()

if cl1 is MyClass {

    print("父类进行子类类型的检查不可以成功")

}

if cl2 is BaseClass {

    print("子类进行父类类型的检查可以成功")

}

对于类型转换,使用as关键字,与类型检查相似,也对继承关系的类,有转换的原则:

  • 一个父类的集合可以接受子类类型的实例
  • 在使用父类集合的实例时,可以将其转化为子类类型
class BaseClass {

}

class MyClass : BaseClass {

    var count:Int?

}

class MyClassOne : BaseClass {    

}

var cl1 = BaseClass()

var cl2 = MyClass()

cl2.count = 99

var cl3 = MyClassOne()

var array:[BaseClass] = [cl1, cl2, cl3]

for i in 0..<array.count {

    var obj = array[i]

    if obj is MyClass {

        print((obj as! MyClass).count!)        

    }

}

在使用类型转换时,需要使用as?as!as?是一种比较安全的转换方式,会讲类型转换后的结果映射成optional值,而as!是一种强制转换的方式,如果转换失败会产生运行时错误。

Any AnyObject类型

通用类型类似OC中的id类型,Swift中的AnyObject类型可以作为引用类型的通用类型,而Any可以描述任意类型,包括值类型和引用类型。

泛型

简单来说,泛型用来表达一种未定的数据类型。举个例子:

func exchange<T>(param1: inout T , param2: inout T) {

    let tmp = param1

    param1 = param2

    param2 = tmp

}

上面的函数,可将两个inout且类型相同的参数进行值的呼唤,这个函数的功能并没有对参数的类型有具体的要求,所以可以用泛型来解决这个问题。

泛型除了可以定义函数参数外,对于定义数据类型也有十分重要的意义。分析一下array和dictionary结构体的实现,可以发现,在声明这类集合类型时,开发者可以自行设置所要存储的元素的类型。模仿系统集合类型的实现思路,实现一个自定义结构体类型:

struct Stack<ItemType> {

    var items:[ItemType] = []

    mutating func push(param:ItemType) {

        self.items.append(param)

    }

    mutating func pop() -> Void {

        self.items.removeLast()

    }

}

var obj = Stack<Int>()

obj.push(param: 5)

var objS = Stack<String>()

objS.push(param: "hi")

mutating关键字,用来修饰方法,使得在值类型的方法中,方法内部可以直接修改当前实例本身。不使用关键字修饰,编译器则会报错:Cannot use mutating member on immutable value: 'self' is immutable

泛型可以表达任何数据类型,也可以对泛型进行约束,就是泛型只允许是我们期望的几种数据类型,或是满足某些条件的类型。可以通过继承或是protocol来进行约束,或通过where子句来进行约束。

使用继承方式约束泛型,必须为某一基类或者继承与基类的子类:

//定义基类
class BaseClass {
}
//只有BaseClass的实例或其子类的实例才可以成为Stack中的元素
struct Stack<ItemType:BaseClass> {
    ...
}

使用遵守协议的方式约定泛型,协议中可以定义一些方法和属性,遵守协议的类型需要对其中定义的方法和属性进行实现

protocol MyProtocal {
}
//只有遵守了MyProtocal协议的类型才可以成为Stack中的元素
struct Stack<ItemType:MyProtocal> {
    ...
}

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

//T C都要遵守协议
class MyClassTC<T, C> where T:MyProtocal,C:MyProtocal {

    var param1:T

    var param2:C

    init(param1:T, param2:C) {

        self.param2 = param2

        self.param1 = param1

    }

}

扩展与协议

使用扩展对已经存在的数据类型进行补充,扩展支持如下功能:

  • 添加计算属性
  • 定义实例方法和类方法
  • 定义新的构造方法
  • 定义下标方法
  • 定义嵌套类型
  • 使已有类型遵守协议(扩展中需要实现协议中的方法)
  • 对协议进行扩展新的方法和属性

协议的使用和OC基本一致,在其中可以声明属性和方法,可以是实例方法也可以是静态方法。协议和类一样拥有继承的语法,因此可以使用is ,as等关键字进行检查和转换。