1、Objective-C是一门语言,而 Cocoa 是这门语言用于 MacOS X 开发的一个类库。它们的关系类似于 C++ 和 Qt,Java和 Spring 一样。
2、每一个对象都是每一个对象都是 id类型的。 id 是一个指针。
3、nil等价于指向对象的 NULL 指针。
4、Nil等价于指针 nil 的类。
5、SEL用于存储选择器 selector的值。所谓选择器,就是不属于任何类实例对象的函数标识符。这些值可以由 @selector获取。选择器可以当做函数指针,但实际上它并不是一个真正的指向函数的指针。
6、我们前面看到的类 NSObject,NSString都有一个前缀 NS。这是 Cocoa框架的前缀(Cocoa 开发公司是 NeXTStep)。
7、[object doSomething];
这并不是方法调用,而是发送一条消息。看上去并没有什么区别,实际上,这是Objective-C的强大之处。例如,这种语法允许你在运行时态添加方法。
8、严格说来,每一个类都应该是 NSObject的子类。
9、id类型是动态类型检查的,相比来说,NSObject *则是静态类型检查。
10、Objective-C里面没有泛型,那么,我们就可以使用 id很方便的实现类似泛型的机制了。
11、在 Objective-C里面,属性 attributes被称为实例数据 instance data,成员函数member functions被称为方法 methods。
12、实例方法以减号 -开头,而 static 方法以 + 开头。
13、在 Objective-C中,只有成员数据可以是 private,protected和 public 的,默认是 protected 。方法只能是 public 的。
14、Objective-C中的继承只能是 public 的,不可以是 private 和 protected 继承。
15、Objective-C中不允许声明 static 属性。
16、Objective-C中可以向任何对象发送任何消息。如果目标对象不能处理这个消息,它就会将消息忽略(这会引发一个异常,但不会终止程序)。
17、self指当前对象(类似 C++ 的 this),super指父对象。
18、重载的情况。C++和 Objective-C 使用截然不同的两种方式去区分:前者使用参数类型,后者使用参数标签。
19、@selector的值是在编译器决定的,因此它并不会减慢程序的运行效率。
20、严格说来,选择器并不是一个函数指针。它的底层实现是一个 C字符串,在运行时被注册为方法的标识符。
21、最后,你应该记得我们曾经说过 Objective-C 里面的 self 指针,类似于 C++ 的this 指针,是作为每一个方法的隐藏参数传递的。其实这里还有第二个隐藏参数,就是_cmd。_cmd 指的是当前方法。
-(void) f:(id)parameter //等价于 C 函数 void f(id self, SEL _cmd, id parameter)
22、代理 delegation是 Cocoa 框架中 UI 元素的一个很常见的部分。代理可以将消息转发给一个未知的对象。通过代理,一个对象可以将一些任务交给另外的对象。
23、Objective-C也有继承的概念,但是不能多重继承。协议 protocol和分类categories模拟实现多重继承。
24、在 Objective-C中,所有方法都是虚的。
25、纯虚方法则是使用正式协议 formal protocols来实现。
26、混合使用协议、分类和子类的唯一限制在于,你不能同时声明子类和分类。不过,你可以使用两步来绕过这一限制:
@interface Foo1 : SuperClass //ok
@end @interface Foo2 (Category) //ok
@end
// 下面代码会有编译错误
@interface Foo3 (Category) : SuperClass
@end
// 一种解决方案
@interface Foo3 : SuperClass //第一步
@end
@interface Foo3 (Category) //第二步
@end
27、在 Objective-C中,所有对象都是动态分配的。
重点关注:
28、self = [super init...]
在上一篇提到的代码中,最不可思议的可能就是这句 self = [super init...]。回想一下,self是每个方法的一个隐藏参数,指向当前对象
。因此,这是一个局部变量。那么,为什么我们要改变一个局部变量的值呢?事实上,self必须要改变。我们将在下面解释为什么要这样做
。
[super init] 实际上返回不同于当前对象的另外一个对象。单例模式就是这样一种情况。然而,有一个 API可以用一个对象替换新分配的
对象。Core Data(Apple提供的 Cocoa 里面的一个 API)就是用了这种 API,对实例数据做一些特殊的操作,从而让这些数据能够和数据库
的字段关联起来。当继承 NSManagedObject类的时候,就需要仔细对待这种替换。在这种情形下,self就要指向两个对象:一个是 alloc
返回的对象,一个是 [super init]返回的对象。修改 self 的值对代码有一定的影响:每次访问实例数据的时候都是隐式的。正如下面的代
码所示:
@interface B : A {
int i;
}
@end
@implementation B
-(id) init {
// 此时,self 指向 alloc返回的值
// 假设 A 进行了替换操作,返回一个不同的
self id newSelf = [super init];
NSLog(@"%d", i); //输出 self->i 的值
self = newSelf; //有人会认为 i 没有变化
NSLog(@"%d", i); //事实上,此时的 self->i, 实际是 newSelf->i,
// 和之前的值可能不一样了
return self;
}
@end
...
B* b = [[B alloc] init];
self = [super init] 简洁明了,也不必担心以后会引入 bug。然而,我们应该注意旧的 self指向的对象的命运:它必须被释放。第一规则
很简单:谁替换 self指针,谁就要负责处理旧的 self指针。在这里,也就是 [superinit]负责完成这一操作
29、初始化错误
初始化出错可能发生在三个地方:
1. 调用 [super init...]之前:如果构造函数参数非法,那么初始化应该立即停止;
2. 调用 [super init...]期间:如果父类调用失败,那么当前的初始化操作也应该停止;
3. 调用 [super init...]之后:例如资源分配失败等。
在上面每一种情形中,只要失败,就应该返回 nil;相应的处理应该由发生错误的对象去完成。这里,我们主要关心的是1, 3情况。要释放当
前对象,我们调用 [self release]即可。
30、在调用 dealloc之后,对象的析构才算完成。因此,dealloc的实现必须同初始化方法兼容。事实上,alloc将所有的实例数据初始化成 0
是相当有用的。
@interface A : NSObject {
unsigned int n;
}
-(id) initWithN:(unsigned int)value;
@end
@implementation A
-(id) initWithN:(unsigned int)value {
// 第一种情况:参数合法吗?
if (value == 0) //我们需要一个正值
{
[self release];
return nil;
}
// 第二种情况:父类调用成功吗?
if (!(self = [super init])) //即是 self 被替换,它也是父类
return nil; //错误发生时,谁负责释放 self?
// 第三种情况:初始化能够完成吗?
n = (int)log(value);
void* p = malloc(n); //尝试分配资源
if (!p) // 如果分配失败,我们希望发生错误
{
[self release];
return nil;
}
}
@end
31、有三种方法可以增加引用计数器,也就意味着仅仅有有限种情况下才要使用release释放对象:
使用 alloc显式实例化对象;
使用 copy[WithZone:]或者 mutableCopy[WithZone:]复制对象(不管这种克隆是伪);
使用 retain retainretainretain。
32、直接指定(完整代码)
-(void) setString:(NSString*)newString {
// 没有强链接,旧值被改变了
self->string = newString; //直接指定
}
33、使用 retain指定(完整代码)
// ------ 不正确的实现 ------
-(void) setString:(NSString*)newString
{
self->string = [newString retain];
// 错误!内存泄露,没有引用指向旧的"string",因此再也无法释放
}
-(void) setString:(NSString*)newString
{
[self->string release];
self->string = [newString retain];
// 错误!如果 newString == string(这是可能的),
// newString引用是 1,那么在 [self->string release]之后
// 使用 newString 就是非法的,因为此时对象已经被释放
}
-(void) setString:(NSString*)newString
{
if (self->string != newString)
[self->string release]; //正确:给 nil 发送 release是安全的
self->string = [newString retain]; //错误!应该在 if 里面
// 因为如果 string == newString,
// 计数器不会被增加
}
// ------ 正确的实现 ------
// 最佳实践:C++程序员一般都会"改变前检查"
-(void) setString:(NSString*)newString
{
// 仅在必要时修改
if (self->string != newString) {
[self->string release]; //释放旧的
self->string = [newString retain]; // retain新的
}
}
// 最佳实践:自动释放旧值
-(void) setString:(NSString*)newString
{
[self->string autorelease]; //即使 string == newString也没有关系,
// 因为 release 是被推迟的
self->string = [newString retain];
//... 因此这个 retain 要在 release之前发生
}
// 最佳实践:先 retain在 release
-(void) setString:(NSString*)newString
{
[self->newString retain]; //引用计数器加 1(除了 nil)
[self->string release]; // release时不会是 0
self->string = newString; //这里就不应该再加 retain 了
}
34、复制(完整代码)
无论是典型的误用还是正确的解决方案,都和前面使用 retain指定一样,只不过把retain换成 copy。
35、当返回实例数据指针时,外界就可以很轻松地修改其值。这可能是很多 getter不希望的结果,因为这样一来就破坏了封装性。
@interface Button
{
NSMutableString* label;
}
-(NSString*) label;
@end
@implementation Button
-(NSString*) label
{
return label; //正确,但知道内情的用户可以将其强制转换成NSMutableString,
// 从而改变字符串的值
}
-(NSString*) label
{
// 解决方案 1 :
return [NSString stringWithString:label];
// 正确:实际返回一个新的不可变字符串
// 解决方案 2 :
return [[label copy] autorelease];
// 正确:返回一个不可变克隆,其值是一个 NSString(注意不是 mutableCopy)
}
@end
36、循环retain
必须紧身避免出现循环 retain。如果对象 A retain对象 B,B和 C 相互 retain,那么B和 C 就陷入了循环 retain:
A → B←→ C
如果 A release B,B不会真正释放,因为 C 依然持有 B。C也不能被释放,因为 B 持有 C。因为只有 A能够引用到 B,所以一旦 A release B,就再也没有对象能够引用这个循环,这样就不可避免的造成内存泄露。这就是为什么在一个树结构中,一般是父节点 retain 子节点,而子节点不 retain父节点。
37、如果开启垃圾收集器,retain、release和 autorelease 都被重定义成什么都不做
38、异常处理
Objective-C 中使用 @try…@catch…@finally
Cocoa 中有一个 NSException类,推荐使用此类作为一切异常类的父类。因此,catch(NSException *e)相当于 C++ 的 catch(…)。
39、线程安全
@synchronized
由 @synchronized(…)包围的块会自动加锁,保证一次只有一个线程使用。在处理并发时,这并不是最好的解决方案,但却是对大多数关键块的最简单、最轻量、最方便的解决方案
@implementation MyClass
-(void) criticalMethod:(id) anObject {
@synchronized(self) {
// 这段代码对其他 @synchronized(self) 都是互斥的
// self 是同一个对象
}
@synchronized(anObject) {
// 这段代码对其他 @synchronized(anObject) 都是互斥的
// anObject是同一个对象
}
}
@end
40、字符串
Objective-C 中唯一的 static对象
41、如果没有给出属性的参数,那么将使用默认值;否则将使用给出的参数值。这些参数值可以是:
readwrite(默认)或者readonly:设置属性是可读写的(拥有getter/setter)或是只读的(只有getter);
assign(默认),retain或copy:设置属性的存储方式;
nonatomic:不生成线程安全的代码,默认是生成的(没有atomic关键字);
getter=…,setter=…:改变访问器默认的名字。
42、对于 setter,默认行为是 assign;retain或者 copy 用于数据成员被修改时的操作。
43、RTTI(Run-TimeTypeInformation)
RTTI 即运行时类型信息,能够在运行的时候知道需要的类型信息。
对象在运行时获取其类型的能力称为内省。
class, superclass, isMemberOfClass, isKindOfClass
conformsToProtocol
respondsToSelector, instancesRespondToSelector