泛型编程
认知泛型 Generics
泛型是一种参数化类型的机制(类型站位符),为算法和类型的实现提供了:
更高的复用性
更强的安全型
更好的性能
泛型支持包括:函数、类、结构、枚举
//泛型类型
class Stack<T> {
var items = [T]()
func push(item: T) {
items.append(item)
}
func pop() -> T {
return items.removeLast()
}
}
var stack1=Stack<Int>() //将占位符T换成Int
stack1.push(10)
stack1.push(20)
var s2 = Stack<String>() //使用String版本的函数
s2.push("Hello")
s2.push("World")
print(s2.dynamicType) //将打印出泛型函数实例s2的动态类型,即Stack<String>
//泛型函数
func swap<T>(inout first:T, inout second:T){
let temp=first
first=second
second=temp
}
var data1=100.87
var data2=200.22
swap(&data1, &data2) //泛型函数会根据参数的类型进行自动类型推断
//继承一个泛型类时,替换类型必须要和基类一样(这里都是T)
class ExStack<T> : Stack<T> {
//...
}
//这里基类的占位符是U,子类的是T,这样不可以
class ExStack2<T> : Stack<U> {
//...
}
//基类明确使用Int,子类用占位符这种情况是可以的
class ExStack3<T> : Stack<Int> {
//...
}
//基类明确使用Int,子类不使用泛型也是可以的
class ExStack4 : Stack<Int> {
//...
}
//总结:基类的占位符要么来自子类相同的站位符,要么在继承时先确定好占位符的具体类型。
类型参数实例化
“泛型类型的实例化”即用实际类型替换占位符。泛型类型参数实例化的过程可以采用自动类型推断
Swift采用编译时泛型模型,即在编译时进行泛型类型的实例化,产生新的类型吗或函数码。
如果类型参数是值类型,泛型实例化会为不同的值类型生成不同的类型码(尺寸不同)。
如果类型参数是引用类型,泛型实例化会为所有不同的引用类型生成同一份类型吗(因为引用类型都是相同等尺寸的指针)
协议的关联类型
通过typealias可以为protocol定义一到多个关联类型。
在实现协议时,可将关联类型设置为泛型参数(或自动类型推断)
协议的关联类型可以看作是支持“泛型版的协议”。
protocol Container {
typealias ItemType //关联类型,也是一个占位符,自己命名
func append(item: ItemType)
var count: Int { get }
subscript(i: Int) -> ItemType { get }
}
class Stack<T>: Container {
typealias ItemType=T //ItemType是Container 里面的占位符
var items = [T]()
func push(item: T) {
items.append(item)
}
func pop() -> T {
return items.removeLast()
}
func append(item: T) {
self.push(item)
}
var count: Int {
return items.count
}
subscript(i: Int) -> T {
return items[i]
}
}
泛型约束
类型约束为泛型类型参数提供了更多的功能约定
Swift要求显示的类型约束,编译器在泛型类型参数实例化时会检查约束的有效性。
泛型类型约束支持两类约束
基类约束
协议约束
//协议约束:Comparable用来约束占位符T必须支持可比较大小,即可以使用>或<符号
//如果将Comparable换成一个类,就叫基类约束, 内部只可以实现基类所支持的函数
func max<T:Comparable>(array:[T])-> T {
var value = array[0]
for index in 1..<array.count {
if array[index] > value {
value = array[index]
}
}
return value
}
Where子句约束:指定关联类型必须符合某种协议、或者和其他关联类型相等。
//用where关键字,将T.ItemType约束成支持相等符号的实例
func find<T:Container where T.ItemType: Equatable>(sequence :T, item:T.ItemType)->Bool{
for index in 0..<sequence.count{
if(sequence[index]==item){
return true
}
}
return false
}
函数类型与闭包
**函数类型 **
函数类型定义:(参数类型1,参数类型2,...)->返回值类型
函数类型是一种引用类型,类似于函数指针。可以将函数类型应用于任何使用类型的地方:变量、参数、返回值。
函数类型实例化支持:
全局函数
嵌套函数
成员函数(实例方法 与 静态方法)
func add(x:Double, y:Double)->Double {
return x+y
}
//变量compute是一个函数类型,该函数类型有两个Double参数,返回值是Double类型
var compute: (Double, Double)->Double
compute = add //将函数add赋值给了相同函数类型变量compute,这里的add不用加(),add()表示调用函数。
let result = compute(100,200) //此时调用compute(100,200)等于调用add(100,200)
函数类型的内存图
上图compute在栈空间有一个地址,指向堆空间的内容,堆空间的内容有两个指针,一个是对象指针,如果是实例方法,这个指针指向的是实例对象的地址,如果是全局函数这个指针为0。第二个指针是函数指针,指向函数的地址。
认识闭包Closure
闭包是函数类型的实例,一段自包含的代码块,可被用于函数类型的变量、参数或返回值
三种闭包形式:
全局函数:具名函数,但不捕获任何值
嵌套函数:在函数内部嵌套定义具名函数,可捕获包含函数中的值
闭包表达式:匿名函数类型实例。不具名的代码块,可捕获上下文的值
闭包是引用类型,闭包变量拷贝具有引用语义。
闭包和函数类型实例拥有同样的内存模型。
//闭包表达式,{(first:Rectangle, second:Rectangle)->Bool in return first.width*first.length<=second.width*second.length} 就是一个函数闭包
rects.sort({(first:Rectangle, second:Rectangle)->Bool in
return first.width*first.length<=second.width*second.length
})
闭包表达式
{ (参数1,参数2... )->返回值类型 in
语句块
}
闭包表达式的几种简化缩写形式:
自动类型推断:省略参数类型和返回值类型。
rects.sort({ first, second in
return first.width*first.length<=second.width*second.length
})
单表达式闭包可以省去return关键字。
rects.sort({ first, second in
first.width*first.length<=second.width*second.length
})
使用参数缩略形式$0,$1...省略参数声明和in
rects.sort({ $0.width*$0.length<=$1.width*$1.length
})
将操作符函数自动推导为函数类型
尾随闭包:当闭包表达式为函数最后一个参数,可将其写在括号后。
//尾随闭包,如果只有一个参数,可以把()去掉
//rects.sort(),如果有闭包参数将闭包参数放在最后
rects.sort {
first, second in
fitst.width*first.length<=second.width*second.length
}
自动闭包:不接受任何参数,直接返回表达式的值。允许延迟计算。
var cities = ["Beijing","Shanghai","New York", "Paris","London"]
print(cities.count) //此时的城市数是5
let filter = { cities.removeLast() } //()->String 将闭包赋值给filter,此时还没有执行removeLast()
print(cities.count) //由于上一句并没有执行removeLast()此时的城市数还是5
print("Deleting \(filter())!") //执行了filter的闭包。 显示Deleting London!
print(cities.count) //此时的城市数是4, London被删除掉了
函数类型与闭包的变量捕获
函数类型和闭包可以捕获其所在上下文的任何值:
函数参数
局部变量
对象实例属性
全局变量
类的类型属性
//捕获实例属性
class Rectangle{
var width = 0
var length = 0
func getComputHandler()-> ()->Int {
return {
return self.width*self.length //这个闭包就捕获了实例属性self.width和self.length
}
}
}
//捕获参数或局部变量
func addHandler(step: Int)-> ()->Int{
var sum = 0
return{
sum +=step //这里捕获了参数step和局部变量sum
return sum
}
}
let addByTen = addHandler(10)
print(addByTen()) //显示结果 10
print(addByTen()) //显示结果 20
print(addByTen()) //显示结果 30
如果捕获值生存周期小于闭包对象(参数和局部变量),系统会将被捕获的值封装在一个临时对象里,然后再闭包对象上创建一个对象指针,指向该临时对象。
临时对象和闭包对象之间是强引用关系,生存周期跟随闭包对象。
扩展 Extension
扩展支持为现有类型增加功能(可以没有源代码),支持一下类型:类、结构、枚举、协议
除了不能增加实例存储属性和析构器外(因为扩展不能改变原来内的内存模型),使用扩展可以增加:
计算属性(实例属性和类型属性)
类型存储属性
方法(实例化方法和类型方法)
初始化器
下标
为类型实现协议
内嵌类型
class Rectangle:Shape {
var x:Int
var y:Int
var length:Int
var width:Int
init(x:Int, y:Int, length:Int, width:Int) {
self.x = x
self.y = y
self.length = length
self.width = width
}
}
//对Rectangle类增加一个扩展
extension Rectangle {
static var max = 100
//增加了一个实例计算属性
var area: Int {
return self.length*self.width
}
//增加了一个方法
func moveTo(x:Int, _ y:Int) {
self.x = x
self.y = y
}
//增加了一个新的便捷初始化器
convenience init(x1:Int, y1:Int, x2:Int, y2:Int){
let l = abs(x2 - x1)
let w = abs(y2 - y1)
}
}
使用扩展的注意点
扩展不可以增加实例存储属性,但可以增加类型的存储属性。换言之,扩展不能更改实例对象的内存模型。
扩展只能增加便捷初始化器,不能增加指定初始化器。
扩展中添加新的方法,但不可以override基类方法。
结构和枚举的扩展,如果实例方法更改self,需要声明mutating
协议与扩展
扩展实现协议:在为类型实现扩展时,增加实现某些协议。
实现过程中必须实现协议的所有成员。
也可以添加实现非协议的成员。
//定义协议
protocol Drawable {
var discription: String{
get
}
func draw()
}
//扩展实现协议,必须实现Drawable里的所有成员
extension Rectangle:Drawable{
var discription:String{
return"x=\(x), y=\(y), length=\(length), width=\(width)"
}
func draw() {
print(self.discription)
}
//自己增加的非协议的成员实现
func erase() {
print("erase")
}
}
扩展协议类型:可以扩展协议来提供方法和属性的默认实现。
协议扩展内的成员都必须提供默认实现,可以调用原协议内的成员
所有遵守协议的类型自动继承这些默认实现,但可以提供自己独特的实现
扩展协议时,也可以添加where子句约束
//扩展协议类型
extension Drawable{
//默认实现
func encode()( {
print("Drawable.encode")
print(self.discription)
}
}
使用扩展的建议
使用场景
适合在没有源代码的情况下,向已经封装的类中添加方法或属性。
为一个类在某些特殊场景下增加功能。
对于复杂的大型文件分割实现。
添加扩展
自己创建的类
系统的类
第三方库
内存管理
ARC是Swift默认的内存管理机制,其针对堆上的对象,由编译器自动生成操作引用计数的指令(retain或release)来管理对象的引用计数增加或减少。程序员无法手工控制。
哪些类型的对象受ARC管理:类class、闭包closure(存放在堆上的对象)
哪些对象不受ARC管理:基础数值类型,结构struct、枚举enum、元组tuple
关于Auto Release
Swift原生对象不支持autorelease消息,也就没有必要使用Autorelease Pool来管理内存峰值。
如果Swift调用Objective-C函数返回autorelease对象,那么如果出现内存峰值过高的风险,仍然需要使用Autorelease Pool来管理
Autorelease Pool在Swift中使用尾随闭包形式实现。
func useObjecAutoreleaseObject(){
if let filepath = NSBundle.mainBundle().pathForResource("2015",ofType:"jpg") {
for _ in 0..<5 {
autoreleasepool {//在闭包内调用autoreleasepool,每当
for _ in 0..<1000 {
let data = NSData.dataWithContentsOfMappedFile(filepath)
}
}
}
}
循环引用于内存泄漏
对象间的循环引用会造成ARC引用计数无法释放被引用的任何一个对象,从而造成内存泄漏。
//假设有一个Computer类,其中有个属性是Monitor类的实例。有一个Monitor类,其中有一个属性是Computer类的实例
var imac:Computer?
var screen:Monitor?
imac = Computer(name:"Jason's iMac")
screen = Monitor(no:29)
//造成循环引用
imac!.display = screen
screen!.device = imac
imac = nil
screen = nil //由于循环引用,这样并不能释放内存
循环引用的内存图
解决循环引用造成的内存泄漏有三种方式:
1.在合适的地方,手动将循环引用解除。
var imac:Computer?
var screen:Monitor?
imac = Computer(name:"Jason's iMac")
screen = Monitor(no:29)
imac!.display = screen
screen!.device = imac
imac!.display = nil //手动的释放了实例imac!中的引用
imac = nil
screen = nil //由于之前手动的释放了一边的引用,所以这里可以释放内存
2.如果允许对象引用为nil,可将引用声明为弱引用(weak)
class Monitor{
var no:Int
weak var device:Computer? //设置成弱引用,弱引用使用时将不再让ARC计数加一,并且这个值可以为nil,如果Computer的实例提前释放的话
init(no:Int) {
self.no = no
print("Monitor init")
}
deinit{
print("Monitor deinit")
}
}
弱引用内存图
3.如果不允许对象引用为nil,可将对象声明为无主引用(unowned)
class Monitor{
var no:Int
unowned var device:Computer //设置无主引用,使用时将不再让ARC计数加一,但是无主引用不可为nil,如果Computer的实例提前释放,将还会指向原来的地址,要确保不要访问,不然会抛出异常
init(no:Int) {
self.no = no
print("Monitor init")
}
deinit{
print("Monitor deinit")
}
}
无主引用的内存模型
弱引用在对象被释放后,ARC会将引用设置为nil。无主引用在对象被释放后,ARC不会设置nil,访问时会抛运行时错误(空悬指针)。
闭包的内存管理
如果闭包满足以下两个条件,将产生循环引用,造成内存泄露:
1.闭包内使用实例成员,会捕获self,产生“从闭包对象->self的引用”。
2.将闭包对象设置为self的属性,产生“从self->闭包对象的引用”。
class Employee {
var name:String
var printer:(()->())? //设置一个闭包属性
init(name:String) {
self.name = name
self.printer = { //初始化闭包值的时候又将self.name捕获了。之后又将这个捕获值赋值给了self里的属性printer,从而形成了循环引用
print("name: \(self.name)")
}
print("Employee init")
}
}
闭包属性循环引用内存图
解决闭包产生的循环引用有以下方式:
1.手工解决循环引用
var employee:Employee?
employee?.printer = nil //手动断开循环指针
employee = nil
2.在捕获列表中指定[weak self]弱引用
class Employee {
var name:String
var printer:(()->())?
init(name:String) {
self.name = name
self.printer = {
[weak self] in //指定了弱引用,只有在闭包里面的捕获列表才可以这么设置
print("name: \(self!.name)")
}
print("Employee init")
}
}
weak self循环引用内存图
3.在捕获列表中指定[unowned self]无主引用
class Employee {
var name:String
var printer:(()->())?
init(name:String) {
self.name = name
self.printer = {
[unowned self] in //指定了无主引用,只有在闭包里面的捕获列表才可以这么设置
print("name: \(self.name)")
}
print("Employee init")
}
}
unowned self循环引用内存图
Weak-Strong Dance
对象在弱引用或无主引用期间,随时“有可能”被释放,从而导致引用为nil,或者抛运行时错误。
为了避免在弱引用使用期间,对象被释放可能导致nil的问题,可以使用Weak-Strong Dance来解决,其核心是延长对象生存周期,确保对象不被释放,有两种方式:
1.将弱引用临时转换为“强引用局部变量”。
{ [weak self] in
if let strongSelf = self {
strongSelf.process("first process")
usleep(500)
strongSelf.process("second process")
}
}
2.使用withExtendedLifetime函数来延长弱引用对象生存周期。
{ [weak self] in
withExtendedLifetime(self) {
self?.process("first process")
usleep(500)
self?.process("second process")
}
}
网友评论