Swift 学习笔记4 —— 文件结构,作用域和生命周期

·420 字·2 分钟
Swift Swift
n3xtchen
作者
n3xtchen
Sharing Funny Tech With You

文件结构 #

一个 Swift 程序有一个或多个文件组成。在 Swift 中,文件是一个有意义的个体。能存在于文件顶级目录有如下组件:

  • 模块导入语句:一个模块是除了文件外最高级的单元。一个模块可以由几个文件组成;一个带模块的文件,对于其它文件而言,是可见的;如果没有 import 语句,一个模块就不能看到另一个模块。例如,如果你想要在 iOS 程序中与 Cocoa 交互,你就需要在文件的第一行,引入 import UIKit
  • 变量声明:文件的最外层声明一个变量,我们称这个变量为全局变量:它将存活在整个程序运行周期中。
  • 函数声明:文件的最外层声明一个函数,我们称这个函数为全局函数:所以代码都可见和可访问,不需要发送任何消息给任何对象。
  • 对象类型声明:类,struct 或者 enum 的声明。

例如,一个合法的 Swift 文件包含一个导入语句,一个变量声明,一个函数声明,一个类声明,一个结构体声明和一个枚举声明:

import UIKit
var one = 1
func changeOne() {
}
class Manny {
}
stuct Moa {
}
enum Jack {
}

这个例子看似愚蠢(实际上也挺愚蠢的),但是说明清楚一个文件的结构。现在,我们看一个复杂点的例子:

import UIKit
var one = 1
func changeOne() {
	let two = 2
	func sayTwo() {
		println(two)
	}
	class Klass {}
	sturct Struct {}
	enum Enum {}
	one = two
}
class Manny {
	let name = "manny"
	func sayName() {
		println(name)
	}
	class Klass {}
	struct Struct {}
	enum Enum {}
}
struct Moe {
    let name = "moe"
    func sayName() {
        println(name)
    }
    class Klass {}
    struct Struct {}
    enum Enum {}
}
enum Jack {
    var name : String {
        return "jack"
    }
    func sayName() {
        println(name)
    }
    class Klass {}
    struct Struct {}
    enum Enum {}
}

作用域和生命周期 #

Swift 中,也有作用域(scope)。这是关于对于其他东西可见的能力。一个东西嵌套在另一个东西中,构建嵌套等级关系。这个等级是:

  • 模块是一个域
  • 文件是一个域
  • 对象声明是一个域
  • 花括号是一个域

当被声明的时候,它就已经被赋予了一定的等级。等级中它的位置——它的域——决定它是否可见。

在上一个代码例子中,Manny 类中声明了一个变量 name 和一个方法 sayNamesayName 方法外部是可见的,因此 name 在这个方法中是可见的。类似,changOne 中的声明的方法,都可以访问这个文件声明的 one 变量;确实,这个文件中所有代码都可以访问文件级别的 one 变量

域是分享信息方式中最重要的方式。Manny中的两个不同的函数都可以访问 Manny 等级声明的 nameJackMoe 中的代码也都可以访问文件等级的 one 变量。

任何东西也都有它的生命周期。域中的东西和域的存活时间相同。因此,文件活多久,one 就能活多久——即程序的运行时长。它是全局的,持久的。然后,Manny 中声明的 name 只和 Manny 的生命周期一致。等级嵌套的越深,它的生命周期越短;举个例子:

func silly() {
    if true {
        class Cat {}
        var one = 1
        one = one + 1
	 }
}

在这个代码中,Cat 类和变量 one 仅存活很短暂的时间,在 if 构造体内。当 silly 被调用,Cat 被声明,然后存在;然后 one 被声明,存活;然后执行 one = one + 1,然后域结束,然后 Catone 烟消云散。

对象成员 #

三个对象类型中(类,结构体和枚举),在其顶级作用域声明的东西都有特别的名称,大部分是因为历史原因。让我们把 Manny 类作为例子:

class Manny {
	let name = "manny"
	func sayName() {
		println(name)
	}
}

在代码中:

  • name 是在对象声明的顶级域中声明的变量,我们称之为对象的 属性
  • sayName 是在对象声明的顶级域中声明的函数,我们称之为对象的方法

在对象声明的顶级域中声明的东西 —— 属性和变量,我们把它们统称为对象成员。对象成员是特别重要的,因为它定义了你可以传递给对象的信息。

命名空间(NameSpace) #

命名空间就是程序的命名领域。命名空间有一个特性,如果不传递所在命名空间的名称,里面的东西就无法被访问。命名空间的好处就是允许我们在不同的地方使用同一个名字,而不会导致冲突。显然,命名空间和域是非常类似的概念。

