0%

Swift 这门语言在很多方面相对于 Objective-C 都是有很大的加强,影响巨大的如 协议结构体枚举函数 等,这些使得 Swift 敢于宣称自己支持 面向协议的编程面向函数的编程;其实即使在一些细碎的地方,Swift 仍然有很多值得称道的点,Switch 语句就是其中一个。

Switch 在 Objective-C 中的使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
typedef enum : NSUInteger {
QWPDirectionEast,
QWPDirectionSouth,
QWPDirectionWest,
QWPDirectionNorth
} QWPDirection;

QWPDirection direction = QWPDirectionWest;

switch (direction) {
case QWPDirectionEast:
NSLog(@"east");
break;
case QWPDirectionSouth:
NSLog(@"south");
break;
case QWPDirectionWest:
NSLog(@"west");
break;
case QWPDirectionNorth:
NSLog(@"north");
break;
}

在 Objective-C 中,Switch 语句很多时候会搭配枚举使用,这是因为枚举在 Objective-C 中只能为整型,而 Switch 的 condition 也必须为整型。

这里的 Switch 可以总结如下几个特征:

  • condition 必须为整型;
  • 每个 case 必须互斥;
  • 如果不希望穿透,就必须写 break。

Swift 中的 Switch

在 Swift 中,相对于 Objective-C:

  • condition 不仅仅可以为整型,几乎可以为任何类型,如 String、Double,甚至是 Tuple;
  • case 之间不必一定互斥,只要所有的 case 可以包含所有的情况就可以;
  • 默认不能穿透;

既然不能穿透了,那么如果我们希望实现类似 Objective-C 中的穿透该怎么做呢?事实上把希望执行相同代码的情况用逗号分隔就好了。如下面的例子:

1
2
3
4
5
6
7
8
9
10
11
12
let level = "A"

switch level {
case "A", "a":
print("优")
case "B", "b":
print("良")
case "C", "c":
print("一般")
default:
print("错误")
}

其实 Swift 中是有一个穿透的关键字的,叫做 fallthrough,之前看过有人用这个关键字模仿 Objective-C 的写法,即 Objective-C 中不写 break 的地方,Swift 中就加上 fallthrough 实现穿透。

其实这样不太好,并不是 fallthrough 最合理的使用场景,因为大多情况下只需要将不同的 case 通过逗号分隔写在一行就好了,为何还要 fallthrough 呢?

这就涉及到 Swift 中的 Switch 的另一个特征了,就是 case 之间可以不必互斥,这就是两个 case 之间可以存在公共部分,甚至一个 case 可以是另一个 case 的子集。这就给我们更多的可能性去实现之前无法实现的逻辑。

1
2
3
4
5
6
7
8
9
10
11
12
13
let vector: (x: Int, y: Int) = (0, 0)

switch vector {
case (0, 0):
print("原点")
fallthrough
case (_, 0):
print("x轴")
default:
print("其他")
}

// 最终打印:原点 x轴

上面这个例子,第一个 case 是 第二个 case 的真子集,当第一个 case 满足的时候,第二个 case 一定满足(反之不成立),这就是一个使用 fallthrough 的好时机,因为原点也在x轴上嘛。

case 语句中也不仅仅可以填写确定的值,甚至可以增加条件,如:

1
2
3
4
5
6
7
8
let vector: (x: Int, y: Int) = (0, 0)

switch vector {
case let (x, y) where x == y:
print("在 x = y 的线上")
default:
print("其他")
}

一个 App 与文件系统的交互完全限制在 app's sandbox directory 中,具体目录结构如下。注意沙盒并不是 Data Container 而是包括 Bundle Container 等一系列目录,这些目录将一个 App 限制在一定的范围内。

图 1 - 沙盒目录

常用目录解释

Bundle Container

Bundle Container 下就是 App 本身。

APPName.app

模拟器下的目录:

1
/Users/qiweipeng/Library/Developer/CoreSimulator/Devices/1679AE2F-140F-400A-B21E-DF9A94CE842E/data/Containers/Bundle/Application/76D971A9-3397-4814-A043-80EA68438B78/Demo.app

