Swift 4.0 基础学习总结(二)

在 Swift 4.0 基础学习总结(一)中,我们学习的Swift中的基本数据类型以及控制流,Swift简介的语法以及提供的更加适用亲民的方法让我们已经初步感受到这门语言的魅力。今天来继续学习Swift 4.0基础知识。

函数

函数相当于OC中的方法。是一个独立的代码块,用来执行特定的任务。
函数书写格式:

func 函数名(参数一:参数类型,参数二:参数类型,...) -> 返回值类型 {
    代码块
    return 返回值
}
// 如果没有返回值 -> 及Void 可以省略
// 函数的调用
函数名 (参数名:参数一,参数名:参数二);
  1. 没有参数没有返回值的函数
func about() -> Void { // 可写作 func about() {
    print("Hello Swift")
}
about()
  1. 有参数没有返回值的函数
func sayHi (saySting : String) -> Void {
    print(saySting)
}
sayHi(saySting: "hello swift")
  1. 没有参数有返回值的函数
func sayHi () -> String {
    return "hello swift"
}
print(sayHi())
  1. 有参数有返回值的函数
func sayHi (sayString : String) -> String {
    return sayString + "hello swift"
}
print(sayHi(sayString: "xx_cc "))

没有定义返回类型的函数实际上会返回一个特殊的类型 Void。它其实是一个空的元组,作用相当于没有元素的元组,可以写作 () 。

内部参数(形式参数名)和外部参数(实际参数标签)。
内部参数指在函数内部能看到的标识符名称。外部参数即在函数外部能看到的标识符名称。默认情况下所有参数同时是内部参数和外部参数,像我们上面的示例中 func 函数名(参数一:参数类型,参数二:参数类型,...) -> 返回值类型,参数名一和参数名二既是内部参数也是外部参数,当我们调用此类函数的时候可以看到其参数名,看一下示例。

// 默认情况
func sum(num1:Int , num2:Int) -> Int {
    return num1 + num2
}
// 调用方法时可以看到 num1,num2两个标识符参数名
// sum(num1: <#T##Int#>, num2: <#T##Int#>)

当希望为参数添加外部函数名,使方法的功能显示更清晰时,可以在相应的标识符参数名前添加其外部参数名。

