2009年2月2日星期一

iPhone开发之Objective-C学习

http://www.flyblog.info/catprogramming/143.html

如果想在苹果Mac OSX系统进行开发,Objective-C是最重要的开发语言,现在Apple已经把开发的重点放在了基于Cocoa库的图形界面, 支持C++的Carbon图形库已经让出了主导地位, 而Cocoa库正是应用Objective-C语言开发的。 如果你有C/C++的编程经验, 那么在进行Objective-C的开发将会容易些,因为Objective-C本身就是C/c++的超集, 它包含C/C++的所有特性和支持标准C库, 但其语法本身更优美 简洁,更能体现OO思想也就是面向对象。

在学习Objective-C之前先把Objective-C(以下会以O-C代替)、Cocoa、Carbon以及XCode等几个名词的概念搞清楚, Objective-C是开发语言,前面说过,O-C是c/c++的超集合, Cocoa是在苹果系统上O-C实现的图形开发库相当于windows系统的win32 SDK和MFC, Carbon是早期苹果操作系统上的C/C++图形库也相当于MFC,XCode是个开发集成环境,相当于windows系统上的Visual Stdio系列。

以上介绍了苹果系统开发的基本概念,现在介绍一些Objective-C基础知识,
变量类型
Objective-C支持C的所有类型,也就是说char,unsigned char,int,long,float,double等均可使用;
结构类型 如日期的结构定义
struct Date
{
unsigned char day;
unsigned char month;
unsigned char year;
};


其它类型比如枚举、及预定义变量均和c语言用法相同
文件引用、类型导入
Objective-C中依然可以使用#include来引入其他文件,不过推荐使用#import来导入其它类型,官方文档介绍说这样可以减少出错的几率
id类型
id是Objective-C中的特殊类型, 他指向一个对象,你可以理解为c中的void*
字符串
Objective-C中可以有c类型的字符串,比如”string”,也可以用NSString类型字符串,一般用@”string”表示, 其中‘@’是将字符串”string”转换为NSString类型

以上是一些基本的语言知识, 其中大多数都和c/c++相同,目前为止除了#import之外,其它的内容对于C/C++开发人员来说都非常熟悉了。

这里先从如何调用类的方法开始说起, 看下面的代码
[object method];
[object methodWithParamter:param];


没错,Objective-C就是这样进行方法调用的,与C/c++不同的是 ‘[]’的使用, [] 表示对一个类对象的方法进行调用,其中object是类对象, method就是所调用的方法, 你可以调用带有参数的方法,不过在方法名称和参数之间要保留 ‘:’ 来告知编译器如何编译。
方法也可以有返回值,如下
 result = [object methodWithRetValue];
result = [object methodwithPrameterAndRetValue:param];


你也可以这样调用类的方法,比如NSString1 NSString* string = [NSString string];


这里方法string就是NSString类特殊方法,类似于c++中的静态成员函数

多重方法调用,比如C++ 中的 result = object1.function1 ( object2.function2() );在O-C中可以这样来实现1 result = [object1 function1:[object2 function2]];


代码中 最外层[]表示object1 调用function1方法,里面的[]表示object2 调用function2所得的结果作为function1的参数传入

多参数方法调用,比如c/c++中的 object.function(arg1, arg2,arg3 );在O-C中就该是这样的
首先是方法申明1 -(void) function:(int)arg1 secondArg:(float) arg2 thirdArg:(NSString*)arg3;


调用时就是这样的1 [object function:arg1 secondArg:arg2 thirdArg:arg3];


也就是方法名称后面直接跟参数列表, 其中第一个参数不需要别名,其它参数必须设置别名,O-C使用参数别名还确定所调用参数的,这样编译器才能正确解释,参数之间用1-N个空格分开。

