zoukankan      html  css  js  c++  java
  • Foundation框架中常用类的介绍

    http://blog.csdn.net/mengtnt/article/details/6087536

    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框架中的常用类就不会有什么困难了。

    NSString 、NSMutableString类

        cocoa把字符串处理封装到NSString类中了,这个类提供了字符串处理的常用方法,详细请见apple develop document。在这里你要了解到NSString类的那些特征那,下面请看一段代码:

        NSString *temp =[[NSString alloc] initWithString:@"test"];
        //NSLog(@"%d",[temp retainCount]);
        [temp release];

        //NSLog(@"%d",[temp retainCount]);

        NSMutableString *str=[[NSMutableString alloc] initWithString:temp];
        [str appendFormat:@"NSString"];
        NSLog(@"%@",str);

    你猜NSLog会输出什么,是我们想要的testNSString么?有兴趣的可以在xcode中创建一个控制台程序,把上面的代码复制进去,可以 看到确实是我们预期的结果。学习过objective c内存管理的肯定就会有疑问了,temp已经释放了,为什么str中还能和temp中的字符串拼接那?

    为了揭开NSString这个类的秘密,首先把代码中被注释的那两句话,在程序中一同运行。那么retainCount又是什么那?引用计数,这个 你不会没有听过吧,那你就要再好好学习下objective c的内存管理了。程序再次运行时,你会发现控制台中会输出两个-1,也就是alloc和release根本就没有起到任何作用,难道NSString类不 适用于objective c的内存管理么?显然不是,原因就在于NSString类中的字符串都定义在常量区,常量区并不是程序员为对象在内存中申请的空间,而是编译起给一些变量 分配的空间,所以说你根本无法取得它的引用计数,因为这个字符串的分配,根本不受你的控制,完全是编译起去控制的。所以你看到的都是-1。那么如果给 NSMutableString类型的str取retainCount那答案会是什么哪?有兴趣的可以实验下,应该是1。因为 NSMutableString虽说它初始化的字符串在常量区,但是NSMutalbeString不像NSString简单的存取字符串的信息,它还要 存取字符串的容量以及一些其他信息供字符串扩展,所以自然和NSString就不太一样了。具体NSMutableString是怎么实现的那就要问问 apple的工程师了。

    NSArray NSMutableString类

        首先还是先看段代码,眼见为实,代码可以说明一切。假如你有两个类Person和Animal那么你就可以写如下的代码,来访问你定义再array对象中的实例了:

        Person *aMan = [[Person alloc] init];

        Animal *dog   = [[Animal alloc] init];

        NSArray *aryMammal = [[NSArray arrayWithObjects:aMan,dog]];

        [aMan release];

        [dog release];

        for(id someThing for aryMammal)

        {

             NSLog(@"%@", [someThing getName]];  //假如说你定义的类有这个方法可以获得对象的名字

        }

    按照我们学过的objective c内存管理的理论分析的话,程序会在for循环中,抛出一个不能识别的selector发向一个实例的异常。但是你可以运行一下,程序非常的正常,没有任 何的异常。这又是什么原因哪?先不急着看下面的分析,你可以用上面的策略retainCount来看下对象aMan和dog的变化。之后你就很容易发现问 题在哪里了。下面还是为一些不愿动手的同志,分析一下结果吧。如果照上面所说的在NSArray后面用retainCount来获取aMan和dog的的 引用计数的,你会发现这两个实例的retainCount都变成了2,自然release之后对象还是没有销毁的。所以由这里可以推测出我们向一个容器里 面放对象的时候,对象的引用计数会自动加1,apple工程师这样设计就可以大大减少程序崩溃的可能性。

        下面来看下NSMutableArray的一些特征。当你想要遍历一个NSMutableArray的时候,然后找到某个特殊的对象,用另外一个对象替代这个对象的时候,代码如下:

       int i = 0;

        for(id temp in mutableAry)

       {

            if([temp equal:aObject])

                [mutableAry replaceObjectAtIndex:i withObject:aObject];

            i++;

       }

    这样的代码可以运行么?答案是只要你的程序进到了if语句里面,下次执行for循环的时候程序就会崩溃,报出的错误是bad access,在之后的博客里我会和大家一块分享下,xcode程序运行中的常见错误。下面就来分析下这种错误的原因吧。 用for(id temp in mutableArray)的时候,切记不能在for循环中为mutableArray添加任何东西,因为for循环在开始的时候,会把array中的所 有数据都放到一个stack中,stack中存着array中对象的指针。如果你改变了array中的对象,原来对象的指针就销毁了,而stack中仍然 存着这个指针,访问的时候程序会bad access从而crash。

        下面再来看下NSArray的一个特殊的用法,就是在数组中放置dictionary的时候,数组同样可以用valueForKey的方法来访问dictionary中的value。代码演示如下:

        NSDictionary *dic1 = [NSDictionary dictionaryWithObjectsAndKeys:@"1",@"sky",nil];
        NSDictionary *dic2 = [NSDictionary dictionaryWithObjectsAndKeys:@"2",@"zhiwei",nil];

        NSDictionary *dic3 = [NSDictionary dictionaryWithObjectsAndKeys:@"1",@"mengtnt",nil];
        NSMutableArray *ary = [[NSMutableArray alloc] init];
        [ary insertObject:dic1 atIndex:0];
        [ary insertObject:dic2 atIndex:0];
        [ary insertObject:dic3 atIndex:0];
        NSString *str = [[ary valueForKey:@"1"] objectAtIndex:1];
        NSLog(@"%@",str);

    在从array中取得dictionary的value的时候,切记要指定index因为数组中的dictionary可能有好多key为1的数 据,它会根据index位置获得正确的数据,如果上面的程序这样写NSString *str = [[ary valueForKey:@"1"] objectAtIndex:0];那么str中应该存取的就是mengtnt,而如果objectAtIndex:2 那结果会输出一个Null。默认情况下如果在相应的位置没有这个dictionary的key,编译器会自动给这个位置加一个NSNull的对象,大家可 不要搞混了,NSNull也是个对象,也是继承自NSObject,千万不能把它当作nil处理。只不过apple为了在集合类中表示空对象而定义的一个 类,因为不可能在集合中随便插入nil对象。这样就可以避免程序随便的进入nil对象,而造成程序异常。。

        首先来看下一个操作文件以及目录的重要类NSFileManager。下面还是看一段代码,来一一说明此类的用法

        NSFileManager *fm;
        NSDirectoryEnumerator *dirEnum;
        NSArray *dirArray;
         // 需要创建一个file manager的实例
        fm = [NSFileManager defaultManager];
       
        // 获取当前工作目录的路径
        path = [fm currentDirectoryPath];
       
        // 遍历这个目录
        dirEnum = [fm enumeratorAtPath: path];

        NSLog(@"Contents of %@:", path);

        while ((path = [dirEnum nextObject]) != nil)

        {
               
                NSLog(@"%@", path);

        }

        // 另一种遍历目录的方法
       
        dirArray = [fm contentsOfDirectoryAtPath: [fm currentDirectoryPath] error: nil];

        NSLog(@"Contents using contentsOfDirectoryAtPath:");

        for (path in dirArray)

            NSLog(@"%@", path);

    相信仔细看了这些代码之后,会对NSFileManager这个类如何遍历目录,有了一定的认识吧。其实这些代码都非常简单,只要查看apple developer文档都能找到的。虽说理解起来简单,但是有时候一动手写代码还是会遇到很多问题的。例如下面

            NSArray *aryProvsioning = [FileManager contentsOfDirectoryAtPath:@"/vault/provisioning" error:nil];
            for(NSString* provisioningFile in aryProvsioning)
            {
                NSLog(@"delete file:%@",provisioningFile);
                [FileManager removeItemAtPath:[NSString stringWithFormat:@"/vault/provisioning/%@",provisioningFile]
                                        error:nil];
            }

    这段代码的功能是删除/vault/provisioning下的所有文件,有兴趣的可以亲 自实验以下,确实可以删除该文件夹下面的所有文件。但是有种情况不行,就是该文件夹下又存在文件夹的情况下,该方法removeitemAtPath就不 能删除,也就是该方法只能删除文件不能删除目录,所以在用的时候要格外小心,一定要认真阅读开发文档。所以上面如果想要删除该文件夹下面的所有文件及目 录,就要写成函数来递归调用。

    void deleteAllAtPath(NSString *path)

    {

            NSArray *aryProvsioning = [FileManager contentsOfDirectoryAtPath:path error:nil];

            if (aryProvsioning == nil)

                return;
            for(NSString* provisioningFile in aryProvsioning)
            {
                deleteAllAtPath(provisioningFile);

                NSLog(@"delete file:%@",provisioningFile);
                [FileManager removeItemAtPath:[NSString stringWithFormat:@"/vault/provisioning/%@",provisioningFile]
                                        error:nil];
            }

    }

        下面再来看一个读写文件的类NSFileHandle,其实它的实现就是调用c语言种文件流的操作。通过取得文件流的指针,然后定位指针的位置从而读取出文件中的内容,同样在文件中写入东西也是同样的道理,下面来看一小短代码。

        NSFileHandle *TXTFileHandle;

        NSString    *szTXTFileTemp = @"111.txt";
        TXTFileHandle = [NSFileHandle fileHandleForWritingAtPath:szTXTFileTemp];

        //防止该文件不存在,如果不存在,新建该文件并写入空数据。
        if(!TXTFileHandle)
        {
            [@"" writeToFile:szTXTFileTemp atomically:NO encoding:1 error:nil];
            TXTFileHandle = [NSFileHandle fileHandleForWritingAtPath:szTXTFileTemp];
        }

        [TXTFileHandle seekToEndOfFile];

        char * conTemp = "mengtnt";

        [TXTFileHandle  writeData:[NSData dataWithBytes:conTemp length:8];

        [TXTFileHandle closeFile];

        //下面是读取

        TXTFileHandle = [NSFileHandle fileHandleForReadingAtPath:szTXTFileTemp];

        NSData *fileData = [TXTFileHandle readDataToEndOfFile];

        NSlog(@"%@",[[NSString alloc] initWithData:fileData  encoding:NSUTF8StringEncoding]);

        [TXTFileHandle closeFile];

      以上就是cocoa编程中文件操作的常见用法,下面再看两个类,虽说里面的方法都很简单,但是不注意一些细节还是会出问题的。

        NSThread类,开启一个线程的方法在简单不过了,[NSThread detachNewThreadSelector:@selector(StartThreadTest:) toTarget:self withObject:nil];其中selector就是你要调用的线程方法。

    - (void)StartThreadTest:(id)idThread
    {
        NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
        //一些UI事件以及其他事件处理
        [pool release];
    }

    不知道大家注意了没有,为什么线程中要加release pool那。特别是线程中有处理UI事件的时候一定要加上pool,否则你会发现应用程序运行异常的慢,甚至有的时候会crash掉。可能是线程的资源在 使用时,很难判断何时使用完毕,所以编译器也就不方便自动释放这些资源,这样就要手动加上pool,使线程运行到此处的时候释放掉某些资源。究其深层的原 因,自己也是一知半解。这里仅仅是自己的一些想法,还望那位高手能指点下分析的是否正确。

        最后跟大家提以下NSTimer的用法,NSTimer说白了就是一个定时器,就是用来每隔一段时间运行某段代码,    [NSTimer scheduledTimerWithTimeInterval:0.2
                                                 target:self
                                               selector:@selector(DetectSN:)
                                               userInfo:nil
                                                repeats:YES];

    当你相停止timer的时候就调用invalide方法。需要注意的一点是当NSTimer被设置无效的时候,要想使用这个timer对象,必须重新开辟一个NSTimer的对象。

        那么关于NSFoundation一些类的用法就先介绍到这里吧,这些都是自己平时整理一些资料,这些类都是比较容易用到,同时也容易出错的类。希望能给 大家一些帮助,同时在调试中也提高了自己的能力。以后我就来简单的分享下自己cocoa编程中ApplicationKit框架的一些简单用法,不过我一 直在UI设计方面存在先天不足,也希望在介绍ApplicationKit框架,各位老鸟看到我设计的一些UI的时候,能给点建设性的意见,以让自己摆脱 这方面的苦恼。

  • 相关阅读:
    Linux系统
    Maven常用命令有哪些?
    .Maven的工程类型有哪些?
    什么是Maven?
    Shiro 的优点
    比较 SpringSecurity 和 Shiro
    Maven的工程类型有哪些?
    Maven仓库是什么
    什么是Maven?
    什么是 JavaConfig?
  • 原文地址:https://www.cnblogs.com/ligun123/p/2150417.html
Copyright © 2011-2022 走看看