本文通过触类旁通的启发方式,方便具备C/C++经验的筒子们快速掌握Objective-C。
基本语法
首先看一段简单的Objective-C的代码.
Objective-C支持和C++一样的分离编译模式。C++中是*.h和*.cpp文件; Objective-C是 *.h和 *.m文件来组成一个类。
下面是Rectangle.h文件
#import <Foundation/NSObject.h>
@interface Rectangle: NSObject {
int width;
int height;
@private
int privateVar;
int privateVar2;
}
+(int) staticMethod;
-(Rectangle*) initWithWidth: (int) w height: (int) h;
-(void) setWidth: (int) w;
-(void) setHeight: (int) h;
-(void) setWidth: (int) w height: (int) h;
-(int) width;
-(int) height;
-(void) print;
@end
- #import 这个是引入头文件定义,和C中的#include类似,区别是它会避免重复引入。
- 在头文件中使用
@interface Classname : Baseclass @end
来定义一个类的接口。 - 在大括号内
{...}
定义的是类的成员变量。默认访问级别是protected;也可以像C++中一样加入@private / @public / @protected
来指定访问级别。比较特殊的是@package
,它类似于C#中的internal
,表示在同一个程序集中是公有的。 - 接下来就是类方法定义了。加号开头的方法表示的是这个类的静态方法,如
+(int) staticMethod;
;减号开头的是普通的成员函数。紧接着是方法的返回类型和方法名称,冒号后面是参数(如果有参数)。 - 函数的参数定义格式比较特殊;第一个参数不要写命名参数,后面直接接参数的类型和参变量,如
-(void) setWidth: (int) w;
。而对于后面的参数则需要加上命名参数,如-(Rectangle*) initWithWidth: (int) w height: (int) h;
,这里的height
是参数的命名,而h
是实际的参变量。 - Objective-C的类一般都是直接或者间接地从NSObject派生来的。NSObject中封装了对RTTI的支持以及引用计数。
- Objective-C是支持属性的,这个后文会说到。
- 私有成员函数可以不出现在头文件中进行定义,毕竟它是私有的嘛,没必要出现。
下面看Rectangle.m文件,这个就是类的实现文件了。
#import "Rectangle.h"
#import <stdio.h>
@implementation Rectangle
-(Rectangle*) initWithWidth: (int) w height: (int) h {
self = [super init];
if ( self ) {
[self setWidth: w height: h];
}
return self;
}
-(void) setWidth: (int) w {
width = w;
}
-(void) setHeight: (int) h {
height = h;
}
-(void) setWidth: (int) w height: (int) h {
width = w;
height = h;
}
-(int) width {
return width;
}
-(int) height {
return height;
}
-(void) print {
printf( "width = %i, height = %i", width, height );
}
@end
@implementation Classname @end
中定义的是类的实现部分。- 方法的参数定义和头文件一致,都是参数命名 + 类型 + 参变量
self
等同于this
指针super
表示直接基类,类似于C#中的base
,或者MSVC扩展中的__super
[super init]
表示一次调用,可以看成是super->init()
或者super.init()
NSObject
绝大部分Obj-C的类都从NSObject派生而来。当NSObject被初始化的时候,它内部的isa结构会保存关于此对象类型的描述信息。你可以看到从NSObject派生来的类都具有如下方法支持RTTI
- isEqual
- isKindOfClass
- isMemberOfClass
- isSubclassOfClass
Objective-C的类有2种内存管理方式:引用计数和垃圾回收。但是在iOS上垃圾回收是不支持的,而只能使用引用计数方式。NSObject有下列方法来支持引用计数的内存管理:
- + (id)new
- + (id)alloc
- + (id)allocWithZone:(NSZone *)zone
- - (id)init
- - (id)retain
- - (NSUInteger)retainCount
- - (oneway void)release
- - (id)autorelease
- - (void)dealloc
下面一段代码是一个典型的对象初始化与销毁。
Rectangle * rect = [[Rectangle alloc] initWithWidth: 20 height:50];
[rect release]
在C++中, new一个对象实际上分为2个步骤:1. 申请对象所需的内存; 2.调用构造函数。而在Objective-C中这2个步骤被拆开来。
- 首先
[Rectangle alloc]
调用从NSObject继承来的+ (id)alloc
方法申请内存; 返回类型id
等同于C中的void*
指针,只是更加严格,它是一个指向对象的指针。 - 其次调用构造函数
initWithWidth
。注意在Obj-C中,构造函数的名称是没有任何限制的,但一般都是以init
作为前缀。 - 在构造函数内部必须显式调用基类的构造函数
self = [super init];
, 在设置self
后,(也就是this
指针),构造函数的返回参数必须也是self;当然如果构造函数内部出现错误你可以马上release
并返回nil
.nil
就是id
类型的空指针。 - 在构造函数返回后,将返回值也就是新创建的对象指针通过
Rectangle *
保存下来。 - 在使用完后,调用
[rect release]
进行释放。
在释放的时候,并不需要显式地调用dealloc
来释放内存。因为NSObject中有引用计数这个机制来保证对象内存的准确释放(当然前提是release没有被落下)。如果你有过Windows下COM编程的经历,一定会记得IUnknown
接口中的AddRef
和Release
。在NSObject和它们对等的就是retain
和release
方法。C++ STL中有个非常近似的类就是shared_ptr。
- (NSUInteger)retainCount
获得它的值。当一个对象被NSObject初始化的时候引用计数器为1,如果需要复制这个对象的话则需要调用
retain
一次,也就类似于
IUnknown
中的
AddRef
方法在达到引用计数器增1; 当对象不再需要的时候调用
release
来达到引用计数器减1,当所有引用都
release
的话引用计数器变为0,触发
dealloc
方法的执行。所以
dealloc
并不需要在外部调用;所以,
dealloc
可以被当成析构函数使用,在基类中重载它。当触发的时候首先释放资源然后调用基类的
dealloc
。下图就是此过程很好的阐述。
NSObject也提供一个+ (id)new
的方法,它实际上是alloc
和init
的组合,实际中很少用到,毕竟构造函数不一定是init
.
还有一个经常用到的方法是- (id)autorelease
。因为很多情况下release
并不能被准确调用,比如从方法中返回一个新创建的对象,这种情况下不能期望外部调用在使用完后能够准确释放。而autorelease
很好地解决这类问题。当一个对象的autorelease
方法被调用后,实际上该对象实例的生存期控制权就交到了NSAutoreleasePool
手上,当NSAutoreleasePool
被释放的时候所有关联的对象也会一起释放。这个就好比C++ STL中的smart_ptr
,将一个对象的生存期控制转交给另外一个对象控制。