首页 > 代码库 > 从0开始学Swift笔记整理(四)

从0开始学Swift笔记整理(四)

这是跟在上一篇博文后续内容:

——重写方法

重写实例方法
在子类中重写从父类继承来的实例方法和静态方法。先介绍实例方法的重写。
下面看一个示例:
class Person {   
    var name: String
    var age: Int   
    func description() -> String { //实例方法
        return "\(name) 年龄是: \(age)"
    }   
    class func printClass() ->() { //静态方法
        print( "Person 打印...")
    }   
    init (name: String, age: Int) {
        self.name = name
        self.age  = age
    }
}
class Student: Person {   
    var school: String   
    convenience init() {
        self.init(name: "Tony", age: 18, school: "清华大学")
    }   
    init (name: String, age: Int, school: String) {
        self.school = school
        super.init(name: name, age: age)
    }   
    override func description() -> String { //重写实例方法description,重写的方法前面要添加关键字override
 print("父类打印 \(super.description())")  
 return "\(name) 年龄是: \(age), 所在学校: \(school)。"
    }   
    override class func printClass() ->() { //重写静态方法printClass
        print( "Student 打印...")
    }
}
let student1 = student()
print("学生1:\(student1.description())") //调用了description方法
Person.printClass()  
Student.printClass()  
使用super.description()语句调用父类的description方法,其中super指代父类实例。
重写静态方法printClass,在静态方法中不能访问实例属性。
调用了description方法。由于在子类中重写了该方法,所以调用的是子类中的description方法。输出结果是:
父类打印 Tony 年龄是: 18
学生1:Tony 年龄是: 18, 所在学校: 清华大学。
为了测试静态方法重写,调用Person.printClass()语言,它是调用父类的printClass静态方法,输出结果是:
Person 打印...
调用Student.printClass()语言,它是调用子类的printClass静态方法,输出结果是:
Student 打印...
重写静态方法
与类的静态属性定义类似,静态方法使用class或static关键字,但是使用哪一个要看子类中是否重写该方法。class修饰的静态方法可以被重写,static关键字就不能被重写。
示例代码如下:
class Account {   
    var owner: String = "Tony"     //账户名   
    //不能换成static
    class func interestBy(amount: Double) -> Double {   //静态方法
        return 0.08886 * amount
    }
}
class TermAccount: Account {//定期账户   
    //可以换成static
    override class func interestBy(amount: Double) -> Double { //静态方法
        return 0.09 * amount
    }
}
//调用静态方法
print(Account.interestBy(10_000.00 ))
print(TermAccount.interestBy(10_000.00 ))
由于被重写所以代码class func interestBy(amount: Double) -> Double中的class不能换成static。静态方法interestBy可以使用class或static,除非在TermAccount的子类中重写方法interestBy。

——下标重写

下标是一种特殊属性。子类属性重写是重写属性的getter和setter访问器,对下标的重写也是重写下标的getter和setter访问器。
下面看一个示例:
class DoubleDimensionalArray {    
    let rows: Int, columns: Int
    var grid: [Int]   
    init(rows: Int, columns: Int) {
        self.rows = rows
        self.columns = columns
        grid = Array(count: rows * columns, repeatedValue: 0)
    }   
    subscript(row: Int, col: Int) -> Int { //定义下标
       
        get {
            return grid[(row * columns) + col]
        }       
        set {
            grid[(row * columns) + col] = newValue
        }
    }        //定义下标   
}
class SquareMatrix: DoubleDimensionalArray {   
    override subscript(row: Int, col: Int) -> Int { //重写父类下标       
        get {   
            return super.grid[(row * columns) + col] 
        }       
        set {     
            super.grid[(row * columns) + col] = newValue * newValue 
        }       
    }
}
var ary2 = SquareMatrix(rows: 5, columns: 5)
for var i = 0; i < 5; i++ {
    for var j = 0; j < 5; j++ {
        ary2[i,j] = i + j
    }
}
for var i = 0; i < 5; i++ {
    for var j = 0; j < 5; j++ {
        print("\t\t \(ary2[i,j])")
    }
    print("\n")
}
其中super.grid[(row * columns) + col]语句中使用super调用父类的grid属性。
其中super.grid[(row * columns) + col] = newValue * newValue语句是给父类的grid属性赋值。

——final关键字

