Swift学习总结
协议
协议是方法的集合,它可以把看似不想关的对象的公共行为放到一个协议中。
协议在Swift开发中大致有三种作用:
1.能力 - 遵循了协议就意味着具备了某种能力;
2.约定 - 遵循了协议就一定要去实现协议中的方法;
3.角色 -一个类可以遵循多个协议,一个协议可以被多个类遵循,遵循协议就意味着扮演了某种角色,遵循多个协议就意味着可以扮演多个角色。
Swift中的继承是单一继承(一个类只能有一个父类),如果希望让一个类具备多重能力可以使用协议来实现。
protocol Singsongs{
func sing()
}
protocol Teach{
func teach()
}
class Student:singSongs{
var name:String
init(name:String){
self.name = name
}
func sing(){
print("\(name)正在唱歌")
}
}
class Teacher:Singsongs,Teach{
func teach(){
print("老师正在教学生唱歌")
}
func Singsongs(){
print("老师正在唱歌")
}
}
协议扩展
可以在协议扩展中给协议中的方法提供默认实现,也就是说如果某个类遵循了协议但是没有实现这个方法就直接使用默认实现,那么这个方法也就相当于是一个可选方法(可以实现也可以不实现)
在定义子类的时候必须先写父类,协议无关紧要
class Student:Human,Listen,Speak,Read,Write{
var name:String
init(name:String){
self.name = name
}
func listen(){
...
}
...
...
}
依赖倒转原则(面向协议编程)
1.声明变量的类型时应该尽可能使用协议编程
2.声明方法参数类型的时候应该尽可能使用协议类型
3.声明方法返回类型时应该尽可能使用协议类型
协议中全是抽象概念(只有声明没有实现) 遵循协议的类可以各自对协议中的计算属性和方法给出自己的实现版本 ,这样当面向协议编程时就可以把多态的优势发挥到淋漓尽致 可以写出更通用更灵活的代码(符合开闭原则)
开闭原则
实现开闭原则最关键有两点:
1.抽象是关键(在设计系统的时候一定要设计好的协议);
2.封装可变性(桥梁模式 - 将不同的可变因素封装到不同的继承结构中)
接口(协议)隔离原则:协议的设计要小而专不要大而全
协议的设计也要高度类聚
结构
结构大体上和类相仿,但是也有许多区别
struct Student{
var name:String
var age:Int
func study(courseName:String){
print("\(name)正在学习")
}
}
区别1: 结构的对象是值类型, 类的对象是引用类型
值类型在赋值的时候会在内存中进行对象的拷贝
引用类型在赋值的时候不会进行对象拷贝只是增加了一个引用
结论: 我们自定义新类型时优先考虑使用类而不是结构除非我们要定义的是一种底层的数据结构(保存其他数据的类型)
值类型的结构
区别2: 结构会自动生成初始化方法
区别3: 结构中的方法在默认情况下是不允许修改结构中的属性除非加上mutating关键字
//创建一个学生对象,然后用stu去引用它,所以此时学生对象引用计数为1
var stu:Student? = Student()
//此处没有创建新的学生对象,原来的学生对象引用计数加1
var stu1 = stu
var stu2 = stu
var stu3 = stu
stu1 = nil
stu2 = nil
stu3 = nil
stu = nil
//附一个nil,引用计数-1当引用计数为0,ARC自动清理内存释放学生对象
//ARC即时性的内存清理优于Java中的Garbage Collection(垃圾回收)
var stu1:Student? = Student()
//强引用(strong(默认))会增加引用计数,弱引用(weak)修饰的引用不会增加引用计数
weak var stu2 = stu1
weak var stu3 = stu2
stu1 = nil
//如果想释放内存 程序员可以手动将一个引用赋值为nil
var stu:Student?
func foo(){
//stu是一个局部变量/常量 在函数调用结束后局部变量就消失了
//所以学生对象的引用计数也就变成0了 所以会被ARC释放掉
let stu = Student()
print(stu)
}
foo()
//创建任何子类对象的时候一定是先创建了父类对象
//引用转移(会导致原来对象上的引用计数-1 新对象引用计数 + 1)
var stu1: Person = Student()
var stu = Student()
var tea = Teacher()
自动释放池
//通过向autoreleasepool函数中传入一个闭包来实现
//autoreleasepool { () -> () in
//自动释放池中的对象引用在池的边界会收到引用计数-1的消息
//将来做iOS开发是某个地方会创建很多的临时对象
//那么最好在此处设置一个自动释放池避免内存瞬时峰值过高造成闪退
// let stu = Student()
//}
离开自动释放池时 stu1会收到引用计数-1的消息 stu2也会收到引用计数-1的消息
如果程序中出现了类与类之间双向关联的关系 必须要将其中一端设置为weak引用
否则将会形成循环引用导致ARC无法释放内存
如果允许使用可空类型,通常使用weak来破除循环引用。如果不能使用可空类型,就使用unowned来破除
如果员工对象关联的部门对象被释放了,如果还要员工对象去操作它所关联的部门对象将导致程序崩溃
EXC_BAD_ACCESS
泛型 (generic)
让类型不再是程序中的硬代码(写死的东西)
func bubbleSort(array: [T]) -> [T] {
var newArray = array
for i in 0.. newArray[j + 1] {
mySwap(&newArray[j], &newArray[j + 1])
swapped = true
}
}
if !swapped {
break
}
}
return newArray
}
// 定义一个虚拟类型T, 调用函数时根据传入的参数类型来决定T到底是什么
func mySwap(inout a: T, inout _ b: T) {
let temp = a
a = b
b = temp
}
泛型限定
// 限定T类型必须是遵循了Comparable协议的类型
func myMin(a: T, _ b: T) -> T {
return a < b ? a : b
}
let array1: Array = [23, 45, 99, 12, 68, 51, 70, 66]
let array2 = bubbleSort(array1)
print(array1)
print(array2)
异常处理(Error Handling)
如果一个方法抛出了异常 那么在声明方法时必须要写上throws关键字
throws关键字是提醒方法的调用者方法可能会出状况 调用时要写try
init(num: Int, den: Int) throws {
_num = num
_den = den
if _den == 0 {
// 如果程序中出现问题就抛出错误(异常)
// 被throw关键字抛出的必须是遵循ErrorType协议的东西
throw FractionError.ZeroDenominator
}
else {
simplify()
normalize()// 如果一个方法抛出了异常 那么在声明方法时必须要写上throws关键字
// throws关键字是提醒方法的调用者方法可能会出状况 调用时要写try
init(num: Int, den: Int) throws {
_num = num
_den = den
if _den == 0 {
// 如果程序中出现问题就抛出错误(异常)
// 被throw关键字抛出的必须是遵循ErrorType协议的东西
throw FractionError.ZeroDenominator
}
else {
simplify()
normalize()
}
}
}
func add(other: Fraction) -> Fraction {
// 如果能够确保方法调用时不出异常那么可以在try关键字后加!
// 这样就可以在不写do...catch的情况下调用可能出状况的方法
return try! Fraction(num: _num * other._den + other._num * _den, den: _den * other._den)
}
如果能够保证代码不出错可以在try后面加!
如果不确定代码是否出错可以在try后面加?
需要注意的是有?的地方会产生Optional(可空类型)
稍后可能还需要对可空类型进行拆封, 拆封方式有二:
- 不安全的做法: xxx!
- 安全的做法: 用if let = xxx { }进行拆封
func foo() {
let f1 = try? Fraction(num: 3, den: 0)
let f2 = try? Fraction(num: 0, den: 9)
if let a = f1, b = f2 {
let f3 = a + b
print(f3.info)
}
else {
print("无效的分数无法进行加法运算")
}
}
foo()
对于可能出状况的代码要放在do...catch中执行
在可能出状况的方法前还要写上try表示尝试着执行
如果在do中没有出现任何状况那么catch就不会执行
如果do中出现了状况代码就不会再向下继续执行而是转移到catch中
在do的后面可以跟上多个catch用于捕获不同的异常状况 但是最多只有一个catch会被执行
//do {
// let f1 = try Fraction(num: 3, den: 4)
// let f2 = try Fraction(num: 0, den: 9)
//
// print(f1.info)
// print(f2.info)
//
// let f3 = f1 + f2
// print(f3.info)
// let f4 = f1 - f2
// print(f4.info)
// let f5 = f1 * f2
// print(f5.info)
// let f6 = try f1 / f2
// print(f6.info)
//}
//catch FractionError.ZeroDenominator {
// print("瓜西西的, 分母不能为0!!!")
//}
//catch FractionError.DivideByZero {
// print("卵球了, 除以0是不行的!!!")
//}
//catch {
// print("出错了! 我也不知道什么问题")
//}
计算机的硬件组成
计算机的硬件由五大部件构成:
运算器、控制器、存储器、输入设备、输出设备
运算器 + 控制器 => CPU (中央处理器)
存储器 => 内存 (RAM - Random Access Memory)
程序员可以使用的内存大致分为五块区域:
栈 (stack) - 我们定义的局部变量/临时变量都是放在栈上
- 特点: 小、快
堆 (heap) - 我们创建的对象都是放在堆上的 - 特点: 大、慢
静态区 (static area) - 数据段 - 全局量
- 只读数据段 - 常量
- 代码段 - 函数和方法
Swift语言阶段总结
经过了三周的学习,把Swift的语法过了一遍,对这门编程语言也有了大致的了解了,虽然没有什么基础,但勉强还是跟上了进度(应该是跟上了吧>o<),不过呢,最大的毛病是总感觉能看懂怎么用,但自己一写就不行了。可能是对某些语法理解不够深的原因吧,不过这也再次提醒了我,毕竟算不上高智商,不经过无数的练习,是学不会的。而我现在的努力程度,貌似差的有点多。。
下一周又将迎来一个新的阶段,虽然还不知道难度怎么样,但是现在我也只能选择向前,没有退缩的余地。等以后成功的时候,总会感谢现在拼搏的自己,加油!