文档章节

Swift

工匠心
 工匠心
发布于 2016/06/23 01:09
字数 3172
阅读 17
收藏 2
  • 函数的定义
    • 格式 func 函数名(行参列表) -> 返回值 {代码实现}
    • 调用 let result = 函数名(值1, 参数2: 值2...)

func sum(a: Int, b: Int) -> Int {

    return a + b

}

 

let result = sum(10, b: 20)

  • 没有返回值的函数,一共有三种写法
    • 省略
    • ()
    • Void

func demo(str: String) -> Void {

    print(str)

}

func demo1(str: String) -> () {

    print(str)

}

func demo2(str: String) {

    print(str)

}

 

demo("hello")

demo1("hello world")

demo2("olleh")

  • 外部参数
    • 在形参名前再增加一个外部参数名,能够方便调用人员更好地理解函数的语义
    • 格式:func 函数名(外部参数名 形式参数名: 形式参数类型) -> 返回值类型 { // 代码实现 }
    • Swift 2.0 中,默认第一个参数名省略

func sum1(num1 a: Int, num2 b: Int) -> Int {

    return a + b

}

 

sum1(num1: 10, num2: 20)

 

闭包

与 OC 中的 Block 类似,闭包主要用于异步操作执行完成后的代码回调,网络访问结果以参数的形式传递给调用方

OC Block 概念回顾

  • 闭包类似于 OC 中的 Block
    • 预先定义好的代码
    • 在需要时执行
    • 可以当作参数传递
    • 可以有返回值
    • 包含 self 时需要注意循环引用
    •  

闭包的定义

  • 定义一个函数

//: 定义一个 sum 函数

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

    return num1 + num2

}

sum(num1: 10, num2: 30)

 

//: 在 Swift 中函数本身就可以当作参数被定义和传递

let mySum = sum

