iOS编程基础:Swift、Xcode和Cocoa入门指南(txt+pdf+epub+mobi电子书下载)


发布时间:2020-07-13 00:02:20

点击下载

作者:(美)马特·诺伊贝格(MattNeuburg)

出版社:机械工业出版社

格式: AZW3, DOCX, EPUB, MOBI, PDF, TXT

iOS编程基础:Swift、Xcode和Cocoa入门指南

iOS编程基础:Swift、Xcode和Cocoa入门指南试读:

前言

2014年6月2日,苹果公司在WWDC大会最后宣布了一项令人震惊的公告:“我们开发了一门全新的编程语言。”开发者社区对此感到非常惊讶,因为他们已经习惯了Objective-C,因此开始怀疑苹果公司是否有能力将既有资产迁移过来。不过,这一次开发者社区错了。

Swift发布后,众多开发者立刻开始检视这门新语言:学习并批判它,决定是否该使用它。我的第一步就是将自己所有的iOS应用都转换为Swift;这足以说服我自己,虽然Swift有各种各样的缺点,但它值得每一个iOS编程新兵去掌握;自此以后,我的书都会假设读者使用的是Swift。

Swift语言从一开始的设计上就具备如下主要特性:

面向对象

Swift是一门现代化的、面向对象的语言。它是完全面向对象的:“一切皆对象。”

清晰

Swift易于阅读和编写,其语法糖很少,隐藏的捷径也不多。其语法清晰、一致且明确。

安全

Swift使用强类型,从而确保它知道(并且你也知道)在每一时刻每个对象引用都是什么类型的。

小巧

Swift是一门小巧的语言,提供了一些基本的类型与功能,除此之外别无其他。其他功能需要由你的代码,或你所使用的代码库(如Cocoa)来提供。

内存管理

Swift会自动管理内存。你很少需要考虑内存管理问题。

兼容于Cocoa

Cocoa API是由C和Objective-C编写的。Swift在设计时就明确保证可与大多数Cocoa API交互。

这些特性使得Swift成为学习iOS编程的一门优秀语言。

其他选择Objective-C依然存在,如果你喜欢还可以使用它。实际上,编写一个同时包含Swift代码与Objective-C代码的应用是很容易的;有时也需要这么做。不过,Objective-C缺少Swift的一些优势。Objective-C在C之上增加了面向对象特性。因此,它只是部分面向对象的;它同时拥有对象与标量数据类型,其对象需要对应于一种特殊的C数据类型(指针)。其语法掌握起来很困难;阅读与编写嵌套的方法调用会让人眼花,它还引入了一些黑科技,如隐式的nil测试。其类型检查可以而且经常关闭,这会导致程序员犯错,将消息发送给错误的对象类型并导致程序崩溃。Objective-C使用了手工的内存管理;新引入的ARC(自动引用计数)减轻了程序员的一些负担,并且极大地降低了程序员犯错的可能性,不过错误依旧有可能发生,内存管理最终还是要靠手工来完成。

最近向Objective-C增加或修订的特性(ARC、合成与自动合成、改进的字面值数组与字典的语法、块等)让Objective-C变得更加简单和便捷,不过这些修复也使语言变得更加庞大,甚至会引起困惑。由于Objective-C必须要包含C,因此其可扩展和修订的程度会受到限制。另一方面,Swift则是个全新的开始。如果你梦想完全修订Objective-C,从而创建一个更棒的Objective-C,那么Swift可能就是你所需要的。它将一个先进、合理的前端置于你与Cocoa Objective-C API之间。

因此,Swift就是本书通篇所使用的编程语言。不过,读者还需要对Objective-C(包括C)有所了解。Foundation与Cocoa API(这些内建的命令是你的代码一定会用到的,从而让iOS设备上的一切可以实现)依旧使用C与Objective-C编写。为了与它们进行交互,你需要知道这些语言需要什么。比如,为了在需要NSArray时可以传递一个Swift数组,你需要知道到底是什么对象可以作为Objective-C NSArray的元素。

因此,本书虽然不会讲解Objective-C,但我会对其进行足够充分的介绍,从而使你在文档和互联网上遇到这类问题时能够知道解决方案,我还会时不时地展示一些Objective-C代码。本书第三部分关于Cocoa的介绍会帮助大家以Objective-C的方式来思考——因为Cocoa API的结构与行为基本上是基于Objective-C的。本书最后的附录会详细介绍Swift与Objective-C之间的交互方式,同时还会介绍如何以Swift和Objective-C混合编程来编写应用。本书范围

