本小节的内容稍有点多,这三块内容基本没太大关系,只是笔者想在这一节中把swift的oop收下尾,所以全写在一起了。
值类型和引用类型
和其它语言一样,函数调用时对于参数到底是指针引用还是类似String这样的值传递(不可变),在swift中也需要在结构体、类中进行引用和区分。
引用传递
即多个引用指向同一个内存地址,一变全变。
类
class GreekGod {var name: Stringinit(name: String) {self.name = name}
}let hecate = GreekGod(name: "Hecate")
let anotherHecate = hecate
anotherHecate.name = "AnotherHecate"
结构体
struct Pantheon {var chiefGod: GreekGod
}let hecate = GreekGod(name: "Hecate")
let pantheon = Pantheon(chiefGod: hecate)// GreekGod的name值为Hecate
let zeus = GreekGod(name: "Zeus") // GreekGod的name值为Zeus
pantheon.chiefGod = zeus //这行代码会报错,因为结构体中的属性相当于let类型的,一旦初始化后不能改变
hecate.name = "Trivia" //这样的话值可以改变
let greekPantheon = pantheon
greekPantheon.chiefGod.name // Trivia
复制
swift未提供任何深复制的方法,如果需要的话需要自己写代码。
浅复制
其原理大概如下所示:
let athena = GreekGod(name: "Athena")//定义了一个数组:[{name "Athena"}, {name "Trivia"}, {name "Zeus Jr."}]
let gods = [athena, hecate, zeus]
let godsCopy = gods
gods.last?.name = "Jupiter"//以下输出全是:[{name "Athena"}, {name "Trivia"}, {name "Jupiter"}]
gods
godsCopy
- 值类型相等的比较用 ==
- 引用类型相等的比较用 ===,内存地址比较
深复制
//: Playground - noun: a place where people can playimport Cocoafileprivate class IntArrayBuffer {var storage: [Int]init() {storage = []}init(buffer: IntArrayBuffer) {storage = buffer.storage}
}struct IntArray {private var buffer: IntArrayBufferinit() {buffer = IntArrayBuffer()}private mutating func copyIfNeeded() {if !isKnownUniquelyReferenced(&buffer) {print("Making a copy of \(buffer.storage)")buffer = IntArrayBuffer(buffer: buffer)}}mutating func insert(_ value: Int, at index: Int) {copyIfNeeded()buffer.storage.insert(value, at: index)}mutating func append(_ value: Int) {copyIfNeeded()buffer.storage.append(value)}mutating func remove(at index: Int) {copyIfNeeded()buffer.storage.remove(at: index)}func describe() {print(buffer.storage)}
}var integers = IntArray()
integers.append(1)
integers.append(2)
integers.append(4)
integers.describe()print("Copying `integers` to `ints`")
var ints = integers
print("Inserting into `ints`")
ints.insert(3, at: 2)integers.describe()
ints.describe()/*
[1, 2, 4]
Copying `integers` to `ints`
Inserting into `ints`
Making a copy of [1, 2, 4]
[1, 2, 4]
[1, 2, 3, 4]
*/
内存ARC管理
Swift会自动处理好大部分内存问题,但并没有使用垃圾回收器(gc)。它使用的是引用计数系统(现在是自动计数,在新版本的系统中不能使用原来的retain等方法了,称为ARC技术-automatic reference counting),当计数为0时会释放内存,然后自动调用deinit()
方法,在deinit这个方法中一般也会做一些资源释放的操作。
概述
Swift是在ARC的基础上构建的,另外Swift也没有开放获取引用计数的接口。
无论是ObjectiveC还是Swift都会存在两种数据类型,值和引用。所谓的内存管理就是对上述两种不同的数据类型进行管理操作。
- 值类型:枚举和结构属于值类型,他们的的内存分配和管理比较简单,新建值类型的实例时,系统会自动为实例分配合理大小的内存。任何传递实例的动作都会创建实例的副本。当实例不再使用时,Swift会自动回收内存。
- 类对象:也称引用类型,新建类的实例时系统也会自动分配内存给类实例,但是在传递过程中不会复制副本,而是引用指向同一个地址指针,无论在任何地方修改类实例,所有的引用都会看到变化。
比如下面的示例,Person为一个普通的自定义类对象
class Person : CustomStringConvertible {let name: Stringinit(name: String){self.name = name;}deinit{print("\(self) is being destroy")}
}
用下面的代码调用
//~~ created Optional (Person(Bob))
var bob: Person? = Person(name:"Bob")//~~ (Person(Bob) is is being destroy
bob = nil //这行代码执行时会调用 deinit 方法
内存泄露
内存泄露一般是由循环引用引起的,比如下面的代码:
class Asset : CustomStringConvertible {let name: Stringlet value: Doublevar owner: Person? //强引用 Personvar description: String {if let actualOwner = owner {return "Asset(\(name), worth \(value), owned by \(actualOwner))"} else {return "Asset(\(name), worth \(value), not owned by anyone)"}}init(name: String, value: Double) {self.name = nameself.value = value}deinit {print("\(self) is being deallocated")}
}
class Person : CustomStringConvertible {let name: Stringvar assets = [Asset]() //强引用 Assetvar description: String {return "Person(\(name))"}init(name: String) {self.name = name} deinit {print("\(self) is being deallocated")}//人拥有的资产func takeOwnership(of asset: Asset) {asset.owner = self //创建资产的归属,反向引用assets.append(asset)}
}
测试代码
//创建人
var bob: Person? = Person(name: "Bob")//创建三个资产
var laptop: Asset? = Asset(name: "Shiny Laptop", value: 1_500.0)
var hat: Asset? = Asset(name: "Cowboy Hat", value: 175.0)
var backpack: Asset? = Asset(name: "Blue Backpack", value: 45.0)//创建资产的归属,正向引用
bob?.takeOwnership(of: laptop!)
bob?.takeOwnership(of: hat!)backpack = nil
当执行backpack = nil时,只有backpack被回收了,但它创建的asset.owner = Person的关系中的的bob对象并没有被释放。因为bob是一个引用类型,但此时这些资产实例无法访问了,但内存不会被回收,因为每个实例的引用计数都大于0。
引用类型
在Swift中一共有两种引用类型,有些语言会更多,比如java有4种之多。
- strong:强引用,引用一次会增加一次计数;默认情况下所有的引用都是强引用类型,
- weak:弱引用不会增加所指向实例的计数;
强引用
可通过nil赋值手动释放内存,但会比较麻烦,这种就怕被循环引用,因为循环引用外外层对象释放了,但内层对象有可能不被释放,进而造成内存泄漏(memory leak)。比如下面的代码,一旦实例化后就很难处理。
弱引用
解决上述循环依赖不释放内存的情况的方案一种是手动设置为nil,但很容易出错,所以Swift提供了弱引用类型来解决这个问题。如下面的代码:
class Person : CustomStringConvertible {let name: Stringlet accountant = Accountant()weak var assets = [Asset]() //用weak关键字来声明弱引用。
}
这样在上例中,只有在main函数中实例化var bob: Person? = Person(name: “Bob”)时bob的引用计数才会+1,在Assert中的引用都不会增加计数,这样当把bob=nil时,计数就会降为0,自然也就全部释放了。
弱引用有以下几点需要注意:
- 弱引用必须用var声明,不能用let;
- 弱引用必须声明为可空类型,因为能变成nil的类型只有可空类型;
ObjectiveC和Swift混合调用
因为Swift是一门很新的语言,所以在开发中有一种很常见的情况是,需要在Objective-C实现的现有工程基础上工作。
objective-c调用swift代码
ObjectiveC调用swift代码可能会存在一些问题,另外ObjectiveC只能调用swift的类不能调用struct结构体。
详细步骤
- 创建一个Command Line Tool类型的项目,名称为 swift2objective ,工程代码选择Objective-C语言;
- 创建名为"Contacts.swift"的文件,这个类必须要继承一个ObjC的类,一般选择继承NSObject对象
- 创建.swift文件时,XCode默认提示要生成一个桥接头文件"swift2objective-Bridging-Header.h",默认为空内容,此文件作用是暴露接口,全局一个;
- 在.m文件中引入头文件,此例中为"#import “Contacts-Swift.h”(一种规范命名方式),此文件作用是暴露.swift代码,注意这个不是上面生成的头文件,是编译时生成的一个动态文件;
创建Swift文件
- 创建名为 Contacts.swift 的文件时,生成一个桥接文件,这个文件的命名由:工程名称 + -Bridging-Header.h,如下:
在Contacts.swift文件中写入如下代码:
//~~~Contacts.swift
import Foundation//要从 Objective-C调用Swift,继承Objective-C类这一步是必要的,swift必须用类,因为结构体对于OjeC是不可见的
class Contacts:NSObject{let name: String = "aaa"@objc public func sayHello(){ //注意前面的@ojbc一定要写,否则调不到print(name);}
}
调用Swift类
在main.m文件中添加如下代码
//~~~ main.m
#import <Foundation/Foundation.h>//注意这行引用
#import "swift2objective-Swift.h"int main(int argc, const char * argv[]) {@autoreleasepool {Contacts *contact = [[Contacts alloc] init];[contact sayHello];}return 0;
}
swift2objective-Swift.h 是一个编译期生成的文件,在源码中看不到,可从项目属性中设置,如下图:
上面的程序会输出如下代码
swift调用objective-c代码
swift可以调用全部的ObjectiveC类库
详细步骤
接着上面的工程,
- 创建名为"Address"的Cocoa class类,它会生成Address.m和Address.h两个文件;
- 在上面生成的"swift2objective-Bridging-Header.h"桥接文件中添加"#import “Address.h”;
- 然后就可以在Contacts.swift文件中调用了
创建Cocoa class文件
#import <Foundation/Foundation.h>NS_ASSUME_NONNULL_BEGIN@interface Address : NSObject- (void) printContact;@endNS_ASSUME_NONNULL_END
在桥接头文件中引入.h声明文件
这个文件中只一行代码,桥接文件的作用是把ObjectiveC代码暴露给swift代码。
//~~~swift2objective-Bridging-Header
//
// Use this file to import your target's public headers that you would like to expose to Swift.
//
#import "Address.h"
swift调用Cocoa class类
修改Contacts.swift文件,源码如下:
//~~~ Contacts.swift
import Foundation//要从 Objective-C调用Swift,继承Objective-C类这一步是必要的,swift必须用类,因为结构体对于OjeC是不可见的
class Contacts:NSObject{let name: String = "aaa"@objc public func sayHello(){ //注意前面的@ojbc一定要写,否则调不到print(name);Address.init().printContact(); //调用Objective-C代码}
}
测试结果如下: