《From C++ to Objective-C》笔记

版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/jjunjoe/article/details/8590611

From C++ to Objective-C》笔记


1Objective-C是一门语言,而 Cocoa 是这门语言用于 MacOS X 开发的一个类库。它们的关系类似于 C++ QtJava Spring 一样。


2、每一个对象都是每一个对象都是 id类型的。 id 是一个指针。


3nil等价于指向对象的 NULL 指针。


4Nil等价于指针 nil 的类。


5SEL用于存储选择器 selector的值。所谓选择器,就是不属于任何类实例对象的函数标识符。这些值可以由 @selector获取。选择器可以

当做函数指针,但实际上它并不是一个真正的指向函数的指针。


6、我们前面看到的类 NSObjectNSString都有一个前缀 NS。这是 Cocoa框架的前缀(Cocoa 开发公司是 NeXTStep)。


7[object doSomething];

这并不是方法调用,而是发送一条消息。看上去并没有什么区别,实际上,这是 Objective-C的强大之处。例如,这种语法允许你在运行时态添加方法。


8、严格说来,每一个类都应该是 NSObject的子类。


9id类型是动态类型检查的,相比来说,NSObject *则是静态类型检查。


10Objective-C里面没有泛型,那么,我们就可以使用 id很方便的实现类似泛型的机制了。


11、在 Objective-C里面,属性 attributes被称为实例数据 instance data,成员函数 member functions被称为方法 methods


12、实例方法以减号 -开头,而 static 方法以 + 开头。


13、在 Objective-C中,只有成员数据可以是 privateprotected public 的,默认是 protected 。方法只能是 public 的。


14Objective-C中的继承只能是 public 的,不可以是 private protected 继承。


15Objective-C中不允许声明 static 属性。


16Objective-C中可以向任何对象发送任何消息。如果目标对象不能处理这个消息,它就会将消息忽略(这会引发一个异常,但不会终止程序)。


17self指当前对象(类似 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 元素的一个很常见的部分。代理可以将消息转发给一个未知的对象。通过代理,一个对象可以将一些

任务交给另外的对象。


23Objective-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中,所有对象都是动态分配的。


重点关注:

28self = [super init...]

在上一篇提到的代码中,最不可思议的可能就是这句 self = [super init...]。回想一下,self是每个方法的一个隐藏参数,指向当前对象

。因此,这是一个局部变量。那么,为什么我们要改变一个局部变量的值呢?事实上,self必须要改变。我们将在下面解释为什么要这样做

[super init] 实际上返回不同于当前对象的另外一个对象。单例模式就是这样一种情况。然而,有一个 API可以用一个对象替换新分配的

对象。Core DataApple提供的 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指针。在这里,也就是 [super init]负责完成这一操作


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对象 BB C 相互 retain,那么 B C 就陷入了循环 retain

A → B←→ C

如果 A release BB不会真正释放,因为 C 依然持有 BC也不能被释放,因为 B 持有 C。因为只有 A能够引用到 B,所以一旦 A release B,就再也没有对象能够引用这个循环,这样就不可避免的造成内存泄露。这就是为什么在一个树结构中,一般是父节点 retain 子节点,而子节点不 retain父节点。


37、如果开启垃圾收集器,retainrelease 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(默认),retaincopy:设置属性的存储方式;

nonatomic:不生成线程安全的代码,默认是生成的(没有atomic关键字);

getter=…setter=…:改变访问器默认的名字。


42、对于 setter,默认行为是 assignretain或者 copy 用于数据成员被修改时的操作。


43RTTI(Run-TimeTypeInformation)

RTTI 即运行时类型信息,能够在运行的时候知道需要的类型信息。

对象在运行时获取其类型的能力称为内省。

class, superclass, isMemberOfClass, isKindOfClass

conformsToProtocol

respondsToSelector, instancesRespondToSelector


展开阅读全文

没有更多推荐了,返回首页