《swift 与Cocoa 框架开发》摘要

前言

Objcet-c 与 c++ 都是C语言的后代,而且都是面向对象的程序设计语言. OC 与 C++的主要区别就是它是一种动态语言. 这里的绑定指将函数绑定到它们处理的具体数据. 绑定, 要么像C++ 那样在编译时进行, 即静态绑定, 好处是可以提高运行时性能, 但降低了灵活性. OC采用动态绑定, 在运行时绑定, 会稍慢一些, 但增强了语言的灵活性.
然而OC也有一些问题, 因为它是以C语言为基础的, 所以它从父语言那里继承了大量古怪的特性,比如预处理器和指针运算. 这些功能虽然很强大, 但是会降低代码的可读性和安全性, 所以OC 有些过时了.
在这基础上, 苹果公司发布了Swift, 它的设计目标是更容易掌握, 且在防范程序员错误方面比OC做得更好

第一章 Cocoa开发工具

  1. 开发者计划
    加入后可以提前获取OS的下一个版本, 99美元/年

  2. 用Xcode创建项目
    Product Name: 产品名称, 对用户可见, 将来可以修改
    Organization Identifier 机构标示符: 用于生成捆绑包ID(bundle ID ),
    捆绑包ID是一个字符串, 一般是域名的倒转+项目名. 如com.oreilly.MyUserfulApplication

  3. 方案选择(Scheme selector)
    项目可以包含多个目标(target), 也就是应用程序最终创建的产品. 目标之间可以共享资源, 比如代码, 声音, 图片等

  4. IOS模拟器
    HOME键 = Command+Shift+H
    锁定设备 = command + L

第二章 使用Swift设计程序

  1. Swift语言致力于实现如下目标:

    1. 安全 C语言中的很多缺陷在Swift中都很难再碰到. 重视强类型化, 除了一些纪委特殊的情况, 它是不允许对象为null的.
    2. 现代 Swift包含了大量现代语言的特性, 可以轻松的表达代码逻辑. 包括: 模式匹配swich, 闭包, 所有值都是对象的概念
    3. 强大 Swift可以访问整个Objective-C运行时, 而且可以无缝连接到Objective-C的类
  2. playground
    用来编写Swift代码, 可以即时查看代码结果的攻击

  3. 变量和常量
    1. var定义常量.let定义变量. Swift鼓励程序员多使用let, 因为会更安全
    2. 常量必须拥有值. 如果在创建一个常量时没有指定一个值, 会编译错误
    3. 变量可以不包含值, 但在访问它之前不想有值
    4. Swift中不同类型不能合并, 如: anInteger += "yes" 是不合法的
    5. Swift中没有值用nil 表示, 它是与其他类型都不同的一种类型
    6. Swift中所有变量都是需要有取值的, 如果希望一个变量在某时候为nil, 那就可以使用可选变量. 只有可选变量才能被设置为nil, 如果一个变量没有被声明为可选变量, 那就不允许它为nil
    var anOptionalInteger: Int ?
    7. 对于可选变量可以使用拆包(unwrap) 获取其值. ** 如果对一个可选变量拆包, 而它没有值, 程序会抛出一个运行时错误, 并会崩溃**
    8. Swift中规定所有类型都必须进行显式转换, 尝试将一种类型的值赋给另外一种类型的变量, 会报错
    9. 打印函数中出现多种类型

    var a: Int = 1
    println("This is a Integer : a = \(a)")
    
  4. 元组
    元组是数据的简单集合, 利用元组, 可以将多个值一起捆绑到单个值中

    let aTuple = (1, "yes")
    let xyz2 = (x:1, y:2, z:3)
    println(xyz2.x) // 可以通过标签访问
    println(xyz2.0) // 可以通过下标访问
    
  5. 数组
    方括号用来创建数组, append: 在最后追加元素; inert 在指定位置插入元素

    let arrayOfIntegers: [Int] = [1,2,3]
    let arrayOfIntegers2 = [1,2,3]
    let arrayOfIntegers3 = [Int]()
    
  6. 字典

    字典用于将键映射到值, 当希望表示一组相关信息时, 字典是很有用的. 字典可以包含任意类型的值

    var crew = ["Caption": "wang", "first officer": "jing", "Second officer": "can"]
    
  7. 函数可以存储在变量中.

  8. 闭包, 指一段匿名代码块, 可以像函数一样使用, 可以方便的将闭包传给其他函数, 告诉他们应该执行某一任务.
    swift 允许将代码存储在变量中. 这样做时, 代码称为闭包.

    var numbers = [9,1,3,8,4]
    // 内置函数sorted 进行升序排序
    var sortedNumbers = sorted(numbers)
    
    // 第二个参数觉得哪个排在前面
    var sortedNumbers2 = sorted(numbers, { (n1: Int, n2: Int) -> Bool in
        return n1>=n2
    })
    
  9. 初始化和反初始化

    class InitAndDeinitExample {
        init() { // 在对象被创建时调用
            println("I have been created!")
        }
        // 便捷初始化器, 调用上述指定初始化器所必需的
        convenience init (text: String) { // 如果在初始化失败时, 需要返回nil, 在init后可以加? 实现
            self.init() // 必需的, 注释掉会报错
            println("I was called with the convenience initializer");
        }
        deinit { // 在对象消失时调用, 此方法在对象的retainCount=0时运行. 当对象为nil时可以触发
            println("I' m going away!")
        }
        func method() {
            //self.init() // 编译错误, 普通方法中不能调用init()
        }
    }
    var example : InitAndDeinitExample?
    example = InitAndDeinitExample() // I have been created!
    example = nil //I' m going away!
    //I have been created!
    //I was called with the convenience initializer
    example = InitAndDeinitExample(text: "hello") 
    
  10. 存储属性和计算属性
    闭包, 指一段匿名代码块, 可以像函数一样使用, 可以方便的将闭包传给其他函数, 告诉他们应该执行某一任务.