上次学习了Objective-C的基础知识和函数调用的基本规则, 现在我们在来了解一下新的概念——Accessors,简单意思就是访问器。 在Objective-C中所有的类成员变量默认都是私有的, 你不可能直接访问这些成员变量。你需要用setValue 和 getValue或者value来赋值或者取值,你也可以在申明变量时制定setter和getter,下面是Objective-C1.x的语法格式
 [friend setName:@"Mike"];
value = [friend name];


普通情况下你没必要在取值的时候添加get前缀, 实际上每一次调用[friend name]都会发送get消息给对象friend。

从Mac OSX10.5 开始支持O-C2.0, 在O-C2.0 中你可以使用点(.)来对成员变量(属性)进行赋值和取值操作, 比如
 friend.name = @"Mike";
value = friend.name;


了解了如何访问类成员变量,我们在来看看如何创建一个类对象。 一般情况下可以有两种方式创建类对象, 第一种是调用类的静态成员创建自动释放对象(稍后会介绍)1 NSString* str = [NSString string];


调用NSString类的 string 方法返回一个自动释放的对象, 这样赋值很简单,不过要注意对象的作用域,后面将详细介绍自动释放对象。另一种方式是直接分配对象并初始化,如1 NSString* str = [[NSString alloc] init];


这是一个典型的嵌套调用, 这在上次已经介绍过了, 先是[NSString alloc]分配字符串对象空间, 然后调用init方法初始化, 这样创建的对象需要显式释放,也就是需要调用[str release]去释放内存空间。

到现在为止,已经学习了如何调用类方法以及创建对象。 到目前为止我们还没有看到如何才能定义一个类, 上两次的内容已经多次涉及到类及类的成员,现在我们可以学习一下如何才能定义一个自己的类。
设计一个类(接口)

一般来讲创建一个类需要两部分,首先是申明类的头文件ClassName.h, 还有类实现的源文件ClassName.m, 如果你想在程序中混合C/C++编程那么就需要使用.mm或者.M ,这样编译器会以此判断该类中混合了Objective-C 和 C语言。 ClassName.h 头文件中定义类的成员变量以及公有成员函数, 源文件中则包含实际的实现代码,如果你想定义私有函数可以放在实现源文件中,不过需要注意的是必须在该函数被调用调用之前。下面用一个具体的例子来学习如何定义自己的类
 #import
@interface Friend : NSObject
{
NSString* name;
int age;
}
@end


我们从NSObject类继承一个新的类Friend,#import导入Cocoa库的Cocoa.h 头文件, @interface 表示申明一个类定义,也是类定义的开始, 在花括号内是两个成员变量, 最后@end表示类定义结束。对于成员变量可以使用Assessors访问器来访问,也可以申明方法来存取变量
#import
@interface Friend : NSObject
{
NSString* name;
int age;
}
-(NSString*) getName;
-(int) getAge;
-(void) setName:(NSString*) newName;
-(void) setAge:(int) newAge;
@end;


可以定义存取的方法,如 setName 和 getName。 再来看看实现文件
#import
@implement Friend
-(NSString*) getName
{
return name;
}
-(int) getAge
{
return age;
}
-(void) setName:(NSString*) newName
{
name = newName;
}
-(void) setAge:(int) newAge
{
age = newAge;
}
-(id) init
{
if( (self=[super init]) )
{
[self setName:@"Mike"];
[self setAge:22];
}
return self;
}
-(void) dealloc
{
[name release];
[super dealloc];
}
@end;


除了要实现定义的成员函数, 还需要重新实现初始化以及析构函数init 和 dealloc, init中的第一行显示由父类来创建一个类对象付给self, 如果成功则给成员变量赋初值, 然后返回self。 在析构释放内存空间时,先把子类中的所有需要释放的对象都release, 然后发送release消息给父类释放内存。

怎么样,定义新类是不是很简单呢? 这里需要注意的几个问题:
类定义的开始与结束

由@interface开始,@end; 结束
实现类时也需要有开始结束的标致

