协议
1.协议:协议只给出方法的声明,不给出具体方法的实现过程,协议是方法的集合(计算属性相当于就是方法),谁遵循协议就要定义方法。
2.协议在Swift中的作用:
- 1.能力 - 遵循了协议就意味着具备了某种能力
- 2.约定 - 遵循了协议就一定要实现协议中的方法
- 3.角色 - 一个类可以遵循多个协议,一个协议也可以被多个类遵循,遵循协议就意味着扮演了某种角色,遵循了多个协议就意味着可以扮演多个角色
3.协议与继承的区别在于:Swift中的继承是单一继承(一个类只能有一个父类),如果希望让一个类具有多重能可以使用协议来实现。
例:我们先建一个Father的类,这个类有吃、喝、嫖、赌四种行为(方法)
class Father {
func eat(){
}
func drink(){
}
func wench(){
}
func gamble(){
}
}
然后我们再创建一个Monk的类,和一个Musician的类
protocol Monk {
func eatVegitable()
func chant()
func knockTheBell()
}
protocol Musician{
func playPiano()
func playViolin()
}
最后创建一个Son的类,它继承了Father,并且遵循了Monk、Musician协议
class Son: Father, Monk, Musician {
override func gamble() {
print("正在斗地主")
}
func eatVegitable() {
}
func chant() {
}
func knockTheBell() {
}
func playPiano() {
}
func playViolin() {
}
func steal(){
}
}
这个Son类继承于Father当然就拥有了它的属性,同时它遵循了Monk、Musician协议,就拥有了它们的能力,但前提是必须要实现它们的方法。
所以当我们创建一个Son的对象它就拥有了如下的方法:
4.协议的继承
5.协议的组合
let array: [protocol<Monk,Musician>] = [Son()]
当然协议也有多态。
6.协议的扩展
协议扩展 - 可以在协议扩展中给协议中的方法提供默认实现
也就是说如果某个类遵循了协议但是没有实现这个方法就直接使用默认实现
那么这个方法也就相当于是一个可选方法(可以实现也可以不实现)
extension Fightable{
func fight(){
print("正在打架.")
}
}
总结:协议中全是抽象概念(只有声明没有实现),遵循协议的类可以各自对协议中的计算属性和方法给出自己的实现版本,这样当我们面向协议编程时就可以把多态的优势发挥到淋漓尽致,可以写出更通用更灵活的代码(符合开闭原则)
- 实现开闭原则最关键有两点:
- 1.抽象是关键(在设计系统的时候一定要设计好协议);
- 2.封装可变性(桥梁模式- 将不同的可变因素封装到不同的继承结构中)
- 3.接口(协议)隔离原则:协议的设计一定要小而专不要大而全,协议的设计也要高内聚
7.委托回调和代理模式
关于这两种方法我们可以举一个考生找枪手代考的例子:
//枪手代考: 委托回调
protocol ExamDelegate: class {
func answerTheQuestion()
}
class LazyStudent {
var name: String
weak var delegate: ExamDelegate? //委托方添加一个属性,其类型是遵循了协议的被委托方
init(name: String) {
self.name = name
}
func joinExam() {
print("姓名: \(name)")
delegate?.answerTheQuestion()
}
}
class Gunman: ExamDelegate {
func answerTheQuestion() {
print("奋笔疾书各种答案")
}
}
let stu = LazyStudent(name: "王大锤")
let gun = Gunman()
stu.delegate = gun
stu.joinExam()
//枪手代考: 代理模式
protocol ExamCandidate: class {
func answerTheQuestion()
}
class LazyStudent: ExamCandidate {
var name: String
init(name: String) {
self.name = name
}
func answerTheQuestion() {
}
}
class Gunman: ExamCandidate {
var name: String
var target: LazyStudent? //代理方添加一个属性,其类型是被代理方
init(name: String) {
self.name = name
}
func answerTheQuestion() {
if let stu = target {
print("姓名: \(stu.name)")
print("奋笔疾书答案")
print("提交试卷")
}
}
}
let stu = LazyStudent(name: "王大锤")
let gun = Gunman(name: "骆昊")
gun.target = stu
gun.answerTheQuestion()
** 注意:**就学生和枪手这个例子而言,委托回调相当于学生只是带了个作弊器,而代理模式才是真正的枪手帮学生代考,这两种方法还是有一定的区别的。
结构体
1.我们分别创建一个类和一个结构体:
class Student1 {
var name: String
var age: Int
init (name: String, age: Int){
self.name = name
self.age = age
}
func study(courseName: String){
print("\(name)正在学习.")
}
}
struct Student2 {
var name: String
var age: Int
func study(courseName: String){
print("\(name)正在学习.")
}
mutating func getOlder(){
age += 1
}
}
2.创建对象,找出区别
let stu1 = Student1(name: "罗大号", age: 35)
var stu3 = stu1 //此处内存中仍然只有一个学生对象
stu3.name = "罗小号"
stu3.age = 18
print(stu1.name)
print(stu1.age)
执行结果为:
罗小号
18
Program ended with exit code: 0
创建结构体的对象:
let stu2 = Student2(name: "翠花", age: 26)
var stu4 = stu2 //此处内存中复制了一个学生对象
stu4.name = "荷花"
stu4.age = 18
print(stu2.name)
print(stu2.age)
执行结果为:
翠花
26
Program ended with exit code: 0
总结: 区别1:重点:结构的对象是值类型,类的对象是引用类型,值的类型在赋值的时候会在内存中进行对象的拷贝,引用类型在赋值的时候不会进行对象拷贝,只是增加了一个引用.
区别2: 结构会自动生成初始化方法
区别3: 结构中的方法在默认情况下是不允许修改结构中的属性的,除非加上mutating
结论: 我们自定义新类型是优先考虑使用类而不是结构,除非我们要定义的是一种底层的数据结构
自动引用计数(ARC)
1.我们先创建几个类:
class Person {
init(){
print("创建一个人")
}
deinit{
print("人嗝屁了")
}
}
class Teacher: Person {
override init() {
super.init()
print("创建一个老师")
}
deinit{
print("老师嗝屁")
}
}
class Student: Person{
override init() {
super.init()
print("创建一个学生对象!")
}
deinit {
print("学生对象嗝屁!")
}
}
说明:1.因为创建对象的方式多种多样,所以可以定义多种初始化方法,对象就可以使用多种初始化方法。
2.子类只能调用直接父类的初始化方法,子类构造器必须调用父类的非便利构造器(指派构造器)
2.//创建一个学生对象,然后用stu1去引用它,所以此时学生对象引用计数为1
var stu1: Student? = Student()
//此处没有创建新的学生对象,原来的学生对象的引用计数+1
var stu2 = stu1 var stu3 = stu1
//学生对象引用计数-1
stu1 = nil
//学生对象引用计数-1
stu2 = nil
//学生对象引用计数-1
//当学生对象引用计数为0时,ARC会自动清理内存释放学生对象
//ARC即时性的内存清理 优于java的Garbage Collection(垃圾回收)
stu3 = nil
//弱(weak)修饰的引用不会导致自动引用计数增加,默认是强引用(会增加引用计数)
weak var stu2 = stu1 weak var stu3 = stu1
3.释放内存的方法
- 1.如果想释放内存,程序员可以手动将一个引用赋值成nil
stu1 = nil
- 2.stu是一个局部变量,在函数调用结束后局部变量就消失了,所以学生对象的引用计数也就变成0了,所以会被ARC释放
func foo(){
let stu = Student()
print(stu)
}
foo()
结果如下:
创建一个人
创建一个学生对象!
Day08_17_01.Student
学生对象嗝屁!
人嗝屁了
Program ended with exit code: 0
- 3.引用转移(会导致原来对象上的引用计数-1,新对象+1)
var stu: Person = Student()
stu = Teacher()
stu = Person()
打印结果如下:
创建一个人
创建一个老师
学生对象嗝屁!
人嗝屁了
创建一个人
老师嗝屁
人嗝屁了
Program ended with exit code: 0
从结果可以看出:创建子类对象的时候一定是先创建了父类对象;栈(FIOL)是先进后出的结构。
- 4.自动释放池:通过向atuoreleasepool函数传入一个闭包来实现
autoreleasepool { () -> () in
//自动释放池中的对象引用在池的边界引用计数会收到引用计数-1的消息
//将来做IOS开发是如果在某个地方会创建很多的临时对象
//那么最好在此处设置一个自动释放池避免内存瞬时峰值过高
let stu1 = Student()
let stu2 = stu1
}
离开自动释放池时 stu1会收到引用计数-1的消息,stu2也会收到引用计数-1的消息
4.类与类之间的循环引用
我们来创建两个类(部门和员工)相互引用:
class Dept{
weak var manager: Emp?
init(){
print("创建一个部门")
}
deinit{
print("销毁一个部门")
}
}
class Emp {
var dept: Dept?
init(){
print("创建一个员工")
}
deinit{
print("销毁一个员工")
}
- 如上形成循环引用,导致ARC无法释放内存
解决方法:1.如果程序中出现了类与类之间的双向关联关系 必须将其中一端设置成weak引用,如果允许使用可空类型通常使用weak来破除循环引用
2.如果不允许使用可空类型就必须使用unowned来破除循环引用
class Emp {
weak var dept: Dept?
init(){
print("创建一个员工")
}
deinit{
print("销毁一个员工")
}
或者这样:
class Emp {
unowned var dept: Dept
init(dept: Dept){
self.dept = dept
print("创建一个员工")
}
deinit{
print("销毁一个员工")
}
//需要注意的是如果员工对象关联的部门对象被释放了
//如果还要通过员工对象去操作它所关联的部门对象将导致程序崩溃
// EXC_BAD_ACCESS
泛型
1.泛型(generic) - 让类型不再是程序中的硬代码(写死的东西)
2.定义一个虚拟的类型T(也可以用其它字符代替),调用函数是根据传入的参数类型来决定T到底是什么
func mySwap<T>(inout a: T, inout _ b: T){
(a,b) = (b,a)
}
var x = "hello", y = "good"
mySwap(&x, &y)
print(x,y) //结果:good hello
3.泛型限定
<T: Comparable>限定T类型必须是遵循了Comparable协议的类型
func myMin<T: Comparable>(a: T, _ b: T) -> T {
return a > b ? a : b
}
var z = "hello", s = "good"
print(myMin(z, s)) //打印结果:hello
上述代码中虚拟类型T是不可以直接比较的,但是遵循了Comparable协议后就能比较了。