class Rectangle {
    var width : Double = 0.0
    var height : Double = 0.0 // 存储属性
    var area : Double { // 计算属性
        // getter
        get {
            return width * height
        }
        // setter
        set {
            width = sqrt(newValue) // newValue是内置变量
            height = sqrt(newValue)
        }
    }
}


var rect = Rectangle()
rect.width = 3.0
rect.height = 4.0
println(rect.area)
rect.area = 9
println("width = \(rect.width) height = \(rect.height)")

11 属性观察器
用于在属性发生变化前触发(willSet) 和属性刚刚发生变化后触发(didSet). 对于属性的实际处理方式, 属性观察器不会进行任何修改, 它只是在属性变化前后增加附加行为

class PropertyObserverExample {
    var number: Int = 1 {
        willSet(newNuber) { // 参数表示即将要设定的值
            println("About to change to \(newNuber)")
        }
        didSet(oldNumber) { // 参数表示被修改之前的值
            println("Jst changed from \(oldNumber) to \(self.number)")
        }
    }
}


var example = PropertyObserverExample()
example.number = 4

12 惰性属性
惰性属性就是直到首次访问时才会设定的属性, 在创建对象的时候不会初始化. 用于某些类的设置工作需要耗费大量时间时, 将这些工作推迟到将来真正需要时再进行

    class SomeExpensiveClass {
        init(id: Int) {
            println("Expensive class \(id) created!")
        }
    }

    class LazyPropertyExample {
        var expensiveClass1 = SomeExpensiveClass(id: 1)
        lazy var expensiveClass2 = SomeExpensiveClass(id: 2)
        init() {
            println("LazyPropertyExample class created!")
        }
    }

    // error: lazy is only valid for members of a struct or class
    // lazy只能用在结构体或类中
    //lazy var exampl = SomeExpensiveClass(id: 2)

    var lazyPropertyExample = LazyPropertyExample()
    // 到这里会输出
    // Expensive class 1 created!
    // LazyPropertyExample class created!

    lazyPropertyExample.expensiveClass1
    // 没有输出

    lazyPropertyExample.expensiveClass2
    // Expensive class 2 created!

13 协议

protocol Blinking {
    // 这个属性必须至少是可获取的
    var isBlinking: Bool {get}
    // 这个属性必须是可获取和可设置的
    var blinked: Double {get set}
    // 这个函数必须存在, 但具体由子类实现
    func startBlinking(blinkSpeed: Double) -> Void
}


class Light: Blinking {
    var isBlinking: Bool = false
    var blinked: Double = 0.0

    func startBlinking(blinkSpeed: Double) {
        println("Light 's startBlinking() is invoke!")
    }

    // 这里不能用override, swift 中的 override 只能用于重写父类的方法, 而不是协议的方法
}

var aBlinkingThing: Blinking?

aBlinkingThing = Light() // 类型为父类, 创建的对象是子类

// 报错: cannot assign to the result of this expression, 因为protocol中定义isBlinking是不可设置的
//aBlinkingThing?.isBlinking = true
aBlinkingThing?.blinked = 2.0
aBlinkingThing?.startBlinking(4.0)

// 关于继承
class Light2: Light {

    // 这里会报错:cannot override with a stored property.
    // 子类不能定义和父类相同的变量
    //var isBlinking: Bool = false