命名空间帮助解释在一个对象域的顶级声明一个对象的重要性,例如:

class Manny {
	class Klass {}
}

声明 Klass 的方式有效地被隐藏在 Manny 中。Manny 就是一个命名空间!Manny 中的代码可以直接访问 Klass。但是 Manny 外部的代码就不能;它必须明确指定命名空间才能够正常访问。具体做法是,Manny后面跟着点号 .,然后才是 KlassManny.Klass

命名空间并不提供隐私功能;它只是一种惯例。这样,我们可以给 Manny 一个 Klass 类,也可以给 Moa 一个 Klass 类;它们之间不冲突,因为它们在不同命名空间内,而且我可以很容易区分它们。

它并没有逃离你的注意力,应用命名空间的语法世界上就是点号信息传递语法。它们是同样的东西。

结果信息传递允许你在各自域中看见自己的代码,而相互之间就不能直接访问。Moa 中的代码可以通过简单的方式访问 MannyKlass,即 Manny.Klass;但是他可以这么做的前提就是 MannyMoa 是可见的(显然,它们声明在同一个域内)。

模块 #

顶级命名空间就是模块。默认,你的应用是一个模块,也是一个命名空间;命名空间名大致上也是应用的名称。例如,如果我的应用名为 MyApp,如果我声明一个 Manny 类在一个文件的最外层,那它的实际名称应该是 MyApp.Manny。但是我不需要使用这个名称,因为我的代码在同一个命名空间内,并可以直接访问 Manny

框架也是一个模块,即便它们也有命名空间。例如,Cocoa 基础框架(NSString 所在的地方) 就是一个模块。当你编写 iOS 应用的时候,你需要导入 Foundation(或者,更有肯能,你要导入 UIKit,它本身会导入 Foundation),因此允许你使用 NSString,而不需要写 Foundation.NSString。当然如果你蠢到在你的模块中定义了 NSString,那你需要明确引用才能使用框架的 NSString

文件之上的层级就是文件导入的库了。你的代码永远都会隐性导入 Swift 本身;你也可以在你纹尖头明确引入它;虽然你不需要这么做,但也无害。

这很重要,因为它是解决大部分问题:println 就在这里面,那为什么你可以在任何对象外部使用它呢?println实际上是在 Swift.h 中声明的函数;它集合其他普通顶级函数一样。你可以这么用 Swift.println("hello"),但是你不需要这么足,因为不需要有冲突需要解决。

实例 #

对象类型 - 类,结构和美剧 —— 有一个重要的共性:他们都可以背诗丽华。结果,当你声明一个对象类型是,你只是定义了一个类型。实例一个类型就是创造一个东西 —— 那个类型的实例。

因此,例如,我可以声明一个狗类:

class Dog {
}

我可以赋予它一个方法:

class Dog {
	func bark() {
		println("woof")
	}
}

但是实际上在我的程序还没有狗的对象。我仅仅是描述了我想要的狗的类型。为了得倒真的狗,我就必须创造一个。创造狗的时机过程就是实例化狗的过程。结果就是得到一个新对象——一个狗实例。

Swifit 中,通过对象类型名称作为函数名,调用它来创建一个实例。它需要使用括号。当你在对象类型名后加上括号,你就会发送一个特殊的信息给对象类型:实例化你自己!现在,我来实例化一个对象:

let fido = Dog()

现在声明了一个 Dog 实例。注意下 let,如果我使用下述语句重新声明一个 Dog 对象:

let fida = Dog()

fidafido 是同一个对象,他们指向的是同一个内容空间;如果 let 修改成 var,情况就不一样了:

var fido = Dog()
var fida = Dog()

这两个对象值是相同的,但是它们指向的是不同内存空间,意味着,修改其中一个,不会修改到另一个。

还有一个需要注意的,就是使用 let 声明的对象不是常量,是可变的。

既然声明好了一个对象,那我们试着调用下方法:

fido.bark()	// 输出 woof

默认情况,属性和方法是从属于实例的。你不能使用它们作为信息传递给对象类型本身;你必须得有一个接受信息的实例。下面的语句是不合法的:

Dog.bark()	// 导致编译错误

当然,如果你声明的是一个函数,那这样做就是合法,它是另一类函数——类函数或者静态函数。

属性也是同理的。现在我们声明一个实例属性:

class Dog {
	var name = ""
}

它允许我们设置一个 Dog 名,但前提是你需要一个 Dog 实例:

let fido = Dog()
fido.name = "fild"

如果你想要直接使用类型访问属性,你则需要声明特殊的属性——类属性或者静态属性。