其中,Demo.app 上一级目录,即 76D971A9-3397-4814-A043-80EA68438B78 每次 App 运行都会改变(具体可以观察那个文件夹,每次 command + R 的时候,那个文件夹名就会变一下),这是为了安全考虑。

这个目录包含应用和它的所有资源,只可以读取,无法修改和写入。不会被备份

Data Container

图 2 - 模拟器下的 Data Container

这些目录中:

  • Documents 文件夹和 Library 文件夹(但是除了 Caches 文件夹)会被 iTunes 或者 iCloud 备份;
  • tmp 文件夹中的数据当程序关闭就会被清除;
  • Caches 文件夹当手机空间不足时候很可能就会被清空;

Documents

这个目录存储用户生成的内容,应该并且只应该把希望暴露给用户的文件放在这里,如软件的备份文件,用户可以访问并可以转存。或者如 Mindnode 中的每个思维导图的文件,这是用户生成的也是用户希望可以自己看到并掌控的文件。

这个目录会被 iTunes 或者 iCloud 备份,因此不会丢失。

Library

这个目录放的应该是非用户的数据,用户不需要看到。这个目录中除了 Caches 目录的其他目录会被 iTunes 或者 iCloud 备份。

Preference:

  • 这里面应该存放应用的偏好设置,比如某个按钮的开关状态,这是不会被删除且会被备份的;

Caches:

  • 这里存放缓存数据,比如网络加载的图片等;
  • 缓存数据在 iOS 5.0 以后,只有当手机空间不足的时候,系统才会主动去清理,并且当应用运行的时候一定不会清理;

tmp

这了的文件是应用运行过程中的临时文件,当应用程序不再运行的时候系统就会删除这里的文件。这个目录不会被备份。

路径的获取方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 1. 获取 App 包路径
NSString *boundlePath = [NSBundle mainBundle].bundlePath;
NSLog(@"包路径:%@", boundlePath);

// 2. 获取 Data Container 根路径
NSString *homePath = NSHomeDirectory(); // 由名字可见,这个目录可以理解为本 app 的用户的家目录
NSLog(@"Data Container 路径:%@", homePath);

// 3. 获取 Documents 路径
// NSSearchPathDirectory 要找的什么文件
// NSSearchPathDomainMask 搜索域,这里是用户域下
// expandTilde 是否展开路径,如果填 NO 的话前面就会显示 ~
NSString *documentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
NSLog(@"Documents 路径:%@", documentsPath);

// 4.获取 Library/Caches 路径
NSString *cachesPath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) firstObject];
NSLog(@"Library/Caches 路径:%@", cachesPath);

// 5.获取 tmp 路径
NSString *tmpPath = NSTemporaryDirectory();
NSLog(@"tmp 路径:%@", tmpPath);

作用

配置好 PCH 文件后,可以把常用的分类、第三方框架的头文件都导入到 PCH 文件中,这样默认就是每个类都会默认导入这些头文件;同样可以在这里进行宏定义

  • 优点是方便,写代码的时候就不需要每次都导入好多常用的头文件;
  • 缺点是即使该类不需要全部或者部分分类或框架,也会通通导入,会降低编译速度,并且最关键的是不利于项目的迁移;
  • 项目中应该谨慎使用 PCH 文件,只把最常用,几乎所有类都需要的头文件放在 PCH 文件中;

使用

  1. command + N 创建新文件,选择 PCH File,然后命名为 项目名-PrefixHeader 后创建;
  2. 之后在注释提示位置导入需要的头文件等;
  3. 之后在 Target –> Build Setting –> 搜索 prefix header –> 在对应项填入 PCH 文件的路径 $(SRCROOT)/$(PRODUCT_NAME)/项目名-PrefixHeader.pch
  4. Precompile Prefix Header 的值改为 YES,PCH 文件中预编译的头文件就会被缓存,这样可以加快编译速度;

其中,PCH 文件创建后默认如下所示:

1
2
3
4
5
6
7
#ifndef Demo_PrefixHeader_pch
#define Demo_PrefixHeader_pch

// Include any system framework and library headers here that should be included in all compilation units.
// You will also need to set the Prefix Header build setting of one or more of your targets to reference this file.

#endif /* Demo_PrefixHeader_pch */