上一篇主要介绍总结了Swift语言的基本句法结构,本篇说明Swift语言的一些高级特性,例如内存管理,文件和外部数据处理,以及错误处理。
仍从类和对象等基础部分学起,博主在学习中觉得面向对象语言在本质上没有区别,但每一种都在细节定义上有各自的特点,Swift虽然框架上和OC类似,而在OC的基础上又有许多不同和改进。
首先贴出的代码主要穿插概念梳理和解释,后面贴出的代码是对前面代码的实例化和演示。其次是根据不同具体概念的解释和实现。
PS:寒假的学习任务下来了,疫情原因老师最近有点担心越拖越回不去,所以上午跟我们讲明天就可以自由安排行程。准备后天就回家了,回家转战场继续努力,这一个月的学习基本是在补OC/swift语言基础和ios开发入门。
一月和二月份的学习任务,第一个任务为学习GCD的使用掌握多线程,第二个任务是UI的基本使用,第三个任务是IOS开发的音视频相关,第四个是独立从头写之前学长学姐们已完成的一个项目。所以这两个月无特殊情况安排就是上午理论学习,下午实战开发,晚上搞一搞算法题或者摸鱼。博客会持续更新!
//
// Vehicle.swift
// swift-test
//
// Created by Azure on 2021/1/4.
// Copyright © 2021 Azure. All rights reserved.
//
import Cocoa
/*
类和对象
*/
//与OC,java,c++以及其他很多语言一样,swift使用类Class定义对象的模版
//endwith:定义类后可以创建类的实例,实例有自己的一份属性和方法
class Vehicle: NSObject {
//类中有属性和方法。属性是类中的变量,方法是类中的函数。
//该示例类中有两个属性,属性的声明方式和变量相同
var color: String?//一个是Optional类型的字符串变量,名为color
var maxSpeed = 80//一个是整数,名为maxSpeed
//类中的方法与普通的函数一样。
func description() -> String{
return "A (String(describing: self.color)) vehicle"
//方法中的代码可以通过self关键字访问类的属性,类似OC。self指代当前运行代码的对象。
}
func travel(){
print("Traveling at (maxSpeed) kph")//( )为字符串插值句法
//如果属性明显属于当前对象,可以省略self关键字
}
}
/*
初始化和终止化
*/
//swift中类和对象的定义及初始化/终止化方法都可以看作是OC定义的补充和延伸
class InitAndDeinitExample {
/*初始化
swift中创建对象的方式是调用一个特殊的方法,即初始化方法。这个方法用于设置对象的初始状态,名称都是init
在swift中初始化方法分两种:便利初始化方法(convenience initializer)和指定初始化方法(designatedinitializer)。
指定初始化方法设定对象所需要的一切,通常尽量使用默认设置。便利初始化如其名,以更便利的方法设定实例,允许初始化提供更多信息。便利初始化方法的执行过程必须调用指定初始化方法
*/
//注意override关键字,指定初始化方法即主初始化方法
init() {
print("I have been created!")
}
//变量初始化方法,必须调用主初始化方法
convenience init(text: String) {
self.init()//
print("I was called convenience initializer!")
}
//可能返回nil的初始化方法,即可失败初始化方法(failable initializer)
convenience init? (value: Int){
self.init()
if value > 5{
//对象构建失败
return nil
}
else{
print("I was called failable initializer!")
}
}
/*终止化
删除对象时运行终止化方法(deinitializer) deinit中的代码。终止化方法在对象的保有量为零时运行,在对象从内存中删除之前调用。
*/
deinit {
print("I'm going away!")
}
}
/*
继承
*/
class car: Vehicle {
var engineType = "V8"
//子类重写父类方法
override func description() ->String{
return "It's a '(engineType)' car"
}
}
/*
属性
*/
//计算属性
class Rectangle:NSObject {
var weidth : Double = 0.0//存储属性
var length : Double = 0.0
//计算属性
var area : Double{
//计算属性的读值方法
get{
return weidth * length
}
//计算属性的设值方法,可选
set{
//设为正方形
weidth = sqrt(newValue)
length = sqrt(newValue)
}
}
}
//观测器
//使用属性时,可能想在属性的值变化时执行一些代码。为此swift为属性提供了观测器(observer)功能。
//观测器在属性的值变化前后运行。属性观测器添加在属性后面的花括号里,分为willSet和didSet两种代码块。这两个块都有参数,willSet在属性的值变化前运行,传入的参数即将要设定的值,传给didSet的则是旧值
class PropertyObseverExample {
var number:Int = 0{
willSet(newNumber){
print("About to change to (newNumber)!")
}
didSet(oldNumber){
print("just changed from (oldNumber) to (self.number)!")
}
}
}
//惰性属性
//属性在被声明为惰性,在首次访问时才能赋值,可以延迟一些耗时的工作,在真正需要时再计算。
//定义惰性属性的方法是在属性前面加lazy关键字。惰性加载能节省内存,不初始化用不到的属性。
class SomeExpensiveClass{
init(id : Int){
print("Expensive class (id) created!")
}
}
class LazyPropertyExample{
var expensiveClass1 = SomeExpensiveClass(id:1)
//我们要构建一个类,其标注为惰性属性
lazy var expensiveClass2 = SomeExpensiveClass(id:2)
init(){
print("frist class created!")
}
}
/*
协议
*/
//协议(protocol)可以理解为对类的要求。协议指定类可以声明的属性和方法。
//协议与类十分相似,但不提供具体的代码,只定义有什么属性和方法,以及如何访问这些属性和方法。感觉有点像C++里的虚类
//假如想定义一个协议描述的对象有没有闪动,可以这样做:
protocol Blinking {
//这个属性必须(至少)能读值
var isBlinking : Bool {
get
}
//这个属性即要能读值,也要能设值
var blinkSpeed: Double{
get set
}
//必须有这个方法,但具体做什么由实现方法决定
func startBlinking(blinkSpeed: Double) ->Void
}
//有了协议后,可以定义符合协议的类。类符合协议的意思是,向编译器保证,它会实现协议中的全部属性和方法。除了协议中定义的属性和方法之外,类可以定义其他属性和方法,而且一个类可以符合多个协议
class TrafficLight: Blinking {
var isBlinking: Bool = false
var blinkSpeed: Double = 0.0
func startBlinking(blinkSpeed: Double) {
print("I am a traffic light , and i am now blinking!")
isBlinking = true
self.blinkSpeed=blinkSpeed //使编译器将参数与同名属性分开
}
}
class LightHouse : Blinking {
var isBlinking: Bool=false
var blinkSpeed: Double=0.0
func startBlinking(blinkSpeed: Double) {
print("I am a lighthouse, and i am now blinking!")
isBlinking = true
self.blinkSpeed = blinkSpeed //使编译器将参数与同名属性分开
}
}
/*
扩展
*/
//使用别人编写的类型时,如果想添加功能,但无法访问源码,或者不想搅乱源码
//想把自己编写的类型按功能分成几个部分,提升可读性
extension Int {
var doubled : Int {
return self * 2
}
func multiplyWith(anotherNumber: Int) -> Int {
return self * anotherNumber
}
}
实例化:
//
// main.swift
// swift-test
//
// Created by Azure on 2020/12/30.
// Copyright © 2020 Azure. All rights reserved.
//
import Foundation
//类和对象
var redVehiclde = Vehicle()//创建实例的方法
redVehiclde.color = "Red"//类中的属性访问
redVehiclde.maxSpeed = 90
redVehiclde.travel()
var str=redVehiclde.description
print(str)
//初始化和终止化
var example : InitAndDeinitExample?
//使用指定初始化方法
example = InitAndDeinitExample()
example = nil
//使用便利初始化方法
example = InitAndDeinitExample(text: "nice to meet you")
//使用可失败初始化方法
var failableExample = InitAndDeinitExample(value: 3)
print(failableExample ?? 1)
//子类继承
var buleCar = car()
buleCar.maxSpeed = 120
buleCar.color = "Bule"
buleCar.travel()//子类继承父类方法
print(buleCar.engineType)
var overrideExample = buleCar.description//子类重写父类方法
print(overrideExample)
var superExample = buleCar.superclass?.description()//子类调用父类方法
print(superExample ?? 1)
//计算属性
var rect = Rectangle()
rect.length=3.0
rect.weidth=4.0
var getTest=rect.area//12.0
print(getTest)
rect.area=9
var setTest=rect.weidth//3.0
print(setTest)
//观测器
var proper = PropertyObseverExample()
proper.number = 4//属性观测器对属性本身没有影响,只是在属性变化前后添加一些行为
//惰性属性
var lazyExample = LazyPropertyExample()
var t1=lazyExample.expensiveClass1//已初始化,什么也不打印
var t2=lazyExample.expensiveClass2//打印带其id的初始化输出
//协议
var aBlinkingThing : Blinking//可以是符合Blinking协议的任意对象
aBlinkingThing = TrafficLight()
aBlinkingThing.startBlinking(blinkSpeed: 4.0)
var speed1 = aBlinkingThing.blinkSpeed
print(speed1)
aBlinkingThing = LightHouse()
aBlinkingThing.startBlinking(blinkSpeed: 5.0)
var speed2 = aBlinkingThing.blinkSpeed
print(speed2)
//扩展
print(2.doubled)
print(4.multiplyWith(anotherNumber: 32))
上述代码的执行结果截图:
访问控制
swift定义了三级访问控制,指定信息能被应用的哪些部分访问:
- 公开:公开的类、方法和属性能被应用的任何一部分访问。例如,构建IOS应用的UIKit中的所有类都是公开的。
- 内部:内部实体(数据和方法)只能在定义它们的模块中访问。模块可以事应用、库或框架。这便是无法访问的UIKit内部的原因,因为它们定义为只能在UIKit框架内部访问。这是默认的访问控制级别:如果不指定,默认使用内部级别。
- 私有:私有实体只能在定义它们的文件中访问。因此,定义类时可以把内部实现隐藏起来,不让同一模块中其他的类访问,这样有助于把类开放的信息减到最少。
方法和属性的访问控制级别由所在的类决定。
方法的访问级别不能比所在的类宽松。
例如,私有类中不能定义公开方法:
public class accessControl{ //code }
默认情况下,所有属性和方法的访问控制级别都是internal。如果愿意,可以显式指定一个成员的访问级别时internal,但是不必这样做
//只在模块中访问
//这里的internal是默认值,可以省略
internal var internalProperty = 123
类是个例外,类默认定义为private级别:如果不为类的指定访问控制级别,默认为private,而不是internal。实体成员的访问级别不能比实体本身宽松。
使用public声明的方法和属性对应用中的任何一部分都可见:
public var publicProperty = 123
使用private声明的方法和属性只在声明它们的源码中可访问:
private var privateProperty = 123
最后,可以把属性的设置方法设为私有的,这样属性是只读的。在声明只读属性的源码文件中可以自由读/写属性,但是在其他文件中只能读取属性的值。
private(set) var privateProperty = 123
运算符重载:
示例:
var arraone = [1,2]
var arratwo = [3,4]
//arraone=arraone+arratwo
func + (_ nums1:[Int],_ nums2:[Int])->[Int]{
if nums1.count != nums2.count{
print("they can't add!")
return [Int]()
}
else{
var nums=nums1
for i in 0 ..< nums2.count {
nums[i] += nums2[i]
}
return nums
}
}
var test = arraone + arratwo
print(test)
输出截图:
未重载对比代码:
var arraone = [1,2]
var arratwo = [3,4]
arraone=arraone+arratwo
print(arraone)
未重载对比输出:
泛型
swift是静态类型语言,因此swift编译器要明确知道代码处理的信息是何种类型。这样损失了一些灵活性,因此产生泛型。
泛型在编写代码时不关心其中处理的是什么信息。数组使用泛型,数组并不处理存储其中的数据,只是把数据按顺序存储在集合中。
泛型有点类似与C++中的模版temple结构。
创建泛型的方式见下例:
class Tree <T> {
//在类中T指代任何一个类型
var value : T
private(set) var children :[Tree <T>] = []
init(value: T){
self.value = value
}
func addChildren(value : T) -> Tree <T>{
let newChid = Tree<T>(value: value)
children.append(newChid)
return newChid
}
}
泛型的使用:
//整型树
let integerTree = Tree <Int>(value: 5)
integerTree.addChildren(value: 4)
integerTree.addChildren(value: 3)
print(integerTree)
//字符串树
let StringTree = Tree <String>(value:"hello")
StringTree.addChildren(value:"yes")
StringTree.addChildren(value:"no")
print(StringTree)
运算类型截图:
结构体
结构体声明方式如下:
struct Ponit{
var x:Int
var y:Int
}
//swift中结构体为值类型,始终以副本形式在代码中传递,结构体不能继承。
let p = Ponit(x:3, y:2)
print(p)
运行截图:
Swift标准库、Foundation、Cocoa 和 Cocoa Touch
swift把不同平台的功能放在不同的库。常用的库有四个:
- Swift标准库:
包含全部最底层的类型和函数,例如Int,String 等,以及数学函数、数组和字典等。标准库无需使用特殊的访问方式,所有的Swift程序都可使用。
- Foundation:
稍微高层的库,提供更多工具和类型,例如用于广播应用层面通知的NSNotificationCenter,用于读/写JSON数据的NSJSONSerialization。使用Foundation库需要import语句导入。
- Cocoa:
针对OS X的库,包含按钮、窗口、图像视图和菜单功能。使用Cocoa库需要import语句导入。
- Cocoa Touch:
针对IOS,提供的工具和功能与Cocoa一样,例如视图、触摸输入、传感器功能等。Cocoa Touch库使用import UIKit导入。
导入Cocoa和Cocoa Touch时会导入Foundation,不需要再次导入。
数据
cocoa使用NSData对象表示处理的数据。NSData对象的创建方式很多。
举例:把字符串转换成NSData对象,可以使用字符串中的dataUsingEncoding方法,如下所示:
let stringToConvert = "Hello,Swift"
let data = stringToConvert.dataUsingEncoding(NSUTF8StringEncoding)
数据来源:URL/文件
文件数据加载过程:首先找到其在硬盘中的存储位置。然后把它的内容加载到内存中。
为获取内置文件,首先要使用NSBundle类的pathForResource方法找到文件在硬盘中的存储位置。然后,为NSData提供URL或文件路径,构建一个NSData对象。
//从URL中加载
if let URL = NSURL(string = "https://oreilly.com"){
let loadedDataFromURL = NSData (contentsOfURL:URL)
}
//从文件中加载
if let filePath = NSBundle.mainBundle()
.pathForResource("SomeFile", ofType: "txt"){
let loadedDataFromPath = NSData(contentOfFile:filePath)
}
NSBundle表示一个构建包bundle,即包含全部可用资源的对象。NSBundle类可用于加载和卸载代码、图像、硬盘,以及任何无需直接处理文件系统的资源。
错误处理
旧版为以指针形式传递NSError对象中的信息。新版更简洁易读,安全性较高,因为所有的错误都能捕获,也不用处理指针了。
举例:
//银行可能出现的错误
enum BankError : Error{
case NotEnoughFunds//余额不足
case CannotBeginWithNegativeFunds //一开始的资产为负值
case CannotMakeNegativeTransaction(amount:Float)//不能存取负值
}
//一个简单的银行账户类
class BankAccount{
private (set) var balance : Float = 0.0 //账户余额
init(amount:Float) throws{//初始化账户
guard amount > 0 else{//确保初始余额不为负值
throw BankError.CannotBeginWithNegativeFunds
}
balance += amount
}
func deposit(amount: Float) throws{ //向账户中存钱
guard amount > 0 else{//确保存入值大于0
throw BankError.CannotMakeNegativeTransaction(amount: amount)
}
balance += amount
}
func withdraw(amount: Float)throws{ //从账户中取钱
guard amount > 0 else{//确保存出值大于0
throw BankError.CannotMakeNegativeTransaction(amount: amount)
}
guard balance >= amount else{//确保有钱可取
throw BankError.NotEnoughFunds
}
balance -= amount
}
}
//调用可能抛出错误的函数、方法和初始化方法时,要放在do-catch块中。do块中是可能抛出错误的方法,而且要在可能抛出错误的方法前加上try。如果调用的方法抛出异常,则do块停止执行,转而运行catch分支
do{
let vacationFund = try BankAccount(amount: 5)
try vacationFund.deposit(amount: -1)
try vacationFund.withdraw(amount: 41)
}catch let error as BankError {
//捕获抛出的异常
switch(error){
case .NotEnoughFunds:
print("not enough funds in account!")
case .CannotBeginWithNegativeFunds:
print("Tried to start an account with negative money!")
case .CannotMakeNegativeTransaction(let amount):
print("Tried to do s transaction with a negative amount of (amount)")
}
}
三种错误抛出截图:
错误一
错误二
错误三
剩余待更部分为内存管理、Cocoa和Cocoa Touch采用的设计模式(模型-视图-控制模式、委托模式)、应用结构(应用的受托对象、窗口控制器和视图控制器、nib文件和故事板),明天再更!