Cocoa Programming学习总结

  1. OC的对象消息机制
    书中讲到,NSObject类中有一个成员变量isa,其指向一个结构体,这个结构体用于表示类的结构体,称之为类结构体。结构体中包括:该类独有的数据成员的类型(注意是类型,不是数据成员的值,地址什么的),函数列表,以及一个指向父类的类结构体指针(因为OC不允许多重继承,所以一个就够了)。其中函数列表通过一个映射列表实现的,给出一个键值将得到一个函数地址如果该键值存在的话。

    而且OC里面的类函数调用,或者称之为消息传递(更为贴切一点),都将被编译器改写为一个叫objc_sendMsg(实体对象指针,函数索引值,参数列表)的函数。该函数将通过isa指针,找到类结构体中对应的函数,如果存在则调用该函数,并传递相应参数。否则,通过类结构体的父指针找到父类的类结构体,查找函数是否存在,存在则调用,否则一直向上追溯,如果最终没有找到,则抛出异常。

  2. 派生自Cocoa的类的责任。
    书中讲到,Cocoa如果要派生类的话,需要确保及遵守一下几点。
    1.为新类编写一个指派初始化函数,并在该函数中调用父类的指派初始化函数。指派初始化函数是指真正处理函数各项初始化事物的函数,其他初始化函数一般都是调用指派初始化函数来完成初始化。所以一般指派初始化函数接受的参数最多,而其他的初始化函数一般都是为了方便用户调用便利函数。
    2.为新类重写父类的指派初始化函数,因为指派初始化都与每个类来说都很重要。如果子类没有重写父类的指派初始化函数,那么子类对象存在直接调用父类指派初始化函数的可能,如果那样的话,子类的重要数据成员就没有完成初始化,很容易造成错误。比父类更往上的类,我们将不在重写其指派初始化函数,因为用于直接调用概率比较小,如果真的调用的,那么后果不确定。所以这个地方我觉得也是OC略带瑕疵的地方。


  3. 对象创建销毁机制
    1.OC对象在被销毁之后,会调用-(void)dealloc函数。如果派生类想在自己被销毁的时候,能够清楚一些自己申请的内存或者释放持有的某个对象时,就可以通过重载-(void)dealloc函数。dealloc函数必须先清理自己的垃圾,然后才能,也必须调用父类的dealloc函数,因为父类可能在自己dealloc函数中有自己的重要操作。

    2.在调用alloc,new,copy,mutablecopy等创建函数时,对象中的引用计数为1.调用release会使对象引用计数减一,该函数会判断,当引用计数为0时调用dealloc函数销毁。autoRelease操作不会修改任何引用计数,而是将对象放入自动销毁池中,该池在某种条件下(界面程序一般是在一个消息事件出里完成后就调用一次,游戏程序一般是每帧调用一次),将为池中所有对象调用release函数,使其引用计数为0,继而被销毁。所以一般我们创建完一个对象之后,如果调用了autoRelease,那么如果你对其感兴趣,需要额外再调用一次retain,以使其引用计数为2,以使其不会被自动销毁池销毁(因为自动销毁池调用release之后对象引用计数为1,所以不会被销毁)。

    OC中有两个垃圾回收机制,一个就是常用的引用计数,另外一个就是新的Garbage Collector。前面一种存在计数错误而引起崩溃,以及“孤岛”(多个对象相互引用且与外部不相连接)问题。后者更为健全,GC模式将会通过类对象图去查找所有不能够访问的对象,这些对象就是不需要的垃圾(因为没人能访问到他),并将其清楚。这也就解决的“孤岛”问题。当然也有其弊端,就是需要花费一定的时间去遍历对象图。而且在GC模式中,所有的retain/release操作将相当于void。当一个对象不再使用时,通过将对象指针置为nil,来实现连接断开,以使GC能发现该对象不在类使用而正常清除。一般我们选择引用计数模式。

  4. accessor method
    对于类对象指针成员变量的accesstor函数,我们可以有多个编写方式。
    1.
    - (void)setFoo:(NSCalendarDate *)x
    {
        [x retain];
        [foo release];
        foo = x;
    }
    2.
    - (void)setFoo:(NSCalendarDate *)x
    {
        if (foo != x) {
          [foo release];
          foo = [x retain];
        } 
    }
    3.
    - (void)setFoo:(NSCalendarDate *)x
    {
        [foo autorelease];
        foo = [x retain];
    }

    其中,我们需要注意,一般都是调用retain之后,才调用release。你可能会认为,在情况1中无所谓,因为两个对象,谁先调用无所谓,对象不会在一个函数执行过程中销毁。但是情况1中存在特例,那就是当x指针就是本身的时候,那么你先调用release,对象就可能就会被销毁,此时再调用retain,立马掉入万丈深渊。

  5. OC中的respondsToSelector:(SEL)aSelector
    我们可以通过此函数来查看一个类实例是否重写了某个SEL。常用于Delegate的接口判断。当我们制定一个Delegate之后,这个Delegate不必重写所有的接口。因为在调用Delegate的接口时,都会调用respondsToSelector来确保Delegate重写了该函数。且respondsToSelector会将结果缓存起来,以避免每次调用的遍历过程开销。

  6. key-value coding
    由于OC语言的实现机制,使得OC的多态是运行时级别的,比C++更灵活(当然也有效率代价)。通过valueForKey:,mutableArrayValueForKey等接口,可以通过变量名称获取变量指针。这些接口是通过访问对应变量的accessor方法来获取指针。通过accessor方法改变变量,会产生变量更改的消息,所有监听此变量的对象都会被通知。
    而且mutableArrayValueForKey:的灵活性更近一步,在获取arr指针之后,我们调用arr方法时,OC会去查询arr所在对象的相应函数。如
    id arrayProxy = [playlist mutableArrayValueForKey:@"songs"];
    int songCount = [arrayProxy count];
    上面代码,在[arrayProxy count]执行时,先去调用[playlist countOfSongs]。确实很令人震惊,为什么这么做,这是为了给其包含对象(playlist)一个可以改写子对象的机会吧。由于刚接触cocoa,上述代码我测试未能成功。但是下列代码测试成功了(是通过《Cocoa Programming for Mac OS 3rd》第九章的例子测试成功的)。
    [arrayProxy insertObject:p atIndex:4] // is the same as
    [playlist insertObject:p inSongsAtIndex:4]; // if the method exists
    [arrayProxy removeObjectAtIndex:3] // is the same as
    [playlist removeObjectFromSongsAtIndex:3] // if the method exists

  7. -(void)forwardInvocation:(NSInvocation*)x
    当一个对象被发送了一个它不明白的消息的时候(即它没有这个函数),OC会查看这个对象是否重写了-(void)forwardInvocation:(NSInvocation*)x函数,如果重写了,则OC就调用该函数,如果未重写则报出异常。这样我们可以看到,一个未知消息的集中处理站点就是-(void)forwardInvocation:(NSInvocation*)x。好好利用它,可以带来一想不到的好处。

    如NSUndoManager就重写了这个函数。我们可以在NSUndoManager中添加一个undo操作函数。一旦点击Undo,这个函数会被执行。NSUndoManager没有单独写一个接口去接受对象,函数地址,参数(以方便以后调用undo操作函数),而是利用forwardInvocation:(NSInvocation*)x中的x,直接传递的undo操作函数(即x,NSInvocation就是对象,函数地址,参数的封装对象)加入到undo堆栈中。所以我们往NSUndoManager中添加Undo操作函数的方法是:
    [[[self undoManager] prepareWithInvocationTarget:self] removeObjectFromEmployeesAtIndex:index];
    上面语句就像[self removeObjectFromEmployeesAtIndex:index]函数调用,传递给了undoManager。

  8. Cocoa对象的Delegate将自动称为Cocoa对象的监听者
    当一个对象(A)是一个Cocoa对象(称为B,记住一定要是Cocoa对象)的delegate,那么当B调用postNotification发送通知消息(C)的时候,会查询其delegate(即A)是否有相应的处理函数(D),如果存在则调用。其中Cocoa对象发送的通知消息(C)和delegate的处理函数(D)存在如下对应关系:

    NSWindowDidResizeNotification——>windowDidResize:(NSNotification*)notification

    从上面我们可以看出来转换规则,即将通知消息的NS前缀和Notification后缀去掉,将剩下的字符串首字母小写作为delegate的处理函数。


你可能感兴趣的:(OC,Mac平台专属)