Extra Cookie

Yet Another Programmer's Blog

Objc Property 的事儿

请看下面几行代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// MyObject.h
@interface MyObject : NSObject {
   NSString *name;
}
@property (nonatomic, retain) NSString *name;
@end

// MyObject.m
@implementation
@synthesize name;
@end

// MyObject.h
@interface MyObject : NSObject {
}
@property (nonatomic, retain) NSString *name;
@end

第一个 interface 里面声明了一个 instance variable 名为 name,然后定义其为 property。 第二个 interface 里面直接定义了一个 property 名为 name。

第二个忘了声明 instance variable 吗?

如果你使用的 Objective-C Runtime 版本高于 ios 3.x 或者高于 64bit Snow Leopard,那么第二个方法是正确的。

声明一个 instance variable,要么在 interface 的大括号里面声明,或者使用 property,又或者全部都用,而如果用了 property,只有一个不同,那就是可以用 synthesize 来自动生成 setter/getter。

那么到底该如何选择呢?

之前有人说如果不在大括号中声明,只用 property,debug 时候会看不到这个变量,我在 Xcode 4 中测试了下,是可以看到的,所以这个不是问题。

So, feel free to use it.

有这样的两个类

1
2
3
4
5
6
7
8
9
10
11
@interface Father : NSObject
{
    NSString *father;
}
@end

@interface Child : Father
{
    NSString * child;
}
@end

Child 继承自 Father,如果我们需要往 Father 里面新加一个 instance variable,如果不重新编译 Child 的话,势必就影响了 Child,因为变量的访问是通过 offset 进行的,对于子类而言,实例化后父类是在子类中占据位置的,如果父类发生更改,那么 offset 值就会变化,这个时候,如果还是按之前的 offset 来访问的话,就很可能导致 crash。

这在很多面向对象语言里面如 C++ 都是个问题,但是在 objc 里面已经不是问题,因为现代的 runtime 动态的对 offset 值进行更改,哪怕编译时得到的 offset 值是常量。

所以可以动态地往父类里面加变量而又可以保持与子类的二进制兼容性。

甚至都不需要更改父类的头文件,

1
2
3
4
5
6
7
@interface Father ()
@property NSString *name;
@end

@implementation Father
@synthesize name;
@end

上面代码采用 private catagory 来隐藏新加入的 property,

但是这样有一个问题,比如

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@interface BaseObject : NSObject
{
}
@property (nonatomic, copy) NSString *propertyOne;
@end

@implementation BaseObject
@synthesize propertyOne=myIvar;
@end

@interface SubObject : BaseObject
{
}
@property (nonatomic, copy) NSString *propertyTwo;
@end

@implementation SubObject
@synthesize propertyTwo=myIvar;
@end

这样的 code 可以工作吗?

如果父类和子类中有同名 instance variable,然后 synthesize,肿么办?

实际上两个 myIvar 是不同的,有不同 offset 值,并且 synthesize 的 instance variable 总是 private 的,也就是说父类的这个同名变量对于子类而言是不可见的。

不过要注意,不能将上述父子类代码放在一个文件中,会编译报错的,因为子类对于父类的 instance varaible 可见了。

MVC 中,controller 通常会包含一些 IBOutlet,通常会采用 retain property,那么在删除的时候,可以这样

1
self.button = nil;

这样可以吗,没有用 [button release] 会 memory leak 吗?

其实这样才是简洁的写法,因为其是 retained property,所以 synthesize 的 setter 大概如下

1
2
3
4
5
6
- (void)setButton:(UIButton *)b
{
    [button release];
    button = b;
    [button retain];
}

所以 self.button 本身就执行了 [button release],然后赋值为 nil,再 retain,是没有影响的。

Comments