本书实际上是我的另一本书《Programming iOS 9》的配套参考书,该书以本书的结束作为起点。它们之间是互补的。我相信,这两本书的结构合理、内容通俗易懂。它们提供了开始编写iOS应用所需的完整基础知识;这样,在开始编写iOS应用时,你会对将要做的事情以及方向有着深刻的理解。如果编写iOS程序类似于用砖盖房子,那么本书将会介绍什么是砖以及如何使用它,而《Programming iOS 9》则会给你一些实际的砖并告诉你如何将其堆砌起来。

阅读完本书后,你将知道Swift、Xcode以及Cocoa框架的基础,接下来就可以直接开始阅读《Programming iOS 9》了。相反,《Programming iOS 9》假设你已经掌握了本书所介绍的内容;一开始它就会介绍视图与视图控制器,同时假设你已经掌握了语言本身和Xcode IDE。如果开始阅读《Programming iOS 9》并且想知道书中一些没有讲解过的东西,如Swift语言基础、UIApplicationMain函数、nib加载机制、Cocoa的委托与通知模式、保持循环等,那就不要尝试在该书中寻找答案了,我并没有在那本书中介绍这些内容,因为这里都介绍过了。

本书的3部分内容将会介绍iOS编程的基础知识:

·第一部分从头开始介绍Swift语言。我没有假设你知道任何其他的编程语言。我讲解Swift的方式与其他人不同,如苹果公司的方式;我会采取系统的方式,逐步推进,不断深入。同时,我会讲解最本质的内容。Swift并不是一门庞大的语言,不过却有一些微妙之处。你无须深入到全部内容当中,我也不会面面俱到地讲解。你可能永远都不会遇到一些问题,即便遇到了,那也说明你已经进入到了高级Swift的世界当中,而这已经超出了本书的讨论范围。比如,读者可能会惊奇地发现我在书中从来都没有提到过Swift playground和REPL。本书的关注点在于实际的iOS编程,因此我对Swift的介绍将会关注在这些常见、实际的方面上;以我的经验来看,这些内容才是iOS编程当中用得最多的。

·第二部分将会介绍Xcode,这是我们进行iOS编程的地方。我将介绍何为Xcode项目,如何将其转换为应用,如何通过Xcode来查阅文档,如何编写、导航与调试代码,以及如何在设备上运行应用并提交到App Store等过程。该部分还有重要的一章用来介绍nib与nib编辑器(Interface Builder),包括插座变量与动作,以及nib加载机制;不过,诸如nib中的自动布局限制等专门的主题则不在本书的讨论范围之中。

·第三部分将会介绍Cocoa Touch框架。在进行iOS编程时,你会使用到苹果公司提供的大量框架。这些框架共同构成了Cocoa;为iOS编程提供API的Cocoa叫作Cocoa Touch。你的代码最终将是关于如何与Cocoa进行通信的。Cocoa Touch框架提供了iOS应用所需的底层功能。不过要想使用框架,你需要按照框架的想法去做,将代码放到框架期望的位置处,实现框架要求你实现的功能。有趣的是,Cocoa使用的是Objective-C,你使用的是Swift:你需要知道Swift代码如何与Cocoa的特性与行为进行交互。Cocoa提供了重要的基础类,并添加了一些语言与架构上的设施,如类别、协议、委托、通知,以及关于内存管理的基本功能。该部分还会介绍键值编码与键值观测。

本书读者将会掌握任何优秀的iOS开发者所需的基础知识与技术;但本书并不会介绍如何编写一个有趣的iOS应用,书中会大量使用我自己编写的应用与实际的编程场景来阐述理论知识。接下来各位读者就可以阅读《Programming iOS 9》了。版本

本书使用的是Swift 2.0、iOS 9与Xcode 7。

总的来说,本书并不会对老版本的iOS与Xcode做过多介绍。我也不会有意在书中对老版本的软件进行讲解,毕竟这些内容在我之前的书中都有过介绍。不过,本书会针对向后兼容性给出一些建议(特别是在第9章)。

Xcode 7所包含的Swift语言(Swift 2.0)相比于之前的版本Swift 1.2发生了很大的变化。如果之前使用过Swift 1.2,那么你就会发现如果不做一些修改,代码是无法在Swift 2.0中编译通过的。与之类似,书中的代码使用Swift 2.0编写而成,它也完全无法与Swift 1.2保持兼容。你之前可能有过Swift 1.2的编程经验,随着我的不断讲解,你会发现不少重要的语言特性在Swift 2.0中都发生了变化。不过,我并不会介绍Swift 1.2;如果想要了解(虽然我不知道你为什么要了解),那么请参考本书的前一版。致谢

首先感谢O’Reilly Media的工作人员,正是他们才让一本书的写作过程充满了快乐:Rachel Roumeliotis、Sarah Schneider、Kristen Brown、Dan Fauxsmith与Adam Witwer。我也不会忘记编辑Brian Jepson,虽然他并未参与本版的工作,但对我的影响却一直都在。

一直以来,一些优秀的软件对我起到了巨大的帮助作用,我在写作本书的过程中一直都心存谢意。这些软件主要有:

·git(http://git-scm.com)

·SourceTree(http://www.sourcetreeapp.com)

·TextMate(http://macromates.com)

·AsciiDoc(http://www.methods.co.nz/asciidoc)

·BBEdit(http://barebones.com/products/bbedit/)

·Snapz Pro X(http://www.ambrosiasw.com)

·GraphicConverter(http://www.lemkesoft.com)

·OmniGraffle(http://www.omnigroup.com)

我通过忠实的Unicomp Model M键盘(http://pckeyboard.com)完成了全书的输入与编辑工作,如果没有它我是不可能在如此长的时间内轻松敲下这么多字的。请通过http://matt.neuburg.usesthis.com了解我的工作环境。《Programming iOS 4》前言

编程框架体现了一个人的品格,它是创建者对于目标与心智的反映。第一次使用Cocoa Touch时,我对其品格的评价是这样的:“喔,创建它的人真是绝顶聪明啊!”一方面,内建的界面对象数量有意得到了限制;另一方面,一些对象的功能与灵活性(特别是UITableView等)要比其OS X的对应者更加强大。更为重要的是,苹果公司提供了一种聪明的方式(UIViewController)来帮助开发者创建整个界面并使用一个界面替换掉另一个,这些以一种可控、层次化的方式来实现,这样小小的iPhone就可以在一个应用中显示多个界面了,还不会让用户迷失或感到困惑。

iPhone的流行(大量免费与便宜的应用起到了很大的帮助作用)以及随后iPad的流行让很多新的开发者看到为这些设备编写程序是值得的,虽然他们对OS X可能没有相同的感觉。苹果公司自己的年度WWDC开发者大会也反映出了这种趋势,其重心也由OS X逐渐向iOS倾斜。

不过,人们渴望编写iOS程序的想法也导致了这样一种趋势:还没有学会走就开始跑了。iOS赋予了开发者强大的能力,心有多大舞台就有多大,不过这也是需要基础的。我常常看到一些iOS开发者提出的问题,虽然他们在编写着应用,但其对基础知识的理解非常肤浅。

这种情况促使我写作了这本书,本书旨在介绍iOS的基础知识。我喜欢Cocoa,也一直希望能有机会写一本关于Cocoa的图书,不过iOS的流行却让我编写了一本关于iOS的图书。我尝试采取一种合乎逻辑的方式进行说明和讲解,介绍iOS编程所需的原则与元素。正如之前的图书一样,我希望你能完整阅读这本书(学习新东西肯定会不停翻书),并将其作为案头参考。

本书并不是要代替苹果公司自己的文档与示例项目。那些都是非常棒的资源,随着时间的流逝将会变得越来越好。在准备本书写作的过程中我也将其作为参考资源。但是,我发现它们并不是按照顺序以一种合理的方式来完成一个功能的。在线文档会假设你的预备知识;它不能确保你会按照给定的方式来完成。此外,在线文档更适合作为参考而不是指南。完整的示例(无论注释有多么充分)都是难以跟着学习的;它可以作为演示,但却无法作为教学资源。

另外,图书的章节号和页码连续,内容的连贯性比较强;我可以在你学习视图控制器之前假定你已经知道视图了,因为第一部分位于第二部分之前。此外,我还会将自己的经验逐步分享给你。在全书中,你会发现我不断提及“常见的初学者错误”;除了一些其他人的错误,在大多数情况下,这些都是我曾经犯过的错误。我会告诉你一些陷阱,因为这些都是我曾经遇到过的,我相信你也一定会遇到。你还会看到我给出了不少示例,目的是解释一个大应用的一小部分内容。这并非用于讲解编程的一个已经完成的大程序,而是开发这个程序时的思考过程。我希望你在阅读本书时能够掌握这种思考过程。本书约定

本书中使用以下排版约定:

斜体(Italic)

表示新术语、URL、电子邮件地址、文件名和文件扩展名。

等宽字体(Constant width)

表示代码示例,以及插穿在文中的代码,包括:变量或函数名、数据库、数据类型、环境变量、语句,以及关键字。

等宽粗体(Constant width bold)

表示新术语、URL、电子邮件地址、文件名和文件扩展名。

等宽斜体(Constant width italic)

表示新术语、URL、电子邮件地址、文件名和文件扩展名。这个元素表示提示或建议。这个元素表示一般注解。这个元素表示警告。如何使用示例代码

本书在这里帮助你完成你的工作。总的来讲,你可以在你的程序和文档中使用本书中的代码。你不需要联系我们以征得许可,除非你正在复制代码中的重要部分。比如,使用书中的多段代码写一个程序并不需要获得许可。

若将O’Reilly公司出版的书中的例子制成光盘来销售或发行则需要获得许可。在回答问题时,引用本书和列举书中的例子代码并不需要许可。把本书中的代码作为你产品文档的重要部分时需要获得许可。

我们希望但并不要求你在引用本书内容时说明引文的文献出处。引用通常包括题目、作者、出版社和ISBN号。例如,《iOS 9 Programming Fundamentals with Swift》,Matt Neuburg(O’Reilly)。Copyright 2016 Matt Neuburg,978-1-491-93677-1。

如果你感觉你对代码示例的使用超出合理使用以及上述许可范围,请通过permissions@oreilly.com联系我们。®Safari图书在线

Safari图书在线(www.safaribooksonline.com)是一个按需数字图书馆,它采用图书和视频两种形式发布专业级的内容,作者都是来自技术和商业领域的世界顶尖专家。

技术专家、软件开发者、网站设计者和商业及创新专家都使用Safari图书在线作为他们研究、解决问题、学习和职业资格培训的首要资源。

Safari图书在线为各种组织、政府机构和个人提供丰富的产品和定价程序。订购者可在一个全文可检索数据库中浏览数以千计的图书、培训视频和预出版手稿。它们来自O’Reilly Media、Prentice Hall Professional、Addison-Wesley Professional、Microsoft Press、Sams、Que、Peachpit Press、Focal Press、Cisco Press、John Wiley & Sons、Syngress、Morgan Kaufmann、IBM Redbooks、Packt、Adobe Press、FT Press、Apress、Manning、New Riders、McGraw-Hill、Jones & Bartlett、Course Technology等的众多出版社。关于Safari图书在线的更多信息,请在线访问我们。如何联系我们

美国:

O’Reilly Media,Inc.

1005 Gravenstein Highway North

Sebastopol,CA 95472

中国:

北京市西城区西直门南大街2号成铭大厦C座807室(100035)

奥莱利技术咨询(北京)有限公司

我们为本书提供了网页,该网页上面列出了勘误表、范例和任何其他附加的信息。您可以访问如下网页获得:

http://oreil.ly/HP-Drupal

要询问技术问题或对本书提出建议,请发送电子邮件至:

bookquestions@oreilly.com

要获得更多关于我们的书籍、会议、资源中心和O’Reilly网络的信息,请参见我们的网站:

http://www.oreilly.com.cn

http://www.oreilly.com第一部分 语言

本部分将会从头开始介绍Swift这门语言,整个介绍是非常严密且有序的。通过本部分的介绍,你将熟悉并适应Swift,从而能够进行实际的编程工作。

·第1章从概念与实践上介绍Swift程序的结构。你将学习到Swift代码文件的组织方式,以及面向对象的Swift语言最重要的底层概念:变量与函数、作用域与命名空间,以及对象类型与实例。

·第2章将会介绍Swift函数。我们首先会从函数的声明与调用方式基础开始;接下来介绍参数——外部参数名、默认参数与可变参数。然后将会深入介绍Swift函数的功能,同时还会介绍函数中的函数、作为一等值的函数、匿名函数、作为闭包的函数,以及柯里化函数。

·第3章首先会介绍Swift变量——变量的作用域与生命周期、如何声明与初始化变量,以及一些重要的Swift特性,如计算变量与setter观察者等。然后会介绍一些重要的内建Swift类型,包括布尔、数字、字符串、范围、元组与Optional。

·第4章将会介绍Swift对象类型——类、结构体与枚举。本章将会介绍这3种对象类型的工作方式,如何声明、实例化与使用它们。接下来会介绍多态与类型转换、协议、泛型及扩展。本章最后将会介绍Swift的保护类型(如AnyObject)与集合类型(Array、Dictionary与Set,还包括Swift 2.0新引入的用于表示位掩码的选项集合)。

·第5章内容比较庞杂。我们首先会介绍用于分支、循环与跳转的Swift流程控制结构,包括Swift 2.0的一个新特性——错误处理。接下来将会介绍如何创建自己的Swift运算符。本章最后将会介绍Swift访问控制(私有性)、内省机制(反射)与内存管理。第1章 Swift架构纵览

首先对Swift语言的构建方式有个总体认识并了解基于Swift的iOS程序的样子是很有用的。本章将会介绍Swift语言的整体架构与本质特性,后续章节将会对细节进行详尽的介绍。1.1 基础

一个完整的Swift命令是一条语句。一个Swift文本文件包含了多行文本。换行符是有意义的。一个程序的典型布局就是一行一条语句:print("hello")print("world")(print命令会在Xcode控制台提供即时反馈。)

可以将多条语句放到一行,不过这就需要在语句间加上分号:print("hello"); print("world")

可以将分号放到语句的末尾,也可以在一行上单独放置一个分号,不过没人这么做(除了习惯原因之外,因为C和Objective-C要求使用分号):print("hello");print("world");

与之相反,单条语句可以放到多行,这样做可以防止一行中出现过长的语句。不过在这样做的时候要注意语句的位置,以免对Swift造成困扰。比如,左圆括号后面就是个不错的位置:print( "world")

一行中双斜线后面的内容会被当作注释(即所谓的C++风格的注释):print("world") // this is a comment, so Swift ignores it

还可以将注释放到/*...*/中,就像C一样。与C不同,Swift风格的注释是可以嵌套的。

Swift中的很多构建块都会将花括号用作分隔符:class Dog { func bark() { print("woof") }}

根据约定,花括号中的内容由换行符开始,并且通过缩进增强可读性,如上述代码所示。Xcode会帮助你应用该约定,不过实际情况却是Swift并不在意这些,像下面这样的布局也是合法的(有时也更加便捷):class Dog { func bark() { print("woof") }}

Swift是一门编译型语言。这意味着代码必须要先构建(通过编译器,由文本转换为计算机可以理解的某种底层形式),然后再执行并根据指令完成任务。Swift编译器非常严格;在编写程序时,你经常会构建并运行,不过你会发现第一次甚至都无法构建成功,原因就在于编译器会识别出一些错误,如果想让代码运行,你就需要修复这些问题。有时候,编译器会给出一些警告;这时代码可以运行,不过一般情况下,你应该有所警戒并修复编译器报出的警告。编译器的严格性是Swift最强大的优势之一,可以在代码开始运行前提供最大程度的审计正确性。Swift编译器的错误与警告消息涵盖范围非常广,从洞察性极强到一般性提示再到完全误导人。很多时候,你知道某行代码有问题,不过Swift编译器却不会清晰地告诉你什么地方出错了,甚至连是哪行都不会告诉你。对于这些情况,我的建议是将可能有问题的代码行放到简单的代码块中,直到发现问题所在位置。虽然提示消息有时起不到帮助作用,不过请保持与编译器的亲密接触吧。请记住,虽然编译器有时无法准确地进行描述,但它知道的一定比你多。1.2 万物皆对象

在Swift中,万物皆对象。这与各种现代化面向对象语言是一致的,不过这表示什么意思呢?这取决于你所理解的对象,那“万物”又是什么意思呢?

首先来定义一下对象,大概来说,对象指的是你可以向其发送消息的某个实体。一般来说,消息指的是一种命令指令。比如,你可以向一只狗发送命令:吼叫!坐下!在这种情况下,这些短语就是消息,而狗则是你向其发送消息的对象。

在Swift中,消息发送语法采用的是点符号。首先是对象,然后是一个点,接下来是消息(有些消息后会跟圆括号,不过现在请不用管它,消息发送的完整语法是接下来将会详细介绍的一个主题)。如下是有效的Swift语法:fido.bark()rover.sit()

万物皆对象的想法表明即便是“原生”的语言实体都可以接收消息。比如,1。它是个数字字面值,除此之外别无其他。如果你曾经使用过其他编程语言,那么在Swift中像下面这样做就不会觉得有什么奇怪的了:let sum = 1 + 2

不过,让人奇怪的是1后面可以跟着一个点和一条消息。这在Swift中是合法且有意义的(现在可以不用管其含义):let x = 1.successor()

还可以更进一步。回到之前那个1+2代码示例上来。实际上这是一种语法技巧,是表示并隐藏实际情况的一种便捷方式。就好像1实际上是个对象,+是一条消息;不过这条消息使用了特殊的语法(运算符语法)。在Swift中,每个名词都是一个对象,每个动词都是一条消息。

也许判别Swift中的某个实体是不是对象的根本标准在于你能否修改它。在Swift中,对象类型是可以扩展的,这意味着你可以定义该类型下自己的消息。比如,正常情况下你无法向数字发送sayHello消息。不过你可以修改数字类型使得希望达成:extension Int { func sayHello() { print("Hello, I'm \(self)") }}1.sayHello() // outputs: "Hello, I'm 1"

这样,情况就发生了变化。

在Swift中,1是个对象。在其他一些语言(如Objective-C)中显然不是这样的;1是个原生或标量内建数据类型。区别在于,当我们说万物皆对象时,一方面指的是对象类型,另一方面指的是标量类型。Swift中是不存在标量的;所有类型最终都是对象类型。这就是“万物皆对象”的真正含义。1.3 对象类型的3种风格

如果了解Objective-C或是其他一些面向对象语言,你可能好奇于Swift中的对象1是个什么概念。在很多语言(如Objective-C)中,对象指的是一个类或一个类的实例。Swift拥有类与实例,你可以向其发送消息;不过在Swift中,1既不是类也不是实例:它是个结构体(struct)。Swift还有另外一种可以接收消息的实体,叫作枚举。

因此,Swift拥有3种对象类型:类、结构体与枚举。我喜欢称它们为对象类型的3种风格。后续内容将会介绍它们之间的差别。不过它们都是确定的对象类型,彼此之间的相似性要远远高于差异性。现在,只需知道这3种风格的存在即可。(如果了解Objective-C,那么你会惊讶于Swift中的结构体与枚举竟然都是对象类型,不过它们并非对象。特别地,Swift中的结构体要比Objective-C的结构体更加重要,使用更为广泛。Swift与Objective-C对待结构体和枚举的不同方式在Cocoa中显得尤为重要。)1.4 变量

变量指的是对象的名字。从技术角度来说,它指向一个对象;它是一个对象引用。从非技术角度来看,你可以将其看作存放对象的一个盒子。对象可能会发生变化,或是盒子中的对象被其他对象所替换,但名字却不会发生变化。

在Swift中,不存在没有名字的变量,所有变量都必须要声明。如果需要为某个东西起个名字,那么你要说“我在创建一个名字”。可以通过两个关键字实现这一点:let或是var。在Swift中,声明通常会伴随着初始化一起——使用等号为变量赋值,并作为声明的一部分。下面这些都是变量声明(与初始化):let one = 1var two = 2

如果名字存在,那么你就可以使用它了。比如,我们可以将two中的值修改为one中的:let one = 1var two = 2two = one

上面最后一行代码使用了前两行所声明的名字one与two:等号右侧的名字one仅仅用于引用盒子中的值(即1);不过,等号左侧的名字two则用于替换掉盒子中的值。这种语句(变量名位于等号左侧)叫作赋值,等号叫作赋值运算符。等号并不是相等性断言,这与数学公式中的等号不同;它是一个命令,表示“获取右侧的值,然后使用它替换掉左侧的值”。

变量的这两种声明方式是不同的,通过let声明的名字是不能替换掉其对象的。通过let声明的变量是个常量;其值只能被赋予一次并且不再变化。如下代码是无法编译通过的:let one = 1var two = 2one = two // compile error

可以通过var声明一个名字来实现最大的灵活性,不过如果知道永远不会改变变量的初始值,那么最好使用let,这样Swift在处理时效率会更高;事实上,如果本可以使用let,但你却使用了var,那么Swift编译器就会提示你,并且可以帮你修改。

变量也是有类型的,其类型是在变量声明时创建的,而且永远不会改变。比如,如下代码是无法编译通过的:var two = 2two = "hello" // compile error

一旦声明two并将其初始化为2,那么它就是一个数字了(确切地说是一个Int),而且一直都将如此。你可以将其替换为1,因为1也是个Int,但不能将其值替换为“hello”,因为"hello"是个字符串(确切地说是一个String),而String并非Int。

变量有自己的生命——更准确地说是有自己的生命周期。只要变量存在,那么它就会一直保存其值。这样,变量不仅是一种便于命名的手段,还是一种保存值的方式。稍后将会对此做详细介绍。根据约定,如String或Int(或Dog、Cat)等类型名要以大写字母开头;变量名则以小写字母开头,请不要违背该约定。如果违背了,那么你的代码虽然还是可以编译通过并正常运行,但其他人却不太容易理解。1.5 函数

如fido.bark()或one=two这样的可执行代码不能随意放置。一般来说,这样的代码必须要位于函数体中。函数由一系列代码构成,并且可以运行。一般来说,函数有一个名字,这个名字是通过函数声明得到的。函数声明语法的细节将会在后面进行详细介绍,先来看一个示例:func go() { let one = 1 var two = 2 two = one}

上述代码描述了要做的一系列事情——声明one、声明two,将one值赋给two——并且给这一系列代码赋予一个名字go;不过该代码序列并不会执行。只有在调用函数时,该代码序列才会执行。我们可以在其他地方这样执行:go()

这会向go函数发出一个命令,这样go函数才会真正运行起来。重申一次,命令本身是可执行代码,因此它不能位于自身当中。它可以位于不同的函数体中:func doGo() { go()}

请等一下!这么做有点奇怪。上面是一个函数声明;要想运行该函数,你需要调用doGo,它才是可执行代码。这看起来像是无穷无尽的循环一样;似乎代码永远都不会运行。如果所有代码都必须位于一个函数中,那么谁来让函数运行呢?初始动力一定来自于其他地方。

幸好,在实际情况下,这个问题并不会出现。记住,你的最终目标是编写iOS应用。因此,应用会运行在iOS设备(或是模拟器)中,由运行时调用,而运行时已经知道该调用哪些函数了。首先编写一些特殊函数,这些函数会由运行时本身来调用。这样,应用就可以启动了,并且可以将函数放到运行时在某些时刻会调用的地方——比如,当应用启动时,或是当用户轻拍应用界面上的按钮时。Swift还有一个特殊的规则,那就是名为main.swift的文件可以在顶层包含可执行代码,这些代码位于任何函数体的外部,当程序运行时真正执行的其实就是这些代码。你可以通过main.swift文件来构建应用,不过一般来说没必要这么做。1.6  Swift文件的结构

Swift程序可以包含一个或多个文件。在Swift中,文件是一个有意义的单元,它通过一些明确的规则来确定Swift代码的结构(假设不在main.swift文件中)。只有某些内容可以位于Swift文件的顶层部分,主要有如下几个部分

模块import语句

模块是比文件更高层的单元。一个模块可以包含多个文件,在Swift中,模块中的文件能够自动看到彼此;但如果没有import语句,那么一个模块是看不到其他模块的。比如,想想如何在iOS程序中使用Cocoa:文件的第1行会使用import UIKit。

变量声明

声明在文件顶层的变量叫作全局变量:只要程序还在运行,它就会一直存在。

函数声明

声明在文件顶层的函数叫作全局函数:所有代码都能看到并调用它,无需向任何对象发送消息。

对象类型声明

指的是类、结构体或是枚举的声明。

比如,下面是一个合法的Swift文件,包含(只是为了说明)一个import语句、一个变量声明、一个函数声明、一个类声明、一个结构体声明,以及一个枚举声明。import UIKitvar one = 1func changeOne() {}class Manny {}struct Moe {}enum Jack {}

这个示例本身并没有什么意义,不过请记住,我们的目标是探求语言的组成部分与文件的结构,该示例仅仅是为了演示。

此外,示例中每一部分的花括号中还可以加入变量声明、函数声明与对象类型声明。事实上,任何结构化的花括号中都可以包含这些声明。比如,关键字if(Swift流程控制的一部分,第5章将会介绍)后面会跟着结构化的花括号,它们可以包含变量声明、函数声明与对象类型声明。如下代码虽然毫无意义,但却是合法的:func silly() { if true { class Cat {} var one = 1 one = one + 1 }}

你会发现我并没有说可执行代码可以位于文件的顶部,因为这是不行的。只有函数体可以包含可执行代码。函数自身可以包含任意深度的可执行代码;在上述代码中,one=one+1这一行可执行代码是合法的,因为它位于if结构中,而该if结构又位于函数体中。但one=one+1这一行不能位于文件顶层,也不能位于Cat声明的花括号中。

示例1-1是一个合法的Swift文件,其中概要地展示了其结构的各种可能性。(请忽略枚举声明中关于Jack的name变量声明;枚举中的顶层变量有一些特殊规则,稍后将会介绍。)

示例1-1:合法的Swift文件的概要结构import UIKitvar one = 1func changeOne() { let two = 2 func sayTwo() { print(two) } class Klass {} struct Struct {} enum Enum {} one = two}class Manny { let name = "manny" func sayName() { print(name) } class Klass {} struct Struct {} enum Enum {}}struct Moe { let name = "moe" func sayName() { print(name) } class Klass {} struct Struct {} enum Enum {}}enum Jack { var name : String { return "jack" } func sayName() { print(name) } class Klass {} struct Struct {} enum Enum {}}

显然,可以一直递归下去:类声明中可以包含类声明,里面的类声明中还可以包含类声明,以此类推。不过这么做毫无意义。1.7 作用域与生命周期

在Swift程序中,一切事物都有作用域。这指的是它们会被其他事物看到的能力。一个事物可以嵌套在其他事物中,形成一个嵌套的层次结构。规则是一个事物可以看到与自己相同层次或是更高层次的事物。层次有:

·模块是一个作用域。

·文件是一个作用域。

·对象声明是一个作用域。

·花括号是一个作用域。

在声明某个事物时,它实际上是在该层级的某个层次上进行的声明。它在层级中的位置(即作用域)决定了是否能被其他事物看到。

再来看看示例1-1。在Manny的声明中是个name变量声明和一个sayName函数声明;sayName花括号中的代码可以看到更高层次中花括号之外的内容,因此它可以看到name变量。与之类似,changeOne函数体中的代码可以看到文件顶层所声明的one变量;实际上,该文件中的一切事物都可以看到文件顶层所声明的one变量。

作用域是共享信息的一种非常重要的手段。声明在Manny中的两个不同函数都会看到在Manny顶层所声明的name变量。Jack中的代码与Moe中的代码都可以看到声明在文件顶层的one。

事物还有生命周期,这与其作用域是相关的。一个事物的生命周期与其外部作用域的生命周期是一致的。因此,在示例1-1中,变量one的生命周期就与文件一样,只要程序处于运行状态,one就是有效的。它是全局且持久的。不过,声明在Manny顶层的变量name只有在Manny存在时才存在(稍后将会对此做出说明)。声明在更深层次中的事物的生命周期会更短;比如,看看下面这段代码:func silly() { if true { class Cat {} var one = 1 one = one + 1 }}

在上述代码中,类Cat与变量one只在代码执行路径通过if结构这一短暂的时间内才会存在。当调用函数silly时,执行路径就会进入if结构中,Cat会被声明并进入存活状态;接下来,one被声明并进入存活状态;然后代码行one=one+1会被执行;接下来作用域结束,Cat与one都会消失殆尽。1.8 对象成员

在3种对象类型(类、结构体与枚举)中,声明在顶层的事物具有特殊的名字,这在很大程度上是出于历史原因。下面以Manny类作为示例:class Manny { let name = "manny" func sayName() { print(name) }}

在上述代码中:

·name是声明在对象声明顶层中的变量,因此叫作该对象的属性。

·sayName是声明在对象声明顶层中的函数,因此叫作对象的方法。

声明在对象声明顶层的事物(属性、方法以及声明在该层次上的任何对象)共同构成了该对象的成员。成员具有特殊的意义,因为它们定义了你可以向该对象所发送的消息!1.9 命名空间

命名空间指的是程序中的具名区域。命名空间具有这样一个属性:如果事先不穿越区域名这一屏障,那么命名空间外的事物是无法访问到命名空间内的事物的。这是一个好想法,因为通过命名空间,我们可以在不同地方使用相同的名字而不会出现冲突。显然,命名空间与作用域是紧密关联的两个概念。

命名空间有助于解释清楚在一个对象顶层声明另一个对象的意义,比如:class Manny { class Klass {}}

通过这种方式来声明Klass会使得Klass成为一个嵌套类型,并且很好地将其“隐藏”到Manny中。Manny就是个命名空间!Manny中的代码可以直接看到Klass,不过Manny外的代码则看不到。需要显式指定命名空间才能穿过命名空间所代表的屏障。要想做到这一点,必须先使用Manny的名字,后跟一个点,然后是术语Klass。简而言之,需要写成Manny.Klass。

命名空间本身并不会提供安全或隐私;只是提供了便捷的手段而已。因此,在示例1-1中,我给Manny一个Klass类,也给Moe一个Klass类。不过它们之间并不会出现冲突,因为它们位于不同的命名空间中,如果必要,我可以通过Manny.Klass与Moe.Klass来区分它们。

毫无疑问,显式使用命名空间的语法依旧是消息发送点符号语法,事实上,它们是一回事。

实际上,你可以通过消息发送进入本无法进入的作用域。Moe中的代码不能自动看到Manny中声明的Klass,不过可以采取一个额外的步骤来实现这个目标,即通过Manny.Klass。之所以可以这么做是因为它能看到Manny(因为Manny声明的层级可以被Moe中的代码看到)。1.10 模块

顶层命名空间是模块。在默认情况下,应用本身就是个模块,因此也是个命名空间;大概来说,命名空间的名字就是应用的名字。比如,假如应用叫作MyApp,那么如果在文件顶层声明一个类Manny,那么该类的真实名字就是MyApp.Manny。但通常情况下是不需要这个真实名字的,因为代码已经在相同的命名空间中了,可以直接看到名字Manny。

框架也是模块,因此它们也是命名空间。比如,Cocoa的Foundation框架(NSString位于其中)就是个模块。在编写iOS程序时,你会import Foundation(还有可能import UIKit,它本身又会导入Foundation),这样就可以直接使用NSString而不必写成Foundation.NSString了。不过你可以写成Foundation.NSString,如果在自己的模块中声明了一个不同的NSString,那么为了区分它们,你就只能写成Foundation.NSString了。你还可以创建自己的框架,当然了,它们也都是模块。

如示例1-1所示,文件层级之外的是文件所导入的库或模块。代码总是会隐式导入Swift本身。还可以显式导入,方式就是以import Swift作为文件的开始;但没必要这么做,不过这么做也没什么弊端。

这个事实是非常重要的,因为它解决了一个大谜团:如print来自于哪里,为何可以在任何对象的任何消息之外使用它们?事实上,print是在Swift.h头文件的顶层声明的一个函数——你的文件可以看到

试读结束[说明:试读内容隐藏了图片]

下载完整电子书


相关推荐

最新文章


© 2020 txtepub下载