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

Categories: Study

20161020 摄于杭州西湖 Apple Store

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

大纲

《上》主要讲述在 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,两个框架在系统中的位置如下图, 同时可以看到常见的框架所在位置。

其中 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 类,好比一个字符串链表,可对其进行 增删改 操作。

NSArray

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

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

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

NSDictionary

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

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

NSDate

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

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

//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 转 NSDate
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 类丰富我们的处理

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];

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

- (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 当做字符串来手工解析

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

NSNumber \ NSValue \ NSNULL 包裹类

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

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

// NSNumber 类提取基础型

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

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

// 结构体包裹,譬如 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 的优雅体验和统一化

Read More

Weex + Ui - Weex Conf 2018

【2018-01-21】本文是2018年 Weex Conf 中议题《Weex + Ui》的内容文档整理,主要给大家介绍飞猪 Weex 技术体系从无到有的过程,包括 Weex Ui 组件库的开发和发展,重点分享在 Weex Ui 层建设的一些经验。