    // 这里可以使用 override
    override func startBlinking(blinkSpeed: Double) {
        println("Light 's startBlinking() is invoke!")
    }

    // 这里不能用override
}

14 扩展
扩展可用于以下两种情况, 在 Swift 中可以扩展任意类型, 包括内置类型
1. 正在使用别人编写的一个类型, 希望向其中增加功能, 但不能访问其源代码, 或者不想为它花费太多时间.
2. 正在使用自己编写的一个类型, 但希望将其功能划分到不同部分, 已提高可读性
注意: 扩展只能增加计算属性, 不能增加存储属性

extension Int { // 扩展的语法: extension + 要扩展的类型
    var doubled: Int {
        return self * 2
    }

    func mutiplyWith(anotherNumber: Int) -> Int {
        return self * anotherNumber
    }
}

println(2.doubled) // 4
println(4.mutiplyWith(8)) //32

15 访问控制
  - pubic
   可供 App 中任意部分访问. 例如,用于生成 IOSApp 的 UIKit 中的所有类都是public 的
  - internal
   只能在定义这些数据的模块内访问. 模块是一个应用程序, 库或者框架. 这就是我们不能访问 UIKit 内部工作机制的原因. 因为它被定义为 UIKit 框架的 internal. internal 是默认的访问级别.
  - private
    只能供声明它的文件中访问. private 的一种应用, 是将变量的 setter 设置为 private, 这样这个属性对外就是只读的, 但是在它的源文件内部可以随意修改.
另外, 一个方法或属性能够拥有的访问控制种类, 不能大于它所在的类.

16 运算符
swift 中的运算符实际上是一个函数. 例如+ 可以表示成这样:

func +(left: Int, right: Int) {
    return left + right
}

swift 允许自定义新的运算符或者重载已有的运算符.

class Vector2D {
    var x: Float = 0.0
    var y: Float = 0.0

    init (x: Float, y: Float) {
        self.x = x
        self.y = y
    }

}


func +(left: Vector2D, right: Vector2D) -> Vector2D{
    let result = Vector2D(x: left.x+right.x, y: left.y + right.y)
    return result
}

var first = Vector2D(x: 2, y: 3)
var second = Vector2D(x: 4, y: 6)

var result = first + second
println(result.x) //6.0
println(result.y) //9.0

17 泛型
Swift 是一种静态类型化语言. 也就是说, Swift 编译器需要确切知道你的代码正在处理什么类型的信息. 这意味着, 你不能将字符串传给打算用来处理日期的代码, 而这在 Objective-C中是允许的. 泛型的出现, 解决了这个问题, 使得在编写代码时, 可以不需要准确的知道这些信息的类型. 数组就是泛型的一个应用示例: 数组在创建使用时并没有对存储的数据进行任何操作. 它只是将其存储为一个有序集合.

// 定义一个不确定类型的树
class Tree<T> {
    var value: T
    var children: [Tree<T>] = []

    init(value: T) {
        self.value = value
    }

    func addChild(value: T) -> Tree<T> {
        let newChild = Tree<T>(value: value)
        children.append(newChild)
        return self
    }
}

let StringTree: Tree<String> = Tree<String>(value: "Hello")
StringTree.addChild(" The")
StringTree.addChild(" world")

for item in StringTree.children {
    println(item.value)
}

//result:
// The
// world

18 模块
在 Swift 中, 代码是分为模块的. 在定义一个框架或应用时, 添加到其中的所有代码都被存放到目标的模块中. 为了可以访问这些代码, 可以使用 import 关键字. 比如
import UIKit
>   Swift 中的对象是内存托管的. 当使用一个对象时, Swift 会将其放到内存中, 当不再需要它时 Swift 会将其从内存中删除. Swift 中用于跟踪哪些对象是否在使用, 是通过引用计数(reference counting) 来实现的. 在将一个对象指定给一个变量时, 一个称为 retain count 的计数器加1 . 当不再将该对象指定给该变量时, retain count 会减1. 如果这个计数器降到0, 就表示没有变量引用此对象, 于是该对象会被从内存中删除.
>  好处是这些工作都是 Swift 在编译器级别进行的, 当编译器读取我们编写的代码时, 会自动跟踪对象. 但有一种情况叫做循环引用(retain cycle). 指两个对象之间相互引用, 但又没有别的部分引用, 所以就会一直留在内存中, 而且其他部分也无法访问. Swift 中引入弱引用来解决这一问题. 弱引用是一种变量, 它会应用一个对象, 但不会改变这个对象的引用计数. 当我们不太关心这个对象是否要保留在内存中是就可以使用弱引用.

