标签:声明 speed 文本 ash eve 下拉 nsa iphone 头部
当学完本章时,你将获得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组合键来又一次显示)。
单击Create a New Project。
也能够在Xcode中选择File | New | Project(或按下Cmd+Shift+N组合键)。之后将会出现如图2-2所看到的的模板选择窗体。
默认情况下,模板选择窗体被嵌套在一个新的、名为工作区(workspace)的大窗体中。这个单独的工作区窗体中包括Xcode所有的编辑器和检查器(inspector)特性。
在图2-2所看到的的模板选择窗体中,选择左边的iOS菜单下的Application,然后选择SingleView Application(单视图应用程序),单击Nextbutton。
在这个窗体中,
请遵循下列步骤:假设没有公司标识符。苹果公司建议使用edu.self。
(3) 单击Nextbutton。
但假设指定前缀
为“MS”。那么Xcode会创建名为MSSlideMenuController的文件和类,从而避免潜在的冲突。在公司内部项目或开源项目中,这样的格式非经常见。但可读性并非最好。下面是一个滑动菜单类的各种不同的名称。这并非我实际编写过的一个类。是我想出来的。但最好还是试着观察一下列表中的这些类名条目,依照你浏览代码时的方式,看看哪一个更easy高速识别:
SlideMenuViewController
MSSlideMenuViewController
MS_SlideMenuViewController
msSlideMenuViewController
ms_slideMenuViewController
极其可能的是,所有大写且没有分隔符的版本号最难识别。小写的稍稍easy点。但难以识别的程度仅次于前者。更easy识别的是那些带有分隔符的版本号。你应当选择能与符号名称简易搭配使用的类前缀。
长远来看,你将节省大量的开发与调试时间。
上一个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可
用于停止正在执行的应用程序、正在执行的构建或已发起的其它动作。动作包含构建应用
程序、执行应用程序或是下载应用程序到设备上。左側button在
图中已被选中,仅仅显示一件东西。即主编辑区。中间的Assistant(辅助编辑)button将编辑区划分为两块,在右側会显示与主编辑器相关的文件。在右側通常情况下会显示头文件。最后一个
button用于显示源码差异,而且能够查看文件的不同版本号。这一点在使用源码仓库跟踪代
码变动时很实用。注意,不能同一时候显示辅助编辑和源码视图。
最后一个button显示
Utilities(区域8和9)。它还能够显示Interface Builder(界面生成器)、数据模型编辑器或执行时调试測量仪器。
在此处你可以找到Interface Builder中构造应用程序所需的
可拖曳组件、代码片段,甚至包含图片和其它媒体文件。注意在工具区域的底部可能仅仅看到含有库选择图标的一行。要展开库。请单击并向上拖动选择器工具栏。顶部工具栏的左边包括用于在执行中暂停以及单步执
行的控件。右側包括当前选中的文件。底部被切割成两个主要区域,一个用于检查变量,另一个用于打开控制台。在整本书中,你将学习到有关的很多其它详细知识,特别是在第14章“Instruments和调试”中。如今你对Xcode已有一定了解,该创建第一个应用程序了。当执行一个新项目时。它会显示一个空白的白色屏幕。
Interface Builder会在
编辑区域打开。在很罕见的情况下,Interface Builder并没有打开。这时你要检查并确定没有单击显示源码差异的第三个button(參见图2-5中的区域4)。假设源码控制button未选中,尝试选择一个.m或.h文件。然后再次选择故事板文件。假设还是无论用,那么退出Xcode并重新启动。图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中产生的概念加以混合。
在2010年,苹果公司更新了Objective-C语言,添加了Block这个C语言扩展。它
提供了匿名函数。并同意开发人员将Block作为对象进行处理(你将在第13章中学习很多其它内容)。在2011年夏,苹果公司引入了自己主动引用计数(Automatic Reference Counting,ARC),这个扩展大大简化了开发,它同意应用程序猿将注意力集中在应用程序语义上,而不用操心内存管理(准确地说,ARC是编译时扩展而非语言扩展)。近期,Objective-C被扩展为支持字面量(定义静态对象的方式)以及索引(訪问数组和字典中元素的方式)。苹果公司持续改进Objective-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类似。
<span style="font-size:14px;">[someOtherObject printCarInfo]; // This method prints out the info on the default car</span>
除了
Objective-C的类型之外,方法中的类型能够使用标准C语言中相同的类型。不像函数调用。
Objective-C限制了能够实现和调用方法的主体。方法属于类。而且类的接口定义了哪些方法是公开的。或者说。是面向外部世界的声明。在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对象结构的接口。
<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採用驼峰式命名法。可是以小写字母开头。示比例如以下:
在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类型。你将在下面内容
中以及全书中学习创建与使用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指令,而不会有不论什么损失。<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>
除了fuelAmount之外,
不能为当前的汽车对象单独设置某个值。使用訪问器方法(access method)读取不论什么单个元素的
值是可行的。比如代码清单2-2底部定义的-(int)year。由于头文件为fuelAmount使用了
@property,所以setter和getter方法,以及下划线版本号的变量已经为你创建好了。你将在本章
后边的“2.3.2节“属性”中看到相关很多其它内容。这些副本能够被改动。而不
会改变当前汽车对象的make和model。注意。你唯一须要注意的是,这仅仅适用于长期存在的实例变量。暂时字符串和对象不须要被拷贝。2. 创建对象
你已经学到,类定义一个或很多其它个对象。
类在执行时怎样变成对象?要创建对象。你需要让类为新对象分配足够的内存,而且返回一个指向这块内存的指针。
然后让新对象初始化自己。你通过调用alloc方法处理内存分配,而且初始化发生在调用init时。假设正在创建SimpleCar对象。那么能够使用例如以下两行代码:
<span style="font-size:14px;">SimpleCar *myCar = [SimpleCar alloc]; [myCar init];</span>
一组嵌套消息的返
回值来自最后一条消息。由于myCar已
经指向正确的对象,而第二行不须要使用这个返回值。使用嵌套能够将这两行缩短为一行:<span style="font-size:14px;">SimpleCar *myCar = [[SimpleCar alloc] init];</span>
它分配足够存储类定义中全部实例变量的新内存块,将全部实例变量清理为0
或nil,并返回指向这个内存块開始位置的指针。新分配的块是实例,代表内存中单独的对象。
某些类,比方视图。使用自己定义的初始化方法,比如initWithFrame:。如你在本章后边将看到的,能够编写自己定义的初始化方法。比方initWithMake:model:year:fuelAmount:。这种紧随内存分配进行初始化的模式广泛存在。你在内存中创建这个对象。然后预设全部关键
的实例变量。这两个方法是由NSObject定义的,可用于创建和初始化不论什么SimpleCar实例。由于它
继承自NSObject类。Objective-C中的全部类都终于继承自NSObject,NSObject处于它们继承树的顶端。假设选择并右击UIResponder,选择Jump to Definition(跳到定义),那么Xcode 会为你显示UIResponder 的声明。在那里能够看到,它也继承自NSObject。
它们是作为类簇(class cluster)来实现的。也就是,类自身会依据一些标准,创建一些其它类的对象。NSArray 和NSString 都是类簇的演示样例。它们以最能有效利用内存的方式。使用不同的类创建对象。全部这些类都在文档中做了清晰标记。在子类化系统类之前,要细致检查一下其是否为类簇。
在子类对象上调用此方法将运行新的方法。
这取决于这种方法怎样实现,要么特殊化。要么覆盖超类行为。特殊化(Specializing)的意思
是(运行新逻辑的同一时候)还让超类方法运行,方法是将消息发送到super对象,super是代表超类的特殊标识符。覆盖(Overriding)的意思是并不将消息发送到超类,超类的行为从不运行。一个不错的演示样例就是初始化方法。须要确保继承链中的每个类都有计划初始化自身。只是
方法仅仅须要记得调用自身的超类。初始化方法总是包括下面形式的一行:
<span style="font-size:14px;">self = [super init];</span>
变量自身,或更准确说内存地址,保存的是值而不
是对象的地址。_year变量是原始类型(int)的演示样例,因此不须要*字符。<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项目时同样的步骤:
确保Devices被设置为
Universal。Organization和Company Identifier文本框中应该已经填入你在创建HelloWorld项目时填写的内容。假设须要的话能够改动这些内容,然后单击Nextbutton。
(2) 在新文件对话框中,选择iOS下的Cocoa Touch。然后选择Objective-C class。如图2-9所看到的,然后单击Nextbutton。
编辑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中带数字凝视的代码行中所发生的事情:这就是前面所说的自己定义init方法。
注意这两个方法前面的
连字符,它表明这些方法由对象实例进行实现。比如,要调用[myCar printCarInfo]而不是[CarprintCarInfo]。后者会将消息发送到Car类而不是实际的Car对象。你会在本书后面看到类方法
和实例方法的对照差别(类方法由“+”而不是“-”表示),只是,更加完整的讨论超出了本书范围。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类的初始化逻辑之前运行完成。在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); }
注
意当初始化失败时,返回nil是正确的做法。这是为了节省空间,而不是典型的
代码惯例。依照惯例。花括号单独占一行。能够在Xcode 自己主动生成的代码中看到,如
ViewController.m。
- (id)init { return [self initWithMake:nil model:nil year:1900 fuelAmount:0.0f]; }
不论什么其它的初始化方法都能够调用这个完整的初
始化方法。这是Objective-C的还有一常见模式。initWithMake:model:year:fuelAmount:被称作基
本初始化方法(base initializer),由于它是不论什么其它自己定义初始化方法都能够调用的最主要的一个。你将在整本书中看到这个模式的使用。.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]; }
每一个getter方法都会返回相关的实例变量的值。
通常。每一个公有的实例变量都能够用getter和setter隐藏起来。变量自己能够在.m文件里声明,这样就仅仅有它们的汽车对象能够直接訪问这些变量。即使在这个简单的类里。这也意味着须要声明和定义8个额外的方法,以及大量的反复代码。幸运的是。有一种更好的方法。
编译器还能够生成下划线版本号的变量。声明属性是非常easy的:
@property float fuelAmount;
float _fuelAmount; - (float)fuelAmount; - (void)setFuelAmount:(float)fuelAmount;
不论什么非汽车对象都必须使用getter和setter方法。
变
量和方法的实现是在编译时被加入的。而假设须要做一些特殊的事情,那么能够在.m文件里,实现特定的訪问器方法。这样,编译器就会使用你的方法替代。毕竟代码清单2-3中的类定义代码定义了訪问器,而且下
划线实例变量是私有的。那么为什么使用属性?原来,除了节省空间之外,使用属性比使用
公开声明的方法还有很多其它优点。特别是封装和点表示法。这就
是类既能够作为属性的client,也能够作为属性提供者的原因。类不仅限于表示数据;它们还能够实现接口行为、自己定义视图,甚至实现server通信。
属性总是调用方法,
而这些方法会訪问对象数据。因为属性依赖于方法将数据带到对象外部。因此并没有破坏对象的封装。通过使用属性,编译器会自己主动生成必需的訪问器方法。
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."); } }
并且变量是对象级的元素而不是局部变量。这样显得更清
晰。虽然对于这么短的方法,这样做似乎无关紧要。但能够想象。在须要通读更长代码的情况下,这样做的益处。也能够对初始化方法做类似改动,虽然这是有风险的,尤其是你会使用自己定义訪问器。在自己定义訪问器中。使用点表示法可能是最最危急的——參阅以下的旁注。“为何使用下划线:不用点。不用訪问器。”为何使用下划线:不用点。不用訪问器一种常见的错误来源。就是在属性的自己定义訪问器或可能调用自己定义訪问器的方法中使用点表示法。
举一个简单的演示样例:
- (void) setMake:(NSString*)newMake { if(![newMake isEqualToString:self.make) { self.make = newMake; } }
它检查新的make 值是否与旧的make 值同样,
假设不同就,将汽车对象的make 属性设为新值。可是此处有一些隐藏的问题。[self setMake:newMake];
这
是一个无限循环——当然,更准确说。在应用程序崩溃之前是无限循环。_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时的结果消息。NSString值的双引號前都有个@前缀,
浮点值的末尾有个f。当前版本号的CarValet应用程序是下一章须要的。
本节内容涵盖CarValet项目的两种不同的变化形式。在本章的演示样例代码中也包括了这两种变化形式。
读者中有人可能会怀疑,在汽车对象创建后改动make、model和year是否是个好主意。
到眼下为止,你已经依照属性默认的读写配置使用它们。然而,将属性设置为仅仅读是非常easy的。
全部须要做的就是在声明属性时添加一点额外内容。属性声明的一般形式例如以下:
@property <(qualifier1, qualifier2, ...)> <type> <property_name>;
当中。限定符能让你改动非常多东西。包含将属性设置为仅仅读。并为内存管
理指定不同级别的对象全部权,甚至改动默认getter和setter方法的名称。因此,假设想要不同
的行为,仅仅须要改动默认值就可以。想将make、model和year设
置为仅仅读,仅仅须要改动.h文件里的定义:@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]; }
@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; }
还有一个属性限定符是变量的原子
性。也就是说,是否在不论什么时刻仅有一个对象能訪问这个变量(原子或同步訪问),还是多个
对象能够同一时候在多个线程中訪问这个对象(非原子或非同步訪问)?然而,设置原子性(atomic)的成本高得离谱,而且有无限等待解锁的危急。
@property (nonatomic) NSString *make;
原子属性。用加锁/解锁行为。可确保对象从開始到结束得到完整更新,
之后才会运行兴许的读取或变更行为,可是原子属性应当仅在须要时才使用。有人提出,訪问器通常并非加锁的合适地点。并不能确保线程安全。即使全部属性都是原子的。对象也可能会被设置为无效状态。更深入的讨论可參见Learning Objective C 2.0,第2版,Robert Clair著。
@property (nonatomic) float fuelAmount;
otherCar.showLiters = YES; [otherCar printCarInfo];
可是,这是本章末尾挑战题3的内容。
可以将这种方法命名为-(float)milesUntilEmpty。
Car类向它所继承的NSObject类中加入了一些实例变量和方法。
本章的演示样例代码在目录“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乘以油箱中燃料的加仑数。在真实的混合动力汽车中,算法将非常可能复杂
得多。代码清单2-11 加入一辆混合动力汽车到ViewController.m 文件里 HybridCar *myHybrid = [[HybridCar alloc] initWithMake:@"Toyota" model:@"Prius" year:2012 fuelAmount:8.3f MPG:42.0f]; [myHybrid printCarInfo];
假设执行
CarValet应用程序,就将在调试控制台看到例如以下信息:当继续
阅读本书,而且继续编写自己的应用程序时。Objective-C的语法和模式会变成你的第二天性。苹果公司也提供很好的
Objective-C 2.0 简单介绍。 位于http ://developer.apple.com/Mac/library/do cumentation/Cocoa/Conceptual/ObjectiveC/Introduction/introObjectiveC.html。你还将
学习很多其它关于Objective-C和iOS编程中一些重要技术的知识。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开发移动与平板应用》试读电子书免费提供,有须要的留下邮箱,一有空即发送给大家。
别忘啦顶哦!
《iOS开发全然上手——使用iOS 7和Xcode 5开发移动与平板应用》之Objective-C新手训练营
标签:声明 speed 文本 ash eve 下拉 nsa iphone 头部
原文地址:http://www.cnblogs.com/zhchoutai/p/7295615.html