四 cocoa中Foundation框架的介绍


自己好久没更新博客了,不能说没有时间,还是自己太懒惰了。有空就想玩玩游戏,聊聊天,总觉得有的是时间改天在写,就这样一拖再拖都一个月了。自己实在不好意思再拖了,所以做学问贵在持之以恒,难点也在这里。所以那些能够坚持不懈,持之以恒的人我都佩服的五体投地,这种品质真的很难培养啊。不发牢骚了,言归正传。下面我就分3部分,来分享一下自己的学习心得。首先会介绍下Foundation框架的大致架构,然后就重点分析下NSObject的组成,最后讲下cocoa编程中方法调用的精髓:SELECTOR IMP。

Foundation框架的架构

cocoa程序编写主要用到2个框架Foundation和ApplicationKit。其中Foundation框架主要定义了一些基础类,供程序员来使用,而Application kit主要是一些用户界面设计的类。Foundation框架中的所有类都继承自NSObject这个对象,等下会讲到这个对象,这里就暂且知道有这样一个超类就行了。Foundation框架的主要目标有一下几点:

  1. 为内存管理,对象的创建,消息的传递定义基本的对象。

  2. 用Unicode编码定义字符串类,以及方便的支持语言本地化

  3. 支持对象的持久保存和发布。

这几点都是apple官方文档上写的设计Foundation框架的目标。

    下面就分析下Foundation类设计的大致策略,首先是建立对象和清理对象的策略,对象释放池NSAutorelasePool是一个重要的对象,用来自动回收对象,从而让程序员方便的进行内存管理。其次就是可变大小的对象(例如NSMutableString),这些对象可以对我们常用的容器类,例如数组,字典,集合,堆栈进行方便的扩展,而不必关系这些容器扩展的方法。第三点就是类簇,就是一个抽象类和一些具体类的组合,这样就组成了我们需要的各种对象和方法,方便以后的调用。最后一点就是通知NSNotificationCentor,其实这个特点用到了一个重要的设计模式,观察者模式。相信如果看过五人组写的设计模式这本书,你一定对这个方法深有体会,因为它是设计模式中一个重要的方法。NSNotificationCentor类就是在这个基础上设计的。

    那么Foundation框架到底有那些类组成哪?我会在以后的博客里,挑选一些常用的类进行详细的分析。这里就大致说以下Foundation框架有那些类组成吧。Foundation类的根是NSObject和NSCoping协议组成的,这两个定义了类的基本属性和方法。继承自NSObject的类大致可以分为这样几类:基本的数据类型的类,如 NSNumber 、一些集合类如NSString NSArray、一些代表系统信息的类如NSDate、还有一些系统实体的类如NSTread NSTast等等。

NSObject介绍

假如没有面向对象的语言,那么结构化的程序设计语言如何模拟类哪?那自然就想到了结构体,其实objective c是在c语言的基础上发展的,同样NSObject也是用结构体来实现的。首先来看下一段结构体模拟类的代码吧。

typedef struct my_objc_class *MetaClass;

typedef struct my_objc_class *MyClass;
struct my_objc_class {     
    MetaClass           class_pointer;         
    struct my_objc_class*  super_class;            
    const char*         name;                 
    long                version;               
    unsigned long       info;                  
    long                instance_size;          
    struct objc_ivar_list* ivars;              
    struct objc_method_list*  methods;          
    struct sarray *    dtable;                  
    struct my_objc_class* subclass_list;           
    struct my_objc_class* sibling_class;
    struct objc_protocol_list *protocols;         
    void* gc_object_type;
};

//这里就是自己定义一个类了。

MyClass student;