class Test {
    // weak var 是隐式可选的
    weak var weakRef : ClassType?
}   

19 字符串和数据
  - 字符串是由 Character 对象组成的序列, 每个对象表示一个 Unicode 字符
  - 字符串的比较使用"=="表示判断两个字符串是否内容相同. "===" 表示判断两个字符串是否引用了同一个对象.
  - 在 Cocoa 中, 数据被表示为 NSData 对象. 包括磁盘上和网络上的数据等
20 序列号和反序列化

// 定义一个可以被序列化和反序列化的对象
class SerializableObject: NSObject, NSCoding {
    var myName: String?
    // 在这里接收参数NSCoder, 将要存储的数据编码并存储数据到 key 中
    func encodeWithCoder(aCoder: NSCoder) {
        aCoder.encodeObject(myName!, forKey: "name")
    }
    override init() {
        self.myName = "My Object"
    }
    // 在这里反编码获取到原值
    required init(coder aDecoder: NSCoder) {
        self.myName = aDecoder.decodeObjectForKey("name") as? String
    }
}
//required 的使用:
//required修饰符只能用于修饰类初始化方法。
//当子类含有异于父类的初始化方法时,子类必须要实现父类的required初始化方法,并且也要使用required修饰符而不是override。
//当子类没有初始化方法时,可以不用实现父类的required初始化方法。

let anObject = SerializableObject()
anObject.myName = "My Thing That I am saving"

// 将她转换为数据
let objectCovertedToData = NSKeyedArchiver.archivedDataWithRootObject(anObject)
// 从数据中获取原来的值
let loadedObject: AnyObject? = NSKeyedUnarchiver.unarchiveObjectWithData(objectCovertedToData)
println(loadedObject?.myName)

20 Cocoa 中的设计模式
  - MVC 模式
  模型: 负责数据存储
 视图: 负责呈现用户界面, 接受鼠标移动, 触屏输入等
 控制器: 作为视图和模型的中介, 提供应用程序主要的操作逻辑
  - 委托模式
  - 通知模式

第三章 OS X和 IOS 上的应用程序

3.1 什么是应用程序

应用程序实际上就是文件夹, 其中包含了编译后的二进制文件以及它们可能需要的所有资源. 在 Xcode 中编译一个项目并生成一个应用程序时, Xcode 会创建应用程序包, 并向其中复制必要的资源. 如果正在创建的是 Mac 应用程序, 可以直接将其压缩, 然后发送给其他人运行. 但如果是 IOS 上就不行, 因为 App 在这些设备上运行, 必须通过代码签名和配置.

3.1.1 应用程序 框架 实用工具及其他

框架指可以加载的代码和资源包, 可供其他应用程序使用. 它的结构与应用程序非常类似, 都包含一个二进制文件和任意资源, 但框架不是独立的, 它们被设计来供其他程序使用.比如, 在 Mac 中每个应用程序都会用到 AppKit.framework 这个框架, 在 IOS 上对应的框架是 UIKit.framework.

苹果公司用 Cocoa 表示 OS X 上一组供应用程序使用的库. IOS 上对应的是 Cocoa Touch. 它针对触屏设备进行了调整.

3.1.2 App 的构成

Mac App 的包结构

Iphone App 的包结构

3.1.3 用 NSBundle 在应用程序中查找资源

NSBundle 可以在应用目录中查找需要的资源

// 从 URL 中加载数据
if let fileUrl = NSBundle.mainBundle().URLForResource("someFile", withExtension: "txt") {
    let loadedDataFromUrl = NSData(contentsOfURL: fileUrl)
}

// 从文件中加载数据
if let fileUrl = NSBundle.mainBundle().pathForResource("111", ofType: "txt") {
    let loadedDataFromUrl = NSData(contentsOfFile: fileUrl)
}

3.2 应用程序的生命周期

