Swift 学习笔记4 —— 文件结构,作用域和生命周期
文件结构 #
一个 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
和一个方法 sayName
;sayName
方法外部是可见的,因此 name
在这个方法中是可见的。类似,changOne
中的声明的方法,都可以访问这个文件声明的 one
变量;确实,这个文件中所有代码都可以访问文件级别的 one
变量
域是分享信息方式中最重要的方式。Manny
中的两个不同的函数都可以访问 Manny
等级声明的 name
。Jack
和 Moe
中的代码也都可以访问文件等级的 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
,然后域结束,然后 Cat
和 one
烟消云散。
对象成员 #
三个对象类型中(类,结构体和枚举),在其顶级作用域声明的东西都有特别的名称,大部分是因为历史原因。让我们把 Manny
类作为例子:
class Manny {
let name = "manny"
func sayName() {
println(name)
}
}
在代码中:
name
是在对象声明的顶级域中声明的变量,我们称之为对象的 属性sayName
是在对象声明的顶级域中声明的函数,我们称之为对象的方法
在对象声明的顶级域中声明的东西 —— 属性和变量,我们把它们统称为对象成员。对象成员是特别重要的,因为它定义了你可以传递给对象的信息。
命名空间(NameSpace) #
命名空间就是程序的命名领域。命名空间有一个特性,如果不传递所在命名空间的名称,里面的东西就无法被访问。命名空间的好处就是允许我们在不同的地方使用同一个名字,而不会导致冲突。显然,命名空间和域是非常类似的概念。
命名空间帮助解释在一个对象域的顶级声明一个对象的重要性,例如:
class Manny {
class Klass {}
}
声明 Klass
的方式有效地被隐藏在 Manny
中。Manny
就是一个命名空间!Manny
中的代码可以直接访问 Klass
。但是 Manny
外部的代码就不能;它必须明确指定命名空间才能够正常访问。具体做法是,Manny
后面跟着点号 .
,然后才是 Klass
,Manny.Klass
。
命名空间并不提供隐私功能;它只是一种惯例。这样,我们可以给 Manny
一个 Klass
类,也可以给 Moa
一个 Klass
类;它们之间不冲突,因为它们在不同命名空间内,而且我可以很容易区分它们。
它并没有逃离你的注意力,应用命名空间的语法世界上就是点号信息传递语法。它们是同样的东西。
结果信息传递允许你在各自域中看见自己的代码,而相互之间就不能直接访问。Moa
中的代码可以通过简单的方式访问 Manny
的 Klass
,即 Manny.Klass
;但是他可以这么做的前提就是 Manny
对 Moa
是可见的(显然,它们声明在同一个域内)。
模块 #
顶级命名空间就是模块。默认,你的应用是一个模块,也是一个命名空间;命名空间名大致上也是应用的名称。例如,如果我的应用名为 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()
fida
和 fido
是同一个对象,他们指向的是同一个内容空间;如果 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"
如果你想要直接使用类型访问属性,你则需要声明特殊的属性——类属性或者静态属性。