在类的定义中使用final关键字声明类、属性、方法和下标。final声明的类不能被继承,final声明的属性、方法和下标不能被重写。
下面看一个示例:
final class Person { //声明为final,说明它是不能被继承的   
    var name: String   
    final var age: Int //定义的age属性   
    final func description() -> String { //定义description实例方法
        return "\(name) 年龄是: \(age)"
    }   
    final class func printClass() ->() { //定义printClass静态方法
        print( "Person 打印...")
    }   
    init (name: String, age: Int) {
        self.name = name
        self.age  = age
    }
}
class Student: Person {                            //编译错误   
    var school: String   
    convenience init() {
        self.init(name: "Tony", age: 18, school: "清华大学")
    }   
    init (name: String, age: Int, school: String) {
        self.school = school
        super.init(name: name, age: age)
    }   
    override func description() -> String {         //编译错误 //试图重写description实例方法
        print("父类打印 \(super.description())")
        return "\(name) 年龄是: \(age), 所在学校: \(school)。"
    }   
    override class func printClass() ->() {      //编译错误 //试图重写printClass静态方法
        print( "Student 打印...")
    }   
    override var age: Int {                        //编译错误 //试图重写age属性
    get {
        return super.age
    }
    set {
        super.age = newValue < 8 ? 8: newValue
    }
    }
}
定义Student类,并声明为Person子类时,会报如下编译错误:
Inheritance from a final class ‘Person‘
定义的age属性也是final,那么在试图重写age属性时,会报如下编译错误:
Var overrides a ‘final‘ var
定义description实例方法,它被声明为final,那么在试图重写description实例方法时,会报如下编译错误:
Instance method overrides a ‘final‘ instance method
定义printClass静态方法,它被声明为final,那么在试图重写printClass静态方法时,会报如下编译错误:
Class method overrides a ‘final‘ class method
使用final可以控制我们的类被有限地继承,特别是在开发一些商业软件时,适当地添加final限制是非常有必要的。

类型检查与转换

Swift类的类型检查与转换,包括is操作符、as操作符

is操作符可以判断一个实例是否是某个类的类型。如果实例是目标类型,结果返回true,否则为false。

as?操作符是在不确定是否类型转换能够成功情况下使用,如果成功转换结果是可选类型。如果我们能够确保转换一定成功,可以使用as!操作符在转换的同时进行隐式拆包。

——扩展声明

声明扩展的语法格式如下:
extension 类型名 {   
    //添加新功能
}
声明扩展的关键字是extension,“类型名”是Swift中已有的类型,包括类、结构体和枚举,但是我们仍然可以扩展整型、浮点型、布尔型、字符串等基本数据类型,这是因为这些类型本质上也是结构体类型。打开Int的定义如下:
struct Int : SignedInteger {
    init()
    init(_ value: Int)
    static func convertFromIntegerLiteral(value: Int) -> Int
    typealias ArrayBoundType = Int
    func getArrayBoundValue() -> Int
    static var max: Int { get }
    static var min: Int { get }
}
从定义可见Int是结构体类型。不仅是Int类型,我们熟悉的整型、浮点型、布尔型、字符串等数据类型本质上都是结构体类型。
Swift中的扩展机制可以在原始类型中添加的新功能包括:
· 实例计算属性和类型计算属性
· 实例方法和类型方法
· 构造函数
· 下标
还有嵌套类型等内容也可以扩展,扩展还可以遵从协议。

——扩展计算属性、方法

可以在原始类型上扩展计算属性,包括实例计算属性和静态计算属性。添加计算属性的定义,与普通的计算属性的定义是一样的。
实例计算属性示例:在网络编程时,为了减少流量,从服务器端返回的不是信息描述,而是编码,然后在本地再将编码转换为描述信息。为此定义了如下Int类型扩展:
extension Int {    //定义Int类型的扩展
    var errorMessage : String { //只读计算属性
        var errorStr = ""
        switch (self) { 
        case -7:
            errorStr = "没有数据。"
        case -6:
            errorStr = "日期没有输入。"
        case -5:
            errorStr = "内容没有输入。"
        case -4:
            errorStr = "ID没有输入。"
        case -3:
            errorStr = "据访问失败。"
        case -2:
            errorStr = "您的账号最多能插入10条数据。"
        case -1:
            errorStr = "用户不存在,请到http://51work6.com注册。"
        default:
            errorStr = ""
        }      
        return errorStr
    }
}
let message = (-7).errorMessage  //获得-7编码对应的描述信息
print("Error Code : -7 , Error Message : \(message)") 
注意整个-7包括负号是一个完整的实例,因此调用它的属性时需要将-7作为一个整体用小括号括起来。然而,如果是7则不需要括号。
下面再看一个静态属性的示例:
struct Account {    //定义Account结构体
    var amount : Double = 0.0               //账户金额
    var owner : String = ""                 //账户名
}

