阅读更多

0顶
0踩

编程语言

转载新闻 iOS 编程中的 Type System

2017-02-15 14:28 by 副主编 jihong10102006 评论(0) 有5945人浏览
接之前一篇 Pattern Matching 的文章,Type System 是另一项编程语言,或者说编译器所提供的便利。Pattern Matching 可以让我们少写代码,而 Type System 可以让我们少犯错误,减少 Type 相关的各种 bug。

一般来说,我们写代码时为了降低 bug 率,一是依赖于程序员自身的经验积累,二是靠编译器做各种静态检查,type system 则是属于静态检查这一类。Swift 较之 Objective C 的 type system 有了很大的改进,下面文章中主要是介绍 Swift 相关的一些特性。在开始之前,先聊下如何靠程序员经验来降低 bug。

Bug 第六感
从我自身的体验推断,我相信大部分程序员在写代码的时候,对于代码是否存在 bug 是有一定感知的。只不过有些新入行的朋友,在写代码的时候操之过急,或者由于和产品经理讨论吃了败仗心情不佳,coding 时目标变成了写能 work 的代码,而不是写高质量的代码。

facebook 面试有一个环节叫 whiteboard coding,要求程序员能在白板上写出几乎是 「bug free」的代码,这听起来有点耸人听闻,写代码没有 Xcode 提示就罢了,bug free 更是难上加难了。写一段几乎没有 bug 的代码到底有多难呢?说难不难,说易不易。

写代码时,慢一点,再慢一点。好好的想下代码有可能出错的地方在哪,想清楚了 bug 就少。一般来说,要减少 bug 量,一是靠程序员自身修养,二是靠编译器提供的静态检查。Type System 属于第二类,在深入之前,先简单聊下如何靠自身修养降低 bug 率,提升 bug 感知的第六感。

少写 Bug 的简易准则
要提升程序员的自身修养来降低 bug 率,是个大话题,而且多和自身的知识积累有关,需要长年累月的学习和养成。本文的目的不在于此,所以只介绍一个小技巧来养成感知 bug 的好习惯。

我们可以粗略的将我们所写代码分为 data 和 behavior,behavior 围绕 data 执行各种逻辑。一个函数可以看做是一个 behavior,而函数本身又由若干 data 和 behavior 所构成。很多时候,代码有 bug,是因为 data 出现了预料之外的变化。有一个简易准则可以减少这类 bug:只要遇到 data,就做 aggressive check。

具体到一个自定义的函数,函数会包含哪些 data 呢?细心理一理没几个。
  • 函数入参
  • 内部临时变量
  • 依赖的外部变量
  • 返回的最终结果
这几类 data 是我们在一个函数中最经常遇到的,只要我们对他们做好检查就可保平安。做哪些检查呢?最常见的也就那么几样,比如是否为 0,为 nil,数组元素 count 为 0,如果期待正数则是否为负数,数组是否越界,多线程是否安全等。做下总结就可以完成大部分的可靠性检查。简而言之,只要是使用 data 的时候,就围绕 data 做好应该的检查,做到这点,写一个几乎没有 bug 的函数就不怎么难了。

这个原则更精准的表达是:在任何场景下,无论是定义变量还是使用变量,都对变量的各种可能性做检查和保护。

Type System
回到我们的正题 Type System,Type System 是由编程语言和 type 相关的各种规则所构成。它的用处也简单,可以帮助我们减少和 type 相关的 bug。

编程语言大多都有自己的 Type System,Objective C 和 Swift 都有。在开始讨论 Type System 之前,要明确 Type 的定义。

Type 就像自然语言里的名词,动词,介词等等,可以规范我们的表达。在编程语言中,type 则是一种避免代码表达错误的约束。Type 不仅仅包括诸如 int,float,bool 这类 primitive type,对象的 class type,还包括 function,block 等不那么明显的 type。变量,常量,函数等等都(且一定)具备 type 信息,有些一眼能看出,有些要靠推断。

Static vs Dynamic
有些 type 信息是交由程序员去推断和维护的,有些则是留给编译器去管理的。前者的 type 约束是在 runtime 检查的,偏向「dynamic」,后者则在 compile 的时候就做了 check,偏向「static」。

