# 写给前端看的 iOS 梳理 (上)

> 去年双十一为了排查一些 Weex 性能问题开始接触 iOS 相关学习，恰逢今年 1 月份开始慢慢参与 iOS 业务开发，借此机会整理一下 iOS 开发中的一些基础知识，用于备忘，同时旨在通过系列文章让前端同学在日后碰到 iOS 的代码可以看懂，方便更好的理解端上的一些原理。

- 发布日期: 2018-02-07
- Author: Tw93
- HTML: https://tw93.fun/2018-02-07/ios-1.html
- English translation: https://tw93.fun/en/2018-02-08/ios-1.md
- 全部文章: https://tw93.fun/posts.md

---

![](https://img.alicdn.com/tfs/TB15vVXX7yWBuNjy0FpXXassXXa-4032-3024.jpg){:class="img-zoom"}

_20161020 摄于杭州西湖 Apple Store_

## 序

去年双十一为了排查一些 Weex 性能问题开始接触 iOS 相关学习，恰逢今年 1 月份开始慢慢参与 iOS 业务开发，借此机会整理一下 iOS 开发中的一些基础知识，**用于备忘，同时旨在通过系列文章让前端同学在日后碰到 iOS 的代码可以看懂**，方便更好的理解端上的一些原理。

## 大纲

![](https://img.alicdn.com/tfs/TB1qX7SXHSYBuNjSspfXXcZCpXa-2148-1558.png){:class="img-zoom"}

_《上》主要讲述在 iOS 的开发过程中碰到的文件格式和 Foundation 框架常用的类，《中》和《下》正在整理中_

## 文件格式

- **.h**: 头文件也叫接口文件，提供对外访问的接口，包含类、方法和属性的声明，相当于类的**用户手册**
- **.m**: 实现文件，用于实现 `.h` 中声明的方法，相当于类的**工程细节**
- **Podflie**: 包管理文件，用于定义项目所需要使用的第三方库，类比 `package.json`
- **Podflie.lock**: 依赖库锁文件，记录了需要被安装的 pod 的每个已安装的版本，便于团队协作包统一，类比 `package-lock.json`
- **.xib**: 可视化文件，一个轻量级的用来描述**局部界面**的文件
- **.storyboard**: 界面布局文件，承载对应 UIView 的视图控件
- **.pch**: 预编译头文件，将一个工程中“不会常修改”代码预先编译好放到 pch 里面，用于存放**公用宏**、存放**公用头文件**、还可以**管理日志输出**
- **.plist**: 属性列表文件，一种用来存储串行化后的对象的文件，通常用于**储存用户设置**或者一些捆绑的信息，也是数据持久化解决方案之一
- **.lproj**: 多语言本地化文件夹，也即为每一种语言单独定义一份资源，有图片，文本，Storyboard，Xib 这些文件
- **.xcassets**: 图像资源文件，用了存放图片资源
- **.xcodeproj**: Xcode 工程文件，存储着 Xcode 工程的各项配置参数
- **.xcworkspace**: Xcode 工程文件，使用 pod install 后生成的新文件，打开后可以同时看到项目文件和 pods 依赖包

## Foundation 框架

### 简要介绍

首先介绍一下 Cocoa，Cocoa 是一套 OS X 和 iOS (Cocoa Touch) 中的框架和运行时支持，也即 API 应用程序接口，可类比于 VC 里面的 MFC。

Cocoa 其中最重要和 **UIKit(OSX 为 AppKit)** 和 **Foundation**，两个框架在系统中的位置如下图, 同时可以看到常见的框架所在位置。

<img src="https://img.alicdn.com/tfs/TB1x939XH9YBuNjy0FgXXcxcXXa-825-862.png" width="400">

其中 UIKit 主要用于 iOS 界面构建的，后面详细介绍。

Foundation 框架给我们框架和 App 开发提供了基础层功能，用于访问基本的数据类型，集合和操作系统服务，包括如下：

- **数据存储和持久化**：NSArray、NSDictionary 以及 NSSet 为任何类的 OC 对象提供存储
- **文本和字符串处理**：NSCharacterSet 用于 NSString 和 NSScanner 类中字符串各种操作
- **文件处理**：NSFileManager 类 提供大量用于文件操作检查的方法
- **日期和日期**：NSDate、NSTimeZone 和 NSCalendar 类
- **URL 操作**：提供一组类和协议来处理 URL 访问
- **异常处理**：NSException 类

Foundation 在 JS 里面可以假想成一个原生的公用 Util 库，But 没有...

### 开始之前

OC 中`@`是做什么的 ？

- 通常字符串前面会加一个`@`，用来将一个 C 语言的字符串转化为 OC 中的字符串对象 NSString
- `@`符号 OC 中大部分的关键字都是以`@`开头的,比如 `@interface`,`@implementation`,`@end` `@class`等

很多类之前的 NS 前缀是什么 ?

- **在 OC 里不支持命名空间**，所以只能通过加前缀这种土土的方法来避免命名冲突，所以常常会用 3 个大写字母(2 个的被苹果占了)，可譬如开发者名称、公司名称或者方法名等来区分
- NS 代表的是 NeXTSTEP，为 NeXT 公司开发的操作系统，NeXT 是 Jobs 在 1985 年离开苹果时候创建的公司名称，后来被收购回来后，很多成果被吸收到 OSX 基础中
- 类似的还有，CF == Core Foundation；CG == CoreGraphics.frameworks；CA == CoreAnimation.frameworks；UI == UIKit 等表示

### 对象的可变性

OC 中的类可分为**可变**(mutable)类和**不可变**(immutable)类，这里不可变指的是**字符串在内存中占用的存储空间固定,并且存储的内容不能发生变化**，反之可变是指占用空间可不固定，内容可修改；这个和灵活的 JS 是很不一样的，但我更倾向于对数据类型区分可变和不可变，也便于后续的维护和扩展。

对于确定不会改变的类，它只具备应有的属性方法，可变类通过继承原有不变类，再增加可变方法属性即可实现可变。

可变类是不可变类的子类，同时**所有可变类的实例对象可以作为不可变的实例对象来使用**。

### NSString

在 OC 中使用 NSString 来表示字符串，可变 NSMutableString 继承不可变 NSString 类，好比一个字符串链表，可对其进行 **增删改** 操作。

![](https://img.alicdn.com/tfs/TB146QeXGmWBuNjy1XaXXXCbXXa-2128-1834.png){:class="img-zoom"}

### NSArray

在 OC 中使用 NSArray 来表示数组类， 这里需要注意的是它不像 JS 的数组可以存入任何类型，在 NSArray 中只能够存入 OC 对象，同时也是不可变的，假如需要更改数组，需要使用它的子类 NSMutableArray。

![](https://img.alicdn.com/tfs/TB1DB3lXKuSBuNjy1XcXXcYjFXa-1702-2312.png){:class="img-zoom"}

通过`arrayWithObjects`方式创建的数组记得在最后一项加上`nil`,所以为了方便更推荐直接使用`@[@"lnj", @"lmj", @"jjj"]`方式创建，但是因为生成的是不可变数组，故在 NSMutableArray 中不要这么创建。

很多数组的操作方法和在 JS 中对比大致相同，不过 NSMutableArray 提供了比 JS 中一些更丰富的方法，包括交互两个元素位置这种需要自己写的方法，也即很多时候编写时候可以先查查文档看是否有这个方法， 很多时候可以从里面找到。

### NSDictionary

前端同学第一次听到`字典`这个词可能会有些陌生，可以类比 JS 中的 Object, Java 中的 Map，字典中的数据也是通过键值对保存。

NSDictionary 属于不可变字典类，一般创建之后只能用于查询，不能对其进行修改操作，想修改需要使用 NSMutableDictionary 类。

![](https://img.alicdn.com/tfs/TB1XJwgXMmTBuNjy1XbXXaMrVXa-1738-926.png){:class="img-zoom"}

### NSDate

NSDate 可以用来表示时间, 用来进行一些常见的日期时间的处理，它的使用方式和 JS 的 Date 也是很相似的,`[NSDate date]` 返回的就是当前时间，但是在格式化日期时间上面 Foundation 处理比 js 要优雅很多，直接调用其内置方法即可。

通过和 **NSDateFormatter 类配合使用可以格式化日期**，`stringFromDate`用于 NSDate 转 NSString，`dateFromString`用于 NSString 转 NSData:

```objective-c
//NSDate 转 NSString
NSDate *now = [NSDate date];
NSDateFormatter*formatter = [[NSDateFormatter alloc] init];
formatter.dateFormat = @"yyyy-MM-dd HH:mm:ss";
NSString *str = [formatter stringFromDate:now];
NSLog(@"%@", str);

// NSString 转 NSDate
NSString *str = @"2018-02-08 11:53:12";
NSDateFormatter*formatter = [[NSDateFormatter alloc] init];
formatter.dateFormat = @"yyyy-MM-dd HH:mm:ss";
NSDate *date = [formatter dateFromString:str];
NSLog(@"%@", date);
```

假如想在时间日期上面处理更复杂的内容，还**可以借助 NSCalendar 类丰富我们的处理**：

```objective-c
NSDate *date = [NSDate date];
NSCalendar*calendar = [NSCalendar currentCalendar];
// 利用日历对象获取年月日时分秒
NSCalendarUnit type = NSCalendarUnitYear
                    | NSCalendarUnitMonth
                    | NSCalendarUnitDay
                    | NSCalendarUnitHour
                    | NSCalendarUnitMinute
                    | NSCalendarUnitSecond;
// cmps 包括 year、month、day、hour、minute、second 属性
NSDateComponents *cmps = [calendar components:type fromDate:date];
```

比较日期,可以直接使用如下方法：

```objective-c

- (NSDateComponents *)components:(NSCalendarUnit)unitFlags
fromDate:(NSDate*)startingDate
      toDate:(NSDate *)resultDate
      options:(NSCalendarOptions)opts;
```

从上面看，OC 中很多方法都是很长很长的，并且将每一个参数代表的意思也表示出来了，不会出现错误对应的情况。

### NSURL

写前端久了会慢慢觉得 URL 就是一条线上的链接 H5 地址，其实不然，**URL(Uniform Resource Locator)中文意思是统一资源定位符，除了 http 协议之外，还有`https/ftp/file`协议用来访问一个资源**。

在 OC 中，操作 URL 时候应该用专用的 NSURL 类，而**不建议把 URL 当做字符串来手工解析**。

![](https://img.alicdn.com/tfs/TB1kV1zX_tYBeNjy1XdXXXXyVXa-1634-1116.png){:class="img-zoom"}

随着后面的升级 ，原来在 OS 中很多文件输入输出的地方也开始使用 NSURL 了，也即慢慢地将网络当时 OS 中的一部分了。

### NSNumber \ NSValue \ NSNULL 包裹类

Cocoa Fundation 框架的集合类(NSArray\NSDictionary\NSset)中只可以放入对象，前端同学可能开始时候会很不适应，假如想放入 int\float\double\bool 等基础类型，**需要将其包裹成 OC 对象在进行放入**，这里就需要使用 NSNumber 包裹类了，才能间接的将基础类型放入。

```objective-c
// 直接在前面加入@就可以创建一个 NSNumber 对象型
@10;
@10.5;
@YES;
@(num);

// NSNumber 类提取基础型

- (char)charValue;
- (int)intValue;
- (double)doubleValue;
- (BOOL)boolValue;
```

但是对于结构体、指针这种复杂的数据类型，NSNumber 也无法包裹，这里需要使用 NSValue 类，它是 NSNumber 的父类，可以将任意类型包装成对象。

```objective-c
// 结构体包裹，譬如 NSPoint、NSSize、NSRect

- (NSValue *)valueWithPoint:(NSPoint)point;
//从 NSValue 对象取出之前包装的结构体
- (NSPoint)pointValue;
```

**前面的数组字典中是不能自己放入`nil`的**，因为在里面有`结束`的含义，但是有时候我们又确实需要一个特殊的对象来表示空值，这里就需要 NSNULL 了，可以通过类方法 `+(NSNull *)null` 来定义，同时可以通过`if(obj==[NSNull null])`来判断是否为 NSNull。

实际开发中极少使用 NSNull，而且一般习惯也不会单独判断这个（默认大家都不用），所以用它也会很危险（大多数情况是用 nil，nil 可以接任何消息，但 NSNull 不行，二者特性的区别可能引起 crash）。

## 小结

通过以上文章，最多只能够让大家对 OC 编程有一些了解，谈不上深入，更多的是希望前端对于 H5/Weex 的深入了解和优化不要局限到最上面薄薄的一层，完全可以联系到底层的加载、渲染、图片库、网络库等等来进行优化。

从语言层面来说，OC 和 JS 还是挺有意思的，变量/方法命名的风格正好相反，苹果更加鼓励开发者程序命名尽量使用英文全称并且是尽可能详细，通过看到方法和变量就知道是做啥的，看写得好的 iOS 仿佛看散文一样，譬如 **-(NSArray \*) componentsSeparatedByString:(NSString )separator;** 分割字符串，在 JS 里面直接通过 str.split() 就解决了，更多前端开发者习惯不长的命名，因为需要从网络下载，尽管压缩工具会做，但大家也习惯短命名来表示。
由于编译型语言缘故，写 OC 过程中很多错误可以在编译过程就发现，同时运行速度快，同时很多事情苹果都帮开发者做了，包括工程体系，这样其实写 iOS 可发挥的地方没有前端多；反之 JS 为解释性，性能更多依赖于引擎，加上为语言动态，写起来也爽，很多奇淫技巧可使用，加上 Node 的发展，让其可以做很多有趣事情。

**各有优点，喜欢 Web 的快速和开放，也爱 iOS 的优雅体验和统一化**。