估计你看完这个结构体,会惊奇的说原来这就是NSObject啊,其实这不是NSObject的原型,因为苹果公司一向不喜欢开源,她的代码我们怎么可能得到那。上面的结构体,是GCC中定义的开源代码,从网上都可以下到,这就是一个类是如何用结构体实现的。下面我们就来逐行分析下这个结构体吧,来揭示一下类的本质。

  1. class_pointer 顾名思义就是这个结构体本身的一个指针,相信大部人都知道java和c++中的this关键字的用法吧,在c++中this指针就是这里的class_point。而objective c则是用的self来表示自身的指针的。

  2. super_class 看到这里就知道继承是怎么回事了吧,就是在结构体里放上一个指针指向它的父节点。懂得了这点如果想要实现c++中的多重继承,无非就是用指针数组或者链表就可以了。

  3. name version和info我要放到一块说了,因为她们都大同小异。name就是你定义的类的名字。任何产品都要有个版本吧,类同样也是人们设计出来的产品,version就是要给你的类加一个版本号,方便日后升级。info就是相当于一个ID,来区分你创建了多少个这样的结构体,也就是类。

  4. instance_size 这就是类的关键部分,在类中叫成员变量,这个大小是如何分配的,是根据你在类中定义了多少实例。这样在这个类去初始化的时候,就会读取instance_size中的数据,在c语言中就用malloc给这个结构体分配大小。

  5. ivars 表示的是类成员变量的列表,成员变量在分配好大小之后,ivars就存取了她们的地址,从而可以方便的访问到成员变量。所以说c++对类成员变量作用区域的限制,如pravite都是语法上的限制,如果你知道类是如何设计的,就可以先读取类的地址,接着让指针偏移一个变量的大小就可以访问到类中pravite的变量了。

  6. methods 方法列表,和实例变量设计的类似,无非这个列表是个函数指针列表吧了,存的都是函数指针。这里就不赘余了。

  7. dtable 这个要就是一些语言可以实现动态绑定的关键,在c++中这就是一个虚表,当这个虚表中有数据的时候,函数调用的时候就会首先在methods列表中找到方法的地址,然后从dtable查找符合这个方法应该有多少偏移量,从而访问到正确的地址。objective c中消息同样也是这样实现的。下面是objective c每次方法调用时都会调用lookup这个方法。

     objc_msg_lookup(id receiver, SEL op)
     {
       if (receiver)
         return  sarray_get(receiver -> class_pointer -> dtable, (sidx)op);
       else 
         return  nil_method;
    
     
    
  8. subclass_list 是子类的列表指针这里不再赘余。

  9. sibling_class 是兄弟类的列表,类的结构明显看出来是数据结构中树的原型,用链表表示树的方法多种多样,这里用到的是孩子兄弟表示法。

  10. protocols 这里特意用这个名字是为了方便说明objectivc的协议,如果用面向对象的统一定义解释的话,这个指针是用来实现接口的,在c++中叫纯虚对象。

  11. gc_object_type 类方法和类变量从那而来的,这就是答案。

有了上面的基础下面介绍SELECTOR和IMP就容易多了。

SELECTOR和IMP 下面的内容主要以代码的形式演示下,在cocoa中类的方法调用是如何实现的。这里我定义了2个很简单的类纯属是用来模拟数据的。 athlete和footballPlayer。其中footballPlayer继承自athlete,代码如下:

//
//  athlete.h
//  SelectorAndFuntion
//
//  Created by mengtnt on 10-12-8.
//  Copyright 2010 __MyCompanyName__. All rights reserved.
//
#import <Cocoa/Cocoa.h>
@interface athlete : NSObject 
{
    NSString *name;
}
- (void)show;
@end


#import "athlete.h"
@implementation athlete

- (id)init
{
    self = [super init];
    if (self)
    {
        name = [[NSString alloc] initWithString:@"Rossi"];
    }
    return self;
}


- (void)dealloc
{
    [name release];
    [super dealloc];
}


- (void)show
{
    NSLog(@"My name is %@/n",name);
}

@end

//
//  footballPlayer.h
//  SelectorAndFuntion
//
//  Created by mengtnt on 10-12-8.
//  Copyright 2010 __MyCompanyName__. All rights reserved.
//

#import <Cocoa/Cocoa.h>
#import "athlete.h"

@interface footballPlayer : athlete
{
    NSNumber *goalNumber;
}
@end

#import "footballPlayer.h"


@implementation footballPlayer

- (id)init
{
    self = [super init];
    if (self)
    {
        goalNumber = [[NSNumber alloc] initWithUnsignedInt:100];
    }
    return self;
}


- (void)dealloc
{
    [goalNumber release];
    [super dealloc];
}


- (void)show
{
    NSLog(@"Goal is %@/n",goalNumber);
}

@end

那么利用上面两个数据类,下面这个类就模拟了cocoa中消息的发送。

//  doProx.h
//  SelectorAndFuntion

//  这个类是用来演示Class,SEL,IMP,函数指针在objective c中如何使用。
//  1.SEL IMP 函数名字的关系。其中SEL是apple给每个函数分配的ID,IMP代表函数指针。关系图如下:
//           footballPlayer 类
//  方法名字        方法ID(假设)        地址(假设)
//  show             1001                 0x2001

//  2.其中会用到获取函数ID的方法selector和通过函数ID调用函数的方法performSelector,等等这些函数的用法。
//  另外,反之我们可以用NSSelectorFromString方法来查找此函数名字的ID。

//  3.我们可以利用methodForSelector来获得函数的指针。并且还利用函数指针,来展示objective c中的performSelector是如何实现的。

//  4.同样我们可以通过一个普通的字符串取得这个Class类型(方法NSClassFromString),也可以通过我们生成的对象取得这个Class.
//  Selector和Class比较类似,而Class类型获得的方法ClassName.不同的地方是Selector用于表示方法.


//  Created by mengtnt on 10-12-8.
//  Copyright 2010 __MyCompanyName__. All rights reserved.


#import <Cocoa/Cocoa.h>

#define SHOW @"show"    //函数show的字符串。
#define FOOTBALLPLAYER_CLASS @"footballPlayer"  //类footballPlayer的字符串

@interface doProx : NSObject 
{
    id athleteInstance[2];    //因为要动态的调用athlete中的show和footballPlayer的show方法,所以要定义为id类型。
    SEL show_SEL;   
    IMP show_IMP;
    void(*show_Func) (id, SEL);  //函数指针是用来展示IMP在cocoa中是如何实现的。因为IMP其实定义就是函数指针。
    Class footBallPlayerClass;
}
- (void)setSEL;

- (void)showResult;
@end

#import "doProx.h"
#import "athlete.h"
#import "footballPlayer.h"   //在这里才包含两个头文件,这样才能体现程序动态执行的效果。

@implementation doProx
//分别给athlete赋予不同的对象
- (id)init
{
    self = [super init];
    //第一种获得类对象的方法,是用类方法调用获得此对象
    athleteInstance[0] = [[athlete alloc] init];
    
    //第二种获得类对象的方法,首先通过类的字符串获得类的类型,然后用此类型的类方法调用创建对象。
    footBallPlayerClass = NSClassFromString(FOOTBALLPLAYER_CLASS);
    athleteInstance[1] = [[footBallPlayerClass alloc] init];
    return self;
}

- (void)dealloc
{
    [athleteInstance[0] release];
    [athleteInstance[1] release];
    [super dealloc];
}

//如何获取SEL变量的值。SEL的值是根据函数名字获得的,相同的名字具有相同的ID。
- (void)setSEL
{
    //方法一用函数名字获得。
    //show_SEL = @selector(show);
    
    //方法二用函数的字符串获得。
    show_SEL = NSSelectorFromString(SHOW);
}

- (void)showResult
{
    show_IMP = [athleteInstance[0] methodForSelector:show_SEL];
    show_IMP(athleteInstance[0],show_SEL);
    
    show_Func=(void (*)(id, SEL)) [athleteInstance[1] methodForSelector:show_SEL];
    show_Func(athleteInstance[1],show_SEL);
    
    //展现perform的用法,判断这个对象必须是footballPlayer类型才能执行。
    for(int i = 0;i < 2;i++)
    {
        if ([[athleteInstance[i] className] isEqualToString:FOOTBALLPLAYER_CLASS])
        {
            [athleteInstance[i] performSelector:show_SEL];
        }
    }
    NSString *myName = NSStringFromSelector(_cmd);
    NSLog(@"Running in the method of %@", myName);
}
@end
然后在主函数里面调用这些方法。

#import <Foundation/Foundation.h>
#import "doProx.h"
int main (int argc, const char * argv[]) 
{
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
    doProx *proxy = [[doProx alloc] init];
    [proxy setSEL];
    [proxy showResult];
    [pool drain];
    return 0;
}

有兴趣的可以把代码复制过去调试下,在debuger中观察各个实例变量和方法的指针是如何变化的,相信有了这些基础以后学习Foundation框架中的常用类就不会有什么困难了。

原文链接

如果你喜欢这篇文章,谢谢你的赞赏

图3

如有疑问请联系我