func sum(fistNum num1:Int ,num2:Int) -> Int {
    return num1 + num2
}
// 此时在调用函数时,显示外部参数名,但是在函数内部仍然使用内部参数名
sum(fistNum: <#T##Int#>, num2: <#T##Int#>)

当不希望显示外部参数是,可以在标识符前添加 _ 用来表示隐藏其外部参数名

func sum(fistNum num1:Int , _ num2:Int) -> Int {
    return num1 + num2
}
// 此时调用函数时,没有num2外部参数名
sum(fistNum: <#T##Int#>, <#T##num2: Int##Int#>)

注意:以上三个示例函数,因为参数名不同因而是三个完全不同的函数。即每一个函数都有一个特定的函数类型,它由形式参数类型,返回类型组成。

可变参数
可变参数即,参数个数可变,可以传零或者多个特定类型的值,传入到可变参数中的值在函数的主体中被当作是对应类型的数组,Swift中可变参数的使用相较于OC中更为简单。

// ...表示参数类型固定,但是参数的个数不确定,
func sum(num : Int...) -> Int {
    var total = 0
// 
    for n in num {
        total += n
    }
    return total
}
// 随意添加多个相同类型参数
sum(num: 20,30,40,50,60,70)

多返回值函数
多返回值函数实际上返回值是一个元组,使用元组实现了函数可以返回多种数据。

func sum(num : Int...) -> (min : Int, max : Int) {
    var maxvalue = num[0]
    var minvaleue = num[0]
    for n in num {
        if n > maxvalue {
            maxvalue = n
        }else if n < minvaleue{
            minvaleue = n
        }
    }
    return (minvaleue,maxvalue)
}
let value = sum(num: 20,30,40,50,60,70)
print(value.max)
pring (value.min)

注意:元组在函数的返回类型中有可能“没有值”,用一个可选元组返回类型来说明整个元组的可能是 nil 。在可选元组类型的圆括号后边添加一个?,例如 (Int, Int)? 来表示这是一个可选元组 。
类似 (Int, Int)?的可选元组类型和包含可选类型的元组 (Int?, Int?)是不同的。对于可选元组类型,整个元组是可选的,而不仅仅是元组里边的单个值为可选类型。

默认参数
可以在命名参数的时候为其赋上默认值

func makeCoffee(coffeeName:String = "雀巢") -> String {
    return "制作了一杯\(coffeeName)"
}
makeCoffee(coffeeName: "拿铁") // 制作了一杯拿铁
makeCoffee()  // 制作了一杯雀巢 

注意:在参数较多时,通常把不带有默认值的形式参数放到函数的形式参数列表中带有默认值的形式参数前边,不带有默认值的形式参数通常对函数有着重要的意义——把它们写在前边可以便于发现无论是否省略带默认值的形式参数,调用的都是同一个函数。

指针参数
当需要在函数内修改参数值时,可以传递指针参数,用来传递地址。使用inout表示是一个指针参数。

var m : Int = 20
var n : Int = 10

func swapNum(num1: inout Int ,num2: inout Int){
    let temp = num1
    num1 = num2
    num2 = temp   
}
swapNum(num1: &m, num2: &n)
print(m)  // 10
print(n)  // 20

函数类型
每一个函数都有一个特定的函数类型,它由形式参数类型,返回类型组成。

func sum(num1:Int , num2:Int) -> Int {
    return num1 + num2
}

例如:上面的sum函数需要传入两个Int类型的值,返回一个Int类型的值,那么sum函数的类型就是(Int,Int) -> Int,也读作:sum函数是有两个Int类型形式参数,并且返回一个Int类型值得函数类型。

函数类型的使用
既然Swift拥有函数类型,那么我们可以像使用其他类型一样使用函数类型,我们可以给一个常量或者变量定义一个函数类型,并且为变量指定一个相应的函数值。

func sum (num1 : Int ,num2 : Int) -> Int {
    return num1 + num2
}
// 不同的函数如果有相同的匹配类型的话,就可以指定相同的变量,和费函数的类型一样
var mathFunc : (Int , Int) -> Int = sum
mathFunc(3, 5)

// 和其他类型一样,当指一个函数为常量或者变量的时候,可以使用Swift类型推断
let mathFunc2 = mathFunc

函数类型作为形式参数类型
我们可以将函数类型作为其他函数的形式参数类型,也就是将函数作为其他函数的参数。这样可以极大的扩展我们的函数方法, 将方法的部分实现交给调用者在调用函数时去实现。

func sum (num1 : Int ,num2 : Int) -> Int {
    return num1 + num2
}
// 这个函数需要我们传入一个有两个Int类型参数并且返回值是Int类型的函数类型 和两个Int类型的函数
func sumWithMath (math : (Int ,Int) -> Int , num1 : Int ,num2 :Int) -> Void {
    let sumValue = math (num1, num2)
    print(sumValue)  // print 10
}
sumWithMath(math: sum(num1:num2:), num1: 5, num2: 5)

函数类型作为返回类型
既然函数类型可以像使用其他类型一样,那么函数类型也一定可以作为返回类型使用,即在函数的返回箭头 ->后面直接跟上一个完整的函数类型即可。直接来看下面例子

// 首先定义了两个对参数执行加减操作的函数
func sum (num1 : Int ,num2 : Int) -> Int {
    return num1 + num2
}
func minus (num1 : Int ,num2 : Int) -> Int {
    return num1 - num2
}
// 定义函数,传入一个Bool值,返回加函数还是减函数
func SelectMathWith (add : Bool) -> (Int,Int)->Int {
    if add {
        return sum(num1:num2:)
    }else{
        return minus(num1:num2:)
    }
}
// 拿到方法返回的函数并进行调用
let sumMath = SelectMathWith(add: true)
sumMath(5, 5)

内嵌函数
Swift中可以在函数的内部定义另外一个函数,这就是内嵌函数。内嵌函数在默认情况下在外部是被隐藏起来的,但是仍然可以通过包裹他们的函数来调用他们,包裹的函数也可以返回它内部的一个内嵌函数来在另外的范围里使用。对于上面的例子,可以重写将sum 与 minus函数作为内嵌函数,在函数内部定义

func SelectMathWith (add : Bool) -> (Int,Int)->Int {   
// 函数内部声明内嵌函数,并将其返回供外部使用
    func sum (num1 : Int ,num2 : Int) -> Int {       
        return num1 + num2
    }   
    func minus (num1 : Int ,num2 : Int) -> Int {       
        return num1 - num2
    }   
    if add {
        return sum(num1:num2:)
    }else{
        return minus(num1:num2:)
    }
}
let sumMath = SelectMathWith(add: true)
sumMath(5, 5)

枚举

定于枚举语法和OC中一样,使用enum关键字来定义一个枚举,然后将其所有的你定义内容放在一个大括号中。

enum Derection {
    case up
    case down
    case left
    case right
}
// 多个成员值也可以出现在同一行中,用逗号隔开即可
enum Derection {
     case up,down,left,right
}

需要注意的是在swift中,up,down,left,right并不一定代表0,1,2,3。如果在定义枚举的时候没有为其内容赋具体的值,那么枚举此时的值就是每个枚举相对应的字符串。

我们可以通过点语法来获得枚举的值

// let n1 : Derection = .up 
var n1 = Derection.up
print(n1) // print up
// 当我们为n1赋值时,n1的类型会被推断出来,因此我们在为n1赋一个新的值时,就可以省略类型,直接赋值。
n1 = .down

swift中枚举的不同成员关联值的类型是可以不同的,假设我们登录公司网站的时候可以用自己的姓名(String)或者自己的工号(Int)登录。那么我们可以将两种登录方式定义在枚举中。

enum LoginWay {
    case NameLogin(String)
    case NumLogin(Int)
}

var mylogin = LoginWay.NameLogin("xx_cc");
// 可以变更赋值
mylogin = .NumLogin(50)

// 通过switch来检查登录方式
switch mylogin {
case .NameLogin(let name):
    print(name)
case .NumLogin(let num):
    print(num)
}

在定义枚举时枚举成员可以使用相同类型的默认值,我们可以使用rewValue来访问枚举成员的默认值

enum MethodType : String{
    case get = "get"
    case post = "post"
    case put = "put"
    case delete = "delete"
}
// 而当我们存储的是Int类型时,同OC一样只需要给第一个枚举成员分配默认值即可
enum Type : Int {
    case get = 0, post, put, delete // 仅 Int 可以
}

let type = MethodType.post.rawValue
// 或者通过下面这种方式获取,创建出的值位MethodType可选类型,因为可能为nil
let type1 = MethodType(rawValue: "put")
let str = type3?.rawValue

类和结构体

类和结构体对比

相同点

  • 定义属性用来存储值
  • 定义方法用于提供功能
  • 定义下标脚本用来允许使用下标语法访问值
  • 定义初始化器用于初始化状态
  • 可以被扩展来默认所没有的功能
  • 遵循协议来针对特定的类型提供标准功能

类有而结构体没有的额外功能

  • 允许一个类继承另一个类的特征
  • 类型转换允许你在运行检查和解释一个类实例的类型
  • 反初始化器允许一个类实例释放任何其被分配的资源
  • 引用计数允许不止对一个类实例的引用 // 结构体在代码中通过复制来传递,并不会使用引用计数

定义结构体
结构体是由一系列具有相同类型或不同类型的数据结构构成的数据集合,结构体指的是一种数据结构,当我们定义一个结构体,相当于定义一个全新的swift类型,结构体是值类型,在方法中传递时是值传递。

定义结构体语法

// 定义结构体
struct location {
    // 属性和方法
    var x : Double
    var y : Double
//    var z : Double
    //方法
    func test() -> Void {
        print("这是结构体之中的test方法")
    }
    // 改变成员属性 : 如果在函数中修改了成员属性那么函数前必须加上 mutating
    mutating func moveH(distance : Double) -> Void {
        self.x += distance
    }
    
    // 给结构体扩充构造函数
    // 1. 默认情况下系统会为每一个结构体提供一个默认的构造函数,并且该构造函数要求给每一个成员属性赋值
    // 2. 构造函数都是以init开头的,并且构造函数不需要返回值。
    // 3. 在构造函数结束时,必须保证所有成员属性有被初始化
    init(_ x : Double, _ y : Double) {
        self.x = x
        self.y = y
    }
}

// 结构体使用
//var center = location(x: 20, y: 30) // 系统默认的初始化器
var center = location(20, 30)
center.x // 通过点语法访问结构体属性
center.x = 30 // 也可以通过点语法访问结构体属性并赋新值
center.test() // 结构体 test方法
center.moveH(distance: 15) // 结构体moveH方法
print(center)

结构体和枚举都是值类型
值类型值当它被指定到常量或者变量,或者被传递给函数时会被拷贝的类型。也就是说当我们重新声明一个变量并且为其赋值之后,此时新的变量与之前的赋值的变量是两个完全不同的实例,我们修改新的变量中的值,不会影响之前的变量。

struct direction {
    var left : Int
    var right : Int
}

let direction1 = direction(left: 10, right: 20)
var direction2 = direction1
// 此时direction2中的值与direction1中相同,但是他们是两个完全不同的实例。修改direciton2中的值不会影响direction1中的值。
direction2.left = 30

注意:Swift 的 String , Array 和 Dictionary类型是作为结构体来实现的,这意味着字符串,数组和字典在它们被赋值到一个新的常量或者变量,亦或者它们本身被传递到一个函数或方法中的时候,其实是传递了拷贝。

这种行为不同于基础库中的 NSString, NSArray和 NSDictionary,它们是作为类来实现的,而不是结构体。 NSString , NSArray 和 NSDictionary实例总是作为一个已存在实例的引用而不是拷贝来赋值和传递。

定义类
类的使用
swift也是一门面向对象开发的语言,面向对象的基础是类,类产生了对象,当我们定义类时可以没有父类,如果没有父类那么该类就是 rootClass,但是通常情况下,我们在定义类时继承自 NSObject (非OC中的NSObject)
定义类的语法

class 类名称 : 继承父类 {

}
class Person : NSObject {
    // 一般属性用var 变量 或者可选类型
    // 规范:如果属性是值类型,则初始化空值
    // 如果属性是对象类型,则初始化为nil值
    var name : String = ""
    var age : Int = 0
    var view : UIView?

}

// 创建类的对象
let p = Person() // 小括号调用其实是在调用构造函数

类是引用类型
相对于值类型,在引用类型被赋值到一个常量,变量或者本身被传递到一个函数的时候它是不会被拷贝的,而是通过引用指向同一个实例。实际上,他们只是相同实例的不同命名。
例如

class Person : NSObject {
    var name : String = ""
    var age : Int = 0
}

let person1 = Person()
person1.name = "cc"
person1.age = 18

let person2 = person1
person2.name = "xx"
// 修改person2的name属性,person1的name属性也会被修改
person1.name // "xx"

Swift中可以使用 (===)相同于 和 (!==)不同于福海来判断两个常量或者变量是否引用自同一个类实例。

属性

swift中属性大致分为三种,存储属性,计算属性,和类型属性。存储属性会存储常量或变量作为实例的一部分,反之计算属性会计算(而不是存储)值。类型属性则与类型本身相关联。

存储属性
存储属性作为特性类和结构体实例一部分的向量或变量。我们可以为存储属性提供一个默认值作为它定义的一部分,也可以在初始化的过程中设置和修改存储属性的初始值。以上面的例子为例

class Person : NSObject {
    var name : String = ""
   let age : Int = 0
}
let person1 = Person()
person1.name = "xx_cc"
person1.age = 2 // 我们可以修改name属性不能修改age属性

计算属性
类和结构体同样能都定义计算属性,计算属性实际并不存储值,而是提供一个读取器和一个可选的设置器来间接得到和设置其他的属性和值。
举个例子

class Student {
    // 类的属性定义
    // 存储属性
    var name : String = ""
    var age : Int = 0
    var mathScore : Double = 0.0
    var chinaScore : Double = 0.0
    
    // 计算属性
    // 例子:需求:获取某学生的平均成绩
    var averageScore : Double { // set get方法可以省略,当省略set方法时,此属性也就是只读属性
        return (self.chinaScore + self.mathScore) * 0.5
    }
}
let stu = Student() // 调用系统默认提供的构造函数
stu.mathScore = 99.7
stu.chinaScore = 98
stu.averageScore // 调用计算属性 OC中很多没有参数的方法在Swift中可以写成计算属性

类型属性
Swift中类型属性相当于OC中静态常量变量,当对于特定类型的所有实例都通用的值时,可以使用类型属性来定义属性。类型属性只有一个拷贝,无论我们创建多少类对应的实例。因此类型属性子初始化时必须有一个默认值。

// 类属性 使用static标识并且需要有一个默认值
static var courseCount : Int = 0

监听属性的变化
Swift中属性观察者会坚定属性的变化,每当一个属性的值被设置,属性观察者都会被调用,即使这个值与该属性的当前值相同。我们可以为定义的任意存储属性添加属性观察者。举个例子

class Person {
    var name : String = "" {
        // 属性监听器 :监听属性的改变 一般二选其一 
        // 1. 监听属性即将发生改变 其实还没有改变
        willSet{ //(newName) 可以通过小括号中加变量名修改 newValue的名字
            // 如果在即将改变时想要拿到新的值
            print("属性即将改变")
            print(name) // 当前值 未发生改变
            print(newValue) // 将要赋的值
        }
        // 2. 监听属性已经发生改变
        didSet{
            print(name) // 当前值,已经发生改变新值
            // 旧的值
            print("属性发生改变")
            print(oldValue)
        }
    }
}
let person = Person()
person.name = "cc"
person.name = "xx"
  • willSet 会在该值被存储之前被调用。
  • didSet 会在一个新值被存储后被调用。
    如果你实现了一个 willSet 观察者,新的属性值会以常量形式参数传递。你可以在你的 willSet 实现中为这个参数定义名字。如果你没有为它命名,那么它会使用默认的名字 newValue 。
    同样,如果你实现了一个 didSet观察者,一个包含旧属性值的常量形式参数将会被传递。你可以为它命名,也可以使用默认的形式参数名 oldValue 。如果你在属性自己的 didSet 观察者里给自己赋值,你赋值的新值就会取代刚刚设置的值。

注意:父类属性的 willSet 和 didSet 观察者会在子类初始化器中设置时被调用。它们不会在类的父类初始化器调用中设置其自身属性时被调用。

类的构造函数

构造函数类似于OC中的初始化方法:init方法,默认情况下创建一个类时,必然会调用一个构造函数,即便是没有编写任何构造函数,编译器也会提供一个默认的构造函数,而如果是继承自NSObject,可以对父类的构造函数进行重写。
举个例子

class Person {
    var name : String = ""
    var age : Int = 0

    // 注意:如果有自定义构造函数,那么会将系统提供的构造函数覆盖掉,如果我们不希望系统提供的构造函数 需要明确自己实现
    init() {
        
    }
    
    init(name : String , age : Int) {
        self.name = name
        self.age = age
    }
  
    init(dict : Dictionary ) {
        /*
         let dictName = dict["name"]
         name = dictName as! String // 强制转化为string ,使用起来非常危险
        */
        if let name = dict["name"] as? String {
            self.name = name
        }
        if let age = dict["age"] as? Int {
            self.age = age
        }
    }
}

let p = Person() // 系统默认提供的
let p1 = Person(name: "cl", age: 18) // 使用自己提供的构造函数
let p2 = Person(dict: ["name":"cl","age":18])
p2.name

我们可以使用KVC来为类中的属性赋值,但是使用KVC是有条件的

  1. 继承自NSObject
  2. 必须在构造函数中,先调用Super.init()
  3. 调用setValuesForKeys(dict)
  4. 如果字典中某一个key没有对应的属性,则需要重写setValueforUnderfinedKey方法
    举个例子
class Person : NSObject {
    var name : String = ""
    var age : Int = 18
    // kvc方式赋值
    init(dict : [String : Any]) {
        super.init()
        setValuesForKeys(dict)
    }
 
    override func setValue(_ value: Any?, forUndefinedKey key: String) {
    }
}
let p = Person(dict: ["name":"cl","age":18])

类的析构函数

Swift中析构函数也就是OC中的delloc函数,当对象销毁时系统会自动调用函数,同时Swift会自动释放不再需要的实例以释放资源。

  1. swift通过自动引用计数(ARC)处理实例的内存管理
  2. 当引用计数为0时,系统会自动调用析构函数(不可以手动调用)
  3. 通常在析构函数中释放一些资源(如移除通知等等)
//deinit {
//    // 析构函数 -> 释放过程
//}

class Person {
    var name : String = ""
    var age : Int = 18
    // 重写析构函数来监听对象的销毁
    deinit {
        print("Person对象 -deinit")
    }
}
var p : Person? = Person()
p = nil  // print Person对象 -deinit

你可能感兴趣的:(Swift 4.0 基础学习总结(二))