@implement表示开始, @end;表示结束
一般都从NSObject继承定义新类, 否则将需要自己实现初始化以及释放等等代码
如果垃圾回收功能处于打开状态, dealloc将不会被调用, 这时需要实现finalize方法


Objective-C的内存管理

在Mac OSX 系列操作系统以及iPhone平台上写应用程序时,打开垃圾回收选项,如果程序并不涉及复杂的内存分配, 就几乎可以不用操心内存管理的问题。系统会自动释放部分不用的内存,就像Java那样。 但是如果所编写的程序中有大量内存分配以及频繁释放使用, 这时就需要自己来管理内存。 也就是说,如果你使用alloc方法为对象分配空间, 就应该在使用完后手动发送release消息以释放内存空间。autorelease自动释放类型无需手动发送release消息,否则会使程序崩溃, 系统会退出在autorelease对象作用域时自动释放相应内存空间。如下面的代码中,
//自动释放类型的对象, 系统会自动释放其内存空间
NSString* str1 = [NSString string];
NSString* str2 = [[NSString alloc] init];
//.. 使用 str2
// 在不使用时发送release消息释放内存空间
[str2 release];


Objective-C的内存管理体系引入一种叫引用计数的概念来管理内存,类似C++ COM对象中的引用计数器,当计数器减小到0 时释放内存空间。 简单来说, 在使用alloc创建对象是引用计数为1, 使用retain一次计数加1, 发送release计数减1,如
NSString* string = [[NSString alloc] init];//计数为1
[string retain];// 计数为2
//....
[string release];
[string release];// 需要发送release消息两次才能释放内存空间


这里有一张来自http://cocoadevcentral.com 的图很生动的解释了这一原理


在平时编写程序是无需去在意引用计数, 只要记住一般情况下只有两种情况下才需要显式发送release消息释放空间
1. 保留变量实例,以供后续使用
2. 函数内创建的临时对象

最后, 记住每使用一次alloc就需要发送release或autorelease消息一次, 每调用一次retain也同样需要release, 只要alloc或者retain 和release或者autorelease成对出现就不会存在内存泄露的可能。

Objective-C中的属性
在C++中,类可以有自己的成员变量, 一般公有成员变量可以直接通过类对象访问或修改, 保护成员变量和私有成员变量通过相应的函数来存取,比如
class CPerson
{
public:
int gender;
protected:
int age;
public:
int GetAge(){return age;}
void SetAge( int newValue){ age = newAge;}
};
void test(){
CPerson person;
person.gender = 0;
person.SetAge(20);
printf("性别:%d",person.GetAge() );
printf("年龄:%d",person.gender);
}


这里申明一个人的类CPerson, 其中表示性别的变量 gender是公有成员变量,可以通过类对象peron直接读写,而年龄age则需要通过Set和Get函数来读写。 同样的在Objectivie-C中也有类似的机制, 不过在Object-C中默认的变量都是私有的, 而且所有变量都只能通过存取函数或者访问器来访问,而且在Objective-C中把可以通过访问器访问的变量称为属性, 上面的C++ 代码可以用以下Objective-C 来实现
//.h
@interface Person : NSObject
{
int gender;
int age;
}
@property int gender;
-(int) GetAge;
-(void) SetAge:(int) newValue;
@end
//.m
@implement Person
@synthesize gender;
-(int) GetAge
{
return age;
}
-(void) SetAge:(int) newValue
{
age = newValue;
}
@end


上面的代码中gender就是Person的属性, 如果有对象person则可以通过几种方式读写gender
person.gender = GenderMale
printf("性别 %d", person.gender)
[person setGender:GenderMale];
printf("性别 %d", [person getGender];

只要申明了属性, 那么就自动拥有get和set访问器,这里很重要的是@synthesize,通过它才可以自动为属性添加访问器,如果自定义了访问器那么@synthesize将不会再次添加。 age变量则需要通过访问函数来存取。

没有评论: