当学完本章时,你将得到CarValet应用程序的初始版本号。以及足够完毕本书学习的Objective-C知识。
2.1 使用模板建立Hello World 应用程序
建立Hello World演示样例应用程序的最简单方法,就是使用Xcode的预设模板。在接下来的步骤中,你将建立一个新的项目。而后改动它,使之能打印“Hello World”,并能在iOS模拟器上执行。在建立第一个Xcode项目时,你将学到关于Xcode这一关键工具的总体介绍,以及怎样建立和操纵项目。
2.1.1 建立Hello World 项目
启动Xcode。你将看到如图2-1所看到的的Xcode欢迎页面(假设不当心关闭了这个页面。可经过选择Window | Welcome to Xcode(欢迎使用Xcode)或按下Cmd+Shift+1组合键来又一次显示)。html
单击Create a New Project。git
也可以在Xcode中选择File | New | Project(或按下Cmd+Shift+N组合键)。以后将会出现如图2-2所看到的的模板选择窗体。算法
默认状况下,模板选择窗体被嵌套在一个新的、名为工做区(workspace)的大窗体中。这个单独的工做区窗体中包括Xcode全部的编辑器和检查器(inspector)特性。
编程
在图2-2所看到的的模板选择窗体中,选择左边的iOS菜单下的Application,而后选择SingleView Application(单视图应用程序),单击Nextbutton。api
在这个窗体中,请遵循下列步骤:
(1) 在Product Name中输入HelloWorld。
(2) 将Company Identifier(公司标识符)设置为你所在的公司,使用反向域名命名法。好比,Maurice的公司标识符是com.mauricesharp或com.klmapps,Rod的公司标识符是com.strougo,而Erica的公司标识符是com.sadun。数组
假设没有公司标识符。苹果公司建议使用edu.self。
缓存
(3) 单击Nextbutton。安全
但假设指定前缀为“MS”。那么Xcode会建立名为MSSlideMenuController的文件和类,从而避免潜在的冲突。微信
在本书中。为了让命名更简短。并未使用前缀,但是你应当依据本身的开发人员名称或公司名称设置前缀。
专家提示:前缀格式
最典型的类前缀是一小串大写字母(苹果公司推荐使用3 个字母),通常是你或你公司名称的单词首字母。网络
在公司内部项目或开源项目中,这样的格式非常常见。但可读性并不是最好。下面是一个滑动菜单类的各类不一样的名称。这并不是我实际编写过的一个类。是我想出来的。但最好仍是试着观察一下列表中的这些类名条目,依照你浏览代码时的方式,看看哪个更easy高速识别:
SlideMenuViewController
MSSlideMenuViewController
MS_SlideMenuViewController
msSlideMenuViewController
ms_slideMenuViewController
极其可能的是,全部大写且没有分隔符的版本号最难识别。小写的稍稍easy点。但难以识别的程度仅次于前者。更easy识别的是那些带有分隔符的版本号。你应当选择能与符号名称简易搭配使用的类前缀。
长远来看,你将节省大量的开发与调试时间。
• Devices(设备)——应用程序的目标设备平台。可以是iPhone、iPad 或Universal(通用应用程序)——可以同一时候执行在这两种平台上的应用程序。
上一个Xcode窗体面板询问你Hello World项目的存储位置。
假设愿意的话,Xcode还可以为项目建立本地的git仓库。针对当前这个Hello World演示样例。将Source Control(源代码控制)复选框保持为未选中状态。
选择一个目录并单击建立。如图2-4所看到的。
在单击Createbutton以后,你应该能看到在Xcode中,本身的Hello World新项目已被打开。它带有一些Xcode本身主动生成的文件。至此,你获得一个看似不怎么有趣,但是功能完备的应用程序。假设立马单击Run(执行)button,你的app会显示一个顶部带有状态条的白色屏幕。
开发应用程序时,你将在Xcode中花费大量时间。图2-5显示了展开项目文件并选择ViewController.m文件后的Xcode界面。
图2-5 Xcode 项目界面的组成部分
如下是对Xcode界面的高速导航。
接下来的几章将探讨Xcode界面中不一样组件的不少其它细节知识。如下的数字与图2-5中显示的数字一一相应:
(1) 单击Runbutton(在左边),编译应用程序,并在模拟器或设备中启动应用程序。单击并保持(长按)Runbutton。你会看到额外的选项,比方用于測试或分析项目的选项。
右側的button可用于中止正在执行的应用程序、正在执行的构建或已发起的其它动做。
(2) 在这个分为两段的弹出式菜单button中,左段用于选择和编辑方案(Scheme),也就是关于你想执行什么以及怎样执行该应用程序。右段用于选择在哪儿执行应用程序:是在某种模拟器中仍是在某个已链接的设备上。
(3) Status(状态)区域向你展现上一动做的执行结果或当前动做的进度。
动做包含构建应用程序、执行应用程序或是下载应用程序到设备上。
(4) Editor(编辑器)button用于配置编辑器(图2-5中标为7的区域)中显示的内容。
左側button在图中已被选中,仅仅显示一件东西。即主编辑区。中间的Assistant(辅助编辑)button将编辑区划分为两块,在右側会显示与主编辑器相关的文件。在右側一般状况下会显示头文件。
最后一个button用于显示源码差别,并且可以查看文件的不一样版本号。
这一点在使用源码仓库跟踪代码变更时很实用。
注意,不能同一时候显示辅助编辑和源码视图。
(5) 这个区域控制Xcode中工做区视图的总体布局。第一个button显示或隐藏Navigation(导航器,图2-5中的区域6)。第二个button控制Debugger(调试器。区域10)。
最后一个button显示Utilities(区域8和9)。
(6) Navigator(导航器)可以显示项目的不一样视图。顶部的图标控制视图的类型。在图2-5中,被选中的是目录图标,所以这个区域显示的是文件以及基于组(group)的项目导航器。其它图标是:项目符号导航器。用于在全项目范围内进行搜索的放大镜图标,用于查看构建问题的警告三角形图标。还有单元格測试导航器。调试导航器,断点列表。最后是日志视图。easy混淆的一点是,文件和组导航器中显示的文件结构和Finder中的文件结构。这二者可以是不一样的。在导航器中,组被显示为目录图标。但这些与Finder中的目录是不一样的。假设想让组和Finder目录保持一致。就需要在Finder中加入这些目录,并将源文件保存在正确的位置。
(7) Editor(编辑器)是你将在Xcode中花费大多数时间的地方。眼下,它显示的是源代码编辑器。
它还可以显示Interface Builder(界面生成器)、数据模型编辑器或执行时调试測量仪器。
(8) 工具区域显示了不一样种类的工具,包含文件信息、高速帮助(当前被选中的内容)、数据模型细节查看器、视图属性(比方颜色、button标题以及尺寸,包含屏幕大小和约束)。
(9) 工具区域的底部为库区域。
在此处你能够找到Interface Builder中构造应用程序所需的可拖曳组件、代码片断,甚至包含图片和其它媒体文件。注意在工具区域的底部可能仅仅看到含有库选择图标的一行。要展开库。请单击并向上拖动选择器工具栏。
(10) 调试区域包括三个主要部分。
顶部工具栏的左边包括用于在执行中暂停以及单步执行的控件。右側包括当前选中的文件。底部被切割成两个主要区域,一个用于检查变量,另一个用于打开控制台。在整本书中,你将学习到有关的不少其它详细知识,特别是在第14章“Instruments和调试”中。现在你对Xcode已有必定了解,该建立第一个应用程序了。
2.1.3 加入Hello World 标签
默认状况下。Xcode建立设置为执行在iPhone或iPad上的项目;当建立一个项目时。下一个项目使用前一次的设置。新项目一開始就包括你建立本身的应用程序所需的所有类和支持文件。
当执行一个新项目时。它会显示一个空白的白色屏幕。
在屏幕上看到Hello World的最快方法是往项目里加入标签。保持项目打开,运行下面步骤(见图2-6):
(1) 经过观察方案弹出菜单的右側(图2-5中的区域2),确认项目被设置为执行在模拟器中。假设右側没有显示“iPhone Retina(4-inch)”,单击方案弹出菜单的对应一边并且选择这一条。注意下拉菜单包括两部分:左側是方案弹窗,右側赞成你选择应用程序在哪里执行。你应仅仅将Hello World标签加入到iPhone故事板中。因而。应用程序在iPhone而不是iPad上执行是很重要的。
(2) 在Xcode项目导航器中单击并选择Main_iPhone.Storyboard文件。
Interface Builder会在编辑区域打开。在很罕见的状况下,Interface Builder并无打开。这时你要检查并肯定没有单击显示源码差别的第三个button(參见图2-5中的区域4)。假设源码控制button未选中,尝试选择一个.m或.h文件。而后再次选择故事板文件。假设仍是不管用,那么退出Xcode并从新启动。
(3) 在右下方的查找面板中。输入label并将这个标签对象拖曳到故事板的画布中。
(4) 在Xcode工做区左側在Utility区域中,在Attributes检查器的文本框内输入Hello World,将这个标签调整得好看一些。
图2-6显示这个标签在视图控制器(view controller)的顶部水平居中。
(5) 单击Runbutton,编译并执行Hello World应用程序。
你甚至不需要编写不论什么代码。在下一节。你将学习一些Objective-C基础知识,回到这个Hello World应用程序,并加入一些代码以练习Objective-C。
图2-7 所看到的的模拟器是全尺寸的、4 英寸Retina 显示屏,并且可能很大,特别是当在笔记本电脑屏幕上工做时。
可以使用Simulator Window | Scale 菜单更改显示屏的显示大小。
要成为熟练的iOS开发人员,你需要学习Objective-C。它是iOS和Mac的主要编程语言。Objective-C是一种强大的面向对象编程语言,赞成你利用苹果公司的Cocoa和Cocoa Touch框架,构建应用程序。在这一章,你将学习主要的Objective-C技巧,開始iOS编程。你将学到接口、方法以及不少其它其它知识。要想完毕这些学习。必须往Hello World应用程序中加入一些自定义的类,练习所学的知识。
然而。Objective-C 有太多的内容。咱们无法在这么小的一节里边全部涵盖,而这些没有涵盖的内容中,有一些对于编写产品级质量的应用程序是很重要的。
学习Objective-C的一份重要材料是Learning Objective-C 2.0:A Hands-on Guide to Objective-C for Mac and iOS Developers,2nd edition。做者是Robert Clair。
还可以使用Objective-C Programming:The Big Nerd
Ranch Guide,做者是Aaron Hillegass。这本书涵盖了更高级的知识。
它将C语言的组成部分与Smalltalk-80中产生的概念加以混合。
Smalltalk是最老和最有名的面向对象编程语言之中的一个。是由Xerox PARC开发的一种动态类型的交互式语言。Cox将Smalltalk的对象和消息传递系统层叠到标准C语言之上,从而构建了一种新的语言。这样的方法赞成应用程序猿在继续使用熟悉的C语言进行开发的同一时候。在那种语言内部使用基于对象的特性。在20世纪80年代后期,Objective-C被史蒂夫·乔布斯的计算机创业公司Next的NeXTStep操做系统选做主要开发语言。NeXTStep成为OS X直到iOS的精神和字面意义上的先驱。
Objective-C 2.0在2007年10月随着OS X Leopard一块儿公布,它引入了不少新特性,如属性和高速迭代。
在2010年,苹果公司更新了Objective-C语言,添加了Block这个C语言扩展。它提供了匿名函数。并赞成开发人员将Block做为对象进行处理(你将在第13章中学习不少其它内容)。在2011年夏,苹果公司引入了本身主动引用计数(Automatic Reference Counting,ARC),这个扩展大大简化了开发,它赞成应用程序猿将注意力集中在应用程序语义上,而不用操心内存管理(准确地说,ARC是编译时扩展而非语言扩展)。近期,Objective-C被扩展为支持字面量(定义静态对象的方式)以及索引(訪问数组和字典中元素的方式)。苹果公司持续改进Objective-C语
言。因此要注意新的iOS和Xcode更新版本号的公布说明。
面向对象编程使用了ANSI C没有的表特性。
对象是一种数据结构。它关联了一组公开声明的函数调用。Objective-C中的每个对象包括一些实例变量(instance variable)。也就是这样的数据结构的数据域(或字段)。还包括一些方法(method)。也就是该对象所能运行的函数调用。面向对象的代码使用这些对象、变量和方法来引入一些编程抽象来添加代码的可读性和可靠性。你有时候可能会看到实例变量被缩写为iVar,方法被称做消息(message)。
对象使用类(class)进行定义。
可以将类以为是决定对象终于长什么样的模板:怎样查看状态(实例变量),以及支持什么行为(消息)。
类自己一般不作太多事情。它们的主要用途是建立功能完整的对象。
对象被称做实例(instance),也就是基于类所提供模板的起做用的实体。之因此命名为“实例变量”。是因为它们仅仅存在于类的实例中。而不是类自身的内部。当往第4步的文本框中输入“Hello World”时。实际上也就设置了一个UILabel对象的text实例变量的值。
UILabel类自己并无text变量可被设置。
所有这些事情,以及建立标签实例的代码已经本身主动完毕了。
面向对象编程让你构建可重用的代码并从面向过程开发的正常控制流中解耦。
面向对象的应用程序环绕着对象和它们的方法所提供的本身定义数据结构来开发。iOS的Cocoa Touch以及Mac OS X的Cocoa提供了一个包括大量这样的本身定义对象的仓库。
Objective-C解锁了那个仓库,并赞成你用最少的努力和代码。基于苹果公司的工具箱建立有效而强大的应用程序。
iOS 的Cocoa Touch 中以NS 开头的类名。好比NSString 和NSArray,可追溯到NeXT公司。NS 表明NeXTStep,执行在NeXT 计算机之上的操做系统。苹果公司于1996 年收购了NeXT。
让大多数Objective-C的学习者感到困惑的一点,在于消息传递的语法——用于调用或执行类实例所实现的方法。不像函数调用时使用的“函数名(參数列表)”语法。传递消息给对象时要使用方括号。
一条消息让一个对象运行一个方法。
实现这种方法。产生一个结果,是这个对象的职责。方括号里的第一个元素是消息的接收者,也就是实现这种方法的对象;第二个元素是方法名称以及可能会有的传给那个方法的一些參数。它们一块儿定义了你想要发送的消息。在C语言中。你可能会这么写:
<span style="font-size:14px;">printCarInfo(); // This function prints out the info on the default car</span>
但是在Objective-C里,你这么写:
<span style="font-size:14px;">[self printCarInfo]; // This method prints out the info on the default car</span>
在某些语言中,你可能看到this.printCarInfo()。
在Objective-C中,self表明当前对象,大致上与this相似。
在其它语言中,你可能会使用相似someOtherObject.printCarInfo()的代码。在还有一个对象上调用方法,若是someOtherObject拥printCarInfo()函数。在Objective-C中,可以使用下面代码:
<span style="font-size:14px;">[someOtherObject printCarInfo]; // This method prints out the info on the default car</span>
除了Objective-C的类型以外,方法中的类型可以使用标准C语言中相同的类型。
不像函数调用。Objective-C限制了可以实现和调用方法的主体。方法属于类。并且类的接口定义了哪些方法是公开的。或者说。是面向外部世界的声明。
当函数包括一个或多个參数时,代码就開始看起来不同了。假定必须将汽车对象myCar 传递给printCarInfo()函数。
在C语言中,你会这么写:
printCarInfo(myCar); // Print the info from the myCar object
在Objective-C中,你会这么写:
<span style="font-size:14px;">[self printCarInfo:myCar]; // Objective-C equivalent, but with poor method name</span>
<span style="font-size:14px;">[self printCarInfoWithCar:myCar]; // More clear as to which car it will print out</span>
在C语言中,你会这么写:
<span style="font-size:14px;">printCarInfo(myCar,10); // Print the info using a font size of 10</span>
<span style="font-size:14px;">[self printCarInfoWithCar:myCar withFontSize:10]; // Print using a font size of 10</span>
让咱们再深刻一步。现在假定你有三个參数:汽车对象,信息的字号。还有表示文字是否需要粗体显示的布尔值。
在C语言中,你会使用例如如下代码:
<span style="font-size:14px;">printCarInfo(myCar, 10, 1); // Using 1 to represent the value of true in C</span>
<span style="font-size:14px;">[self printCarInfoWithCar:myCar withFontSize:10 shouldBoldText:YES];</span>
方法名与參数依次交错放置,能有效地让Objective-C 的消息传递变得easy阅读和理解。在C 语言和其它语言中,你不得不时常參考函数定义以肯定每个參数是什么以及參数的顺序。在Objective-C 中。这些都很是清晰。就在你面前。在使用一些含有5 个或不少其它个參数的UIKit 方法调用时,你会特别明显地体会到这一点。
在C语言中。你会使用下面代码:
<span style="font-size:14px;">float mySpeed = calculateSpeed(100,10); // returns the speed based on distance / time</span>
<span style="font-size:14px;">float mySpeed = [self calculateSpeedWithDistance:100 time:10];</span>
要看到不少其它信息,
请看https://developer.apple.com/library/iOS/#referencelibrary/GettingStarted/RoadMapiOS/Languages/
WritingObjective-CCode/WriteObjective-CCode/WriteObjective-CCode/html。
方法可以訪问类中定义的所有东西。换句话说,可以訪问实例变量以及随意类实例中实现的方法。
在这样的意义上。方法如何执行对于调用者对象是透明的。
某个特定方法的实现代码甚至是整个类可以全然改变而不需要将别的不论什么地方改动。
这在升级或替换应用程序中的特性时是很实用的:多是让它们更有效地更新新的硬件特性,甚至完全替换通讯的处理方式。
2.2.2 类和对象
对象是面向对象编程的核心。
可以经过构建类定义对象。类即为建立对象的模板。
在Objective-C中。类定义描写叙述了怎样构建属于这个类的新对象。好比,要建立汽车对象。需要定义Car类。并且在需要时用这个类建立新的对象。
与C语言相似。在Objective-C中实现类需要分两处进行:头文件和实现文件。头文件规定了外部世界怎样与这个类交互:实例变量及其类型。方法及其參数和返回值类型。就像契约,头文件承诺类的实例怎样与别的对象对接。
实现文件的内容即为类怎样提供实例变量的值,以及方法被调用时怎样响应。除了头文件里定义的公有变量和方法。在实现文件里也可以定义变量与方法,并且实现文件通常会包括私有的变量和方法。
每个类使用标准的C.h约定。在头文件里列出实例变量和方法。好比。你可能会像代码清单2-1那样定义SimpleCar对象。此处所看到的的Car.h头文件包括声明SimpleCar对象结构的接口。
代码清单2-1 声明SimpleCar 接口(SimpleCar.h)
<span style="font-size:14px;">#import <Foundation/Foundation.h> @interface SimpleCar : NSObject { NSString *_make; NSString *_model; int _year; } @property float fuelAmount; - (void)configureCarWithMake:(NSString*)make model:(NSString*)model year:(int)year; - (void)printCarInfo; - (int)year; - (NSString*)make; - (NSString*)model; @end</span>
在Objective-C中。你会使用identifiersLikeThis而不是identifiers_like_this。
类名首字母大写,而其它的名称首字母小写。
可以在代码清单2-1中看到。类名SimpleCar以大写首字母开头。实例变量fuelAmount採用驼峰式命名法。但是以小写字母开头。
示比例如如下:
- (void) printCarInfo
在Objective-C中。@符号用在特定的一些keyword中。此处展现的两个元素(@interface和@end)划分了类接口定义的开头与结尾。类定义描写叙述了一个含有5个方法与4个实例变量的对象。
在这4个变量中,仅仅有fuelAmount是公有的(public),意思是它在SimpleCar类的外部可以使用。
花括号里的其它三个变量仅仅能被SimpleCar及其子类使用。这三个变量也可以定义在.m实现文件里,但那样的话它们将仅仅对SimpleCar类可见。假设想让子类(比方ElectricCar)共享这些变量的话,这就成问题了。
私有变量中有两个(_make和_model)是字符串类型。Objective-C一般使用基于NSString对象的类,而不是基于字节(byte)的用char *声明类型的C字符串。正如在这本书中处处可见的。NSString提供的功能远远多于C字符串。对于这个类。可以找出字符串的长度。查找和替换子字符串,颠倒字符串。提取文件扩展名。以及不少其它。这些特性都被编写到iOS(和Mac OS)的对象库里。
私有的_year变量和公有的fuelAmount变量都属于简单类型。前者是int类型,后者是float类型。
使用前置的下划线字符(_)是Objective-C中区分实例变量和getter方法的通常作法。使用x=_year是直接从实例变量得到值,而x=[self year]是调用getter方法-(int)year。可以作一些必要的计算或者在返回以前暂时计算值。setter方法相似getter,仅仅只是是用于设置实例变量的值。
反复一下,使用setter可以运行一些额外的工做,比方更新屏幕上的计数器。
你将在下面内容中以及全书中学习建立与使用getter和setter。
第一个公有方法例如如下所看到的:
<span style="font-size:14px;">configureCarWithMake:model:year:</span>
<span style="font-size:14px;">[myCar configureWithMake:c1 model:c2 year:i];</span>
每个方法都有返回參数。printCarInfo返回void,year返回int。而make和model都返回NSString*类型。与C同样。这些表明方法返回的数据类型。
void表明这种方法不返回不论什么东西。
在C语言中。与printCarInfo和year方法等价的函数声明是void printCarInfo()和int year()。
使用Objective-C的“方法名分散在參数中”的方式。对新应用程序猿来讲可能看起来很是奇怪,但是很是快就会变成你很是喜好的特性。
当方法名告诉你什么什么參数该在哪儿时,就不需要推測该传递什么參数。
你会在iOS编程中屡次见到这个。特别是当使用respondsToSelector:这种方法调用时,这种方法让你在执行时检查对象可否响应特定的消息。
注意代码清单2-1中的头文件使用#import载入头文件,而不是#include。当导入(import)头文件时,Objective-C本身主动跳过已经被加入了的文件。
所以可以往各类各样的头文件里加入@import指令,而不会有不论什么损失。
1. 定义实现文件
.h文件告诉外界怎样与类的对象交互。.m文件或实现文件包括了赋予对象生命与力量的代码。代码清单2-2展现了SimpleCar类的某种实现。
代码清单2-2 SimpleCar 类的实现文件(SimpleCar.m)
<span style="font-size:14px;">#import "SimpleCar.h" @implementation SimpleCar - (void)configureCarWithMake:(NSString*)make model:(NSString*)model year:(int)year { _make = [make copy]; _model = [model copy]; _year = year; } - (void)printCarInfo { NSLog(@"--SimpleCar-- Make: %@ - Model: %@ - Year: %d - Fuel: %0.2f", _make, _model, _year, [self fuelAmount]); } - (int)year { return _year; } - (NSString*)make { return [_make copy]; } - (NSString*)model { return [_model copy]; } @end</span>
configureCarWithMake:model:year为每个私有实例变量设置值。
除了fuelAmount以外,不能为当前的汽车对象单独设置某个值。
使用訪问器方法(access method)读取不论什么单个元素的值是可行的。好比代码清单2-2底部定义的-(int)year。
因为头文件为fuelAmount使用了@property,因此setter和getter方法,以及下划线版本号的变量已经为你建立好了。
你将在本章后边的“2.3.2节“属性”中看到相关不少其它内容。
第一个方法设置三个非公有实例变量的值。printCarInfo将所有实例变量的当前值打印到日志中。最后三个方法是私有实例变量的getter方法。
你可能注意到的一点是。配置方法和getter方法都与字符串的副本打交道。这是一种广泛的防护性实践。避免代码意外地改动字符串的值。但当你意识到每个变量是一个指向NSString对象的指针时,就会理解了。假设将这个指针赋值给字符串參数。那么它将与这个字符串的拥有者指向一样的内存位置。假设原始拥有者改变了字符串的值。汽车对象会获得新的值,因为它指向一样的内存地址。
赋值并返回字符串的副本,会获得不一样内存区域的新对象。
这些副本可以被改动。而不会改变当前汽车对象的make和model。注意。你惟一需要注意的是,这仅仅适用于长期存在的实例变量。暂时字符串和对象不需要被拷贝。
2. 建立对象
你已经学到,类定义一个或不少其它个对象。
类在执行时怎样变成对象?要建立对象。你需要让类为新对象分配足够的内存,并且返回一个指向这块内存的指针。
而后让新对象初始化本身。你经过调用alloc方法处理内存分配,并且初始化发生在调用init时。假设正在建立SimpleCar对象。那么可以使用例如如下两行代码:
<span style="font-size:14px;">SimpleCar *myCar = [SimpleCar alloc]; [myCar init];</span>
一组嵌套消息的返回值来自最后一条消息。
在以前的代码中,第一行为汽车对象分配内存并且返回一个指向那个对象的指针。第二行拿到了分配好的汽车对象并且加以初始化。init方法返回初始化以后的对象。
因为myCar已经指向正确的对象,而第二行不需要使用这个返回值。使用嵌套可以将这两行缩短为一行:
<span style="font-size:14px;">SimpleCar *myCar = [[SimpleCar alloc] init];</span>
你在此处看到的“分配后紧跟init”的模式是实例化对象的最多见方式。SimpleCar类指向alloc方法。
它分配足够存储类定义中所有实例变量的新内存块,将所有实例变量清理为0或nil,并返回指向这个内存块開始位置的指针。
新分配的块是实例,表明内存中单独的对象。
某些类,比方视图。使用本身定义的初始化方法,好比initWithFrame:。如你在本章后边将看到的,可以编写本身定义的初始化方法。比方initWithMake:model:year:fuelAmount:。这种紧随内存分配进行初始化的模式普遍存在。
你在内存中建立这个对象。而后预设所有关键的实例变量。
3. 继承方法
对象在继承实例变量的同一时候会继承方法实现。SimpleCar是一种NSObject,所以所有NSObject能够响应的消息Simple Car也能够响应。这就是myCar可使用alloc和init进行初始化的缘由。
这两个方法是由NSObject定义的,可用于建立和初始化不论什么SimpleCar实例。因为它继承自NSObject类。Objective-C中的所有类都终于继承自NSObject,NSObject处于它们继承树的顶端。
提示:继承的方法
假设查看Hello World 项目的AppDelegate 或ViewController 类的.h 文件。就会看到AppDelegate 继承自UIResponder,并且ViewController 继承自UIViewController。而UIViewController 接下来继承自UIResponder。
假设选择并右击UIResponder,选择Jump to Definition(跳到定义),那么Xcode 会为你显示UIResponder 的声明。在那里可以看到,它也继承自NSObject。
做为还有一个演示样例,当应用程序中有数组时,你有可能会使用NSArray或NSMutableArray—— 一种赞成你增删元素的NSArray。所有数组方法都可以被可改动的数组以及它们的子类使用。可以统计数组中元素的个数。依据索引数字取出对象等。
警告:
有些类对“子类化”(subclassing)并不友好。
它们是做为类簇(class cluster)来实现的。也就是,类自身会依据一些标准,建立一些其它类的对象。NSArray 和NSString 都是类簇的演示样例。它们以最能有效利用内存的方式。使用不一样的类建立对象。所有这些类都在文档中作了清晰标记。在子类化系统类以前,要细致检查一下其是否为类簇。
子类可以实现与超类具备一样选择器的方法。
在子类对象上调用此方法将运行新的方法。这取决于这种方法怎样实现,要么特殊化。要么覆盖超类行为。
特殊化(Specializing)的意思是(运行新逻辑的同一时候)还让超类方法运行,方法是将消息发送到super对象,super是表明超类的特殊标识符。覆盖(Overriding)的意思是并不将消息发送到超类,超类的行为从不运行。一个不错的演示样例就是初始化方法。需要确保继承链中的每一个类都有计划初始化自身。
只是方法仅仅需要记得调用自身的超类。
初始化方法老是包括下面形式的一行:
<span style="font-size:14px;">self = [super init];</span>
4. 指向对象
你已经了解到,使用类建立对象是很easy的事情。当拥有一个新建立(分配)和初始化的对象时。下一步就是引用并使用这个新对象。在Objective-C中,你使用*字符表示变量是指向对象的指针,这在代码清单2-2中的_make和_model变量声明处可以看到。_make和_model变量都指向对象(NSString)并且变量名前边必须有*。
其它变量属于原始类型,不是对象。
变量自身,或更准确说内存地址,保存的是值而不是对象的地址。_year变量是原始类型(int)的演示样例,所以不需要*字符。
当向对象发送消息时。要去掉*字符。在下面代码片断中。myCar对象被建立为SimpleCar类的实例,并且printCarInfo方法被调用:
<span style="font-size:14px;">SimpleCar *myCar = [[SimpleCar alloc] init]; [myCar printCarInfo];</span>
<span style="font-size:14px;">SimpleCar *sameCar = myCar;</span>
<span style="font-size:14px;">id sameCar = myCar;</span>
使用与建立HelloWorld项目时一样的步骤:
(1) 在Xcode中,选择File | New | Project(或按下Cmd+Shift+N组合键)。
(2) 选择iOS Single View Application模板,与你建立HelloWorld项目时选中的模板一样。
(3) 在下一个面板中,在应用程序的名称文本框中输入CarValet。
确保Devices被设置为Universal。Organization和Company Identifier文本框中应该已经填入你在建立HelloWorld项目时填写的内容。
假设需要的话可以改动这些内容,而后单击Nextbutton。
(4) 保存项目,Xcode就会在新窗体中打开这个项目。假设已经打开一个项目。那么在Save面板的底部可能会有Add To(加入到)选项。假设是这种话。确保选中的是相似“Don’t Add to Any Project or Workspace”(不要加入到不论什么项目或工做区)的选项。
注意:
这个演示样例应用程序中的代码——以及本章中其它演示样例的代码——都可以在本书的演示样例代码中找到。參见前言。了解从GitHub 下载本书演示样例代码的具体方法。
与HelloWrold 全然同样。 CarValet 应用程序已经带有Xcode 模板提供的两个类:
AppDelegate和ViewController。现在添加Car类以表示简单的汽车:
(1) 首先,右击Navigation窗体中的CarValet应用程序目录,并选择New File。也可以在菜单中选择File | New | File或按下Cmd+N快捷键。
(2) 在新文件对话框中,选择iOS下的Cocoa Touch。而后选择Objective-C class。如图2-9所看到的,而后单击Nextbutton。
(4) 保存面板的底部有一块区域可用于指定Target成员。此时,重要的是确保CarValet被选中。如图2-11所看到的。在确认该复选框已被选中后,单击Create,Xcode会建立Car类。并且将它放到你的项目中。
编辑Car.h头文件,使它与代码清单2-3保持一致。
代码清单2-3 Car.h 头文件 // Car.h // CarValet #import <Foundation/Foundation.h> // 1 @interface Car : NSObject { // 2 int _year; // 3 NSString *_make; // 4 NSString *_model; // 5 float _fuelAmount; // 6 } - (id)initWithMake:(NSString *)make // 7 model:(NSString *)model year:(int)year fuelAmount:(float)fuelAmount; - (void)printCarInfo; // 8 - (float)fuelAmount; // 9 - (void)setFuelAmount:(float)fuelAmount; - (int)year; // 10 - (NSString*)make; - (NSString*)model; @end
双斜杠可用于单行或行内凝视。可以使用斜杠和星号的组合来包围凝视块——也就是多行凝视:
// this is a one line comment // and so is this, even though it follows the last one [MyObject doSomething]; // and this is an end of line comment /* And finally a lot of comments started by a forward-slash and asterisk that can include lots of lines and ends with an asterisk then forward-slash. */
第一个非凝视行导入了Foundation框架。这个iOS和Mac OS家中的耕田老牛。在Foundation框架中。可以找到各类东西,从数组和日期到谓词,从URL网络链接到JSON处理,还有最最重要的对象NSObject。
接下来是Car类的@interface声明。
经过:NSObject标记,可以了解到Car类继承自NSObject类。
@interface和@end之间的语句对Car类进行了定义。在@interface声明的花括号里,可以看到4个实例变量。用于保存汽车对象所需要的信息。这些方法定义了怎样给汽车对象发送消息。
如下描写叙述了代码清单2-3中带数字凝视的代码行中所发生的事情:
(2) 定义Car对象(NSObject的子类)的接口。
(3) _year是汽车的生产年份,存为一个整体对象,这是一种非对象的原始类型。
(4) _make是汽车的品牌,存为一个NSString对象。
(5) _model是汽车的型号。存为还有一个NSString对象。
(6) _fuel是汽车油箱里的燃料,存为浮点值。
(7) initWithMake:model:year:fuelAmount:方法初始化新分配的对象。并设置汽车的品牌、型号和年份,以及油箱中的燃料。
这就是前面所说的本身定义init方法。
(8) printCarInfo方法向调试控制台打印出汽车的信息。
(9) fuelAmount和setFuelAmount这一对方法,为_fuelAmount实例变量的getter和setter方法。
(10) 剩下的三个方法是其它私有实例变量的getter方法。
initWithMake:model:year:fuelAmount:方法是关于方法名称和參数怎样交替放置的清晰示例。这4个參数分别接收NSString *、NSString *、int和float类型的值。
注意这两个方法前面的连字符,它代表这些方法由对象实例进行实现。好比,要调用[myCar printCarInfo]而不是[CarprintCarInfo]。
后者会将消息发送到Car类而不是实际的Car对象。你会在本书后面看到类方法和实例方法的对照差异(类方法由“+”而不是“-”表示),只是,更加完整的讨论超出了本书范围。
方法调用可以很是长。好比,如下的方法调用初始化iOS的UIAlert对象:
initWithTitle:message:delegate:cancelButtonTitle:otherButtonTitles:
使用托付对象是iOS中的还有一常见模式,它提供了一种方法。经过让两个对象使用定义好的一组消息(称为protocal(协议)来进行通讯。这些消息是协议提供者和托付对象之间的一种契约。本质上,托付对象承诺实现这些消息,而提供者承诺正确地使用它们。协议与类是分开定义的;它们是不一样的事物。不论什么类可以选择以托付对象或实现者的身份使用协议。UIAlert採用这样的模式以通知托付对象。用户单击了哪一个button。当阅读这本书时,你会看到系统对象中更加复杂地使用托付模式的演示样例。你还会在本身构建的一些对象里实现这样的模式——也就是建立协议并向对象加入托付对象代码。
在Xcode 中,组合键Ctrl+Cmd+向上箭头可以让你移到下一个配对文件。而Ctrl+Cmd+向下箭头可以让你移到前一个配对文件。
这对组合键使得在头文件和实现文件之间切换变得很easy。
实现的源码一般包括在.m文件里(m可以表明implementation或method)。顾名思义,类的方法文件提供的是方法的实现。以及这个类怎样运行它的功能。
在更大的类中。除了会在.h文件里定义方法外,你可能会实现其它的获得支持的非公有方法。
不像公有方法。不需要在定义私有方法前声明它们。编译器足够聪明,甚至在使用这些方法的代码的后边才实现这些方法的状况下。编译器也可以识别私有方法在哪里。
在通读本书的过程当中。你会看到关于这一点的不少其它内容。
此外。可以声明仅对这个对象可见的局部实例变量。可以在@implement语句如下的花括号中实现这一点。
好比。可以加入局部的isAL emon标记变量:
@implementation Car { BOOL isALemon; }
代码清单2-4 Car.m 实现文件 // Car.m // CarVale #import "Car.h" @implementation Car - (id)init { self = [super init]; // 1 if(self != nil) { // 2 _year = 1900; // 3 _fuelAmount = 0.0f; // 4 } return self; // 5 }
这保证了NSObject要求的不论什么初始化逻辑。在特定于Car类的初始化逻辑以前运行完成。
(2) 检查一下。确保self实际已经初始化。假设这种话,这个对象的剩余部分将被建立。
(3) _year实例变量默认被设置为1900。
(4) _fuelAmount默认被设置为0.0f。虽然不是严格必要的,但在数字末尾包括f可以告诉编译器这是float值。而不是其它类型的浮点值。
(5) self的值被返回。注意返回的内容依赖于第2步的检查。假设超类返回nil,此处返回nil,不然会返回现已初始化的Car对象。
到此为止,Car对象已经被初始化,但仍然不能响应以initWithMake:开头的本身定义初始化方法和printCarInfo方法,或者不论什么其它方法调用。假设试图调用那些方法,你将遇到执行时应用程序崩溃,同一时候会在Debug区域的Console框中显示一条消息“unrecognized selector sentto instance”。
向nil 发送一条消息,并发送一条未识别的消息(选择器)
在Objective-C 中。发送消息和执行选择器这两个术语在本质上是同一个东西:在对象上调用方法。
虽然向nil 发送不论什么消息都是全然安全的。但向一个未实现这条消息的对象发送一条消息会致使执行时应用程序崩溃。这是Objective-C 刚開始学习的人常犯的错误。并且正如你将在本书后边看到的,存在一些方法,能够在发送消息以前检查对象或类可否响应这条消息(或这个选择器)。此外还要
注意,消息可以在继承树的不论什么地方实现。
也就是说,这条消息可以在特定的类中定义。也可以在这个对象继承的不论什么类中定义。
经过加入代码清单2-5的内容。添加下列两个方法:本身定义初始化方法以及printCarInfo方法。代码清单2-5 Car.m 实现initWithMake:model:year:fuelAmount:和printCarInfo 方法 - (id)initWithMake:(NSString *)make // 1 model:(NSString *)model year:(int)year fuelAmount:(float)fuelAmount { self = [super init]; // 2 if(self != nil) { // 3 _make = [make copy]; // 4 _model = [model copy]; _year = year; _fuelAmount = fuelAmount; } return self; // 5 } - (void)printCarInfo { if(!_make) return; // 6 if(!_model) return; NSLog(@"Car Make: %@", _make); // 7 NSLog(@"Car Model: %@", _model); NSLog(@"Car Year: %d", _year); NSLog(@"Number of Gallons in Tank: %0.2f", _fuelAmount); }
(3) 检查超类是否能够初始化这个对象。并且假设成功的话,初始化对象的剩余部分。
假设失败的话,self的值会是nil。
(4) 现在为Car对象设置所有实例变量。
(5) 到此为止,self要么是nil(假设超类初始化失败的话),要么是已经初始化的对象。
注意当初始化失败时,返回nil是正确的作法。
(6) 仅当Car定义了make和model时才会信息打印。
(7) 用NSLog在控制台打印值。
真实的左花括号使用惯例本书的代码清单让方法的左花括号({)紧随方法名。
这是为了节省空间,而不是典型的代码惯例。
依照惯例。花括号单独占一行。可以在Xcode 本身主动生成的代码中看到,如
ViewController.m。
为避免反复劳动,可以将第一个方法简化为例如如下代码:
- (id)init { return [self initWithMake:nil model:nil year:1900 fuelAmount:0.0f]; }
不论什么其它的初始化方法都可以调用这个完整的初始化方法。这是Objective-C的还有一常见模式。
initWithMake:model:year:fuelAmount:被称做基本初始化方法(base initializer),因为它是不论什么其它本身定义初始化方法都可以调用的最主要的一个。你将在整本书中看到这个模式的使用。
2. 訪问器
.h文件里声明的最后5个方法用于訪问汽车对象的信息。在这个演示样例中。也就是实例变量。在Car.m文件的底部加入代码清单2-6中的代码。
代码清单2-6 Car.m 文件里。訪问器方法的实现 - (float)fuelAmount { return _fuelAmount; // 1 } - (void)setFuelAmount:(float)fuelAmount{ _fuelAmount = fuelAmount; // 2 } - (int)year { // 3 return _year; } - (NSString*)make { return [_make copy]; } - (NSString*)model { return [_model copy]; }
(2) 将_fuelAmount实例变量的值设置为fuelAmount參数的值。
(3) 定义剩下的实例变量的getter方法。
每个getter方法都会返回相关的实例变量的值。一般。每个公有的实例变量都可以用getter和setter隐藏起来。变量本身可以在.m文件里声明,这样就仅仅有它们的汽车对象可以直接訪问这些变量。即便在这个简单的类里。这也意味着需要声明和定义8个额外的方法,以及大量的反复代码。
幸运的是。有一种更好的方法。
2.3.2 属性
属性让你定义实例变量,并让编译器建立訪问器方法——也就是说,可以訪问(get或set)变量或信息的方法。
编译器还可以生成下划线版本号的变量。声明属性是很是easy的:
@property float fuelAmount;
float _fuelAmount; - (float)fuelAmount; - (void)setFuelAmount:(float)fuelAmount;
不论什么非汽车对象都必须使用getter和setter方法。
变量和方法的实现是在编译时被加入的。而假设需要作一些特殊的事情,那么可以在.m文件里,实现特定的訪问器方法。这样,编译器就会使用你的方法替代。
遵循下面步骤更新Car对象以使用属性:
(1) 在编辑器中打开Car.m文件,并移除fuelAmount、setFuelAmount、year、make和model这些方法的实现。
(2) 打开Car.h并移除你在第1步中删除的那些方法的声明。
(3) 改动头文件里定义这些实例变量的部分,与下面内容同样(新代码以粗体显示,确保删掉下划线):
@interface Car : NSObject
@property int year;
@property NSString *make;
@property NSString *model;
@property float fuelAmount;
- (id)initWithMake:(NSString *)make
使用属性可能看起来是多余的。
毕竟代码清单2-3中的类定义代码定义了訪问器,并且下划线实例变量是私有的。
那么为何使用属性?原来,除了节省空间以外,使用属性比使用公开声明的方法还有不少其它优势。特别是封装和点表示法。
1. 封装
封装赞成在应用程序中的其它部分。包含不论什么使用对象的client,隐藏实现细节。对象的内部表示(实例变量)和行为(方法等)与对象向外界声明自身的方式隔离。仅仅要公开声明的细节保持不变,就可以自由地完全改动内部实现。属性提供的是,以一种结构良好并且受限地暴露对象状态和其它信息的方式。
然而,属性并不限于用做公有变量。它们在类的定义中也可发挥重要做用。属性赞成向类定义的其它部分加入时尚的前瞻性开发技术,包含延迟初始化(lazy loading)和缓存。
这就是类既可以做为属性的client,也可以做为属性提供者的缘由。
除了隐藏细节,封装还赞成在其它项目中重用一样代码。设计良好的Car 类不限于CarValet 应用程序。还可以在汽车收藏应用程序、零售库存跟踪应用程序,甚至在游戏中使用。随着你开发不少其它应用程序。细导致用封装可以收获一组可以缩短开发周期的即插即用的类。
类不只限于表示数据;它们还可以实现接口行为、本身定义视图,甚至实现server通讯。
2. 点表示法
点表示法赞成不用方括号。就可訪问对象信息。可以使用myCar.year取代[myCar year]调用,读取year实例变量的值。虽然这可能看起来像是直接訪问year实例变量。但实际上并非如此。
属性老是调用方法,而这些方法会訪问对象数据。由于属性依赖于方法将数据带到对象外部。所以并无破坏对象的封装。
使用my.year会调用[myCar year]。
经过使用属性,编译器会本身主动生成必需的訪问器方法。
假设需要作一些特殊的事情,好比检查远程Webserver。就在.m文件里定义year訪问器方法。而后编译器就会用你写的方法取代本身主动生成的方法。由于方法隐藏,属性简化了代码的显示和布局。好比,可以经过訪问属性,设置表的单元格文字。代码例如如下:
myTableViewCell.textLabel.text = @"Hello World";
[[myTableViewCell textLabel] setText:@"Hello World"];
对那些使用点訪问结构的应用程序猿来讲,记住点訪问结构是在调用方法而不是遍历对象层次结构是很重要的。
要练习使用点表示法。建议将printCarInfo的实现替换为代码清单2-7中的代码。
代码清单2-7 Car.m 中更新后的printCarInfo 实现 - (void)printCarInfo { if(self.make && self.model) { // 1 NSLog(@"Car Make: %@", self.make); // 2 NSLog(@"Car Model: %@", self.model); NSLog(@"Car Year: %d", self.year); NSLog(@"Number of Gallons in Tank: %0.2f", self.fuelAmount); } else { // 3 NSLog(@"Car undefined: no make or model specified."); } }
(2) 使用点标记法,将每个变量的值打印到日志。
(3) 假设没有make和model。就更新日志。
现在代码清单2-7中的代码更易于阅读,更不用说。此处还在汽车对象没有全然定义的情况下加入了一些打印日志的代码。
而且变量是对象级的元素而不是局部变量。这样显得更清晰。虽然对于这么短的方法,这样作彷佛可有可无。但可以想象。在需要通读更长代码的情况下,这样作的益处。
也可以对初始化方法作相似改动,虽然这是有风险的,尤为是你会使用本身定义訪问器。在本身定义訪问器中。使用点表示法多是最最危急的——參阅如下的旁注。“为什么使用下划线:不用点。不用訪问器。”为什么使用下划线:不用点。不用訪问器一种常见的错误来源。就是在属性的本身定义訪问器或可能调用本身定义訪问器的方法中使用点表示法。
举一个简单的演示样例:
- (void) setMake:(NSString*)newMake { if(![newMake isEqualToString:self.make) { self.make = newMake; } }
它检查新的make 值是否与旧的make 值一样,假设不一样就,将汽车对象的make 属性设为新值。但是此处有一些隐藏的问题。
self.make = newMake 可以解释为:
[self setMake:newMake];
这是一个无限循环——固然,更准确说。在应用程序崩溃以前是无限循环。
正确的作法是,在setter 方法中使用下划线版本号的变量。此处赋值变为:
_make = newMake;
代码清单2-8 ViewController.m 文件里的viewWillAppear:方法 - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; Car *myCar = [[Car alloc] init]; // 1 [myCar printCarInfo]; // 2 myCar.make = @"Ford"; // 3 myCar.model = @"Escape"; myCar.year = 2014; myCar.fuelAmount = 10.0f; [myCar printCarInfo]; // 4 Car *otherCar = [[Car alloc] initWithMake:@"Honda" // 5 model:@"Accord" year:2010 fuelAmount:12.5f]; [otherCar printCarInfo]; // 6 }
假设返回看看代码清单2-7,在printCarInfo方法中。你会看到检查make和model不为nil的if语句以及当它们为nil时的结果消息。
(3) make、model、year和fuelAmount被一一设置。
NSString值的双引號前都有个@前缀,浮点值的末尾有个f。
(4) printCarInfo第二次被调用,这一次make和model已设置。所以有关福特汽车的信息会被打印。
(5) 一个新的汽车对象被建立。并使用本身定义初始化方法设置值。
(6) 不像第(2)步中在简单init后调用printCarInfo的情形,此次调用会打印出本田汽车的信息,因为make和model都已被定义。
当执行这段代码时,会在控制台看到例如如下内容:
2013-07-02 08:35:44.267 CarValet[3820:a0b] Car undefined: no make or model specified.
2013-07-02 08:35:44.269 CarValet[3820:a0b] Car Make: Ford
2013-07-02 08:35:44.269 CarValet[3820:a0b] Car Model: Escape
2013-07-02 08:35:44.270 CarValet[3820:a0b] Car Year: 2014
2013-07-02 08:35:44.270 CarValet[3820:a0b] Number of Gallons in Tank: 10.00
2013-07-02 08:35:44.270 CarValet[3820:a0b] Car Make: Honda
2013-07-02 08:35:44.271 CarValet[3820:a0b] Car Model: Accord
2013-07-02 08:35:44.271 CarValet[3820:a0b] Car Year: 2010
2013-07-02 08:35:44.272 CarValet[3820:a0b] Number of Gallons in Tank: 12.50
2.4 属性:另外两个特性
当前版本号的CarValet应用程序是下一章需要的。
本节内容涵盖CarValet项目的两种不一样的变化形式。在本章的演示样例代码中也包括了这两种变化形式。
读者中有人可能会怀疑,在汽车对象建立后改动make、model和year是不是个好主意。
到眼下为止,你已经依照属性默认的读写配置使用它们。然而,将属性设置为仅仅读是很是easy的。
所有需要作的就是在声明属性时添加一点额外内容。属性声明的通常形式例如如下:
@property <(qualifier1, qualifier2, ...)> <type> <property_name>;
当中。限定符能让你改动很是多东西。包含将属性设置为仅仅读。并为内存管理指定不一样级别的对象所有权,甚至改动默认getter和setter方法的名称。
所以,假设想要不一样的行为,仅仅需要改动默认值就能够。
故而。建立仅仅读属性很easy,仅仅需要包括readonly限定符。
想将make、model和year设置为仅仅读,仅仅需要改动.h文件里的定义:
@property (readonly) int year;
@property (readonly) NSString *make;
@property (readonly) NSString *model;
改动成功以后,试着构建这个项目。你会在ViewController.m中获得三个错误,提示你在尝试设置仅仅读属性的值。现在。删除或凝视掉ViewController中设置仅仅读值的这些代码。
但是,假如你想实现这些属性在外部仅仅读。而在内部可以读写。也很是easy,因为可以在.m文件里又一次声明属性。可以经过在Car.m文件的@implementation语句以前添加例如如下代码。添加或覆盖类的接口定义:
@interface Car() @property (readwrite) int year; @property NSString *make; @property NSString *model; @end
- (void)shoutMake;
- (void)shoutMake { self.make = [self.make uppercaseString]; }
... qfuelAmount:12.5f]; [otherCar printCarInfo]; [otherCar shoutMake]; [otherCar printCarInfo]; }
本身定义getter 和setter
在还有一版本号中,有时你并不想使用编译器生成的默认getter和setter方法。好比。若是你想让self.fuelAmount返回值为公升而不是加仑,但仅当新属性showLiters值为YES时才会如此。
于是,你还会想要经过使用isShowLiters。以訪问这个新属性。
加入这个属性仅仅需要一行代码。在已有属性的下边加入下面代码:
@property (getter = isShowingLiters) BOOL showLiters;
你没有使用aCar.showLiters检查这个变量的值,而是使用aCar.isShowingLiters—— 一个更具描写叙述性的名称。设置这个值仍然使用
aCar.showLiters: if(aCar.isShowingLiters) { aCar.showLiters = NO; }
@property (setter = setTheFuelAmountTo:) float fuelAmount;
需要发送一条消息以调用本身定义setter方法。下列语句能运行:
[aCar setTheFuelAmountTo:20.0f];
aCar.setTheFuelAmountTo = 20.f;
- (float)fuelAmount { if(self.isShowingLiters) { return (_fuelAmount * 3.7854) ; } return _fuelAmount; }
在加入这种方法以后,你会注意到一个黄色的警告三角形。内容是“writable atomicproperty…”。这是什么?在此处,这个警告是全然正确的。
还有一个属性限定符是变量的原子性。
也就是说,是否在不论什么时刻仅有一个对象能訪问这个变量(原子或同步訪问),仍是多个对象可以同一时候在多个线程中訪问这个对象(非原子或非同步訪问)?
当在多线程环境中进行开发时,可使用atomic属性。确保赋值依照预期运行。当将一个对象设置为atomic时。编译器会在它被訪问或改动以前,加入本身主动为对象加锁的代码,并且在以后加入解锁代码。这就确保了不管是否有并发线程,设置或读取对象的值均可以被完整地运行。
然而,设置原子性(atomic)的成本高得离谱,并且有无限等待解锁的危急。
所有属性默认都是原子的,但是可以使用nonatomic属性限定符。这是一般更加安全且性能更好的替代者:
@property (nonatomic) NSString *make;
原子属性。用加锁/解锁行为。可确保对象从開始到结束获得完整更新,以后才会运行兴许的读取或变动行为,但是原子属性应当仅在需要时才使用。
有人提出,訪问器一般并不是加锁的合适地点。并不能确保线程安全。即便所有属性都是原子的。对象也可能会被设置为无效状态。
在实践中,大多数属性会被标记为nonatomic。处理可能的线程安全訪问的问题会用到其他机制。
更深刻的讨论可參见Learning Objective C 2.0,第2版,Robert Clair著。
改动Car.h中的fuelAmount属性声明,纠正错误(当改动时,可以顺手为所有属性添加nonatomic限定符,包含showLiters):
@property (nonatomic) float fuelAmount;
otherCar.showLiters = YES; [otherCar printCarInfo];
但是,这是本章末尾挑战题3的内容。
2.5 子类化和继承:挑战一下
随着汽车厂商持续改进使用燃料的方式,客户要求你能够表示一辆混合动力汽车。你的任务是建立继承自Car类的HibridCar类,并且加入一个方法,以返回直到汽车的电池电量和燃料耗尽汽车所跑的里程数。
能够将这种方法命名为-(float)milesUntilEmpty。
线索:
不要忘记跟踪每加仑英里数(Miles Per Gallon,MPG),这样可以计算汽车失去动力前的距离。好比。2013 款丰田普锐斯混合动力汽车可以达到42MPG。假设油箱里剩下10 加仑,那么理论上这辆车在油箱耗尽以前可以行驶402 英里。
思考几分钟。
想到解决方法了吗?
阅读如下的内容。看看怎样作。
继承和子类化
在Objective-C中。每个新类都派生自已有的类。代码清单2-3到2-7中描写叙述的Car类继承自NSObject——Objective-C类树的根类。每个子类加入或改动从父类(也称为超类)继承来的状态和行为。
Car类向它所继承的NSObject类中加入了一些实例变量和方法。
HibridCar类继承自Car类并添加了一些功能。根据混合动力汽车能够达到的MPG。计算汽车在燃料耗尽前能够行驶的距离。代码清单2-9和2-10展现了实现HibridCar类的一种可能方法。
本章的演示样例代码在目录“CarValet HybridCar”中包括代码清单2-9到2-11的项目。
代码清单2-9 HybridCar.h 头文件 // HybridCar.h // CarValet #import "Car.h" @interface HybridCar : Car @property (nonatomic) float milesPerGallon; - (float)milesUntilEmpty; - (id)initWithMake:(NSString *)make model:(NSString *)model year:(int)year fuelAmount :(float)fuelAmount MPG:(float)MPG; @end
这个属性存储了这辆混合动力汽车所能达到的每加仑英里数。milesUntilEmpty返回这辆汽车使用油箱当前含量(fuelAmount)可以行驶的千米数,自定义初始化方法添加了一个MPG參数以设置milesPerGallon。
代码清单2-10显示了HybridCar类可能的实现文件。 代码清单2-10 HybridCar.m 实现文件 // HybridCar.m // CarValet #import "HybridCar.h" @implementation HybridCar - (id)init { self = [super init] ; if (self != nil) { _milesPerGallon = 0.0f; } return self; } - (id)initWithMake:(NSString *)make model:(NSString *)model year:(int)year fuelAmount:(float)fuelAmount MPG:(float)MPG { self = [super initWithMake:make model:model year:year fuelAmount:fuelAmount]; if(self != nil) { _milesPerGallon = MPG; } return self; } - (void)printCarInfo { [super printCarInfo]; NSLog(@"Miles Per Gallon: %0.2f", self.milesPerGallon); if(self.milesPerGallon > 0.0f) { NSLog(@"Miles until empty: %0.2f", [self milesUntilEmpty]); } } - (float)milesUntilEmpty { return (self.fuelAmount * self.milesPerGallon); } @end
方法主体看上去会是这样:
return [self initWithMake:nil model:nil year:1900 fuelAmount:0.0f MPG:0.0f];
然而,这样作会产生隐藏的bug和/或维护成本。若是Car有若干子类,可能有混合动力型、电动型以及柴油型。甚至可能有子类的子类。如GasElectricHybrid、DieselElectricHybrid等。
将生产年份的默认值设置为0,从而能够很是easy地检測到是否存在忘记设置的值。假设每个子类的init方法都使用对应类的本身定义初始化方法。那就不得不改动每个子类中的值。忘记值的改动就会引入bug。
然而,假设init方法使用[super init],而后设置特定于子类的默认值,那么仅仅需要在一个地方进行改动就能够。
此处有个不错的演示样例,使用的initWithMake:model:year:fuelAmount:MPG:是子类本身定义初始化方法继承超类方法,并添加额外功能——详细地设置了milesPerGallon。首先,调用超类的initWithMake:model:year:fuelAmount:方法,初始化Car对象的属性。而后初始化HybridCar对象详细的值。详细化(specialization)是继承中很是强大的一部分,赞成每个类仅仅作它需要作的事情。当详细化与封装结合时,可以让一个类的开发人员仅仅聚焦于那个类,利用继承链上方的公有方法和属性以加速开发过程。
milesUntilEmpty方法用于计算在油箱耗尽前这辆汽车还能再跑多少英里。它使用一个简单的公式,将MPG乘以油箱中燃料的加仑数。
在真实的混合动力汽车中,算法将很是可能复杂得多。
最后一步是往CarValet应用程序的ViewController中添加一个HybridCar类的实例。你需要在ViewController.m文件的顶部加入#import "HybridCar.h"语句。而后将代码清单2-11中的内容加入到viewWillAppear:方法中。
代码清单2-11 加入一辆混合动力汽车到ViewController.m 文件里 HybridCar *myHybrid = [[HybridCar alloc] initWithMake:@"Toyota" model:@"Prius" year:2012 fuelAmount:8.3f MPG:42.0f]; [myHybrid printCarInfo];
假设执行CarValet应用程序,就将在调试控制台看到例如如下信息:
2013-07-03 08:39:45.458 CarValet[9186:a0b] Car Make: Toyota
2013-07-03 08:39:45.458 CarValet[9186:a0b] Car Model: Prius
2013-07-03 08:39:45.459 CarValet[9186:a0b] Car Year: 2012
2013-07-03 08:39:45.459 CarValet[9186:a0b] Number of Gallons in Tank: 8.30
2013-07-03 08:44:39.419 CarValet[9346:a0b] Miles Per Gallon: 42.00
2013-07-03 08:44:39.419 CarValet[9346:a0b] Miles until empty: 348.60
HybridCar类可以用多种不一样的方式进行定义和实现。花点时间建立一些变化形式。关键是要開始适应Objective-C的语法。可以经过完毕本章结尾的挑战题,进行不少其它练习。
当继续阅读本书,并且继续编写本身的应用程序时。Objective-C的语法和模式会变成你的次日性。
2.6 小结
本章提供无删节的、大信息量的对Xcode、Objective-C语法、对象、类、属性和继承的介绍,此外还让你使用Xcode练习建立项目,以及Objective-C概念。
本章仍是在你通读本书时,可以返回查看的一章。要想得到最大的价值。应该试着直接在Xcode中试验本章中讨论的所有内容。花时间摆弄演示样例应用程序。亲本身主动手得到的经验是获取iOS开发关键技能的最好方法。
学习Objective-C需要的不不过一章。假设要认真学习iOS编程。并且这些概念对你来讲还很是生疏。那么请考虑搜寻专门为这个平台的新手介绍这些技术的单一主题书籍。考虑Learning Objective-C 2.0:A Hands-on Guide to Objectiv-C for Mac and iOS Developers。第2版,Robert Clair著;或者Programming in Objective-C。第5版,Stephen G. Kochan著。对于Xcode,查找Xcode 4 Unleashed,第2版。Fritz F. Anderson著;或者Xcode 4 Developer Reference。Richard Went著,Richard Went为Xcode 5提供了更新的版本号(虽然Xcode 4版本号也是实用的)。
苹果公司也提供很好的 Objective-C 2.0 简单介绍。 位于http ://developer.apple.com/Mac/library/do cumentation/Cocoa/Conceptual/ObjectiveC/Introduction/introObjectiveC.html。
在本章。你建立了CarValet应用程序。在下一章,你将在这个项目的基础上构建并学习故事板,这是一种图形化地建立应用程序中所有屏幕的方式。并且将它们组装在一块儿。
你还将学习不少其它关于Objective-C和iOS编程中一些重要技术的知识。
2.7 挑战题
1. 更新printCarInfo方法。当仅仅有make是nil时打印“Car undefined:no make specified”。当仅仅有model为nil打印“Car undefined:no model specified”。假设二者都为nil。那么仍然打印“Car undefined:no make or model specified”。
可以经过调用initWithMake:model:year:fuelAmount:的变种,建立汽车測试对象以检查代码。
2. 建立Car类的子类ElectricCar。花点时间设计该类:实例变量,对已有方法的改动,以及不论什么独有的方法。当作完这些后,要么開始实现,要么继续阅读以了解一些可能的方式。设计ElectricCar类有若干方式。
一部分选项取决于你想从Car类继承多少东西。在描写叙述汽车的实例变量中。惟独fuel多是个问题。
电动汽车使用充电器。可以重用fuel并且伪装它就是charge(充电器)。那么你需要作的不过改动printCarInfo并且添加一个方法。用于打印剩余电量。还可以添加一个实例变量用于表示每千瓦时的行驶距离,并且使用那个值计算这辆汽车剩余的可行驶里程。
3. 在2.4节的“本身定义getter和setter”部分。可以看到怎样依据fuelAmount返回美国加仑或公升数。
但是printCarInfo老是打印加仑数。改动printCarInfo。使得在isShowingLiters为NO时打印加仑数,为YES时打印公升数。当printCarInfo使用公升时。改动Car类,使得可以用英国加仑、美国加仑和公升打印结果。
你需要找到一种方法,设置燃料用哪一种单位进行显示。假设使用BOOL类型,注意有可能多个BOOL变量同一时候被设置为YES。
《iOS开发全然上手——使用iOS 7和Xcode 5开发移动与平板应用》试读电子书免费提供,有需要的留下邮箱,一有空即发送给你们。
别忘啦顶哦!