书名:Learn Objective-C on the Mac Objective-C 基础教程
美:Mark Dalrymple & Scott Knaster 达尔林普尔/纳斯特
出版社:Apress L.P.
译:高朝勤、杨越、刘霞 2009.5
总策划:Dcnisc Santoro Lincoln
技术审查:Clay Andres & Jeff LaMarche
生产编辑:Laura Esterman
文字编辑:Heather Lang
致谢:Aaron Hillegass、Soctt Knaster 、Dave Mark、Greg Miller (KVC 和 NSPredicate )
本书内容:
内容包括(共17章):面向对象编程的基础知识、继承、复合、内存管理、对象初始化、协议、键/值编码等,以及Xcode、Foundation kit 、AppKit 等辅助工具。
Objective-C (Cocoa 工具包和 iPhone SDK)。
本书所有程序都以Foundation 框架为基础。
本书源代码下载:
http://www.turingbook.com
Source Code/Download 页面下载。
博客:
http://www.davemark.com/
开发平台搭建::黑苹果电脑、普通电脑(安装虚拟机+ Mac OS Lion)、Apple Air、Mac m
Chapter 01:启程
20世纪80年代,Bard Cox 发明了Objective-C 。
1985年,Steve Jobs 成了了NeXT Software 公司。NeXT Softer 公司选择了Unix 作为操作系统,创建了NeXTSTEP (使用Objective-C 开发的一款强大的用户界面工具包)。
1996年,Apple 公司收购了NeXT Software公司。NeXTSTEP 被重命名为Cocoa。
Chapter 02:对C的扩展
一:第一个Objective-C 程序(Hello Objective-C)
在Mac OS 上安装了Xcode 工具。
1> 启动Xcode
在/ Developer/Applications/Xcode, 可将Xcode 图标放在Dock 快捷键工具栏中,便于访问。
2> 建立项目
File\New Project\ 选择窗口左侧的 Command Line Utility, 再选择右侧的 Foundation Tool \ choose 按钮。
Xcode 显示一个工作表,要输入项目名称,在此输入:Hello Objective-C
单击Save 按钮,即显示项目窗口,此窗口显示项目的各组成部分以及编辑窗格。
3> Xcode 显示了样本代码保存于Hello Objective-C.m 中,删除全部代码,输入如下代码。
#import
int main(int argc, const char * argv[])
{
NSLog(@"Hello, Objective-C! ");
return(0);
}// main
编译、链接、运行程序:
单击 Bulid and Go 按钮或 按下苹果星花键R, Xcode 就会编译并链接程序,随后运行。
显示程序的输出结果:
Run\ Console 或按下 苹果星花键向上方向键R
代码分析:
Objective-C 代码以.m 扩展名结尾。m 代表message, 指的是Objective-C 的一个主要特性。
在Xcode 中,所以这些编译工作全由 GCC(GNU Compiler Collection,GNU 编译器集合)处理。
#import 是GCC 编译器提供的,Xcode 在编译Objective-C 、C 、C++ 程序时都会使用它。#import 可保证头文件只被包含一次。
NSLog(@" Hello, Objective-C! "); 此代码可向控制台输出:"Hello, Objective-C! "
NSLog() 接受一个字符串作为其第一个参数,可包含格式说明符,添加了其他特性,例如:时间戳、日期戳、自动附加换行符等。
字符串前的@符号,@符号是Objective-C 在标准C 语言基础上添加的特性之一。双引号中的字符串前有一个@符号,表示引用的字符串应该作为Cocoa 的NSString 元素来处理。
框架:
框架是一种聚集在一个单元的部件集合,包含头文件、库、图像、声音文件等。
Apple 公司将Cocoa(由Foundation 和 Application Kit 框架组成,Application Kit 也称 AppKit ) 、Carbon、QuickTime、OpenGL 等技术作为框架集提供。
Cocoa 还有一个框架套件,包含Core Animatin 和 Core Image。
Foundation 框架处理用户界面之下的层中的特性,如:数据结构、通信机制。Foundation 框架的头文件近1MB 的容量,包含14000多行代码,涵盖100多个文件。该框架头文件可以查看Headers 目录(/System / Library / Frameworks / Foundation.framework/ Hearders/ )。
Xcode 使用预编译头文件(一种经过压缩、摘要形式的头文件),在通过#import 导入这种文件时,加载速度会非常快。
Cocoa 的高级特性:用户界面元素、打印、颜色、声音管理、AppleScript 支持等。
NSString 特性:
1> 告知其长度;
2> 将自身与其他字符串比较;
3> 将自身转换为整型值或浮点值。
NSArray:提供数组
NSDateFormatter:用不同方式格式化日期
NSTread:提供多线程编程工具
NSSpeechSynthsizer :使你听到声音
main.m:46: warning: passing arg 1 of 'NSLog' from incompatible pointer type
1> Xcode Groups & Files \ File \ Get Info \ Build 选项卡
在搜索区输入:error, 勾选 Treat Warning as Errors 复选框,并在Configuration 弹出菜单选中:All Configurations.
布尔类型:
c 语言的布尔类型:bool , 具有true 和 false 值。
Objective-C 提供BOOL,具有YES 值和NO 值。
Objective-C 中的BOOL 实际上是一种对带符号的字符类型(signed char)的定义,它使用8位,YES定义为1, NO定义为0 。
Chapter 03:面向对象编程基础知识
间接(indirection)
只要多添加一个间接层,计算机科学中就没有解决不了的问题。
变量与间接
使用文件名的间接
新Web 2.0 公司关键技术:
#import
int main(int argc, const char * argv[])
{
const char * words[4] = {"aardvark", "abacus", "allude", "zygote"};
int wordCount = 4;
int i;
for(i = 0; i < wordCount; i++){
NSLong(@"%s is %d characters long", words[i], strlen(words[i]));
}
return (0);
}// main
#import
int main(int argc, const char* argv[])
{
FILE *wordFile = fopen("/tmp/words.txt", "r");
char word[100];
while(fgets(word, 100, wordFile))
{ // strip off the trailing \n
word[strlen(word) - 1] = '\0';
NSLog(@"%s is %d characters long", word, strlen(word));
}
fclose( wordFile);
return(0);
} // main
Xcode 中提供文件路径
在Xcode 文件列表中展开Executables,并双击程序名。
单击Arguments 区域下的加号,并输入启动参数---- 在本例中是words.txt 文件的路径;
在面向对象的编程中使用间接
过程式编程实例:
#import
typedef enum{ // 通过枚举指定绘制的几种不同形状:圆形、方形、不规则的椭圆形。
kCircle,
kRectangle,
kOblateShperoid
} ShapeType;
[plain] view plaincopyprint?
typedef enum{ // enum 定义绘制形状时可用的颜色。
kRedColor,
kGreenColor,
kBlueColor
}ShapeColor;
[plain] view plaincopyprint?
typedef struct { // 使用一个结构来描述一个矩形,此矩形指定屏幕上绘制形状的区域。
int x, y, width, height;
}ShapeRect;
typedef struct{ // 用一个结构将所有内容结合起来,描述一个形状。
ShapeType type;
ShapeColor fillColor;
ShapeRect bounds;
}Shape;
int main(int argc, const char * argv[])
{
Shape shapes[3];
ShapeRect rect0 = {0, 0, 10, 30 }; // 红色的圆形
shapes[0].type = kCircle;
shapes[0].fillColor = kRedColor;
shapes[0].bounds = rect0;
ShapeRect rect1 = { 30, 40, 50, 60 }; // 绿色的矩形
shapes[1].type = kRectangle;
shapes[1].fillColor = kGreenColor;
shapes[1].bounds = rect1;
ShapeRect rect2 = {15, 18, 37, 29}; // 蓝色的椭圆形
shapes[2].type = kOblateSpheroid;
shapes[2].fillColor = kBlueColor;
shapes[2].bounds = rect2;
drawShapes(shapes, 3);
return (0);
} // main
void drawShapes(Shape shapes[], int count)
{
int i;
for(i = 0; i< count; i++)
{
switch(shapes[i].type)
{
case kCircle: drawCircle(shapes[i].bounds, shapes[i].fillColor); break;
case kRectangle: drawRectangle(shapes[i].bounds,shapes[i].fillColor); break;
case kOblateSpheroid: drawEgg(shapes[i].bounds, shapes[i].fillColor); break;
}
}
}// drawShapes
void drawCircle(ShapeRect bounds, ShapeColor fillColor) // 其他绘图函数几乎和drawCircle 相同。
{
NSLog(@"drawing a circle at (%d %d %d %d) in %@",
bounds.x,
bounds.y,
bounds.width,
bounds.height,
colorName(fillColor);
} // drawCircle
NSString *colorName(ShapeColor colorName)
{
switch(colorName)
{
case kRedColor: return @"red"; break;
case kGreenColor: return @"green";break;
case kBlueColor: return @"blue"; break;
}
return @"no clue" ;
} // colorName
面向对象编程:
void drawShapes(id shapes[], int count)
{
int i;
for(i = 0; i < count; i++)
{
id shape = shapes[i];
[shape draw];// 用于通知某个对象该做什么。第一项是对象,其余部分是需要对象执行的操作。通知对象执行某种操作称为发送消息或称为调用方法。
}
} // drawShapes
一些术语:
类:是一种结构,代表对象的类型。建议首字母大写类名。
对象:是一种结构,包含值和指向其类的隐藏指针。通常不需要首字母大写。
实例:是对象的另一种称呼。
消息:是对象可执行的操作,用于通知对象去做什么。
方法:是为响应消息而运行的代码。根据对象的类,消息可以调用不同的方法。
方法调用程序:是Objective-C 使用的一种机制,用于推测执行什么方法以响应某个特定的消息。
接口:是对象的类应该提供的特性的描述。
实现:是使接口正常工作的代码。
Objective-C 中的OOP
@interface 部分
Circle 类的接口:
[plain] view plaincopyprint?
@interface Circle : NSObject // 名为Circle 的新类定义的接口
{
ShapeColor fillColor; // 数据成员,fillColor 称为Circle 类的实例变量。
ShapeRect bounds; // 数据成员
}
-(void) setFillColor : (ShapeColor) fillColor; // 方法声明,指出了方法返回值的类型、每种方法的名称、某些参数。
-(void) setBounds: (ShapeRect) bounds; // 参数的类型是在圆括号中指定的。紧跟其后的名称 bounds 是参数名。
-(void) draw;
@end // 完成了 Circle 类的声明,但不是必需的。
// 中划线后面:方法的返回类型,位于圆括号中。注意,冒号是方法名称非常重要的组成部分。不含参数的方法结尾处用分号。
中缀符(infix notation):
Objective-C 语法,方法的名称及其参数都是合在一起的。
调用带一个参数的方法:
[circle setFillColor : kRedColor ];
调用带两个参数的方法:
[ textThing setStringValue: @" hello there" color: kBlueColor ];
参数名称:setStringValue: 和 color: ,实际上是方法名称的一部分,结尾处的冒号是名称的一部分,它告诉编译器和编程人员后面出现参数。
@"hello there " 和 kBlueColor 是被传递的参数。
@implementation 部分
@implementation Circle
-(void) setFillColor: (ShapeColor) c // 各个方法的定义。可以定义在@interface 中未出现的方法。可以看成是私有方法,仅在类的实现中使用。
{
fillColor = c;
} // setFillColor
-(void) setBounds: (ShapeRect) b
{
bounds = b;
}// setBounds
-(void) draw
{
NSLong(@"drawing a circle at (%d %d %d %d) in @% ", bounds.x, bounds.y, bounds.width, bounds.height, colorName(fillColor));
}// draw
@end // Circle
@implementation 是一个编译器指令,表明将为某个类提供代码。类名出现在@implementation 之后。该行的结尾处没有分号,在Objective-C 编译器指令后不必使用分号。
如下例:
[plain] view plaincopyprint?
setFillColor: is the first method defined: // 结尾没有分号,@interface 和 @implementation 间的参数名不同是正确的。
-(void) setFillColor: (ShapeColor) c // 在实现中,需区分参数名称和实例变量名称,最简单的方式就是将参数重新命名。
{
fillColor = c; // 使用相同的变量名会隐藏初始变量,可以为参数使用新的名称来避免该问题。可改为 myFillColor = c; self -> myFillColor = c;
}// setFillColor
// 在Objective-C 的调用方法时,一个名为self 的隐藏参数将被传递给接收对象。而这个参数引用的就是该接收对象。
// 传递隐藏的参数是另一种间接操作的示例。因为Objective-C 运行时(runtime)可以将不同的对象当成隐藏的self 参数传递,所以那些对象的实例变量发生更改时,运行时也可进行相应的更改。
实例化对象:
创建形状对象,如红色的圆形和绿色的矩形。这个过程的专业术语叫做实例化(instantiation)。
实例化对象时,需分配内存,然后这些内存被初始化并保存一些有用的默认值,这些默认值不同于新分配内存时得到的随机值。
由于对象的局部变量特定于该对象的实例,因此称为实例变量,通常简写为ivars 。
创建新对象,需要向相应的类发送new 消息。该类接收并处理完new 消息后,就会得到一个可以使用的新对象实例。
Objective-C 特性:可以把类当成对象来向类发送消息。对全体类都通用。
int main(int argc, const char* argv[])
{
id shapes[3];
ShapeRect rect0 = {0, 0, 10, 30};
shapes[0] = [Circle new];
[ shapes[0] setBounds: rect0];
[ shapes[0] setFillColor: kRedColor ];
ShapeRect rect1 = {30, 40, 50, 60};
shapes[1] = [Rectangle new];
[shapes[1] setBounds: rect1 ];
[shapes[1] setFillColor: kGreenColor ];
ShapeRect rect2 = {15, 19, 37, 29};
shapes[2] = [OblateSphereoid new];
[shapes[2] setBounds: rect2];
[shapes[2] setFillColor: kBlueColor];
drawShapes (shapes, 3);
return(0);
}// main
面向对象编程大师 Bertrand Meyer 的开放/ 关闭原则(Open / Closed Principle) 开发出来的软件会表现出更强的健壮性。面向对象编程使用间接技术将数据和对数据执行的操作紧密联系在一起,从而引入了“数据第一,函数第二” 的编程风格。
Chapter 04: 继承
处理类和对象间的关系时,需重视两方面的内容:继承、复合(composition)。
Circle |
fillColor bounds |
setFillColor: setBounds: draw |
Rectangle |
fillColor bounds |
setFillColor: setBounds: draw |
这种图表是根据UML(Unified Modeling Language, 统一建模语言)定义的。UML用图表表示类、类的内容、它们之间关系。
顶部:类名
中间部分:实例变量
底部:类提供的方法
见下链接文件图片
http://my.csdn.net/my/album/detail/1641500
UML 使用末端有箭头的竖线表示继承关系。
注:不要直接更改由继承得到的实例变量的值。一定要使用方法来更改它们。
继承语法:
@interface Circle : NSObject
冒号后的标识符是需要继承的类。
在Ojbective-C 中,可以从非类中继承对象。Objective-C 不支持多继承。但可以通过Objetive-C 的其他特性获取多继承的优点,比如:分类和协议。
现提炼Circle 和 Rectangle 的接口代码。
@interface Circle: Shape
@end // Circle
@interface Rectangle: Shape
@end // Rectangle
@interface Shape : NSObject // 把 Circle 和 Rectangle 类所有重复的代码都绑定到一个包中。
-(void) setBounds: (ShapeRect) bounds;
-(void) draw;
术语:
超类(superclass):
父类(parentclass):
子类(subclass):
孩子类(childclass):
改变方法的实现时,需要重写(override)继承方法。
继承的工作机制:
方法调度
实例变量
创建一个新类时,其对象首先从自身的超类中继承实例变量,然后添加/可选 它们自己的实例变量。为了了解实例变量的继承机制,创建一个新形状来添加新的实例变量。
isa (NSObject) |
fillColor (Shape) bounds |
radius (RoundedRectangle) |
对象实例变量的布局
使用具体种类的对象(Rectangle 或 Circle) 代替一般类型(Shape),这种能力称为多态性(polymorphism)。
@interface RoundedRectangle: Shape
{
int radius;
}
@end // RoundedRectangel
每个方法调用都获得一个名为self 的隐藏参数,它是一个指向接收消息的对象的指针。方法使用self 参数查找它们要使用的实例变量。
指向Circle 对象的 self 参数。
编译器使用“基地址 + 偏移地址”机制实现奇妙的功能。由于偏移地址通过硬编码实现的。
苹果公司的工程师希望向NSObject 中添加其他的实例变量,但无法做到,这样做会改变所有实例变量的偏移位置。这称为脆弱的基类问题(fragile base class problem)。
通过在 Leopard 中引入新的64位Objective-C 运行(它使用间接寻址方式确定变量的位置),苹果公司解决了这个问题。
重写方法
super 关键字
Objective-C 提供某种方式来重写方法,并且仍然调用超类的实现方式。为了调用继承方法的实现,需要使用super 作为方法调用。
@implementation Circle
-(void) setFillColor: (ShapeColor) c
{
if(c == kRedColor)
{
c = kGreenColor;
}
[super setFillColor: c] // 通过调用[super setFillColor:c ]来调用超类的方法。super 调用将运行Shape 的setFillColor: 方法。
}// setFillColor
// and the rest of the Circle @implementation is unchanged
@end //Circle
Chapter 05:复合
继承是在两个类之间建立关系的一种方式,它可以避免许多重复的代码。
类之间的关系也可以通过复合的方式来建立。使用复合可组合多个对象,使之分工协作。
在实际的程序中,会用到同时使用继承和复合来创建自己的类。
在Objective-C 中,复合是通过包含作为实例变量的对象指针实现的。
@interface Unicycle : NSObject
{
Pedal *pedal; // 脚踏板
Tire *tire; // 轮胎
}
@end // Unicycle
严格地讲,只有对象间的组合才能叫做复合。
CarParts 的代码都包含在主程序mainCarParts.m 中。
#import
@interface Tire : NSObject
@end
@implementation Tire
-(NSString *) description
{
return(@"I am a tire. I last a while");
} // description
@end // Tire
@interface Engine: NSObject
@end
@implementation Engine
-(NSString *) description
{
return (@"I am an engine. Vrooom!");
}
@end // Engine
程序的最后一部分是Car 本身,它拥有一个engine对象和一个由4个tires 对象组成的C数组。
它通过复合的方式来组装自己。Car 同时还有个print 方法。
通过NSLog() 可以使用%格式说明符来输出对象。
@interface Car : NSObject
{
Engine *engine;
Tire *tires[4];
}
-(void) print;
@end
@implementation Car
-(id) init // id 型数据,即泛型对象指针
{
if(self = [super init]) // 将[super init] 的结果赋给self 是Objective-C 的标准惯例。
{
engine = [Engine new];
tires[0] = [Tire new];
tires[1] = [Tire new];
tires[2] = [Tire new];
tires[3] = [Tire new];
}
return (self);
}// init// Car 类的init方法 创建了4个新轮胎。将4个新轮胎赋值给tires 数组,接着又创建了一台发动机并将其赋值给engine 实例变量。
-(void) print
{
NSLog(@"%@", engine); // %@ 只是调用每个对象的description方法并显示结果。
NSLog(@"%@", tires[0]);
NSLog(@"%@", tires[1]);
NSLog(@"%@", tires[2]);
NSLog(@"%@", tires[3]);
} //print
@end // Car
// CarParts.m 的最后一部分是main() 函数。
int main(int argc, const char * argv[])
{
Car *car; car = [Car new]; [car print];
return (0);
}// main
存取方法(access method): 如果可以为一辆车选择某种类型的轮胎和发动机,用户就可以定制汽车了。
可以通过存取方法来实现上述想法。
存取方法是用来读取或改变对象特定属性的方法。
若添加一个新方法改变Car 对象中的发动机类,这个方法(这类存取方法) 称为setter 方法。因为它为对象中的某属性赋值。
术语修改方法(mutator) 是用来改变对象状态的方法。
另一种存取方法就是getter 方法。
下面为Car 添加setter 和 getter 方法。
@interface Car : NSObject
{
Engine *engine;
Tire *tires[4];
}
-(Engine *) engine;
-(void) setEngine: (Engine *) newEngine;
-(Tire *) tireAtIndex: (int) index;
-(void) setTire:(Tire *) tire atIndex:(int) index;
-(void) print;
@end // Car
@impementation Car
-(Engine * ) engine // getter 方法engine
{
return(engine);
// 在Objective-C 中所有对象间的交互都是通过指针实现的,所以方法engine 返回的是一个指针,指向Car 中的发动机对象。
}
-(void) setEngine:(Engine *) newEngine // setter 方法setEngine
{
engine = newEngine;
// 将实例变量engine 的值 设为方法参数所指向的值。实际上被复制的并不是engine本身,而是指向engine 的指针值。
}
-(void) setTire: (Tire *) tire atIndex:(int) index;
-(Tire *)tireAtIndex: (int) index;
// 由于汽车的4个轮胎都有自己不同的位置,所以对象Car 有一个轮胎数组。用索引存取器来访问它们,不是直接操作整个tires 数组。
-(void) setTire: (Tire *) tire atIndex:(int) index
{
if(index < 0 || index > 3)
{
NSLog(@"bad index (%d) in setTire:atIndex;",index);
exit(1);
}
tires[index] = tire;
}// setTire:atIndex;
-(Tire *) tireAtIndex:(int) index
{
if(index < 0 || index > 3)
{
NSLog(@"bad index (%d) in tireAtIndex;", index);
exit(1);
}
return (tires[index]);
}
int main(int argc, const char * argv[])
{
Car *car = [Car new];
Engine *engine = [Engine new];
[car setEngine: engine];
int i;
for(i = 0; i < 4; i++)
{
Tire *tire = [Tire new];
[car setTire: tire atindex:i];
}
[car print];
return(0);
}// main
setter 方法:根据它所更改的属性的名称来命名,并加上前缀“set” 。
如:setEgine: setStringValue: setFont: setFillColor: setTextHeight:
getter 方法:则仅仅根据其返回的属性的名称来命名。
get 出现在Cocoa 的方法名称中,就意味着这个方法会通过你当作参数传入的指针来返回数值。
Chapter 06:源文件组织
拆分接口和实现部分:
接口部分:用来提供类的公共描述。接口包含了所有使用该类所需的信息。
编译器编译 @interface 部分后,就使用该类的对象,调用类方法,将对象复合到其他类中和创建子类。
一个文件存放接口部分的代码:类的@interface 指令、公共struct 定义、enum 常量、#defines 和 extern 全局变量等。这些通常都被放在头文件中。头文件名称与类名称相同,只是用.h 做后缀。
实现部分:@implementation 部分告诉 Objective-C 编译器如何让该类工作。这部分代码实现了接口中声明的方法。
实现部分存放的代码:类的@implement 指令、全局变量的定义、私有struct 等。放在与类同名,但以.m 为后缀的文件中。
如果用.mm 做文件扩展名,编译器就会认为用Objective-C++ 编写代码,这样就可以同时使用C++ 语言和 Objective-C 来编程了。
拆分 Car 程序:
CarParts-Split.m 剪切Tire 的@interface 部分,粘贴到Tire.h 中。
#import
@interface Tire:NSObject
@end // Tire
CarParts-Split.m 中的Tire 的@implementation 部分粘贴到Tire.h 中。
#import "Tire.h"
@implementation Tire
-(NSString *) description
{
return(@"I am a tire. I last a while.");
} // description
@end // Tire
导入头文件的两种方式:
尖括号:带尖括号的语句是用来导入系统头文件的。
引号: 带引号语句则说明导入的是项目本地的头文件。
使用跨文件依赖关系:
依赖关系是两个实体之间的一种关系。依赖关系可以存在于两个类之间,也可以存在于两个或多个文件之间。依赖关系是可传递的,即头文件也可以互相依赖。
由于头文件发生变化,重新编译需要花费很长时间,但Xcode 可记录所有的依赖关系。
重新编译:
依赖关系问题的存在是因为Objective-C 编译器需要某些信息才能工作。
编译器需要知道类的全部信息,如:实例变量配置、它所继承的所有类等。
Objective-C 引入关键字@class 来告诉编译器:这是一个类,需要通过指针来引用它。
@class 创建了一个前向引用。
#import
@class Tire;
@calss Engine;
@interface Car : NSObject
{
Tire *tires[4];
Engine *engine;
}
-(void) setEngine : (Engine *) newEngine;
-(Engine *) engine;
-(void) setTire :(Tire *) tire atIndex :(int) index;
-(Tire *) tireAtIndex :(int ) index;
-(void) print;
@end // Car
从CarParts-Split.m 中剪切出Car 的 @implementation 部分并粘贴到 Car.m 中。
#import "Car.h"
#import "Tire.h"
#import "Engine.h"
@implementation Car
-(void) setEngine:(Engine *) newEngine
{
engine = newEngine;
}
-(Engine *) engine
{
return(engine);
}
-(void) setTire: (Tire *) tire atIndex: (int ) index
{
if(index < 0 || index >3 )
{
NSLog(@"bad index (%d) in setTire:atIndex: ", index);
exit(1);
}
tires[index] = tire;
} // setTire : atIndex
-(Tire *) tireIndex :(int) index
{
if(index < 0 || index >3 )
{
NSLog(@"bad index (%d) in setTire: atIndex: ", index);
exit(1);
}
return (tires[index]);
} // tireAtIndex
-(void) print
{
NSLog(@"%@", tires[0]);
NSLog(@"%@", tires[1]);
NSLog(@"%@", tires[2]);
NSLog(@"%@", tires[3]);
NSLog(@"%@", engine);
}
@end // Car
导入和继承:
由于继承其他类而不是通过指针指向其他类,所以不能在头文件中使用@class 语句。
因编译器需要先知道所有关于超类的信息才能成功地为子类编译@interface。需要了解超类实例变量的配置(数据类型、大小、排序)。
在子类中添加实例变量时,它们会被附加在超类实例变量的后面。编译器用这条信息来判断在内存的什么位置能找到这些实例变量,从每个方法调用自身的self 隐藏指针开始寻找。
为了能准确地计算出实例变量的位置,编译器必需先了解该类的所有内容。
Slant6.h
@import “Engine.h”
@interface Slant6 : Engine
@end // Slant6
Slant6.m
#import "Slant6.h"
@implementation Slant6
-(NSString *) description
{
return (@"I am a slant-6 . VROOOM!");
}
@end // Slant6
AllWeatherRadial.h
#import "Tire.h"
@interface AllWeatherRadial : Tire
@end
AllWeatherRadial.m
#import "AllWeatherRadial.h"
@implementation AllWeatherRadial
-(NSString * ) description
{
return (@"I am a tire for rain or shine ");
}
@end
#import
#import "Tire.h"
#import "Engine.h"
#import "Car.h"
#import "Slant6.h"
#import "AllWeatherRadial.h"
int main(int argc, const char* argv[ ])
{
Car *car = [Car new];
int i;
for(i = 0; i < 4; i++)
{
Tire * tire = [AllWeatherRadial new];
[car setTire : tire atIndex : i];
}
Engine * engine = [Slant6 new];
[car setEngine: engine];
[car print];
return (0);
} // main
Xcode 是一个很大的应用程序,它有很强的自定义功能。这里以Xcode 3.1 为例。苹果公司在Xcode 版本升级时添新删旧。若使用Xcode4.2.0 可能有出入。
改变公司名称:
新建Objective-C 源文件时,Xcode 会自动生成注释块:
注释块包括:文件名称、项目名称、创建者、创建时间。
Xcode3.1 没有任何用来修改占位符_MyCompanyName_ 的用户接口。需到Terminal 中去修改它。
Finder \ Utilities文件夹(可用快捷键苹果花键+U 打开),
使用编辑器的技巧与诀窍:
Chapter 08:Foundation Kit 快捷教程
Foundation 框架只是Cocoa 的一部分,并且没有内置于Objective-C 语言中。
Cocoa 是由两个不同的框架组成的:Foundation Kit 和 Application Kit 。
Application Kit 包含了所有的用户接口对象和高级类。
Cocoa Foundation 框架中有很多有用的、面向数据的低级类和数据类型。如:NSString 、NSArray、NSEnumerator、NSNumber。
Foundation 框架拥有100多个类,查看Xcode 自带的文档可以了解它们。
文档存放在/Developer/ ADC Reference Library/ documentation /index.html
#import
int main(int argc, const char * argv[ ])
{
//创建(通过alloc)并初始化(通过init)了一个NSAutoreleasePool 实例。
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
// insert code here...
NSLog(@"Hello, World!");
[pool drain]; // 这个池被排空,这就是Cocoa 内存管理的预览。
return 0;
}
8.1 一些有用的数据类型:
8.1.1范围的作用
typedef struct _NSRange // 表示相关事物的范围
{
unsigned int location; // 该字段存放该范围的起始位置。
unsigned int length; // 该字段是该范围内所含元素的个数。
}NSRange;
下面用三种方式创建新的NSRange。
第一种:直接给字段赋值
NSRange range;
range.location = 17;
range.length = 4;
第二种:应用C 语言的聚合结构赋值机制
NSRange range = { 17, 4 };
第三种:是Cocoa 提供的一个快捷函数NSMakeRange()
NSRange range = NSMakeRange(17 ,4 );
8.1.2 几何数据类型
NSPoint 和 NSSize
typedef struct _NSPoint
{
float x;
float y;
} NSPoint; // NSPoint 代表的是笛卡尔平面中的一个点(x , y);
typedef struct _NSSize
{
float width;
float height;
} NSSize; // NNSize 用来存储长度和宽度
Cocoa 提供一个矩形数据类型,它是由点和大小复合而成的。
typedef struct _NSRect
{
NSPoint origin;
NSSize size;
} NSRect;
Cocoa 提供象NSMakePoint() 、NSMakeSize()、NSMakeRect()。
Objective-C 对象都是动态分配的,而动态分配是一个代价较高的操作,它会消耗大量的时间。将这些结构创建成第一等级的对象都会在使用过程中增加大量的系统开销。
字符串:
Cocoa 提供的字符串类是NSString。
字符串就是一组人类可读的字符序列。
创建字符串
NSString 的 stringWithFormat:方法就是这样通过格式字符串和参数来创建NSString的。
+(id) stringWithFormat : (NSString *) format, ...;
创建一个新的字符串:
NSString *height ;
height = [ NSString stringWithFormat: @"Your height is %d feet, %d inches" , 5, 11];
类方法
stringWithFormat: 的声明中有两个值得讨论的地方。
第一个是定义最后的省略号(...),它告诉编译器这个方法可以接收多个以逗号隔开的其他参数。
第二个是声明中在开始字符:一个加号。
Objective-C 运行时生成一个类的时候,它会创建一个代表该类的类对象。
类对象包含了:指向超类的指针、类名、指向类方法列表的指针。
类对象还包含一个long 型数据,为新创建的类实例对象指定大小(以字节为单位)。
声明方法时添加了加号,那么就是把这个方法定义为类方法。这个方法属于类对象(而不是类的实例对象)并且通常用于创建新的实例。这种用来创建新对象的类方法为工厂方法。
类方法可以用来访问全局数据。
AppKit 中的NSColor 类有一些以不同颜色命名的类方法,如redColor、blueColor。
NSColor *haveTheBlues = [ NSColor blueColor]; // 用蓝色绘图
有些方法是实例方法,要用前导减号(-) 来开始声明。这些方法将会在某个对象实例中运行。
有些方法用来实现常规功能,如创建一个实例对象或访问一些全局类数据,那么最好使用前导加号(+)将它声明为类方法。
关于大小
NSString 中另一个方便的方法(实例方法)是length,它返回的是字符串中的字符个数。
-(unsigned int) length;
使用形式如:unsigned int length = [height length];
或: if([ height length] > 35)
{
NSLog(@"wow, you're really tall! ");
}
NNString 的length 方法能够准确无误地处理国际字符串,如:俄文、中文、日文字符的字符串,以及使用Unicode 国际字符标准的字符串。
比较的策略
isEqualToString: 可以用来比较接收方(接收消息的对象)和当作参数传递来的字符串。返回一个BOOL型数据来表示两个字符串的内容是否相同。
声明如下:
-(BOOL) isEqualToString : (NSString *) aString;
使用方法如下:
NSString * thing1 = @"hello 5";
NSString * thing2;
thing2 = [NSString stringWithFormat : @"hello %d", 5];
if([ting1 isEqualToString: thing2])
{
NSLong(@"They are the same! ");
}
比较两个字符串,可以使用compare: 方法,声明如下:
-(NSComparisonResult) compare: (NSString * ) string;
compare: 将接收对象和传递来的字符串逐个字符地进行比较,它返回一个NSComparisonResult (就是一个enum 型数据)来显示比较结果:
typedef enum _NSComparisonResult
{
NSOrderedAscending = -1,
NSOrderedSame,
NSOrderedDescending
}NSComparisonResult;
正确比较字符串
比较两个字符串是否相等时,应该用isEqualToString:,而不能仅仅只是比较字符串的指针值,如:
if ([ thing1 isEqualToString : thing2 ])
NSLog(@ "The strings are the same!");
和
if( thing1 == thing2 ) // 判断thing1 和 thing2 的指针数值,而不是它们所指的对象。
NSLog(@"They are the same object ! ");
不区分大小写的比较
1> compare: 进行的是区分大小写的比较。
2> compare: options:
-(NSComparisonResult) compare: (NSString *) string options:(unsigned) mask;
options 参数是一个位掩码。
可以使用位或(bitwise-OR) 运算符(|) 来添加选项标记。常用的选项如下:
NSCaseInsensitiveSearch: 不区分大小写字符。
NSLiteralSearch: 进行完全比较,区分大小写。
NSNumericSearch: 比较字符串的字符个数,而不是字符值。
实例:进行字符串比较,忽略大小写但按字符个数的多少正确排序:
if( [thing1 compare: thing2 options: NSCaseInsensitiveSearch | NSNumericSearch ] == NSOrderedSame )
{
NSLog(@" They match! ");
}
字符串内是否还包含别的字符串
1> 第一个检查字符串是否以另一个字符串开头。
2> 判断字符串是否以另一个字符串结尾。
-(BOOL) hasPrefix: (NSString *) aString;
-(BOOL) hasSuffix: (NSString *) aString;
NSString *filename = @"draft-chapter.pages" ;
if( [fileName hasPrefix: @"draft"] )
{
// this is a draft
}
if ( [fileName hasSuffix : @".mov"])
{
// this is a movie
}
字符串内的某处是否包含其他字符串,请使用rangeOfString:
-(NSRange) rangeOfString: (NSString *) aString;
rangeOfString: 发送给一个NSString 对象时,传递的参数是要查找的字符串。
它返回一个NSRange struct 来告诉你与这个字符串相匹配的部分在哪里以及能够匹配上的字符个数。
如:
NSRange range;
range = [fileName rangeOfString: @"chapter"];
返回的 range.start 为6, range.length 为7。
如果传递的参数在接收字符串中没有找到,那么range.start 则等于 NSNotFound
可变性:
Cocoa 提供一个NSString 的子类,叫做NSMutableString 。如果你需要改变字符串,请使用这个子类。
可以使用类方法stringWithCapacity: 来创建一个新的NSMutableString,声明如下:
+(id) stringWithCapacity: (unsigned) capacity;
按如下方式创建一个新的可变字符串:
NSMutableString *string;
string = [ NSMutableString stringWithCapacity: 42 ];
一旦有了一个可变字符串,就可以对它执行各种操作了。一种常见的操作是通过 appendString: 或 appendFormat: 来附加新字符串。如:
-(void) appendString: (NSString *) aString;
-(void) appendString: (NSString *) format, ...;
appendString 接受参数aString, 将其复制到接收对象的末尾。
appendFormat 的工作方式与 stringWithFormat: 类似,将格式化的字符串附加在接收字符串的末尾,而不是创建新的字符串对象。
NSMutableString * string;
string = [NSMutableString stringWithCapacity : 50];
[string appendString: @"Hello there "];
[string appendFormat: @"human %d! ", 39];
可以用deleteCharactersInRange: 方法删除字符串中的字符:
-(void) deleteCharactersInRange: (NSRange) range;
经常将deleteCharactersInRange: 和 rangeOfString: 连在一起使用。
如:列出所有朋友的列表,但你不喜欢Jack,想从列表中删除:
1> 首先,创建朋友列表:
NSMutableString * friends;
friends = [NSMutableString stringWithCapacity: 50];
[friends appendString: @"James BethLynn Jack Evan"];
2> 找到Jack 的字符范围:
NSRange jackRange;
jackRange = [friends rangeOfString: @"Jack"];
jackRange.length++; // eat the space that follows
3> 此例中,字符范围开始于15, 长度为5。删除Jack :
[ friends deleteCharactersInRange: jackRange ];
字符串就变成:"James BethLynn Evan"
因NSMutableString 是 NSString 的子类,可获得两个特性:
1> 任何使用NSString 的地方,都可以用NSMutableString 来替代。任何接受NSString 的方法也都会接受 NSMutableString
2> 继承,与实例方法一样,继承对类方法也同样适用。
NSString 中非常方便的类方法stringWithFormat:也可以用来创建新的NSMutableString。
给定的范围内删除字符或者的特定的位置插入字符。详细见NSString 和 NSMutableString 的文档。
集合家族:
Cocoa 提供许多集合类,如:NSArray 和 NSDictionary。
NSArray
Car 程序在C语言的数组中有以下问题点(Car 有4个轮胎):
C 语言的数组检查项:
1> 检查数组的索引是否有效,索引不能小于0,不能大于数组的长度。
2> 这个长度为4的数组是被硬编码进Car 类的,车的轮胎不能多于4。
NSArray 是一个Cocoa 类,用来存储对象的有序列表。可以在它里面放入任意类型的对象:NSString、Car、Shape、Tire 或者其他你想要存储的对象。
有了NSArray,可以的操作如:
1> 让某个对象的实例变量指向这个数组
2> 将该数组当作参数传递给方法或函数
3> 获取数组中所存对象的个数
4> 提取某个索引所对应的对象
5> 查找数组中的对象
6> 遍历数组等其他操作
NSString 有两个限制:
1> 它只能存储Objective-C 的对象,不能存储C 语言中的基本数据类型,不能存储NSArray 中的随机指针。
2> 不能在NSArray 中存储nil (对象的零值 或 NULL 值)。
类方法arrayWithObjects: 创建一个新的NSArray。
发送一个以逗号分隔的对象列表,在列表结尾添加nil 代表列表结束(这就是不能在数组中存储nil 的原因之一)。
NSArray *array;
array = [NSArray arrayWithObjects: @"one", @"two", @"three", nil ];
-(unsigned) count; // 只用一个数组,就可以获取所包含的对象个数。
-(id) objectAtIndex: (unsigned int ) index; // 可以获取特定索引处的对象。
结合上述两个操作,可以输出数组的内容:
int i;
for( i = 0; i < [array count]; i++ )
{
NSLog(@"index %d has %@.", i, [array objectAtIndex: i] );
}
输出结果:
index 0 has one.
index 1 has two.
index 2 has three.
若引用的索引大于数组中对象的个数,那么Cocoa 在运行时会输出错误。
运行: [ array objectAtIndex: 208000];
出现以下错误:
*** Terminating app due to uncaught exception 'NSRangeException',
reason: ' *** -[ NSCFArray objectAtIndex : ] : index (208000) beyond bounds (3)'
还有一个终止的原因之一是:未捕获的异常(uncaught exception )。
在Cocoa 中,CF表示:苹果公司的 Core Foundation 框架相关的内容。它是用C语言实现的。大部分代码是开源的。
Core Foundation 框架中的许多对象和Cocoa 对象之间是免费桥接的,就是说它们可以互相使用的。
NSCFArray 是苹果公司实现的NSArray,但使用CFArray 执行具体操作。
发生异常时,让Xcode 中断程序并进入调试器,Run -> Show -> Breakpoints 。出现一个窗口,并显示所有当前断点。
我们可以追加两个断点,可以更加轻松追查异常。
1> 将为 -[ NSException raise ] 创建一个断点。
选择 Global Breakpoints , 双击 Double-Click for Symbol 框, 输入-[ NSException raise] , 然后按 return 键。
2> 追加另一个全局断点 objc_exception_thow 。
运行程序有异常抛出时, 调试器会中止程序并指向有问题的代码行。
单击栈跟踪窗格来将焦点移动到相应的源文件。在此示例中,单击栈跟踪列表中的 main 函数来查看代码。
切分数组
NSArray 可以将字符串切分成数组和将数组元素合并成字符串这种操作。
使用 -componentsSeparatedByString: 来切分NSArray。如:
NSString * string = @"oop: ack: bork: greenble: ponies ";
NSArray *chunks = [string componentsSeparatedByString: @ " : "];
使用 -componetsJoinedByString: 来合并NSArray 中的元素并创建字符串:
string = [chunks componetsJoinedByString: @" :-) "];
将创建一个内容如: "oop :-) ack :-) bork :-) greeble :-) ponies " 的NSString 字符串。
可变数组:
与NSString 一样,NSArray 创建的是不可变对象的数组。
数组中包含的对象时可以改变的(Car 程序在安全检查失败后可以获得一套新的 Tire),但数组对象本身是一直都不会改变的。
NSArray 的补充类 NSMutableArray , 可以随意添加和删除数组中的对象了。
NSMutableArray 通过类方法 arrayWithCapacity 来创建新的可变数组。
+( id ) arrayWithCapacity: (unsigned) numItems;
创建可变数组:
NSMutableArray *array;
array = [ NSMutableArray arrayWithCapacity: 17];
使用addObject: 在数组末尾添加对象:
-(void) addObject: (id) anObject;
利用循环代码为数组添加4个轮胎:
for ( i = 0; i < 4 ; i++ )
{
Tire *tire = [ Tire new ];
[ array addObject: tire ];
}
删除特定索引的对象。如:删除第二个轮胎,可以用 removeObjectAtIndex: 来删除它。
-(void) removeObjectAtIndex: (unsigned) Index;
[array removeObjectAtIndex: 1];
可以在特定索引处插入对象,替换对象,为数组排序。
枚举“王国”
在Cocoa 用来描述集合迭代运算的方式。可以使用 NSEnumerator ,但需通过objectEnumerator 向数组请求枚举器:
-( NSEnumerator *) objectEnumerator ;
可按如下方式使用这个方法:
NSEnumerator * enumerator ;
enumerator = [ array objectEnumerator ];
从后向前浏览集合,还有一个方法 reverseObjectEnumberator 可以使用。
在获得枚举器之后,可以开始一个 while 循环,每次循环都向这个枚举器请求它的 nextObject (下一个对象):
- (id) nextObject;
nextObject 返回 nil 值时,循环结束。这也不能在数组中存储 nil 值的另一个原因。没有办法判断 nil 是存储在数组中的数值还是代表循环结束的标志。
NSEnumberator * enumerator;
enumerator = [ array objectEnumerator ];
id thingie;
while( thingie = [ enumerator nextObject ])
{
NSLog(@ " I found %@", thingie );
}
可变数组进行枚举时,不能通过添加或删除对象这类方式来改变数组容器。
快速枚举:
在 Mac OS X 10.5 (Leopard) 中,为了将Objective-C 升级到2.0 版本,有一些小调整。快速枚举的语法与脚本语言类似。
for ( NSString *string in array)
{
NSLog( @ "I found %@", string );
}
这个循环将会遍历数组中的每个元素,并用变量string 存储每个数组值。
快速枚举不能在 Mac OS X 10.4 (Tiger) 系统上使用。
有 3 种方式遍历数组:
1> 通过索引
2> 使用NSEnumerator
3> 快速枚举
若程序需要支持Tiger 系统,就使用NSEnumerator 。Xcode 有重构功能,可以将代码转换成 Objective-C 2.0, 它会自动将NSEnumerator 。
在需要索引访问数组时使用 -objectAtIndex ,如跳跃浏览数组、同时遍历多个数组时。
NSDictionary
字典就是关键字及其定义的集合。
Cocoa 中有一个实现这种功能的集合类NSDictionary。NSDictionary 在给定的关键字(通常是一个NSString 字符串)下存储一个数值(可以是任何类型的对象)。然后就可以用这个关键字来查找相应的数值。
为什么不用数组存储然后在数组里查询数值呢?
字典(也称为散列表或关联数组) 使用的是键查询的优化存储方式。可以立即找出要查询的数据,不需遍历整个数组进行查找。
对于频繁的查询和大型的数据集来说,使用字典比数组要快得多。
NSDictionary 和 NSString 、NSArray 一样是不可变的对象。
但NSMutableDictionary 类允许随意添加和删除字典元素。
使用字典的简单方法:用类方法dictionaryWithObjectAndKey: 来创建字典。
+( id ) dictionaryWithObjectsAndKeys: ( id ) firstObject, ... ;
该方法接受对象和关键字交替存储的系列,以 nil 值作为终止符号(不能在NSDictionary 中存储 nil 值)。
如:创建一个存储汽车轮胎的字典,轮胎用人类可读的标签而不是数组中的任意索引。
Tire * t1 = [ Tire new ];
Tire * t2 = [ Tire new ];
TIre * t3 = [ Tire new ];
Tire * t4 = [ TIre new ];
NSDictionary * tires;
tires = [ NSDictionary dirctionaryWithObjectsAndKeys: t1, @" front-left ", t2, @"front-right", t3, @"back-left ", t4, @"back-right", nil ];
使用方法objectForKey: 获取字典中的值,向方法传递之前用来存储该值的关键字:
-(id) objectForKey: (id) aKey;
如:查找右后边的轮胎,可以这样写:
Tire *tire = [ tires objectForKey: @"back-right"];
可使用dictionaryWithCapacity: 方法来创建新的可变字典并且告诉Cocoa 该字典的最终大小。
创建新的NSMutableDictionary 对象,向类NSMutableDictionary 发送dictionary 消息。
+ (id) dictionaryWithCapacity: (unsigned int ) numltems;
可以使用setObject:forkey: 方法给字典添加元素:
-(void) setObject: (id) anObject forKey: (id) aKey;
创建存储轮胎的字典的方法:
NSMutableDictionary *tires;
tires = [NSMutableDictionary dictionary ];
[ tires setObject: t1 forKey: @"front-left"];
[ tires setObject: t2 forKey: @"fornt-right"];
[ tires setObject: t3 forKey: @"back-left"];
[ tires setObject: t4 forKey: @"back-right"];
可变字典中删除一个关键字,可使用removeObjectForKey: 方法
-(void) removeObjectForKey: (id) aKey;
一只轮胎脱落,就可以把那只轮胎删除:
[ tires removeObjectForKey: @"back-left "];
使用,但不要扩展
在Cocoa 中,许多类实际是以类簇的方式实现的。它们是一群隐藏在通用接口之下的与实现相关的类。
创建NSSring 对象时,实际上获得的可能是NSLiteralString、NSCFString、NSSimpleCString、NSBallOfString 或其他未写入文档的与实现相关的对象。
NNString 或 NSArray 的使用者不用在意系统内部到底用的是哪个类,但给一个类簇创建子类是一件令人沮丧的事情。
可通过将NSString 或 NSArray 复合到你的某个类中或使用类别来解决这种编程问题,而不用创建子类。(Chapter 12 介绍使用类别)
各种数值
NSArray 和 NSDictionary 只能存储对象,不能直接存储任何基本类型的数据,但可以用对象来封装基本数值。
如:将int 型数据封装到一个对象中,然后就可以将这个对象放入NSArray 或 NSDictionary 中了。
NSNumber
Cocoa 提供了NSNumber 类来包装(以对象形式实现)基本数据类型。
创建新的NSNumber 对象:
+(NSNumber *) numberWithChar: (char) value;
+(NSNumber *) numberWithInt: (int) value;
+(NSNumber *) numberWithFloat: (float) value;
+(NSNumber *) numberWithBool: (BOOL) value;
还有其他:无符号版本、各种long 型数据、long long 整型数据。
创建NSNumber 之后,可以把它放入一个字典或数组中:
NSNumber * number;
number = [ NSNumber numberWithInt: 42];
[array addObject: number];
[dirctionary setObject: number forKey: @"Bork"];
将一个基本类型数据封装到NSNumber 中后,通过下面实例方法重新获得它:
-(char) charValue;
-(int) intValue;
-(float) floatValue;
-(BOOL) boolValue;
-(NSString *) stringValue;
将创建的方法和提取方法配对使用时完全可以的。
如:用numberWithFloat: 创建的NSNumber 可以用intValue 来提取数值。NSNumber 会对数据进行适当的转换。
通常将一个基本类型的数据包装成对象叫做装箱(boxing), 从对象中提取基本类型的数据叫做取消装箱(unboxing)。
有些语言有自动装箱功能,它可以自动包装基本类型的数据,可以自动从包装后的对象中提取基础数据。
Objective-C 不支持自动装箱。
NSValue
NSNumber 实际上是NSValue 的子类, NSValue 可以包装任意值。
可以用NSValue 将结构放入NSArray 和 NSDictionary 中。
用下面的类方法创建新的NSValue :
+(NSValue *) valueWithBytes: (const void *) value objCType: (const char *) type;
传递的参数是要包装的数值的地址(如一个NSSize 或 你自己的struct )。得到存储的变量的地址。
@encode 编译器指令可以接受数据类型的名称并生成合适的字符串。
如:把NSRect 放入 NSArray 中:
NSRect rect = NSMakeRect(1, 2, 30, 40);
NSValue *value;
value = [NSValue valueWithBytes: &rect objCType: @encode(NSRect)];
[array addObject: value];
使用方法 getValue: 提取数值:
-(void) getValue: (void *) value;
调用getValue: 时,要传递的是存储这个数值的变量的地址:
value = [ array objectAtIndex: 0];
[ value getValue: &rect ]; // 表明提供的是一个指针,而指针所指向的空间用来存储该方法生成的数据。
Cocoa 提供了将常用的struct 型数据转换成NSValue 的便捷方法,如:
+(NSValue *) valueWithPoint: (NSPoint) point;
+(NSValue *) valueWithSize: (NSSize) size;
+(NSValue *) valueWithRect: (NSRect) rect;
-(NSPoint) pointValue;
-(NSSize) sizeValue;
-(NSRect) rectValue;
按下面方式在NSArray 中存储和检索NSRect:
value = [ NSValue valueWithRect: rect ];
[array addObject: value];
...
NSRect anotherRect = [value rectValue ];
NSNull:
不能在集合中放入 nil 值,在NSArray 和 NSDictionary 中 nil 有特殊的含义。
NSNull 大概是Cocoa 里最简单的类了,只有一个方法:
+(NSNull *) null;
添加到集合中:
[contact setObject: [NSNull null ] forKey: @" home fax machine"] ;
访问它的方法如下:
id homefax;
homefax = [ contact objectForKey: @" home fax machine "];
if( homefax == [NSNull null])
{
// ... no fax machine. rats.
}
查找文件
程序 FileWalker, 查找.jpg 文件并输出找到的文件列表。
该程序用到了NSString、NSArray、NSEnumerator 、NSFileManager (对文件系统进行操作,创建目录、删除文件、移动文件、获取文件信息)以及其他两个用来与文件系统交互的 Foundation 类。
用NSFileManager 创建 NSdirectoryEnumerator 来遍历文件的层次结构。
int main( int argc, const char *argv[])
{ NSAutoreleasePool *pool; // 自动释放池的样板代码
pool = [[NSAutoreleasePool alloc] init];
NSFileManager *manager; // 获取 NSFileManager 对象
manager = [NSFileManager defaultManager]; // defaultManager 的类方法
NSDirectoryEnumerator *direnum;
direnum = [manager enumeratorAtPath: home];
NSMutableArray *files;
files = [NSMutableArray arrayWithCapacity: 42];
NSString *filename;
while(filename = [direnum nextObject])
{
if([[filename pathExtension] isEqualTo: @"jpg"])
{ [files addObject: filename]; }
}
NSEnumerator *fileenum;
fileenum = [files objectEnumerator];
while(filename = [fileenum nextObject])
{ NSLog(@"%@", filename); }
[pool drain];
return (0);
}//main
Cocoa 很多类都是单实例架构,即只需要一个实例。
只需要一个文件管理器,或一个字体管理器,或一个图形内容。
这些类都提供一个类方法用来访问唯一的共享对象。
在程序 FileWalker 中,需要一个目录迭代器。在要求文件管理器创建目录迭代器之前,需要先确定从文件系统中的什么位置开始查找文件。
Unix 系统(和 Mac OS X 系统)有一个代表主目录的速记符号~ (也称为代字符)。
~/Documents 代表目录 Documents
~/junk/oopack.txt 在Mark 的计算机上就是 /Users/marked/junk/oopack.txt
NSString 中有一个方法可以接受~ 字符并将其展开。
NSStirng * home;
home = [@"~" stringByExpandingTildeInPath ]; // stringByExpandingTildeInPath 将 ~ 替换成当前用户的主目录。
在Mark 的计算机上,主目录是/Users/marked
将路径字符串传递给文件管理器:
NSDirectoryEnumerator *direnum;
direnum = [ manager enumeratorAtPath: home ];
NSDictionaryEnumerator 是 NSEnumerator 的子类。每次在这个枚举器对象中调用 nextObject 时,都会返回该目录中一个文件的另一个路径。
这个方法也能搜索子目录,也能为每个文件创建一个属性字典。
迭代循环结束时,将得到主目录中每一个文件的路径。
要查找.jpg 文件,在程序的其他地方对所有这些文件做一些操作。NSMutableArray 就是第一选择。
创建一个可变数组并将匹配的路径添加进去:
NSMutableArray *flles;
files = [ NSMutableArray arrayWithCapacity: 42 ]; // 容量参数并不能限制数组的大小,在任何情况下,这样定义都是可以的。
开始循环:
NSString *filename;
while( filename = [ direnum nextObject ])
{
目录枚举器返回一个代表文件路径的NSString 字符串。像NSEnumerator 一样,当枚举结束时它会返回 nil , 循环终止。
NSString 提供了许多处理路径名称和文件名称的便捷工具。
如:方法pathExtension 输出文件的扩展名(去掉了扩展名前面的点)。
oopack.txt 文件调用pathExtension 将会返回 @"txt"
vikkiCat.jpg 则返回@"jpg"
使用嵌套的方法调用来获取路径扩展名并获得的字符串传递给 isEqualTo: 。
调用返回结果为YES,则该文件名将会被添加到文件数组中。
如:
if ( [[ filename pathExtension ] isEqualTo: @"jpg"])
{ [ files addObject: filename]; }
目录循环结束后,遍历文件数组,用NSLog() 将数组内容输出:
NSEnumerator * fileenum;
fileenum = [ files objectEnumerator ];
while( filename = [ fileenum nextObject ])
{ NSLog(@"%@", filename);}
用自动释放池的样板代码做一些清理工作。最后,通知main() 函数返回0 来表示程序成功退出:
[ pool drain ];
return ( 0 );
} // main
Leopard 系统背后的含义
FileWalker 程序采用典型的迭代方法
FileWalkerPro 程序采用快速枚举的方法
快速枚举语法有个特性:可以将已有的NSEnumerator 对象或其子类传递给它。
NSDictionaryEnumberator 是 NSEnumerator 的子类,可以将-enumeratorAtPath: 的结果传递给快速枚举:
int main( int argc, const char * argv[ ])
{
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init ];
NSFileManager * manager;
manager = [ NSFileManager defaultManager ];
NSString *home;
home = [ @"-" stringByExpandingTildeInPath ];
NSMutableArray * files;
files = [ NSMutableArray arrayWithCapacity: 42];
for( NSString *filename in [manager enumeratorAtPath: home])
{
if( [[filename pathExtension] isEqualTo: @"jpg"])
{ [ files addObject: filename] ; }
}
for(NSString *filename in files)
{ NSLog(@"%",filename);}
}
8.7 小结
1> 类方法,即由类本身而不是某个实例来处理的方法;
2> @encode() 命令;
3> 用于需要生成C型描述的方法:快速枚举。
Cocoa 类:包括NSString、NSArray、NSDictionary等。
NSString 存储人类可读的文本。
NSArray、NSDictionary 存储对象的集合。这些对象都是不可变的。Cocoa 提供了这些类的可变版本,可随意更改它们的内容。
Chapter 09:内存管理
如何使用Objective-C 和 Cocoa 进行内存管理。
内存管理是程序设计中常见的资源管理的一部分。资源包括:内存、打开文件数量、网络连接等。
使用Java 和 脚本语言的程序员无需考虑内存相关的错误。
Cocoa 解决内存管理方案非常简洁,但要精通掌握需费些时日。
如果只打算在Leopard 或更高版本的Mac OS X 操作系统上运行程序,可以利用Objective-C 2.0 的垃圾回收机制。
若在旧版本的Mac OS X 操作系统上运行,或从事iPhone 开发,需阅读本章全部内容。
9.1 对象生命周期
对象的生命周期包括诞生(通过alloc 或 new 方法实现)、生存(借助方法的组合和参数)、交友以及当它们的生命结束时最终死去(被释放)。
当对象的生命周期结束时,它们的原材料(内存)将被回收以供新的对象使用。
9.1.1 引用计数
Cocoa 采用一种称为引用计数(reference counting)的技术,有时也叫做保留计数。每个对象有一个与之相关联的整数,称作它的引用计数器或保留计数器。
通过此技术可以判断对象的使用寿命寿命时候结束了。
当使用alloc、new 方法或者通过copy 消息(生成接收对象的一个副本)创建一个对象时,对象的保留计数器值被设置为1。
要增加对象的保留计数器值,可以给对象发送一条retain 消息。
要减少对象的保留计数器值,可以给对象发送一条release 消息。
当一个对象因其保留计数器归0 即将被销毁时,Objective-C 自动向对象发送一条dealloc 消息。
可以在自己的对象中重写dealloc 方法。可以通过这种方法释放已经分配的全部相关资源。一定不要直接调用dealloc 方法。
可以利用Objective-C 在需要销毁对象时调用dealloc 方法。
要获得保留计数器的当前值,可以发送retainCount 消息。
3 种方法的签名:
-(id) retain; // 可以嵌套执行带有其他消息发送参数的保留调用,增加对象的保留计数器值并要求对象完成某种操作。
-(void) release;
-(unsigned) retainCount;
// 程序创建一个RetainTracker 类的对象,该对象在初始化和销毁时调用NSLog() 函数:
@interface RetainTracker: NSObject
@end
@implementation RetainTracker
-(id) init // init 方法遵循标准的Cocoa 对象初始化方式。
{
if(self = [super init ])
{
NSLog(@"init: Retain count of %d, ", [self retainCount ]);
}
return (self);
}// init
-(void) dealloc
{
NSLog(@"dealloc called. Bye Bye. " [super deallo] );
} // dealloc
@end
int main( int argc, const char *argv [ ] )
{
RetainTrack *tracker = [ RetainTrack new]; // 创建一个新的RetainTracker 类对象时,Objective-C 会发送retain 消息和 release 消息,以增加和减少对象的保留计数器值。
// count: 1
[tracker retain]; // count: 2
NSLog(@"%d", [tracker retainCount ]);
[tracker retain]; // count: 3
NSLog(@"%d", [tracker retainCount ]);
[tracker release ]; // count: 2
NSLog(@"%d", [tracker retainCount ]);
[tracker release ]; //count: 1
NSLog(@"%d", [tracker retainCount ]);
[tracker retain]; // count: 2
NSLog(@"%d", [tracker retainCount]);
[tracker release]; // count: 1
NSLog(@"%d", [tracker retainCount ]);
[tracker release]; //count: 0, dealloc it
return (0);
}
9.1.2 对象所有权
可以创建对象、使用对象、释放对象,内存管理较为容易。
但开始考虑对象所有权(object ownership)这一概念时,内存管理就变得更加复杂了。
当说某个实体“拥有一个对象” 时,就意味着该实体要负责确保对其拥有的对象进行清理。
如果一个对象具有指向其他对象的实例变量,则称该对象拥有这些对象。
当多个实体拥有某个特定对象时,对象的所有权关系就更复杂了。
9.1.3 访问方法中的保留和释放
-(void) setEngine: (Engine *) newEngine
{
[ newEngine retain ];
[engine release ];
engine = newEngine;
} // setEngine
9.2 自动释放
当我们不再使用对象时必须将其释放。但弄清什么时候不再使用一个对象并不容易。
-(NSString *) description
{
NSString *description;
// description = [[ NSString alloc ] initWithFormat: @"I am %d years old ", 4 ]; // 用alloc 方法创建一个新的字符串实例,然后返回该字符串实例。
NSString *desc = [someObject description ];
NSLog(@"%@", desc);
[desc release];
return (description);
}
9.2.1 所有对象全部入池
Cocoa 中有一个自动释放池(autorelease pool)的概念。
自动释放池是一个存放实体的池(集合),这些实体可能是对象,能够被自动释放。
NSObject 类提供了一个 autorelease 方法:
-(id) autorelease;
该方法预先设定了一条在将来某个时间发送的release 消息,返回值是接收消息的对象。
retain 消息采用了相同的技术,使嵌套调用更加容易。
当给一个对象发送 autorelease 消息时,实际上是将该对象添加到 NSAutoreleasePool 中。
当自动释放池被销毁时,会向该池中的所有对象发送 release 消息。
可以使用NSMutableArray 来编写自己的自动释放池,以容纳对象并在 dealloc 方法中向池中的所有对象发送 release 消息。
编写一个能够很好地管理内存的 description 方法:???
-(NSString *) description
{
NSString * description; // 创建一个新的字符串对象。
description = [[ NSString alloc] initWithFormat: @"I am %d years old", 4 ];
return ( [description autorelease] );
}
NSLog(@"%@", [someObject description]);
9.2.2 自动释放池的销毁时间
自动释放池什么时候被销毁,以便可以向其包含的所有对象发送 release 消息?
什么时候首先创建自动释放池?
在 Foundation 库中,创建和销毁自动释放池的方法如下:
NSAutoreleasePool *pool;
pool = [[NSAutoreleasePool alloc ] init ];
...
[pool release];
创建一个自动释放池时,该池自动成为活动的池。
释放该池时,其保留计数器值归0,然后该池被销毁。在销毁过程中,该池释放其包含的所有对象。
使用AppKit 时,Cocoa 定期自动为你创建和销毁自动释放池。
在Xcode 还有另一种销毁自动释放池对象的方式:-drain 方法。该方法只是清空自动释放池而不销毁它。该方法只适用于 Mac OS X 10.4 及更高版本。
9.2.3 自动释放池的工作过程
程序 RetainTracker2
int main(int argc , const char * argv [ ] )
{
NSAutoreleasePool *pool;
pool = [[ NSAutoreleasePool alloc ] init ];
RetainTracker *tracker;
tracker = [ RetainTracker new ]; // count: 1
[tracker retain ]; // count: 2
[tracker autorelease ]; // count: still 2
[tracker release]; // count: 1
NSLog(@"releasing pool ");
[pool release];
// gets nuked, sends release to tracker
return ( 0 );
} // main
============================================================================
书名:Programming in Objective-C 2.0 Objective-C 2.0 程序设计(原书第2版)
作者:[美] 科施恩
出版社:机械工业出版社
出版时间:2009年09月
本书内容:
全书分为四大部分,内容包括(共21章):
第一部分:全面讲解了 Objective-C 语言的基础知识,包括类、对象、方法、数据类型、表达式、程序结构、继承、多态、动态类型和动态绑定、函数、数组、结构和指针等。
第二部分:详细阐述了Foundation框架,涵盖数字、字符串、集合、文件操作、内存管理、对象复制和归档等重要内容;
第三部分:简要介绍了Cocoa 和 iPhone SDK;
第四部分:主要列出了Objective-C 的快速参考。
Chapter 01:前言
20世纪80年代早期 Brad J.Cox 发明了Objective-C。
2007年,苹果公司发布了Objective-C 2.0 。
iPhone 操作系统实际上是某个 Mac OS X 版本。苹果公司提供了强大的软件开发套件(SDK),iPhone 模拟器使得开发人员直接在开发系统上调试应用成为可能,无需在实际的iPhone 或 iPod Touch 设备上下载并测试程序。
本书从例程库(如Foundation 框架和 Application Kit 框架)的角度讲解这门语言。并讲解如何使用一些开发工具(如Mac 的 Xcode 和 Interface Builder 等)。
第一部分 Objective-C 语言(Chapter 02~13)
Chapter 02:Objective-C 程序设计
// First program example
Build and Go 意味着“构建,然后执行上次最后完成的操作”;
编辑第一个Objective-C 程序。
打开Xcode (Version 4.6.2)(4H1003)
1> 选择 Create a new Xcode project
2> 选择左边的 OS X 下的 Application,再选择右边的 Command Line Tool, 选Next
3> 输入Product Name:proj1,组织名称:StartAoA,公司标识:StartAoA ,Type:Core Foundation,选Next
4> 选择存放位置,按Create 完成。
Q:出现‘NSAutoreleasePool’ is unavailable: not available in automatic reference counting mode.
A:Bulid Settings-- Apple LLVM compiler 4.2-- Language--->Objective-C Automatic Reference Counting 将YES 改为NO 。
...
下面用gcc 的GNU Objective-C 编译器来编译并链接这个程序。格式如下:
gcc -framework Foundation files -o progname
$ gcc -framework Foundation prog1.m -o prog1 Compile prog1.m & call it prog1
$ ./prog1 Execute prog1
2013-05-27 11:56:20.210 prog1[7985: 10b] Programming is fun!
$
2.2 解释第一个程序
在Objective-C 中,大小写字母是不同的。
Chapter 03:类、对象和方法
3.1 到底什么是对象
假定你有一辆汽车,你的汽车是汽车的一个实例。car 就是类的名称,这个实例就是从该类创建的。汽车的每个实例都称作一个对象。
如下汽车对象的操作:
3.2 实例和方法
对象是类的独特表示,每个对象都包含一些通常对该对象来说是私有的消息(数据)。方法提供访问和改变这些数据的手段。
Objective-C 对类和实例应用方法:
[ClassInstance method];
左方括号紧跟的名称-- 类的名称 或 该类的实例的名称。空格后面是要执行的方法。最后用右括号和结束分号来终止。
请求一个类或实例来执行某个操作时,就是在向它发送一条消息,消息的接收者称为接收者。
另一种方式所描述的一般格式,如:
[ receiver message ];
你获得一辆新车,去制造厂购买一辆,如:
yourCar = [ Car new]; 得到一辆新车
下面是一些可能为这辆新车编写的示例消息表达式:
[yourCar prep]; 准备好第一次使用
[yourCar drive]; 驾驶汽车
[yourCar wash]; 洗车
[yourCar getGas]; 如果需要就给汽车加油
[yourCar service]; 维修
[yourCar topDown]; 是否为一辆敞篷车
[yourCar topUp];
currentMileage = [yourCar currentOdometer]; 当前的行驶里程
你姐姐Sue 对她的汽车实例使用相同的方法:
[suesCar drive];
[suesCar wash];
[suesCar getGas];
将同一个方法应用于不同的对象是面向对象程序设计的主要应用之一。
3.3 用于处理分数的Objective-C 类
// Simple program to work with fractions
#import
int main( int argc, const char *argv [ ] )
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
int numerator = 1;
int denominator = 3;
NSLog(@"The fraction is %i/%i", numerator, denominator);
[pool drain];
return 0;
}
如果把一个分数定义成单个实体,用单个名称(如myFraction)来共同引用它的分子和分母,这种方法可以使用Objective-C 来实现,并从定义一个新类开始。
// Program to work with fractons - class version
#import
//------ @interface section ----------- 用于描述类、类的数据成分、类的方法
@interface Fraction:NSObject
{
int numerator;
int denominator;
}
-(void) print;
-(void) setNumerator: (int) n;
-(void) setDenominator: (int) d;
@end
//------@implementation section ----------- 实现这些方法的实际代码
@implementation Fraction
-(void) print
{
NSLog(@"%i / %i", numerator, denominator);
}
-(void) setNumerator: (int) n
{
numerator = n;
}
-(void) setDenominator: (int) d
{
denominator = d;
}
@end
//------------ program section ------------ 实现程序预期目的的程序代码
int main( int argc, const char *argv[])
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
Fraction *myFraction;
// Create an instance of a Fraction
myFraction = [Fraction alloc];
myFraction = [myFraction init ];
// Set fraction to 1/3
[myFraction setNumerator: 1];
[myFraction setDenominator: 3];
// Display the fraction using the print method
NSLog[@"The value of myFracton is: "];
[myFraction print];
[myFraction release];
[pool drain];
return 0;
}
3.4 @interface 部分
定义新类时,需要做的一些工作。按照约定,类名以大写字母开头。
1> 通知Objective-C 编译器这个类来自何处。也就是说必须命名它的父类。
2> 确定这个类对象要存储的数据的类型。也就是说必须描述类成员将包含的数据。这些成员叫做实例变量。
3> 必须定义在处理该类的对象时将要用到的各种操作或方法的类型。
格式如下:
@interface NewClassName : ParentClassName
{
memberDeclarations;
}
methodDeclarations;
@end
下面谈论一些Objective-C 中指定名称的有关规则。
3.4.1 选择名称
变量可以用于存储浮点数、字符,甚至是对象(或确切的说,是对对象的引用)。
名称必须以字母或下划线开头,之后可以是任何大小写字母、下划线、0~9 之间的数字组合。
3.4.2 实例变量
3.4.3 类和实例方法
开头的负号通知Objective-C编译器,该方法是一个实例方法。
正号表示类方法,类方法是对类本身执行某些操作的方法。
返回值
若没有指定任何类型,那么id 将是默认类型。
方法的参数
3.5 @implementation 部分
格式如下:
@implementation NewClassName
methodDefinitions;
@end
3.6 Program 部分
Apple 的运行时系统提供了一个称为垃圾回收的机制,它可自动清理内存。
但最好要学会如何自己管理内存的使用,而不是依赖于自动的机制。
iOS平台(如:iPhone) 不能依赖于垃圾回收机制。
3.7 实例变量的访问以及数据封装
实例方法总是直接访问它的实例变量。然而类方法则不能,因为它只处理类本身,并不处理类的任何实例。
Chapter 04:数据类型和表达式
显示long double 的值,如:%Lf , %Le, 而%Lg 告诉NSLog 在%Lf 和 %Le 之间任选一个使用。
short int 整数在任何情况下,分配的空间不少于16位。要显示short int 变量,如:%hi、%ho、%hx。
基本数据类型
定义一个新类,创建一个Calculator 类,可用来执行加、减、乘、除运算。
将累加器设置为特定值、将其清空(或设置为0),以及在完成时检索它的值。
// Implement a Calculator class
#import
@interface Calculator: NSObject
{
double accumulator;
}
@end
用于处理Boolean (0 和 1)值的_Bool,以及分别用于处理复数和抽象数字的_Complex 和 _Imaginary 。
通过关键字typdef 实现 BOOL 替代_Bool 来处理Boolean 值。
Chapter 05:循环结构
等价于
if ([chessGame isOver] == NO && [chessGame whoseTurn] == YOU)
[chessGame yourMove ];
等价于
if ([chessGame isOver] == NO)
if([chessGame whoseTurn] == YOU )
[chessGame yourMove ];
else
[chessGame myMove ];
else if ( expression 2)
program statement 2
else
program statement 3
注:最好使用标准库中的例程islower 和 isupper,需在程序中包含程序行 #import
程序的彻底性(thoroughness)问题
6.2 switch 语句
switch ( expression )
{
case value1:
program statement
program statement
...
break;
case value2:
program statement
program statement
...
break;
case valuen:
program statement
program statement
...
break;
default:
program statement
program statement
...
break;
}
Chapter 07:类
7.1 分离接口和实现文件
类的声明(即@interface 部分)要放在它自己的名为class.h 的文件中。
类的定义(即@implementation 部分)通常放在相同名称的文件中,但扩展名要使用 .m 。
7.2 合成存取器方法
从OC 2.0 开始,可自动生成设置函数方法和获取函数方法(统称为存取器方法)。
1> 在接口部分中使用@property 指令标识属性。这些属性通常是实例变量。
2> 在实现部分中使用@synthesize 指令即可。
7.3 使用点运算符访问属性
从OC 2.0 开始,可以使用点运算符。
一般格式如下:
instance.property
类似的语法如:
instance.property = value
等价于
[instance setProperty: value]
7.4 具有多个参数的方法
7.4.1 不带参数名的方法
7.4.2 关于分数的操作
7.5 局部变量
局部变量没有默认的初始值,所以在使用前要先赋值。
7.5.1 方法的参数
方法的参数名也是局部变量。
执行方法时,通过方法传递的任何参数都被复制到局部变量中。因方法使用参数的副本,所以不能改变通过方法传递的原值。
如果参数是对象,可以更改其中实例变量值。
7.5.2 static 关键字
静态变量的初始值为0。
7.6 self 关键字
关键字 self 用来指明对象是当前方法的接收者。
7.7 在方法中分配和返回对象
Chapter 08:继承
8.1 一切从根类开始
没有父类的类位于类层次结构的最顶层,称为根类(root)。
在OC中,允许你定义自己的根类。根类通常在接口文件中指定。
从术语的角度而言,可以将一个类称作子类和父类。相似地,还可以将类称作子类和超类。
注意:alloc 和 init 是你用过的从未在类中定义过的方法。这是因为你利用了它们都是继承方法的事实。
继承的概念作用于整个继承链。
找出正确的方法
向对象发送消息时,如何选择正确的方法来应用到该对象。规则如下:
1> 检查该对象所属的类,以查看在该类中是否明确定义了一个具有指定名称的方法。
如果有,就使用这个方法。如果没有定义,就检查它的父类。如果父类中有定义,就用这个方法。否则,将继续寻找。
有两种情况,才会检查父类:发现包含指定的方法的类,或者一直搜索到根类也没有发现任何方法。如果是第一种情况,停止查找。若是第二种情况,说明存在问题,就会生成警告消息。
8.2 通过继承扩展 -- 添加新方法
继承通常用于扩展一个类。
分类(category)机制允许以模块方式向现有类定义添加新方法,也就是,不必经常给同一接口和实现文件增加新定义。
8.2.1 Point 类和内存分配
8.2.2 @class 指令
8.2.3 具有对象的类
合成另一种 setter 方法,而不是制作对象的副本。
8.3 重载方法
8.3.1 选择哪个方法
8.3.2 重载dealloc 方法和关键字 super
8.4 通过继承扩展:添加新的实例变量
8.5 抽象类
Chapter 09:多态、动态类型和动态绑定
多态:可以使来自不同类的对象可以定义共享相同名称的方法;
动态类型:使程序直到执行时才确定对象所属的类;
动态绑定:使程序直到执行时才确定对象调用的实际方法。
9.1 多态:相同的名称,不同的类
Chapter 10:变量和数据类型
Chapter 11:分类和协议
Chapter 12:预处理程序
Chapter 13:基本的C 语言特性
13.1 数组
OC 允许用户定义一组有序的数据项,即数组。
OC 中数组元素以 0 索引开头。
13.1.1 数组元素的初始化
13.1.2 字符数组
如果在字符数组结尾添加一个终止空字符('\0'),就产生了一个通常称为字符串的变量。
13.1.3 多维数组
OC 中,第一个索引指的是行数,第二个索引指的是列数。
13.2 函数
13.2.1 参数和局部变量
函数中局部变量的行为同方法中的一样,如果在函数内给变量赋予初始值,那么每次调用该函数时,都会指定相同的初始值。
在函数中(和在方法中一样)定义的变量称为自动局部变量,因为每次调用该函数时,它们都自动“创建”,并且它们的值对于函数来说是局部的。
静态局部变量用关键字static 声明,它们的值在函数调用的过程中保留下来,并且初始化默认为0。
13.2.2 函数的返回结果
第二部分:Foundation 框架(Chapter 14-19)
Chapter 14:Foundation 框架简介
Chapter 15:数字、字符串和集合
Chapter 16:使用文件
Chapter 17:内存管理
Chapter 18:复制对象
Chapter 19:归档
第三部分:Cocoa 和 iPhone SDK
Chapter 20:Cocoa 简介
NSLog 例程在控制台上显示消息,功能有限。但Xcode 和 Interface Builder 应用程序的组合可以满足这个要求。
该组合提供一个包含编辑和调试工具的强大程序开发环境。
1> 可方便访问在线文档。
2> 提供一个可轻松开发复杂图形用户界面(GUI)的环境。
Cocoa 是一种框架,由两个框架组成:Foundation 框架、Application Kit (或AppKit)框架。
AppKit 提供与窗口、按钮、列表等相关的类。
20.1 框架层
应用程序层次结构如下图:
内核:以设备驱动程序的形式提供与硬件的底层通信。负责管理系统资源,包括调度要执行的程序、管理内存、电源、执行基本的 I/O 操作。
核心服务:提供比上面层次更加底层、更加核心。负责对集合、网络、调试、文件管理、文件夹、内存管理、线程、时间、电源的管理。
应用程序服务层:负责对打印、图形呈现的支持,包括:Quartz、OpenGL、Quicktime。
Cocoa 层:包括Foundation 和 AppKit 框架。Foundation 框架提供的类用于处理集合、字符串、内存管理、文件系统、存档等。
AppKit 框架提供的类用于管理视图、窗口、文档和让Mac OS X 操作系统的多信息用户界面。
20.2 接触Cocoa
iPhone 包含一台运行Mac OS X 缩小版本的计算机。iPhone 硬件中的有些功能,如加速器,是电话中独一无二的。MacBook 或 iMac 中也找不到。
Cocoa 框架用于Mac OS X 桌面与笔记本电脑的应用程序开发。
Cocoa Touch 框架用于iPhone 与 ipod Touch 的应用程序开发。在Cocoa Touch下,UIKit 代替了AppKit 框架,以便为很多相同类型的对象提供支持,如:窗口、视图、按钮、文本域等。
Cocoa Touch 提供使用加速器(它与GPS 和 WiFi 信号一样都能跟踪你的位置)的类和触摸式界面,并去掉不需要的类,如:支持打印的类。
Chapter 21:编写iPhone 应用程序
本章介绍两个简单的iPhone应用程序。
1> 第一个应用程序: 如何使用Interface Builder、建立连接,理解委托、出口、操作。
2> 第二个应用程序: 分数计算器。
21.1 iPhone SDK
编写iPhone应用程序,需安装Xcode 和 iPhone SDK。SDK 可免费下载。
本章基于Xcode 3.1.1 和 iPhone SDK for iPhone OS 2.1 。
21.2 第一个iPhone 应用程序
如何在屏幕上放置一个黑色窗口,用户按下一个按钮,该窗口就会显示一些文本。
用Xcode 创建这个应用程序,用Interface Builder 创建用户界面。
Interface Builder 这个工具通过将各种UI 元素(如表、标签、按钮)放入组成 iPhone 屏幕的窗口中,从而设计出用户界面。
iPhone 模拟器复制了iPhone 环境的大部分,包括它的主屏幕、Safari Web 浏览器、Contacts 应用程序等。
为了在iPhone 设备上运行应用程序,需注册iPhone 开发人员计划,支付 Apple 99$ ,收到一个激活码,让你能够获得一个iPhone 开发证书。
21.2.1 创建新的iPhone 应用程序项目
iPhone 应用程序模板