Objective-C学习笔记(1-3)
去年因为项目需要临时学了一段时间Objective-C,可惜直到现在都适应不了这门语言。学习过程中根据不同主题记了一些东西下来,发出来共享,但愿能帮到一些同为入门菜鸟的Objective-C学习者,不过笔记中肯定有错,这个我比较有自知之明。笔记中的所有时间表达(昨天、这段时间)都不以现在为准,因为是好几个月之前写下的。
目录
01构造和拷贝构造
首先是有关构造函数
我想实现子类基类不同数量参数的构造方式,结果昨天试验了几种方法都不行。
参见下面的代码,Person是基类,实现了一个两参数的构造函数initWithArgs。子类Coder重载了这个方法,多了一个参数,试图重用基类的方法:
// person的构造函数
- (id) initWithArgs : (NSString*) aName andAge:(int) aAge {
Person* person = [[Person alloc] init];
[person setName: aName];
[person setAge: aAge];
return person;
}
// coder的构造函数
- (id) initWithArgs : (NSString*) aName andAge:(NSInteger) aAge andLanguage:(NSString*)aLanguage {
return ??? //这里要返回什么
}
结果昨天尝试了N种方法,全都不行!
// 不行, crash!
if ( self = [super initWithArgs:aName andAge:aAge] ) {
[self setLanguage:aLanguage];
}
return self;
// 不行,没有crash,但是基类的参数name和age都设置失败
[super initWithArgs:aName andAge:aAge];
[self setLanguage:aLanguage];
return self;
// 好用,但我们的基类构造函数不就没用了吗
Coder* coder = [[Coder alloc] init];
[coder setName:aName];
[coder setAge:aAge];
[coder setLanguage:aLanguage];
return coder;
问题在于基类的构造定义,要用[self class]获取到当前self指针的类型才对
Person* person = [[[self class] alloc] init];
子类的构造函数:
Coder* coder = [super initWithArgs:aName andAge:aAge];
[coder setLanguage:aLanguage];
return coder;
或者用更单纯一点的调用:
[self initWithArgs:aName andAge:aAge];
[self setLanguage:aLanguage];
return self;
简洁构造函数
类似[类名]With[参数名]这样的构造函数,也是类方法,好处在于使用者不用alloc/release操作。内部通过autorelease实现
+ (id) personWithName:(NSString*)aName andAge:(NSInteger)aAge {
id ret = [[[self class] alloc] initWithArgs:aName andAge:aAge];
return [ret autorelease];
}
注意到没,构造方式很像有参数构造函数,也需要用[self class]指定类型。不然子类调用的时候就会出问题。
使用一下:
Person* siwei = [Person personWithName:@"Si Wei" andAge:25];
NSLog( @"%@", [siwei GetInfo:0 TheSecondArg:NO] );
Coder* coder = [Coder personWithName:@"Si Wei" andAge:25];
NSLog( @"%@", [coder GetInfo:0 TheSecondArg:NO] );
拷贝构造
allocWithZone
通过copy函数实现,它会调用copyWithZone,所以在NSObject为基类的类中实现这个协议即可。注意那个allocWithZone方法和刚才已经出现过两次的[self class]:
// Person 类
- (id) copyWithZone : (NSZone*)zone {
Person* clone = [[self class] allocWithZone:zone];
[clone setName:[self name]];
[clone setAge:[self age]]; // 或者写成 clone->age = self->age;
return clone;
}
对于子类来说,如果父类实现了这个方法(就像上面的Person::copyWithZone),可以直接调用,省去继承而来的数据成员的拷贝操作:
// Coder 类
- (id) copyWithZone : (NSZone*)zone {
Coder* clone = [super copyWithZone:zone];
[clone setLanguage:[self language]];
[clone setComputer:[self computer]]; // shallow copy。也可以写成clone->computer = [[self computer] retain]];
[clone setLeader:[[self leader] copyWithZone:zone]]; // deep copy
return clone;
}
值得注意的是,在上面这段代码中,用了几种不同的copy方式
- [clone setProperty:[self property]]; 值拷贝,适用于各种值类型以及NSString*
- clone->computer = [[self computer] retain]]; 相当于浅拷贝,只引用一下
- [clone setProperty:[[self property] copyWithZone:zone]]; 深拷贝,但前提是这个属性的类必须实现copy重载才行。
所以下面代码的执行结果就一目了然了:coder1的leader重新setAge后,coder2的leader属性没有改变,age还是150。
如果在b)里面使用[clone leader:[self leader]];的话,coder2的leader属性也会随之改变。
Coder* coder1 = [Coder personWithName:@"Si Wei" andAge:25];
[coder1 setLanguage:@"C#"];
Person* leader = [[Person alloc] initWithArgs:@"Leader" andAge:150];
[coder1 setLeader:leader];
Computer* computer = [[Computer alloc] init];
[coder1 setComputer:computer];
Coder* coder2 = [coder1 copy];
NSLog( @"%@", [[coder2 leader] GetInfo:0 TheSecondArg:NO] ); // Leader, 150
[[coder1 leader] setAge:99];
NSLog( @"%@", [[coder2 leader] GetInfo:0 TheSecondArg:NO] ); // Leader, 150
除了allocWithZone之外,还可以使用NSCopyObject()实现拷贝
NSCopyObject
这是二进制拷贝,也就是说对于值类型可以省去赋值的过程(当然也包括NSString*这个指针中的异类)
指针则需要自己进行操作,或者retain、或者赋值。假设基类多了一个指针成员:
- (id) copyWithZone : (NSZone*)zone {
//Person* clone = [[self class] allocWithZone:zone];
Person* clone = NSCopyObject( self, 0, zone );
//[clone setName:[self name]]; // 不再需要
//[clone setAge:[self age]]; // 不再需要
clone->pObject = nil; // 需要,重置指针
[clone setPObject:self->pObject];
return clone;
}
于子类来说,一定要知道父类的copyWithZone实现才能决定用什么方式copy:
- (id) copyWithZone : (NSZone*)zone {
//[clone setLanguage:[self language]]; // 不再需要,父类实现了NSCopyObject()
Coder* clone = [super copyWithZone:zone];
[clone->computer retain]; // 父类实现了NSCopyObject(),虽然指针是拷贝过来了,但需要增加引用计数
[clone setLeader:[[self leader] copyWithZone:zone]]; // 需要也进行copy的对象必须要调用copy,不管是不是NSCopyObject()
return clone;
}
dummy cloning和mutable copying
简单来说就是返回一个自身引用
-(id) copyWithZone:(NSZone*)zone
{
// 返回自身,增加一个引用
return [self retain];
}
作用?对于Mutable对象来说可以实现对于非Mutable对象的dummy clong后增加实现可变特性的方法。例如NSMutableString对于NSString
除了刚才的NSCopy外,还有一个NSMutableCopying协议,需要实现一个方法,它返回可变对象。操作方法是[object mutableCopy]。它的用处就是可以让Mutable Object的copy也是Mutable的,例如NSMutableArray如果用copy的话,返回的对象是不能修改的,这个时候需要用mutableCopy。具体的实现方法和copyWithZone没什么区别。
-(id) mutableCopyWithZone:(NSZone*)zone;
02动态特性和RTTI
KVC(Key-Value Coding)
可以通过字符串访问类成员。有点像字典。
NSLog( @"%@", [[coder1 valueForKey:@"leader"] GetInfo:0 TheSecondArg:NO] );
[coder1 setValue:@"New Si Wei" forKey:@"name"];
NSLog( @"%@", [coder1 GetInfo:0 TheSecondArg:NO] );
对于成员的成员,需要用valueForKeyPath。当然对于自己的成员也可以用。
NSLog( @"%@", [coder1 valueForKeyPath:@"leader.name"] );
调用的顺序大概是
- valueForKey:@”foo”
- getFoo方法、foo方法、isFoo方法
- 如果类accessInstanceVariablesDirectly返回YES那么是依次是_foo、_isFoo、foo和isFoo
- 还没找到就调用类的valueForUndefinedKey,默认行为是抛出异常NSUnknownKeyException
- setValue:forKey:@”foo”
- setFoo方法
- 如果类accessInstanceVariablesDirectly返回YES那么是依次是_foo、_isFoo、foo和isFoo
- 还没找到就调用类的setValue:forUndefinedKey,默认行为是抛出异常NSUnknownKeyException
introspection
OOP语言必备的特性,简单说就是在运行时检查类的类型
从下面代码可以看出来,isKindOfClass检查对象是否是某类以及其子类的实例;isMemberOfClass只检查是否是某类的实例。
Class personClass = [Person class];
Class coderClass = [Coder class];
NSLog( @"%d, %d, %d, %d, %d, %d, %d, %d",
[leader isMemberOfClass:personClass], [leader isKindOfClass:personClass], [leader isMemberOfClass:coderClass], [leader isKindOfClass:coderClass],
[coder1 isMemberOfClass:personClass], [coder1 isKindOfClass:personClass], [coder1 isMemberOfClass:coderClass], [coder1 isKindOfClass:coderClass] );
// YES, YES, NO, NO, NO, YES, YES, YES
实际编程中的一些动态特性类
- SEL:selector相当于指向成员函数的指针,需要指定target才可以调用。通过selector生成的method signature可以用来初始化NSInvocation。
- 直接从方法名取得selector用@selector()或者NSSelectorFromString,逆操作NSStringFromSelector
- 对于实例用performSelector调用具体的方法
- Class:类的元数据,通过[instance class]、[instance className]或者NSClassFromString拿到,也有一个逆操作NSStringFromClass
- IMP:其实就是一个函数指针,返回id,参数为id, selector, …,写着比自定义的方便而已,见下面的例子:
Person* me = [[Person alloc] initWithArgs:@"SW" TheAge:25];
SEL selector = @selector(GetInfo:TheSecondArg:);
NSString* ( *pFunc )( id, SEL, int, BOOL );
pFunc = (NSString* (*)(id, SEL, int, BOOL))[me methodForSelector:selector];
IMP imp = [me methodForSelector:selector];
NSLog( @"%@", pFunc( me, selector, 100, YES ) );
NSLog( @"%@", (NSString*)(imp( me, selector, 100, YES )) );
- Invocation Forward:proxy类的工作,需要重载三个函数
(BOOL)respondsToSelector:(SEL)aSelector:判断selector是否属于自己的某个成员
(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector:通过selector拿到method signature
(void)forwardInvocation:(NSInvocation *)invocation:转发Invocation到相应的成员上
- (NSMethodSignature*) methodSignatureForSelector: (SEL)selector {
NSMethodSignature* methodSig = [fwdTarget methodSignatureForSelector:selector];
if ( methodSig ) {
return methodSig;
}
}
- (void) forwardInvocation: (NSInvocation*) invocation {
[invocation invokeWithTarget:[self fwdTarget]];
}
03内存管理和标准C区别
数据成员访问器
数据成员的setter设定。不管哪个教程,都告诉我们setter是不能这么写的:
- (void) setLanguage : (NSString*) aLanguage {
[self language: aLanguage]; // 糟糕代码!外部如果释放了aLanguage指向的对象,内部使用就会出错
}
也不能这么写
language = [aLanguage retain]; // 糟糕代码!内部成员原先指向的对象没法释放了!
当然这样也不行
[language release];
[self setLanguage:aLanguage]; // 糟糕代码!如果language == aLanguage的话就多release了一次。
所以我们被告知应该这么写
[language autorelease];
[self setLanguage:aLanguage]; // Good
其实从第三个例子考虑,也可以写成
[aLanguage retain];
[language release];
[self setLanguage:aLanguage];
数据成员的getter设定。有的时候对于开放给外界的数据成员不希望进行修改,又不想用const关键字的话,可以考虑返回一个copy
- (Person*) leader {
return [[leader copy] autorelease];
//return leader;
}
当然能这么做的前提是Person类实现了copyWithZone
release和垃圾收集器
什么情况才需要autorelease?显式地调用了alloc、使用了copy(mutableCopy)以及retain之后。
垃圾收集器实现了一个灵活的GC,省得我们自己去retain, release和autorelease。开启了GC之后这三个函数都没用了。不过iPhone开发环境中似乎不能用GC?
__weak和__strong声明。什么时候需要弱引用?集合类,因为如果集合对其中的元素都拥有强引用的话,那么这些引用的实际对象就没法析构了(在集合类被咔嚓之前永远有一个ref count)。而弱引用在对象消失后自动变为nil。
autorelease不能滥用,因为它实际上是lazy deallocation,一般来说thread不结束都没法回收。
对于对象引用的维护,永远是使用者责任更大。因为开发者不知道自己的对象可能被引用多少次。
这些东西不能用
- 模板
- 默认参数
- Friend
- inline(但其实Obj-C兼容C99,也可以使用inline关键字)
- initialization list
- 方法的const修饰
[…] 构造和拷贝构造 […]