extension Account {   //定义Account结构体的扩展静态
    static var interestRate : Double {      //利率 
      return 0.0668
    }
}

print(Account.interestRate) //打印输出interestRate属性
打印输出interestRate属性,访问方式与其他的静态计算属性一样,通过“类型名”加“.”来访问静态计算属性。

扩展方法
可以在原始类型上扩展方法,包括实例方法和静态方法。这些添加方法的定义与普通方法的定义是一样的。

——Cocoa错误处理模式

Swift错误处理模式,在Swift 1.x和Swift 2.0是不同的两种模式。
Swift 1.x代码错误处理模式采用Cocoa框架错误处理模式,到现在Objective-C还沿用这种处理模式,而Swift 2.0之后采用了do-try-catch错误处理模式。
下面的示例代码是从文件中读取字符串到内存中,如果使用Swift 1.x错误处理模式代码如下:
import Foundation

var err: NSError?        //定义可选的NSError?变量
let contents = NSString(contentsOfFile: filePath, encoding: 
NSUTF8StringEncoding, error: &err)
if err != nil {        //判断err变量是否还是nil
// 错误处理
}

NSError?一定是可选的变量,因为要给它初始化为nil。
判断err变量是否还是nil,如果还是nil在代码

let contents = NSString(contentsOfFile: filePath,
encoding: NSUTF8StringEncoding, error: &err)

方法调用过程中没有发生错误,否则说明有错误发生。
上述代码的构造函数,它的Swift语法定义如下:

init?(contentsOfURL url: NSURL,
     encoding enc: UInt,
error error:
NSErrorPointer)

构造函数的最后一个参数是NSErrorPointer(即NSError指针),那么在实际调用时候我们需要传递err变量地址(即&err),&是取地址符。当方法调用完成后,如果有错误则err变量会被赋值。

——do-try-catch错误处理模式

Swift 1.x的错误处理模式存在很多弊端,例如:为了在编程时候省事,给error参数传递一个nil,或者方法调用完成后不去判断error是否为nil,不进行错误处理。

let contents = NSString(contentsOfFile: filePath, 
encoding:
NSUTF8StringEncoding, error: nil)//error参数传递一个nil

或者

var err: NSError?    
let contents =
NSString(contentsOfFile: filePath, encoding:
              
NSUTF8StringEncoding, error: &err)

不好的编程习惯,由于Objective-C和Swift 1.x没有强制处理机制,因此一旦真的发生错误,程序就会发生崩溃。
同样的从文件中读取字符串示例,如果使用Swift 2错误处理模式代码如下:

import Foundation
do {    //要做一些操作
let str = try NSString(contentsOfFile: filePath, encoding:
         NSUTF8StringEncoding)    //要尝试做的事情
} catch let err as NSError {    //如果失败则进入catch代码块
      err.description
}
do-try-catch这种错误模式与Java中异常处理机制非常类似,本意就是尝试(try)做一件事情,如果失败则捕获(catch)处理。

捕获错误。完整的do-try-catch错误处理模式的语法如下:

do {
try 语句
成功处理语句组
}
catch 匹配错误 {
   
错误处理语句组
}

在try 语句中可以产生错误,当然也可能不会产生错误,如果有错误发生,catch就会处理错误。catch代码块可以有多个,错误由哪个catch代码块处理是由catch后面的错误匹配与否而定的。错误类型的多少就决定了catch可以有多少。

错误类型:在Swift中错误类型必须遵从ErrorType 协议,其次考虑到错误类型的匹配,它应该被设计成为枚举类型,枚举类型非常适合将一组相关值关联起来。
如果我们编写访问数据库表程序,实现对表数据插入、删除、修改和查询等操作,我们会需要类似如下代码的错误类型:

enum DAOError: ErrorType {
case NoData
case PrimaryKeyNull
}

NoData表示没有数据情况,PrimaryKeyNull表示表的主键(Primary Key)为空情况。那么我们就可以通过如下代码捕获错误。

do {    
//try 访问数据表函数或方法
}
catch DAOError.NoData {
    print("没有数据。")
}
catch DAOError.PrimaryKeyNull {
   
print("主键为空。")
}

——抛出错误

能放到try后面调用函数或方法都是有要求的,他们是有可能抛出错误,在这些函数或方法声明的参数后面要加上throws关键字,表示这个函数或方法可以抛出错误。
声明抛出错误方法示例代码如下:

