copy from : http://blog.sina.com.cn/s/blog_5df7dcaf0100be6f.html~type=v5_one&label=rela_nextarticle
很多的书一般会从一堆基本理论开始,但是我打算先指引你完成你的第一个Cocoa应用程序.当你完成它以后,一定是即激动又困惑. 这是就是学习理论的时候了.
我们的第一个工程是一个有两个button的随机数获取程序.有一个文本框来显示所得到的随机数.这个简单的例子设计了怎样获得用户输入以及怎样输出.可能在这章中的一些讲解是简单了点,你会有很多的为什么.不过不要担心,我会在后面有很详细的说明.所以,先让我们开始吧.
XCode
Builder
回到XCode
文档
你作了什么?
XCode
装好开发工具后,你可以在/Developer/Applications/下面找到XCode. 把它拖到Dock上吧,你会经常用到它的. 启动XCode.
前面有提到XCode把应用程序用到的所有资源放到一个叫project directory的目录下面.所以我们第一步就是生成这样的一个目录
--新建工程
点击File菜单,选择New Project... 你可以工程模板(看到图2.2),选择我们所要创建的程序模板: Cocoa Application.注意,这里还有很多其他的模板.
在这本书中,我们会创建以下几个类型的工程
. Application: 创建窗体的程序
. Tool : 没有UI的程序.也就是命令行后台程序
. Bundle 或 framework: 可以被Application和Tool使用的资源包. Bundle(也叫Plug-in)在运行时动态加载.通常应用程序在编译需要连接某些framework (比如Cocoa.framework)
我们给我们的应用程序取个名字: RandomApp, 就像图2.3 . 通常第一个字母大写. 你也可以选择一个目录来放置工程目录,默认它会被放置在用户主目录下面. 点节Finish来完成
我们就这样创建了一个工程, 其目录包含了一个程序的结构,你现在可以浏览整个工作结构,编译源程序了.
回来看看我们创建的XCode工程长什么样.在XCode的左边有一个Outline View. Outline View中的每一项包含了对一个程序员有用的信息. 文件, 消息(例如编译错误,查找结果等). 我们可以开始编辑源文件了.点选RandomApp 看看那些文件将被编译.
XCode模板创建了基本的工程结构,你可以直接编译运行它了.点击工具条上一个带铁锤的绿色按钮开始build run工程. 见图2.4
当我们的程序正在启动是,你可以在Dock上看到一个跳跃的图标. 然后程序的名字将会出现在Menu Bar上.这样我们的程序就启动激活了.程序的窗口可能被别的窗口挡住, 如果看不到我们自己的程序窗口,可以重RandomApp Menu上选择Hide Others. 这样我们就看到一个空的窗口了.就像 图2.5
麻雀虽小,五脏具全. 这个程序不能做更多的事,但它包含了基本的程序结构.它有窗口,有菜单可以相应基本的操作. 其实所有的这些功能,我们程序的代码只有一行. 让我们关掉程序,回到XCode来看看.
-- main函数
单选main.m 如果你双击它. 它将会在另外一个窗口打开.因为我一天经常出来很多的文件,所以我选择 single-window风格. 点击工具条上的Editor可以劈开右边的区域得到一个编辑区域. 代码这是就呈现在你的面前了图2.6.(关于XCode开发工具,读者可以自己先查查资料练练手.用上它后你应该会喜欢上它的.)
到现在,你没有修改过main.m, main函数只是简单的调用了NSApplicationMain(). 这个函数会把用户界面对象从一个nib文件中加载. Nib文件是由Interface Builder创建的(NIB - NeXT Interface Builder; NS - NeXTSTEP).一旦把用户界面对象加载后,我么的程序就处在等待用户的输入状态中.当用户作了相应事件操作,我们的代码将自己被调用.如果之前你没有编写过这种用户界面程序,那么这是一个令您吃惊的变化:用户掌握控制权,你所写的代码只是去响应用户的操作.
Interface Builder
是时候看看nib文件了,你可以在XCode左边Outline View的 Resources下面找到 MainMenu.nib . 双击它就可以叫起Interface Builder(图2.7)来打开它.看上去会有很多的窗口出现了.这时你可以把其他的应用程序先隐藏起来.在Interface Builder的菜单下,你可以找到熟悉的Hide Others
我们通过Interface Builder来创建编辑用户界面对象(比如, 窗体和按钮)并把它们存储成文件.我们也可以生成我们自己定义的类的对象.并且把它和那些标准提供的用户界面对象连接起来.这时如果用户通过用户界面对象交互,那么那些连接的对象将会相应而执行其中我们编写的代码. (解析一下: 比如我们创建一个Foo的类,在Interface Builder中实例化一个Foo类的对象叫foo. 然后把foo和窗体上的一个NSButton对象连接起来. 这是当用户点击那个NSButton对象是,程序将调用到foo中的代码来执行想要的动作了).
-- The Library Window
在这里你可以找到很多用户界面对象的模板,你可以把他们拖拽到你的界面上.比如,拖一个你想要得按钮
-- The Blank Window
它代表了一个储存在你的nib文件中的NSWindow类的实例对象.当你从The Library Window拖拽一些界面对象到这个窗体里时,这些界面对象将添加到你的nib文件中了.
当你创建了用户界面对象,并设置好它们的属性. 把它们保存成nib文件就像是把这些对象冷冻成一个文件.当应用程序运行读取这些nib文件时,就想是把这些用户界面对象解冻使用.用官方的话说就是"对象被Interface Builder封装成nib文件.当程序运行时再把它们解包."
-- 布局界面
好了,我们现在要创建我们的界面了,就像图2.8
从 Library window拖一个按钮到我们的空白窗体上(如图2.9).(为了更好的找到按钮对象,你可以在Library window的顶部选择Cocoa->Views&Cells 组)
双击放置好的按钮,把它的title修改成Seed random number generator using time
把该按钮拷贝,再粘帖生成一个新的按钮.title修改成Generate random number.再从Library window拖一个Label文本框到窗体上(图2.10)
为了是文本框和按钮一样宽,我么可以拖动它的左右两边来进行调整.(你可能已经注意到当你拖动到快接近窗体的边缘时,会有一些蓝色的线条出现.这是指示是为了让你更好得遵循 Apple GUI风格故意设计的)
把窗体弄小点
为了让文本框居中,你需要使用到 Inspector. 选中文本框,从Tools菜单上选择Attributes Inspector, 点选居中按钮(图2.11)
提示: 我的Inspector 窗口从早上到现在一直没有关闭过
-- The Doc Window
在nib文件中,有些对象是可见的(eg, 按钮对象), 而有些是不可见的(eg,自定义cotroll类的对象). Doc Window中就包含了这些不可见的对象.
在Doc Window(标题为 MainMenu.nib), 你可以找到main menu 和 window. 这两个前面有介绍就是程序的菜单和窗体. First Responder是一个假定对象,我们会在21章来讨论它. File's Owner是我们会在12章来讨论.
-- 创建类
对于Objective - C, 我们使用两个文件来定义一个类 (当然你也可以把它们合在一个文件中). 头文件和实现文件. 头文件也就是接口文件,声明了类的成员变量和方法.实现文件定义了这些方法.
回到XCode,选择菜单 File->New File来创建一个新的 Cocoa->Objective-C 类. 并命名文件为 Foo.m (图2.12)
文件Foo.h 和Foo.m就创建出来并加入到你的工程了.你可以把它们拖拽到Classes组中.如图2.13
现在我们添加成员变量和方法,指向其他对象的成员变量我们称之为outlets. 可以被用户操作触发调用的方法我们称之为actions. 编辑Foo.h 如下
#import <Cocoa/Cocoa.h>
@interface Foo : NSObject {
IBOutlet NSTextField *textField;
}
-(IBAction)seed:(id)sender;
-(IBAction)generate:(id)sender;
@end
一个Objective - C程序员可以看出来3点
1. Foo是NSObject的子类
2. Foo有一个成员变量textField. 它指向一个NSTextField 对象
3. Foo有两个action 方法: seed 和 generate
在cocoa编程规范里, 成员变量和方法的第一个字母为小写. 如果名字由多个单词组成,除了第一个单词外,后面的单词的首字母为大写,如 favoriteColor. 而类名字首字母为大写 (读者应该去ADC上查看下 Cocoa Code Convention.好的习惯,好品质和效率)
-- 创建对象
接下来,我们为nib文件创建一个Foo类的对象.回到Interface Builder. 从Library window拖出一个蓝色的图标(Cocoa->Objects & Controllers) 到 Doc window如图2.14
通过Identity Inspector, 把它的类属性设置为Foo(图2.15).(这时,actions和outlets将会出现在Inspector中.如果没有,那么检查一下Foo.h. 或是是否完了保存Foo.h) [可以思考下,为什么它们就会出现了呢?]
-- 创建连接
大多数到面向对象语言多会处理对象之间的关系.现在我们就要做这件事了.对于Cocoa来说就是"我要设置对象的Outlet 了". 为了让对象A连接到对象B. 我们使用 Control - drag. 从对象A drag 到 对象B . 图2.16描述了我们这个例子中的对象关系
首先我们要把foo的成员变量textField指向NSTextField对象label. 右键点击(如果你是单键鼠标,那就Control-click吧或是换个双键鼠标吧)foo对象图标,这使会出现Inspector 面板. 从textField边上的圆drag到窗体的Label.如图2.17
这样我们就完成了让foo对象的textField指向窗体上的NSTextField对象了.
好了,现在我们要设置窗体上按钮对象Seed的target outlet. 让它指向我们自定义的类对象foo,来相应用户点击seed按钮,执行foo中定义的响应. 使用Control - drag,从按钮drag到foo对象,当面板出现,选择seed: 图2.18
同样的,设置好按钮Generate,让它的target outlet指向foo. 并设置其action为generate: 方法. 如图2.19
OK, Interface Builder可以暂时休息了.让我hide它,回到XCode吧
回到XCode
如果你是第一次看到Objective-C代码,你一定会不相信,它和C++或是java怎么有这么大.其实这些只是语法伤得不同而已,基本到原理还是一样的.例如,对于java声明一个类
import com.megacorp.Bar;
import com.megacorp.Baz;
public class Rex extends Bar implements Baz {
...methods and instance variables...
}
Rex继承Bar,并实现了Bar的某些方法.再看看Objective-C.就是这样
#import <megacorp/Bar.h>
#import <megacorp/Baz.h>
@interface Rex : Bar <Baz> {
...instance variables...
}
...methods...
@end
如果你熟悉Java,Objective-C对你来说就很简单了.不过记住一点,Objective-C不支持多重继承,也就是说,一个类只能有一个父类.
--类型和常量
Objective-C用到了一小部分C语言没有的数据类型
. id : 指向一个任何类型的类对象 (有点向void*)
. BOOL: typedef 至char. 表示一个布尔值
. YES: 1
. NO: 0
. IBOutlet: 空的宏.可以忽略 (注意, Interface Builder在读取类声明的.h文件.可以通过它来得到指示,那些成员变量是IBOutlet)
. IBAction: void. 和IBOutlet一样. interface builder得到指示.哪些方法是IBAction
. nil : NULL,对象指针赋值为空是使用
--看看头文件
现在打开Foo.h, 我们声明了类Foo的,继承至NSObject. 如下
#import <Cocoa/Cocoa.h>
@interface Foo : NSObject
{
IBOutlet NSTextField *textField;
}
- (IBAction)generate:(id)sender;
- (IBAction)seed:(id)sender;
@end
#import 和 #include的意义一样. 不过有点不同的是: #import能够保证头文件只包含一次. 在这里我们包含了<Cocoa/Cocoa.h>. 它声明了Foo的父类NSObject
你可能已经注意到在声明Foo时用到了@interface, 在C语言中我们没有见过符号@. 对于Objective-C的关键字都是使用@开头的.比如: @end,@implementation,@class,@selector,@protocol,@property和@synthesize.
通常你会发现书写代码是很简单的,我们可以开启XCode syntax-aware选项.你只有输入头几个字母,后面的XCode会自动提示产生. 在XCode Preferences中选择Indentation面. 勾选Syntax-aware indenting.如图2.20
--编辑定义文件
现在可以来看看Foo.m文件了. 它定义了类Foo. 在C++或是Java.定义一个方法就像这样
public void increment(Object sender) {
count++;
textField.setIntValue(count);
}
我自然语言,我们会说:"increment是一个public的成员函数,它接受一个对象参数.该函数没有返回值.它把成员变量count加1.并把count作为参数,给对象textField发送setIntValue的消息"
而在Objective-C中,我们会这样做
- (void)increment:(id)sender
{
count++;
[textField setIntValue:count];
}
Objective-C是一种非常简单的语言,它没有指定可见性(public,protect,private). 所有的方法都是public.所有的成员变量都是protected(实际上,它也支持.不过我们很少用它们, 默认为protected已经足够了)
在第三章,我们会全面的来认识一些Objective-C. 现在先走下去再说
#import "Foo.h"
@implementation Foo
- (IBAction)generate:(id)sender
{
// Generate a number between 1 and 100 inclusive
int generated;
generated = (random() % 100) + 1;
NSLog(@"generated = %d", generated);
// Ask the text field to change what it is displaying
[textField setIntValue:generated];
}
- (IBAction)seed:(id)sender
{
// Seed the random number generator with the time
srandom(time(NULL));
[textField setStringValue:@"Generator seeded"];
}
@end
(注意IBAction等价void.什么都没有返回)
因为Objective-C是C的扩展,所以我们可以调用C和Unix库提供的函数,如random(),srandom()
在你编译执行之前,你可能需要修改一下Xcode的预设置. 首先是console,它可以记录程序的错误.你可能想在每次运行前清除上次的log. 接着,因为你肯能经常在.h和它对应的.m之间切换.如图2.21
--编译运行
我们完成了第一个程序,现在点击Build and Go.(如果这个程序在之前已经运行.Build and Go是灰掉的,请先退出之前的程序)
如果你的程序有错误,会在右上有编译提示. 当点击这些提示时,出错的代码行会显示在下面.[编译器都大同小异,自己都用,摸索一下就会了] 如图2.22
运行你的程序,试试它的功能
恭喜!你完成了自己第一个Cocoa 程序
你有看到console 中的log信息吗?console是我们应该经常关注的地方,因为当cocoa有一些异常时,它会在console中记录一些信息. 在XCode的Preference设置当程序启动是显示console.如图2.23
-- awakeFromNib
你可能注意到程序的一点瑕疵: 当程序启动后, 我们可以让Label显示更有意思的东西.比如说当前的时间.
前面有提到过,对象被冷冻在nib文件里面.在程序启动到接收用户事件前,这些对象会解冻. 这个机制有点不同于编写很多代码来布局用户界面. Interface Builder让我么编辑这些界面对象的属性,然后保存到一个nib文件中.
当一个nib文件中对象要解冻时.它们的awakeFromNib方法会自动被调用[想象一下在main函数中的NSApplicationMain 做了什么. 加载nib文件.遍历nib文件中的所有对象, 调用所以对象的awakeFromNib方法. event loop ...] . 所以我们可以给Foo添加awakeFromNib方法来给文本框设置想要的当前时间了
照着添加下面的代码.可能你会有些不解,以后会知道的.不管怎样,你创建了一个NSCalendarDate对象,得到当前时间.并把它设给了文本框来显示
- (void)awakeFromNib
{
NSCalendarDate *now;
now = [NSCalendarDate calendarDate];
[textField setObjectValue:now];
}
在Foo.m中,方法定义的前后不重要,不过记得在@implementation 和 @end之间添加
代码中,我们没有调用awakeFromNib.它是自动被调用的.再次编译运行,你看到你想要得了吗?(图2.24)
在Cocoa中,有很东西会自动调用(eg. awakeFromNib).随着这本书的深入,你会慢慢知道的更多.我将尽力去为你解决这些疑惑.
文档
在完成这章前.有必要看看我们可以在那里找到帮助文档了.这也有助于你完成后面的练习.最简单的途径就是通过XCode的Help菜单,选择Documentation 如图2.25
你也可以使用 Option-Double-Click点击方法,类或是函数.XCode会自动在帮助文档中查询它们.
你作了什么?
回忆一下,你按部就班的完成了一个简单的Cocoa 程序
. 创建一个新的工程
. 创建布局了界面
. 创建自己的类
. 把界面对象和自己创建的类连接起来
. 给自己的类添加了代码
. 编译
. 测试运用
让我们来简单的讨论一下程序的运行过程: 当进程开始后,调用了NSApplication函数.该函数创建了一个NSApplication对象NSApp. NSApp读取nib文件并把其中的对象解包(解冻).然后给每个对象发送awakFromNib的消息. 然后就开始等代接受事件了. 如图2.26
当接收到用户的键盘或鼠标事件, window server[还记得它吧]会把接收到的事件放到适当的应用程序的事件队列中.NSApp从队列中读取事件并转发给界面对象(比如一个按钮),这是我们自己的代码将调用.如果我们自己的代码改变了某些view.这些view将重画. 这样一个事件检查和反馈的过程就是main event loop. 如图2.27
当从菜单中选择Quit. NSApp的terminate:方法就被调用.进程结束,所有的对象将被销毁.