3.2.1 OS X 应用程序

  1. 打开应用程序的 Info.plist, 从中获取编译后的二进制文件在什么地方, 并启动它. 此时你编写的代码开始运行
  2. 从 nib 文件中加载一些窗口, 控件及屏幕等接口对象到内存中. 对 nib 中的内容进行反序列化, 解包, 并将他们链接在一起, 包括主 nib 文件中的应用程序委托对象
  3. 从 nib 文件中解包一个对象时, 会对其发送 awakeFromNib 消息, 然后该对象开始运行代码. (从 nib 中加载的对象会收到 awakeFromNib 消息, 由代码创建的对象会收到 init 消息)
  4. 向应用程序委托发送 applicationDidFinishLaunching 消息
  5. 应用程序进行运行循环, 作用是为了监听事件(键盘输入,鼠标移动,点击等), 并将这些事件发送到相关目的地, 触发对应的方法
  6. 运行循环持续到应用程序退出时,或打开另一个 App. 此时应用程序委托对象会收到 applicationWillResignActive 消息, 表示该应用程序不再是活动应用, 此时应用程序仍然停留在屏幕上
  7. 稍后当应用程序不可见时, 会受到 applicationDidResignActive 消息
  8. 当用户回到该 App 时, 会受到一对对应的方法: applicationWillBecomeActive 和 applicationDidBecomeActive.
  9. 事件循环在应用程序退出时终止. 此时会收到 applicationWillTerminate 消息, 这是 App 在退出之前最后一次保存文件的机会 ####3.2.2 IOS 应用程序 和 OS X应用程序一样, 也会收到applicationDidFinishLaunching 等消息, 唯一不同的是消息的参数不同.

从 IOS4 之后引入了多任务

当一个应用程序退出时, 比如点击了 Home 键或者打开了另外一个程序, 它会被挂起, 但它还没有退出, 只是停止执行代码, 同时它的内存被锁定. 当应用程序恢复时, 它会从停止的位置重新开始. 这意味着应用程序仍然在内存中, 但是停止使用系统的 CPU 等硬件资源, 但 iPhone 上的内存依旧紧张, 所以如果另一个 App 需要更多内存, 则应用程序会被直接终止,不做通知. 这就意味着, 当应用程序委托被告知要被移入后台时, 必须报错所有关键数据, 否则就会在不被通知的情况下直接终止.

同样的, 应用程序在被唤醒时, 也不会得到通知. 但它们在被移入和移出后台时, 会收到通知
applicationDidEnterBackground (在被移入后台后立即调用, 在此方法运行后应用程序被挂起) 和 applicationWillEnterForeground (在应用程序即将回到屏幕上之前被调用)

由于应用程序有会被因为新程序需要更多内存而被终止, 可以通过减少应用程序占用所用内存来降低被终止的可能性.(16M 以下, 此时系统会将这个应用程序的内存存储到闪存中, 并将其从内存中彻底移除; 当程序恢复时, 会从闪存中重新加载, 这样也就不会被终止了)

应用程序可以申请在后台短时间运行, 这个时间不超过10分钟, 之后, 应用程序将会被终止, 而不是被挂起. 这个时间并不能保证是连续的, 是为了延长电池寿命. IOS7 引入了两种在后台运行任务的方式:

  1. 后台获取
    在代码中设置多长时间唤醒一次来获取更新数据, 如果没有设置, 就不会被唤醒

  2. 后台通知
    通过远程通知获取数据

注意, 尽管 IOS 允许设定多长时间唤醒一次程序来获取数据, 但系统为了避免不必要的耗电, IOS 会以它认定的一个最佳时间来唤醒你的程序. 基于同样理由, 苹果公司会向 ianzhi 向设备发送远程通知的数量. 所以, 如果应用程序的行为表现和你的设置不完全一样, 可能就是因为这个原因

另外, 特殊的应用可以在后台运行更长的时间

  1. 后台播放音频的应用. 可以在任意长的时间内保持活动状态, 直到用户打开了另外一个播放音频的 App
  2. 在后台跟踪用户位置的应用程序可以运行任意长的时间
  3. 如 Skype 之类的国际协议电话应用程序可以定期运行, 以向其服务器签到, 但除非正在通话, 否则不允许不加限制的运行

3.3 应用程序沙盒

沙盒用来对应用程序执行的操作加以限制. 应用程序位于沙盒内部, 不能访问沙盒外部的任何系统资源. 从 IOS App Store 和 Mac App Store 下载的应用程序会被自动放在沙盒中, 但自己分发的 App 则没有这个要求

应用程序的限制

  1. IOS 应用程序的限制
    当在设备上安装一个 App 时, 系统会为它提供一个容器, 用来存储自己的信息, 这个文件夹包括

    1. 文档 : 属于自己的所有文档
    2. 库 : 存储所有设置和配置信息
    3. 混存 : 包含一些数据, 可以重新生成这些数据, 当系统需要释放一些空间时, 会被删除
    4. 偏好设置 : 存储设置和偏好
    5. tmp : 存储临时文件, 会被系统自动定期删除

    应用程序不能处理这个文件夹以外的任何文件

  2. Mac 应用程序的限制

  3. 私有 API 的限制
    有很多私有 API 只有苹果内部才能使用

    3.4 用 NSNotification 发送通知

2015-06-28 15:2652