//删除Note记录方法
func remove(model: Note) throws {
    ...
}
//查询所有记录数据方法
func findAll() throws -> [Note] {
    ...
}

上述代码remove(_:)方法没有返回值,throws关键字放到参数后面。findAll()有返回值throws关键字放到参数和返回值类型之间。

在函数或方法中抛出错误
一个函数或方法能够声明抛出错误,是因为在函数或方法中产生并抛出了错误,这样函数或方法声明抛出错误才有实际的意义。
在产生并抛出错误方式:
· 在函数或方法中通过throw语句,人为地抛出错误。
· 在函数或方法中调用的其他可以抛出错误函数或方法,但是没有捕获处理,    会导致错误被传播出来。

guard语句最擅长处理这种早期判断,条件为false情况下抛出错误。
findAll()语句本身有可能产生错误,但是并没有使用catch语句捕获并处理,这样就导致了这个错误传播给该函数或方法的调用者,如果它的调用者也都不捕获处理,那么最后程序会出现运行期错误。

——使用try?和try!区别

1.使用try?
try?会将错误转换为可选值,当调用try?+函数或方法语句时候,如果函数或方法抛出错误,程序不会发崩溃,而返回一个nil,如果没有抛出错误则返回可选值。
示例代码如下:

//查询所有数据方法
func findAll() throws -> [Note] {
    guard listData.
count > 0 else {
   
//抛出"没有数据"错误。
    throw
DAOError.NoData
    }
   
return listData
}
let datas  = try? findAll()    
print(datas)

上述代码中let datas = try? findAll()语句中使用了try?,datas是一个可选值,本例中输出nil。使用了try?语句没有必要使用do-catch语句将其包裹起来。

2.使用try!
使用try!可以打破错误传播链条。错误抛出后传播给它的调用者,这样就形成了一个传播链条,但有的时候确实不想让错误传播下去,可以使用try!语句。
修改上述代码如下:

//查询所有数据方法
func findAll() throws -> [Note] {
    guard listData.
count > 0 else {
   
//抛出"没有数据"错误。
    throw
DAOError.NoData
    }
   
return listData
}
func printNotes() {
let datas  = try! findAll()       
for note in datas {
      
print("date : \(note.date!) - content: \(note.content!)")
   }
}
printNotes()

代码printNotes()函数没有声明抛出错误,在调用它的时候不需要try关键字,错误传播链条在printNotes()函数内被打破了。
代码将try dao.findAll()语句改为try! findAll(),在try后面加了感叹号(!),这样编译器就不会要求printNotes()方法声明抛出错误了,try!打破了错误传播链条,但是如果真的发生错误就出现运行期错误,导致程序的崩溃。
所以使用try!打破错误传播链条时,应该确保程序不会发生错误。

——Swift编码规范之命名规范

· 匈牙利命名,一般只是命名变量,原则是:变量名=类型前缀+描述,如bFoo表示布尔类型变量,pFoo表示指针类型变量。匈牙利命名还是有一定争议的,在Swift编码规范中基本不采用匈牙利命名。
· 驼峰命名(Camel-Case),又称骆驼命名法,是指混合使用大小写字母来名字。驼峰命名又分为:小驼峰法和大驼峰法。
a) 小驼峰法是第一个单词是全部小写,后面的单词首字母大写,如:myRoomCount;
b) 大驼峰法是第一个单词的首字母也大写,如:ClassRoom。

驼峰命名是Swift编码规范主要的命名方法,更加所命名的内容不同,可以选择小驼峰法还是大驼峰法。下面分类说明一下:
1. 对类、结构体、枚举和协议等类型命名,应该采用大驼峰法,如SplitViewController。
2. 文件名,采用大驼峰法,如BlockOperation.swift。
3. 扩展文件,有的时候扩展是定义在一个独立的文件中的,它的命名是“原始类型名+扩展名”作为扩展文件名,如NSOperation+Operations.swift。
4. 变量和属性,采用应该采用小驼峰法,如studentNumber。
5. 常量,采用大驼峰法,如MaxStudentNumber。
6. 枚举成员,与常量类似,采用大驼峰法,如ExecutionFailed。
7. 函数和方法,采用应该采用小驼峰法,如balanceAccount、isButtonPressed等。

——Swift编码规范之注释规范:文件注释、文档注释、代码注释、使用地标注释

