起步

 

起步:

          很多的书一般会从一堆基本理论开始,但是我打算先指引你完成你的第一个Cocoa应用程序.当你完成它以后,一定是即激动又困惑. 这是就是学习理论的时候了.

          我们的第一个工程是一个有两个button的随机数获取程序.有一个文本框来显示所得到的随机数.这个简单的例子设计了怎样获得用户输入以及怎样输出.可能在这章中的一些讲解是简单了点,你会有很多的为什么.不过不要担心,我会在后面有很详细的说明.所以,先让我们开始吧.

          图2.1是程序运行起来的样子

Picture 1.png ¬

 

XCode

Interface 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:方法就被调用.进程结束,所有的对象将被销毁.

 

困惑吗?兴奋么?欲知后事如何,请听下回分解!

阅读更多

没有更多推荐了,返回首页