对象与使用
创建类
objective-c 中的类可以视作一种单元,也包括 .h 接口文件和 .m 定义文件两部分。对于一个类,基本元素包括数据、属性和方法等。
类的接口文件
创建一个与类名一致的 .h 接口文件,在接口文件中通过 @interface 来声明类的详细接口。
@interface Animal : NSObject
{
NSString * name;
NSString * size;
NSString * color;
}
@property (nonatomic, retain) NSString * hobbit;
-(void)eat: (NSString *) food;
@end
要注意必须用 @end 结束 @interface。
在类的声明中,类名紧跟的冒号后指定类的直接父类,有且只有一个。
在紧跟类名的大括号中可以定义类的成员变量即实例变量,也称作 ivar,其作用域是作为类的一部分定义的任何方法,也即在类的方法中均可以访问,但外部无法访问。
对于类的方法,其返回类型在方法名前的括号定义,其参数在方法名后的冒号后定义。
类的定义文件
创建一个与类名一致的 .m 文件,定义文件中用 @implementation 和 @end 来定义类接口的具体实现。
@implementation Animal
@synthesize hobbit = _hobbit;
-(id)init
{
if (self = [super init])
{
name = @"Animal";
}
return self;
}
-(void)eat: (NSString *)food
{
NSLog(@"%@ eat %@", name, food);
}
-(void)dealloc
{
name = nil;
}
@end
方法
在 objective-c 中,调用方法又称为发送消息,objective-c 会首先寻找接收消息的对象,也叫做实例,然后通过对象的 isa 指针找到对象的类,再通过类的指针找到需要调用的方法。
1. 类的方法
类的方法是指不需要生成实际对象实例就可以调用的方法,在声明和实现时用 + 号修饰。
接口声明
+(NSString *)getType;
方法定义
+(NSString *)getType
{
return @"Animal";
}
2. 对象方法
对象方法是只有在对象实例化以后才能调用的方法,用于计算或改变对象内的数据,用 - 号修饰。
接口声明
-(void)eat: (NSString *) food;
方法定义
-(void)eat: (NSString *)food
{
NSLog(@"%@ eat %@", name, food);
}
3. 特殊对象方法
特殊对象方法是指一些具有特定功能和标准行为的方法,最常见的有初始化方法和析构方法。
-
初始化方法
-(id)init { if (self = [super init]) { name = @"Animal"; } return self; }
初始化方法函数名以 init 打头,返回值类型为 id,初始化方法第一步要先调用指定父类的初始化函数,然后判断是否初始化成功,如果失败则返回 nil 对象,如果成功则继续初始化类的数据成员。当然在指定父lei的初始化函数之前,应该声明当前类所直接继承的父类。
对于 id 数据类型,可以理解为一种指向对象的指针,可以表示所有对象的特殊数据类型,但 id 类型由于不是确定的类型,因此编译器在编译时将无法检测一些错误,并且会在运行时因为查找数据类型而带来性能上的缺失。
很多时候对于一个对象我们需要多种不同的初始化方法来满足具体的初始化条件
-(id)init { if (self = [super init]) { name = @"Animal"; } return self; } -(id)init: (NSString *) name { if (self = [self init]) //内存泄漏 { self->name = name; } return self; }
在初始化函数中也可以调用 self 的其他初始化函数,或是指定初始化函数并被其他初始化函数调用。
self 是一个隐蔽的参数,它在调用方法即发送消息时被传递进方法内,它所引用的就是消息的接受对象。
在方法中用 - 引用成员变量即实例变量。
而 super 既不是参数也不是实例变量,而是一种功能,作用是向该类的超类发送消息,按照继承链依次向上寻找。
-
dealloc 方法
dealloc 方法用于释放对象所分配的资源。
-
description 方法
类似 Java 的 toString 方法,实现对一个对象的字符串表示。
-(NSString *)description { return @"I am an animal"; } NSLog(@"%@", animal);
对象
对象是类的实例,声明和初始化一个对象方式如下。
Animal * animal = [[Animal alloc]init];
这里要注意,在 objective-c 中,对于对象和类的方法的调用是通过中括号来调用的,如果方法有参数则放在冒号后面
[animal eat: @"rabbit"];
alloc 函数是一个类方法,用于开辟一个内存空间给对象,它会返回一个对象,对象类型就是调用 alloc 函数的类的类型。
对于不希望被外部调用和访问的方法,可以不在接口文件中声明,但要注意,这类私有方法必须要在使用方法前声明该方法或直接定义该方法。这段描述来自书上,但我的测试来看是没有这一限制的。
属性
在 objective-c 中,属性和数据成员是不一样的,成员数据会在内存中真正存储数据,而属性只是提供对于成员数据的存取器函数,如赋值函数和取值函数。
属性声明对象的状态
在这里继续对于 Animal 类的完善,如果我们加上属性的话,那么可以写成如下形式
接口文件
@interface Animal : NSObject
{
NSString * name;
NSString * size;
NSString * color;
NSString * birthDate;
}
@property(nonatomic, retain) NSString * name;
@property(nonatomic, retain) NSString * size;
@property(nonatomic, assign) NSString * color;
@property(nonatomic, retain) NSString * birthDate;
@property(nonatomic, readonly) NSString * age;
-(id)init;
-(id)initWithName:(NSString *)name;
-(void)eat: (NSString *) food;
+(NSString *)getType;
@end
定义文件
@implementation Animal
@synthesize name;
@synthesize size;
@synthesize color;
@synthesize birthDate;
@dynamic age;
-(NSString *)calculateAge: (NSString *)birthDate
{
return @"";
}
-(NSString *)age;
{
return [self calculateAge:birthDate];
}
-(id)init
{
if (self = [super init])
{
name = @"Animal";
}
return self;
}
-(id)initWithName: (NSString *) aname
{
if (self = [self init])
{
self->name = aname;
}
return self;
}
-(void)eat: (NSString *)food
{
NSLog(@"%@ eat %@", name, food);
}
+(NSString *)getType
{
NSLog(@"Animal");
return @"Animal";
}
-(void)dealloc
{
name = nil;
}
@end
这里看到对于声明的数据成员,都用同样命名的属性提供了相应的存取方法,同时还带有对于属性的描述特性,简单总结一下。
- getter=<name>, setter=<name>,可以指定存取器函数
- readwrite,readonly,可读可写或仅可读
- assign,retain,copy,针对赋值操作,这里涉及到对内存管理和引用次数的内容,因此简单记录下
- assign 是默认特性,简单进行赋值操作,常用于NSInteger等OC基础类型,以及short、int、double、结构体等C数据类型,因为这些类型不存在被内存回收的问题
- retain 赋值时保留传入参数,实际是保留一个引用次数
- copy 复制传入参数到成员变量
- nonatomic 和 atomic,atomic 可以保证原子性操作,也即线程安全,但会带来性能损失,默认是 atomic,如果是在单线程环境下可以用 nonatomic 特性
- strong,weak,分别表示该属性对于数据成员的强弱饮用,强引用可以保证对象不会被自动回收
- unsafe_unretained,与 weak 不同,被 unsafe_unretained 指针所引用的对象被回收后,unsafe_unretained 指针不会被赋为 nil,可能会导致程序出错
这些特性大部分与内存管理相关。
而针对定义文件,我们需要对属性进行实现,关键字有两个
- synthesize,使编译器生成属性的存取器代码
- dynamic,手动创建存取器函数
这里可以看到,我们将 age 设为了 dynamic,所以我们手动提供了相应的存取器函数,在这里我们实际上没有创建一个 age 的实际数据成员,而是用计算函数来确定,这就是属性对于内部数据的隔离保护和透明存取。
没有要求属性名和数据成员名保持一致,当然如果按照习惯可以将成员数据命名前加上下划线_,在 c# 中这样的变量表示字段。
属性的使用
属性使用有两种方式,传统方式就是一般的类或对象方法的调用。
NSLog(@"%@", [animal getColor]);
但属性还支持点标记访问
NSLog(@"%@", animal.color);
这里要注意对于自定义存取器函数,传统方式只能调用定义的存取器函数来访问,因为属性本身会被内部编译成赋值方法和取值方法。
依赖关系
依赖关系是两个实体之间的关系,前面在类文件头部加入的 import 语句就是建立依赖关系。当依赖的文件发生变化时,需要重新编译所依赖的文件,这种重新编译是从缘端至最末端的,因此有时会带来严重的编译问题。
所以可以用 @class 关键字代替 import语句,告知编译器当前依赖的文件仅仅是一个指向对象的指针,依据objective-c 的动态内存特性,可以稍后再得知其具体的内存空间。同时对于相互依赖的文件,用 import 语句也会报错,可以用 @class 代替。
但对于继承关系来说,由于编译器需要知道超类的所有信息才可以成功编译子类,所以依赖关系不能用 @class 代替。