Swift注释的语法有两种:单行注释(//)和多行注释(/.../)

1、文件注释
文件注释就在每一个文件开头添加注释,文件注释通常包括如下信息:版权信息、文件名、所在模块、作者信息、历史版本信息、文件内容和作用等。

/*
Copyright (C)
2016 Eorient Inc. All Rights Reserved.
See LICENSE.txt
for this sample’s licensing information
Description:
This file contains the foundational subclass of NSOperation.
History:
16/10/24: Created by ZRJ.
16/11/12: Add socket library
16/11/14: Add math library
*/

这个注释只是提供了版权信息、文件内容和历史版本信息等,文件注释要根据自己实际情况包括内容。

2、文档注释

文档注释就是这种注释内容能够生成API帮助文档。文档注释主要对类型、属性、方法或函数等功能。 文档注释是稍微将单行注释(//)和多行注释(/.../)做一点“手脚”后,就成为了文档注释,单行文档注释(///)和多行文档注释(/*.../)。

import Foundation
/**
    The protocol that types may implement if they wish to be
             notified of significant operation lifecycle events.
*/
protocol OperationObserver {       
     
/// Invoked immediately prior to the `Operation`‘s `execute()` method.
     
func operationDidStart(operation: Operation)     
}

3、代码注释
程序代码中处理文档注释还需要在一些关键的地方添加代码注释,文档注释一般是给一些看不到源代码的人看的帮助文档,而代码注释是给阅读源代码人参考的。代码注释一般是采用单行注释(//)和多行注释(/.../)。
有的时候也会在代码的尾端进行注释,这要求注释内容极短,应该在有足够的空白来分开代码和注释。尾端注释示例代码如下:。

init(timeout: NSTimeInterval) {
  
self.timeout = timeout  //初始化
}

4、使用地标注释
随着编码过程深入,工程代码量会增加,任何在这大量的代码中能快速找到需要方法或者是刚才修改过代码呢?
在Swift代码中使用地标注释,然后就可以使用Xcode工具在代码中快速查找了。地标注释有三个:
· MARK,用于方法或函数的注释。
· TODO,表示这里代码有没有完成,还要处理。
· FIXME,表示这里修改了代码。

——代码排版

代码排版包括: 空行、空格、断行和缩进等内容。代码排版内容比较多工作量很多,但是非常重要。
空行:空行将逻辑相关的代码段分隔开,以提高可读性。下列情况应该总是添加空行:
a 类型声明之前。
b import语句前后。
c 两个方法或函数之间。
d 块注释或单行注释之前。
e 方法或函数内的两个逻辑段之间,用以提高可读性。
f 一个源文件的两个片段之间。

空格:在代码中有些位置是需要有空格的,这个工作量也是很大的。下列是使用空格的规范:
1、赋值符号“=”前后有一个空格。var或let与标识符之间有一个空格。所有的二元运算符,应该使用空格将之与操作数分开。一元操作符和操作数之间不因该加空格,如:++、--等。
2、小左括号“(”之后,小右括号“)”之前不要有空格。
3、大左括号“{”之前有一个空格。
4、在方法或函数参数之前间有一个空格,参数冒号与数据类型之间有一个空    格。

断行:一行代码的长度尽量避免超过80个字符,为了便于查看是否一行代码超出80个字符,很多IDE开发工具都可以在编辑窗口设置显示80行竖线。在Xcode中设置过程是打开菜单Xcode→Preferences,选择Text Editing标签,选中Show→Page guide at column。

由于有的代码比较长需要断行,可以依据如下一般规范断开:
1 在一个逗号后面断开。
2 在一个操作符前面断开,要选择较高级别运算符断开,而非较低级别运算符断开。
3 新的一行应该与上一行缩进两个级别(8个空格)

缩进:4个空格常被作为缩进排版的一个单位,在开发时候使用制表符进行缩进,虽然默认情况下一个制表符等于8个空格,但是在不同的IDE工具中可能设置的一个制表符与空格对应个数会有所不同。在Xcode中默认是一个制表符对应4个空格,我们可以在Xcode中打开菜单Xcode→Preferences,选择Text Editing→Indentation标签,可以在Tab width中进行设置。

缩进可以依据如下一般规范:
· 在函数、方法、闭包、控制语句、计算属性等包含大括号“{}”代码块中,代码块中的内容与首行缩进一个级(4个空格)。
· 如果是if语句中条件表达式的断行,那么新的一行应该与上一行缩进两个级别(8个空格),再往后的断行要与第一次的断行对齐。

 

这是我在学Swift整理的基础笔记,希望给更多刚学IOS开发者带来帮助,在这里博主非常感谢大家的支持!

更多的请到参考我下一篇博文。之后还在持续更新中。。。

 

从0开始学Swift笔记整理(四)