Objective-C学习笔记(1-3)

去年因为项目需要临时学了一段时间Objective-C,可惜直到现在都适应不了这门语言。学习过程中根据不同主题记了一些东西下来,发出来共享,但愿能帮到一些同为入门菜鸟的Objective-C学习者,不过笔记中肯定有错,这个我比较有自知之明。笔记中的所有时间表达(昨天、这段时间)都不以现在为准,因为是好几个月之前写下的。

目录

  1. 构造和拷贝构造
  2. 动态特性和RTTI
  3. 内存管理和标准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”
    1. getFoo方法、foo方法、isFoo方法
    2. 如果类accessInstanceVariablesDirectly返回YES那么是依次是_foo、_isFoo、foo和isFoo
    3. 还没找到就调用类的valueForUndefinedKey,默认行为是抛出异常NSUnknownKeyException
  • setValue:forKey:@”foo”
    1. setFoo方法
    2. 如果类accessInstanceVariablesDirectly返回YES那么是依次是_foo、_isFoo、foo和isFoo
    3. 还没找到就调用类的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修饰

One Response to “Objective-C学习笔记(1-3)”