上一篇主要介绍总结了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号是几位 银行swift code代码_银行swift号是几位


访问控制

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)


输出截图:


银行swift号是几位 银行swift code代码_初始化方法_02


未重载对比代码:


var arraone = [1,2]
var arratwo = [3,4]
arraone=arraone+arratwo
print(arraone)


未重载对比输出:


银行swift号是几位 银行swift code代码_对象 普通po转_03


泛型

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)


运算类型截图:


银行swift号是几位 银行swift code代码_银行swift号是几位_04


结构体

结构体声明方式如下:


struct Ponit{
    var x:Int
    var y:Int
}
//swift中结构体为值类型,始终以副本形式在代码中传递,结构体不能继承。
let p = Ponit(x:3, y:2)
print(p)


运行截图:


银行swift号是几位 银行swift code代码_对象 普通po转_05


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)")
    }
}


三种错误抛出截图:


银行swift号是几位 银行swift code代码_对象 普通po转_06

错误一

银行swift号是几位 银行swift code代码_银行swift号是几位_07

错误二

银行swift号是几位 银行swift code代码_初始化_08

错误三


剩余待更部分为内存管理、Cocoa和Cocoa Touch采用的设计模式(模型-视图-控制模式、委托模式)、应用结构(应用的受托对象、窗口控制器和视图控制器、nib文件和故事板),明天再更!