很多技术文章都会讨论编程语言的 dynamic 和 static 属性,我们要分清楚 dynamic 和 static 其实是个宽泛的说法,他们可能包含不同的语义和场景。dynamic 和 static 既可以用来讨论 type system,又可以用来形容函数调用机制。比如我们认为 Swift 是 statically typed,但 Objective C 的 runtime 和 message 机制又显然是 dynamic 的,这两种场景下 static 和 dynamic 说的其实不是一回事。

回到 type system 的场景,讨论下语言是 statically typed 还是 dynamically typed。还是要进一步看场景,在 Objective C 中,type 信息既可以是 static 的,也可以是 dynamic 的,看我们如何使用了,比如下面的代码中 type 信息是 static 的:

int i = 0;
i = @“test”;

因为 type 的上下文信息是完整的,编译器可以做类型判断。而如下代码中 type 信息则是 dynamic 的:

id obj = [NSData new];
obj = [NSObject new];

由于 id 可以指向任意对象类型,id 可以在不同的时间点里指向不同的类型,编译器此时无法根据类型信息作出判断,是否存在类型使用错误的。所以我们会说像 Objective C 这类编程语言在 type system 上,是同时具备 static 和 dynamic 属性的,关键还是看具体的使用场景。

但 Swift 却是货真价实的,纯粹的 statically typed 编程语言,不具备任何 dynamically typed 的属性。比如在 Swift 中,如下代码是无法通过编译的:
[code=swift"]var i
编译器会提示:Type annotation missing in pattern,也就是缺少 type 信息。要声明一个变量,我们可以通过如下两种方式来提供 type 信息:
var i = 0  //方式一,implicit typing
var i: Int  //方式二,explicit typing

方式一是通过赋值来做 type inference,方式二是通过显式的提供 type 信息。Swift 在 type 的使用上非常苛刻,当之无愧为 statically typed。

显然,static type 比 dynamic type 更安全,编译器可以帮我们做类型检查,这也是为什么 Swift 比 Objective C 在 type safety 上更优秀的原因。当然,dynamic type 并非全无好处,初期开发起来速度会快于 static type,而且省去了编译时的 type 检查,每次编译速度更快。缺点是一旦出现 runtime 中的类型错误,要花更多的时间去调试,要写更多的 test case,准备更多的文档。这种缺陷在较大规模的项目上会更明显,Swift 选择 static type 策略应该也有这方面的考虑。

Type Inference
类型推断(type inference)也是 type system 当中的一个常见概念。不少编程语言比如 Swift 都有 type inference 的功能。type inference 有什么用处呢?statically typed 的编程语言决定了变量都必须具备类型信息,意味着我们每次使用变量的时候都需要显式的声明 type 信息,比如在 Objective C中,这样会显得有些繁琐和啰嗦,一旦有了 type inference,我们可以在代码中省略掉很多关于 type 累赘的表述。我们看如下代码:
var i = 0

这行代码中,有两个实体有 type 信息,变量 i 和常量 0,0 默认的 type 信息是 int,i 的 type 信息没有显示的声明出来,但在 Swift 中,由于 0 被赋值给了 i,所以可以通过 type inference 推断出 i 的 type 信息也是 int。这种类型推断会发生在很多程序员意识不到的角落,这种具备传染特性的 type 信息可以层层叠叠,一级一级的输送到更多的其他变量实体。编译器就是通过这种传染的特性来做 type inference 的。

在 Swift 中,type inference 配合 static type 让代码既精炼又安全。

Optional Type
前面提到 type 信息本质上是一种约束,可以避免 type 的使用错误。我们在编写代码时,经常遇到的一种 bug 是对于空对象或者说对象为 nil 情况,漏写了为空的判断。Swift 通过引入 optional type 来强制开发者考虑 nil 的场景,更妙的是,当「是否为 nil 」成为 type 信息之后,编译器也可以一起来帮助检查 nil 的使用场景。看下 optional type 的定义就一清二楚了:
public enum Optional<Wrapped>  {    
    case none
    case some(Wrapped)
}

通过 enum type 来定义 optional type,以表达是否为 nil 的含义。这也是 Swift 为什么要引入 optional type 的根本原因,让编译器以类型检查的方式,来帮助开发者分析是否存在漏判 nil 的场景。

Generic Type
再次强调下,type 本质上是一种约束。当我们定义 int i 时,int 就成为了变量 i 的一种约束。我们可以把这种约束进一步强化,比如引入 generic type(泛型)。generic type 有两个主要特性,其一是允许开发者在后期再指定 type 的值,其二是可以把 type 约束施加到指定的代码范围里。理解这两个特性,是我们掌握 generic type 各种表现形式的基础。

generic type 可以让我们写出更加符合 type safety 的代码,Objective C 和 Swift 都支持定义 generic type,只不过 Swift 中 generic 的概念更加广泛,应用面也大很多,在一些显式的和隐式的地方都存在 generic 的身影。比如前面提到的 optional,其实也是个 generic type。

generic 可以作用于很多其他的复杂 type,比如 optional 就是 generic 作用于 enum 的结果,除了 enum 之外,还有 struct,class,function 等都可以和 generic 搭配使用。我们再看一个 Swift 自带的例子,Array:
public struct Array<Element> : RandomAccessCollection, MutableCollection {
    ...
    public mutating func popLast() -> Element?
    ...
}

只需要在 struct 名字后面以 的形式,就可以在 struct 的作用域内部声明一个新的 xxx type(xxx 在 Array 的 extension 中也是可见的),xxx 可以在使用时再确定具体指代什么 type。使用 Array 的时候,我们也不必显示的指明 xxx 代表什么,可以依赖前面提到的 type inference:
var arr = [Date()]
arr.append(UILabel()) //compile error

上面第二行会报错,这是 Swift 和 Objective C 的差异之处,在 Objective C 中,我们可以在 Array 中放入不同类型的对象,而在 Swift 中,一旦 Array 中的元素类型被 type inference 确定,就不能放入其他类型的对象了。generic type 和 type inference 配合的场景在 Swift 当中经常出现。

Named Type vs Compound Type
named type 指的是我们传统意义上所理解的 data type,例如 int,float,string,自定义的 class 等等。在 Swift 中,设计者引入了 compound type 的概念,可以把 compound type 理解成 named type 的某种集合,比如 function 和 tuple,他们往往都包含多个 named type。

我们知道在 Swift 中,function 是一等公民,可以作为变量声明,参数,返回值等等,要理解并运用这一点,需要在思维上做转换,把 function 也看做一种 data type(compound type),在原先使用 named type 的位置,我们几乎都可以使用 compound type。

compound type 增强了语言的表达力,但其灵活性在一些场景下,也会一定程度的降低代码的可阅读性。compound type 可以包其他 compound type,可以一层层的套嵌,这种 nested compound type 有时候会让代码看上去没那么直观,比如下面一段 Swift 代码:
func someFunc(f: (Int)->(Int, ()->(Int))) {

}

上面的函数里,function 和 tuple 作为 compound type 存在套嵌,代码本身虽然不长,要一眼把其中包含的 type 都识别出来不那么容易。compound type 的使用可能是不少从 Objective C 转向 Swift 的同学初期感觉难以适应的原因之一。

总结
上述所提到的概念都是和 type system 相关的基础知识,虽然基础,却十分重要。对 type system 建立完整全面的认识,多利用语言本身的 type 制约来避免 bug,可以让我们对自己代码的安全性有更好的把握,对于代码质量的提升也有极大的帮助。
来自: MrPeak杂货铺
0
0
评论 共 0 条 请登录后发表评论

发表评论

您还没有登录,请您登录后再发表评论

相关推荐

  • iOS基础网络编程学习笔记

    iOS进行网络交互的过程是在Web Service应用层进行的,而Web Service采用的Http和Https协议 1.Http概述: Http(Hypertext Transfer Protocol)是超文本传输协议,基于TCP/IP协议之上的应用层的协议。Http协议支持C...

  • iOS 编程中 throttle 的那些事

     不知道大家对throttle这个单词是否看着眼熟,还是说对这个计算机基础概念有很清晰的了解了。...学习过程中遇到什么问题或者想获取学习资源的话,欢迎加入IOS学习交流群,群号码:278462937

  • iOS编程中throttle那些事

    话说GCD几乎是iOS面试的必问题,也是个送分题:)。 我一般会机械式的先问:GCD有哪几种Queue?回答:串行Queue和并行Queue。 我继续问:Global Queue有哪几种优先级?回答:有几种吧,大概记得Default,Low...

  • iOS中网络编程长连接

    1、长连接在iOS开发中的应用 常见的短连接应用场景: 一般的App的网络请求都是基于 Http1.0 进行的,使用的是 NSURLConnection、NSURLSession 或者是 AFNetworking,Http1.0 链接最显著的特点就是客户端每一次需要...

  • 《iOS 高级编程》之Tableview进阶指南

    本章内容: ● 学习如何进阶使用UITableView,带给应用更高级的观感(look and feel) ● 学习如何开发自己定制... 在iOS应用中呈现数据时,UITableView可能是最经常使用的用户界面对象。在本章中,将学习到以超越标

  • iOS音频---SystemSound

    iOS音频—SystemSound vibrate 使用如下的方法: AudioServicesPlayAlertSound(kSystemSoundID_Vibrate); AudioServicesPlaySystemSound(kSystemSoundID_Vibrate); 使用了一个名为kSystemSoundID_Vibrate的参数 ...

  • 浅谈IOS网络编程怎样读取数据

    ios网络编程在操作过程中不时需要进行一些读取数据的操作。我们要想实现ios网络编程读取数据一般是需要完成以下的几个操作步骤。而且在操作过程中还要完善相关的数据信息,从而才能够顺利的完成ios网络编程关于读取...

  • iOS 中高级面试题(附答案)

    RunLoop 可以称之为运行循环,在程序运行过程中循环做一些事情,如果没有 RunLoop 程序执行完毕就会立即退出,有 RunLoop 程序会一直运行,并且时时刻刻在等待用户的输入操作。RunLoop可以在需要的时候自己跑起来...

  • iOS编程读书笔记

    1 第一个简单的iOS应用 单击按钮可以改变文字。 1.1 创建Xcode项目 创建项目的时候Xcode会提供一些包含通用代码的模版,可以根据需要选择模版。创建项目是需要填写Organization Name和Company Identifier,也可以...

  • iOS音频播放编程简介

    本质是将短音频注册到系统服务上(System Sound Service) 系统服务限制 1.音频播放时间不能超过30秒 2.音频数据格式是PCM或IMA4(必须) 3.音频文件类型是caf、air、wav类型 使用系统声音服务步骤 ...

  • iOS并发编程--GCD、操作队列、线程

    现在iOS的多线程方案主要有以下这几种: 1. GCD(Grand Central Dispatch):使用dispatch queue(分派队列)执行tasks(任务),苹果公司推荐使用; 2. NSOperationQueue和NSOperation:使用operation queue...

  • iOS基础界面编程———常用UIView控件的使用

    UILabel:类比于Android中的TextView的显示功能; UIPickerView: UIProgressView: UIActivityIndication: UIImageView: UITableBar: UIToolbar: UINavigationBar: UITableViewCell: UIActionShee....

  • iOS灵动岛【电商秒杀】开发实践

    iOS灵动岛【商品秒杀】开发实践

  • iOS网络编程

    ios网络编程(http、socket) 分类: OS-X/iOS开发 2013-03-15 09:08 7369人阅读 评论(3) 收藏 举报 目录(?)[+] http编程综述:亦可称为soap编程。通常情况下,http编程要比socket编程相对要...

  • iOS 崩溃日志在线符号化实践

    ????????关注后回复“进群”,拉你进程序员交流群????????作者丨冉景富来源丨百度App技术1. 什么是符号化?在日常开发中,应用难免会发生崩溃。通常,我们直接从用户导出来的...

  • iOS网络请求架构图URL Loading System

    原文链接 URL Loading System 概览 说明 URL Loading System 是一系列用来访问通过 URL 来定位的资源的类和协议。这项技术的核心在于基于 NSURL 这个类来访问资源,除了加载 URL 的类 NSURLSession 之外,我们把其他...

  • iOS_Crash 三:异常类型

    如果 Swift 运行时遇到编程错误,运行时会捕获该错误并故意使程序崩溃,这些崩溃在崩溃报告中具有可识别的异常信息: 在 ARM 处理器: Exception Type: EXC_BREAKPOINT (SIGTRAP) ... Termination Signal: Trace/BPT...

  • ios网络编程(http、socket)

    通常情况下,http编程要比socket编程相对要简单易用得多。所以用的最广广泛。 一、http编程其实就是http请求。http请求最长用的方法是 get 和 post 方法。 ==》get方法和post方法相比理解起来比较简单,g

  • 核心动画编程指南Core Animation Programming Guide - iOS

    核心动画编程官方文旦的简单记录总结

  • iOS多语言解决方案全面指南

    本文以及相关工具和代码旨在为已上线的iOS项目提供一种快速支持多语言的解决方案。由于文案显示是通过hook实现的,因此对App的性能有一定影响;除了特殊场景的文案显示需要手动支持外,其他任务均已实现自动化。

Global site tag (gtag.js) - Google Analytics