let result = mySum(num1: 20, num2: 30)

  • 定义一个闭包
    • 闭包 = { (行参) -> 返回值 in // 代码实现 }
    • in 用于区分函数定义和代码实现

//: 闭包 = { (行参) -> 返回值 in // 代码实现 }

let sumFunc = { (num1 x: Int, num2 y: Int) -> Int in

    return x + y

}

sumFunc(num1: 10, num2: 20)

  • 最简单的闭包,如果没有参数/返回值,则 参数/返回值/in 统统都可以省略
    • { 代码实现 }

let demoFunc = {

    print("hello")

 

 

循环引用

  • 建立 NetworkTools 对象

class NetworkTools: NSObject {

 

    /// 加载数据

    ///

    /// - parameter finished: 完成回调

    func loadData(finished: () -> ()) {

        print("开始加载数据...")

 

        // ...

        finished()

    }

 

    deinit {

        print("网络工具 88")

    }

}

  • 实例化 NetworkTools 并且加载数据

class ViewController: UIViewController {

 

    var tools: NetworkTools?

 

    override func viewDidLoad() {

        super.viewDidLoad()

 

        tools = NetworkTools()

        tools?.loadData() {

            print("come here \(self.view)")

        }

    }

 

    /// 与 OC 中的 dealloc 类似,注意此函数没有()

    deinit {

        print("控制器 88")

    }

}

运行不会形成循环引用,因为 loadData 执行完毕后,就会释放对 self 的引用

  • 修改 NetworkTools,定义回调闭包属性

/// 完成回调属性

var finishedCallBack: (()->())?

 

/// 加载数据

///

/// - parameter finished: 完成回调

func loadData(finished: () -> ()) {

 

    self.finishedCallBack = finished

 

    print("开始加载数据...")

 

    // ...

    working()

}

 

func working() {

    finishedCallBack?()

}

 

deinit {

    print("网络工具 88")

}

运行测试,会出现循环引用

解除循环引用

  • 与 OC 类似的方法

/// 类似于 OC 的解除引用

func demo() {

    weak var weakSelf = self

    tools?.loadData() {

        print("\(weakSelf?.view)")

    }

}

  • Swift 推荐的方法

loadData { [weak self] in

    print("\(self?.view)")

}

  • 还可以

loadData { [unowned self] in

    print("\(self.view)")

}

闭包(Block) 的循环引用小结

  • Swift
    • [weak self]
      • self是可选项,如果self已经被释放,则为nil
    • [unowned self]
      • self不是可选项,如果self已经被释放,则出现野指针访问
  • Objc
    • __weak typeof(self) weakSelf;
      • 如果self已经被释放,则为nil
    • __unsafe_unretained typeof(self) weakSelf;
      • 如果self已经被释放,则出现野指针访问

 

 

构造函数基础

构造函数是一种特殊的函数,主要用来在创建对象时初始化对象,为对象成员变量设置初始值,在 OC 中的构造函数是 initWithXXX,在 Swift 中由于支持函数重载,所有的构造函数都是 init

构造函数的作用

  • 分配空间 alloc
  • 设置初始值 init

必选属性

  • 自定义 Person 对象

class Person: NSObject {

 

    /// 姓名

    var name: String

    /// 年龄

    var age: Int

}

提示错误 Class 'Person' has no initializers -> 'Person' 类没有实例化器s

原因:如果一个类中定义了必选属性,必须通过构造函数为这些必选属性分配空间并且设置初始值

  • 重写 父类的构造函数

/// `重写`父类的构造函数

override init() {

 

}

提示错误 Property 'self.name' not initialized at implicitly generated super.init call -> 属性 'self.name' 没有在隐式生成的 super.init 调用前被初始化

  • 手动添加 super.init() 调用

/// `重写`父类的构造函数

override init() {

    super.init()

}

提示错误 Property 'self.name' not initialized at super.init call -> 属性 'self.name' 没有在 super.init 调用前被初始化

  • 为必选属性设置初始值

/// `重写`父类的构造函数

override init() {

    name = "张三"

    age = 18

 

    super.init()

}

小结

  • 非 Optional 属性,都必须在构造函数中设置初始值,从而保证对象在被实例化的时候,属性都被正确初始化
  • 在调用父类构造函数之前,必须保证本类的属性都已经完成初始化
  • Swift 中的构造函数不用写 func

子类的构造函数

  • 自定义子类时,需要在构造函数中,首先为本类定义的属性设置初始值
  • 然后再调用父类的构造函数,初始化父类中定义的属性

/// 学生类

class Student: Person {

 

    /// 学号

    var no: String

 

    override init() {

        no = "001"

 

        super.init()

    }

}

小结

  • 先调用本类的构造函数初始化本类的属性
  • 然后调用父类的构造函数初始化父类的属性
  • Xcode 7 beta 5之后,父类的构造函数会被自动调用,强烈建议写 super.init(),保持代码执行线索的可读性
  • super.init() 必须放在本类属性初始化的后面,保证本类属性全部初始化完成

Optional 属性

  • 将对象属性类型设置为 Optional

class Person: NSObject {

    /// 姓名

    var name: String?

    /// 年龄

    var age: Int?

}

  • 可选属性不需要设置初始值,默认初始值都是 nil
  • 可选属性是在设置数值的时候才分配空间的,是延迟分配空间的,更加符合移动开发中延迟创建的原则

 

重载构造函数

  • Swift 中支持函数重载,同样的函数名,不一样的参数类型

/// `重载`构造函数

///

/// - parameter name: 姓名

/// - parameter age:  年龄

///

/// - returns: Person 对象

init(name: String, age: Int) {

    self.name = name

    self.age = age

 

    super.init()

}

注意事项

  • 如果重载了构造函数,但是没有实现默认的构造函数 init(),则系统不再提供默认的构造函数
  • 原因,在实例化对象时,必须通过构造函数为对象属性分配空间和设置初始值,对于存在必选参数的类而言,默认的 init() 无法完成分配空间和设置初始值的工作

调整子类的构造函数

  • 重写父类的构造函数

/// `重写`父类构造函数

///

/// - parameter name: 姓名

/// - parameter age:  年龄

///

/// - returns: Student 对象

override init(name: String, age: Int) {

    no = "002"

 

    super.init(name: name, age: age)

}

  • 重载构造函数

/// `重载`构造函数

///

/// - parameter name: 姓名

/// - parameter age:  年龄

/// - parameter no:   学号

///

/// - returns: Student 对象

init(name: String, age: Int, no: String) {

    self.no = no

 

    super.init(name: name, age: age)

}

注意:如果是重载的构造函数,必须 super 以完成父类属性的初始化工作

重载重写

  • 重载,函数名相同,参数名/参数类型/参数个数不同
    • 重载函数并不仅仅局限于构造函数
    • 函数重载是面相对象程序设计语言的重要标志
    • 函数重载能够简化程序员的记忆
    • OC 不支持函数重载,OC 的替代方式是 withXXX...
  • 重写,子类需要在父类拥有方法的基础上进行扩展,需要 override 关键字

 

KVC 字典转模型构造函数

/// `重写`构造函数

///

/// - parameter dict: 字典

///

/// - returns: Person 对象

init(dict: [String: AnyObject]) {

    setValuesForKeysWithDictionary(dict)

}

  • 以上代码编译就会报错!
  • 原因:
    • KVC 是 OC 特有的,KVC 本质上是在运行时,动态向对象发送 setValue:ForKey: 方法,为对象的属性设置数值
    • 因此,在使用 KVC 方法之前,需要确保对象已经被正确实例化
  • 添加 super.init() 同样会报错
  • 原因:
    • 必选属性必须在调用父类构造函数之前完成初始化分配工作
  • 将必选参数修改为可选参数,调整后的代码如下:

/// 个人模型

class Person: NSObject {

 

    /// 姓名

    var name: String?

    /// 年龄

    var age: Int?

 

    /// `重写`构造函数

    ///

    /// - parameter dict: 字典

    ///

    /// - returns: Person 对象

    init(dict: [String: AnyObject]) {

        super.init()

 

        setValuesForKeysWithDictionary(dict)

    }

}

运行测试,仍然会报错

错误信息:this class is not key value coding-compliant for the key age. -> 这个类的键值 age 与 键值编码不兼容

  • 原因:
    • 在 Swift 中,如果属性是可选的,在初始化时,不会为该属性分配空间
    • 而 OC 中基本数据类型就是保存一个数值,不存在可选的概念
  • 解决办法:给基本数据类型设置初始值
  • 修改后的代码如下:

/// 姓名

var name: String?

/// 年龄

var age: Int = 0

 

/// `重写`构造函数

///

/// - parameter dict: 字典

///

/// - returns: Person 对象

init(dict: [String: AnyObject]) {

    super.init()

 

    setValuesForKeysWithDictionary(dict)

}

提示:在定义类时,基本数据类型属性一定要设置初始值,否则无法正常使用 KVC 设置数值

KVC 函数调用顺序

init(dict: [String: AnyObject]) {

    super.init()

 

    setValuesForKeysWithDictionary(dict)

}

 

override func setValue(value: AnyObject?, forKey key: String) {

    print("Key \(key) \(value)")

 

    super.setValue(value, forKey: key)

}

 

// `NSObject` 默认在发现没有定义的键值时,会抛出 `NSUndefinedKeyException` 异常

override func setValue(value: AnyObject?, forUndefinedKey key: String) {

    print("UndefinedKey \(key) \(value)")

}

  • setValuesForKeysWithDictionary 会按照字典中的 key 重复调用 setValue:forKey 函数
  • 如果没有实现 forUndefinedKey 函数,程序会直接崩溃
    • NSObject 默认在发现没有定义的键值时,会抛出 NSUndefinedKeyException 异常
  • 如果实现了 forUndefinedKey,会保证 setValuesForKeysWithDictionary 继续遍历后续的 key
  • 如果父类实现了 forUndefinedKey,子类可以不必再实现此函数

子类的 KVC 函数

/// 学生类

class Student: Person {

 

    /// 学号

    var no: String?

}

  • 如果父类中已经实现了父类的相关方法,子类中不用再实现相关方法

 

convenience 便利构造函数

  • 默认情况下,所有的构造方法都是指定构造函数 Designated
  • convenience 关键字修饰的构造方法就是便利构造函数
  • 便利构造函数具有以下特点:
    • 可以返回 nil
    • 只有便利构造函数中可以调用 self.init()
    • 便利构造函数不能被重写或者 super

/// `便利构造函数`

///

/// - parameter name: 姓名

/// - parameter age:  年龄

///

/// - returns: Person 对象,如果年龄过小或者过大,返回 nil

convenience init?(name: String, age: Int) {

    if age < 20 || age > 100 {

        return nil

    }

 

    self.init(dict: ["name": name, "age": age])

}

注意:在 Xcode 中,输入 self.init 时没有智能提示

/// 学生类

class Student: Person {

 

    /// 学号

    var no: String?

 

    convenience init?(name: String, age: Int, no: String) {

        self.init(name: name, age: age)

 

        self.no = no

    }

}

便利构造函数应用场景

  • 根据给定参数判断是否创建对象,而不像指定构造函数那样必须要实例化一个对象出来
  • 在实际开发中,可以对已有类的构造函数进行扩展,利用便利构造函数,简化对象的创建

构造函数小结

  • 指定构造函数必须调用其直接父类的的指定构造函数(除非没有父类)
  • 便利构造函数必须调用同一类中定义的其他指定构造函数或者用 self. 的方式调用父类的便利构造函数
  • 便利构造函数可以返回 nil
  • 便利构造函数不能被重写

 

 

懒加载

在 iOS 开发中,懒加载是无处不在的

  • 懒加载的格式如下:

lazy var person: Person = {

    print("懒加载")

    return Person()

}()

  • 懒加载本质上是一个闭包
  • 以上代码可以改写为以下格式

let personFunc = { () -> Person in

    print("懒加载")

    return Person()

}

lazy var demoPerson: Person = self.personFunc()

  • 懒加载的简单写法

lazy var demoPerson: Person = Person()

 

 

只读属性

getter & setter

  • 在 Swift 中 getter & setter 很少用,以下代码仅供了解

private var _name: String?

var name: String? {

    get {

        return _name

    }

    set {

        _name = newValue

    }

}

存储型属性 & 计算型属性

  • 存储型属性 - 需要开辟空间,以存储数据
  • 计算型属性 - 执行函数返回其他内存地址

var title: String {

    get {

        return "Mr " + (name ?? "")

    }

}

  • 只实现 getter 方法的属性被称为计算型属性,等同于 OC 中的 ReadOnly 属性
  • 计算型属性本身不占用内存空间
  • 不可以给计算型属性设置数值
  • 计算型属性可以使用以下代码简写

var title: String {

    return "Mr " + (name ?? "")

}

计算型属性与懒加载的对比

  • 计算型属性
    • 不分配独立的存储空间保存计算结果
    • 每次调用时都会被执行
    • 更像一个函数,不过不能接收参数,同时必须有返回值

var title2: String {

    return "Mr" + (name ?? "")

}

  • 懒加载属性
    • 在第一次调用时,执行闭包并且分配空间存储闭包返回的数值
    • 会分配独立的存储空间
    • 与 OC 不同的是,lazy 属性即使被设置为 nil 也不会被再次调用

lazy var title: String = {

    return "Mr " + (self.name ?? "")

}()

 

© 著作权归作者所有

下一篇: 复习Swift
工匠心
粉丝 7
博文 44
码字总数 43071
作品 0
东城
私信 提问

暂无文章

只需一步,在Spring Boot中统一Restful API返回值格式与统一处理异常

统一返回值 在前后端分离大行其道的今天,有一个统一的返回值格式不仅能使我们的接口看起来更漂亮,而且还可以使前端可以统一处理很多东西,避免很多问题的产生。 比较通用的返回值格式如下:...

晓月寒丶
昨天
59
0
区块链应用到供应链上的好处和实际案例

区块链可以解决供应链中的很多问题,例如记录以及追踪产品。那么使用区块链应用到各产品供应链上到底有什么好处?猎头悬赏平台解优人才网小编给大家做个简单的分享: 使用区块链的最突出的优...

猎头悬赏平台
昨天
28
0
全世界到底有多少软件开发人员?

埃文斯数据公司(Evans Data Corporation) 2019 最新的统计数据(原文)显示,2018 年全球共有 2300 万软件开发人员,预计到 2019 年底这个数字将达到 2640万,到 2023 年达到 2770万。 而来自...

红薯
昨天
65
0
Go 语言基础—— 通道(channel)

通过通信来共享内存(Java是通过共享内存来通信的) 定义 func service() string {time.Sleep(time.Millisecond * 50)return "Done"}func AsyncService() chan string {retCh := mak......

刘一草
昨天
58
0
Apache Flink 零基础入门(一):基础概念解析

Apache Flink 的定义、架构及原理 Apache Flink 是一个分布式大数据处理引擎,可对有限数据流和无限数据流进行有状态或无状态的计算,能够部署在各种集群环境,对各种规模大小的数据进行快速...

Vincent-Duan
昨天
60
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部