GVKun编程网logo

Objective-C 2.0 with Cocoa Foundation--- Class类型,选择器Selector以及函数指针(3)

8

在本文中,我们将带你了解Objective-C2.0withCocoaFoundation---Class类型,选择器Selector以及函数指针(3)在这篇文章中,同时我们还将给您一些技巧,以帮助您

在本文中,我们将带你了解Objective-C 2.0 with Cocoa Foundation--- Class类型,选择器Selector以及函数指针(3)在这篇文章中,同时我们还将给您一些技巧,以帮助您实现更有效的Foundation Kit「Learn Objective-C on Mac、NSObject类现在是Objective-C运行时库的一部分(而不是Foundation组件)吗?、Objective-C 2.0 Class类型,选择器Selector以及函数指针、Objective-C 2.0 with Cocoa Foundation - (Hello word-1)

本文目录一览:

Objective-C 2.0 with Cocoa Foundation--- Class类型,选择器Selector以及函数指针(3)

Objective-C 2.0 with Cocoa Foundation--- Class类型,选择器Selector以及函数指针(3)

 当我们在程序里面通过使用上面的第一,二或者第三行代码成功的取得一个Class类型的变量,比如说我们把这个变量名字命名为myClass,那么我们在以后的代码种可以把myClass当作一个我们已经定义好的类来使用,当然我们可以把这个变量作为参数传递到其他的方法当中让其他的方法动态的生成我们需要的对象。

  5.7,DoProxy.h里面的方法定义

  DoProxy.h里面还有一些实例方法,关于方法的定义的格式,同学们可以参照第三章。我们现在要对DoProxy.h里面定义的方法的做一下简要的说明。

1 - (void) doWithCattleId:(id) aCattle colorparem:(Nsstring*) color;
2 - (void) setAllIVars;
3 - (void) SELFuncs;
4 - (void) functionPointers;

  第一行的方法,是设定aCattle,也就是Cattle或者Bull对象的属性,然后调用saySomething方法,实现控制台的打印输出。

  第二行的方法,是把我们定义的DoProxy类里面的一些变量进行赋值。

  第三行的方法,是调用doWithCattleId方法。

  第四行的方法,是调用了函数指针的方法。

  好的,我们把DoProxy.h的内容介绍完了,让我们打开DoProxy.m。

  5.8,DoProxy.m的代码说明

  有了DoProxy.h的说明,同学们理解DoProxy.m将是一件非常轻松的事情,让我们坚持一下把这个轻松的事情搞定。由于篇幅所限,笔者在这里的讲解将会省略掉非本章的内容。

  DoProxy.m代码如下:

 1 #import "DoProxy.h"
  2 #import "Cattle.h"
  3 #import "Bull.h"
  4 
  5 @implementation DoProxy
  6 - (void) setAllIVars
  7 {
  8     cattle[0] = [Cattle new];
  9     
10     bullClass = NSClassFromString(BULL_CLASS);
11     cattle[1] = [bullClass new];
12     cattle[2] = [bullClass new];
13     
14     say = @selector(saySomething);
15     skin = NSSelectorFromString(SET_SKIN_COLOR);
16 }
17 - (void) SELFuncs
18 {
19     [self doWithCattleId:cattle[0] colorparem:@"brown"];
20     [self doWithCattleId:cattle[1] colorparem:@"red"];
21     [self doWithCattleId:cattle[2] colorparem:@"black"];
22     [self doWithCattleId:self colorparem:@"haha"];
23 }
24 - (void) functionPointers
25 {
26     setSkinColor_Func=(void (*)(id, SEL, Nsstring*)) [cattle[1] methodForSelector:skin];
27     //IMP setSkinColor_Func = [cattle[1] methodForSelector:skin];
28     say_Func = [cattle[1] methodForSelector:say];
29     setSkinColor_Func(cattle[1],skin,@"verbose");
30     NSLog(@"Running as a function pointer will be more efficiency!");
31     say_Func(cattle[1],say); 
32 }
33 - (void) doWithCattleId:(id) aCattle colorparem:(Nsstring*) color
34 {
35     if(notFirstRun == NO)
36     {
37         Nsstring *myName = NsstringFromSelector(_cmd);
38         NSLog(@"Running in the method of %@", myName);
39         notFirstRun = YES;
40     }
41     
42     Nsstring *cattleparemClassName = [aCattle className];
43     if([cattleparemClassName isEqualToString:BULL_CLASS] || 
44        [cattleparemClassName isEqualToString:CATTLE_CLASS])
45     {
46         [aCattle setLegsCount:4];
47         if([aCattle respondsToSelector:skin])
48         {
49             [aCattle performSelector:skin withobjecs:color];
50         }
51         else
52         {
53             NSLog(@"Hi, I am a %@, have not setSkinColor!", cattleparemClassName);
54         }
55         [aCattle performSelector:say];
56     }
57     else
58     {
59         Nsstring *yourClassName = [aCattle className];
60         NSLog(@"Hi, you are a %@, but I like cattle or bull!", yourClassName);
61     }
62 }
63 @end

  第10行代码是通过一个预定义的宏BULL_CLASS取得Bull的Class变量。

  第11和12行代码是使用bullClass来初始化我们的cattle实例变量数组的第2和第3个元素。

  第14行是通过@selector函数来取得saySomething的SEL变量。

  第15行是通过向NSSelectorFromString传递预定义的宏SET_SKIN_COLOR来取得setSkinColor的SEL变量。

  第22行,笔者打算“戏弄”一下doWithCattleId,向传递了不合适的参数。

  第26行,笔者取得了传统的C语言的函数指针,也是使用了第5.5节所述的第一种取得的方法。

  第28行,笔者通过5.5节所述的第二种取得的方法得到了函数指针say_Func。

  第29行和31行分别执行了分别在第26行和28行取得的函数指针。

  第35行是一个BOOL型的实例变量notFirstRun 。当对象被初始化之后,确省的值是NO。第一次执行完毕之后,我们把这个变量设定成为YES,这样就保证了花括号里面的代码只被执行一次。

  第37行我们通过_cmd取得了doWithCattleId这个方法名字用于输出。当然同学们在设计方法的提供给别人使用的时候,为了防止使用方法的人把这个方法本身传递进来造成死循环,需要使用_cmd这个系统隐藏的变量判断一下。笔者在这里没有做出判断,这样写从理论上来说存在一定的风险。

  第42行,我们通过向对象发送className消息来取得这个对象的类的名字。

  第43行和第44行,我们通过Nsstring的方法isEqualToString来判断取得的类的名字是否在我们事先想象的范围之内,我们只希望接受Bull或者Cattle类的对象。

  第46行,本来我们想通过SEL的方式来进行这个牛股的设定,但是由于它的参数不是从NSobjecs继承下来的,所以我们无法使用。我们会有办法解决这个问题的,我们将在后面的章节里面介绍解决这个问题的方法。

  第59行,我们通过类的名字发现了一个假冒的Cattle,我们把这个假冒的家伙给揪出来,然后实现了屏幕打印。

  5.9,本章总结

  本章给同学们介绍了几个新的数据类型,以及使用方法,这些数据类型分别是BOOL,SEL,Class,IMP。

Foundation Kit「Learn Objective-C on Mac

Foundation Kit「Learn Objective-C on Mac

 

Cocoa

Cocoa框架實際上由Foundation Kit和AppKit兩個不同的框架組成.Foundation框架中有很多有用的,面向數據的低級類和數據類型.此框架主要用來處理界面無關的內容,AppKit則包含了所有的用戶接口對象和高級類.

結構體類型

Cocoa中一些類據類型是使用struct實現的,不使用對象實現的原因就在於性能.對象的開銷比較大(對象都是動態分配的).所以對這種數據類型可以使用三種方式賦值.

typedef struct   _NSRange{
    unsigned int location;
    unsigned int length;
}NSRange;

1.
NSRange range;
range.lcation = 17;
range.length = 4;

2.
NSRange range = (17,4);

3.
NSRage range = NSMakeTange(17,4);

其他還有NSPoint,NSSize,NSRect等數據類型也是如此,除了1,2種賦值方法外,也都有對應的函數可以生成數據:NSMakePoint(),NSMakeSize(),NSMakeRect()等.

Nsstring

@""是Nsstring字面量寫法.

Nsstring可以准確地處理Unicode字符串.

+ stringWithFormat: 類級方法,通過格式化字符串和一組參數輸出格式化後的結果.

– isEqualToString: 對象級方法,比較與所給的字符串是否相同.

Cocoa中的字符串是對象,所以不能用 == 號來比較.如果使用 == 號比較兩個字符串對象,那麼事實上是比較了兩個對象的指針.

– compare:與給定的字符串比較(區分大小寫),返回一個枚舉值作為結果.

– compare:options:

– compare:options:range:

– compare:options:range:locale:字符串比較方法,帶選項的版本(如:是否區分大小寫)

– hasPrefix:測試是否以給定的字符串開頭

– hasSuffix:測試是否以給定的字符串結尾

– rangeOfString:查詢給定字符串在字符串對象中的位置.

NSMutableString

Nsstring的子類, NSMutableString是Nsstring的可修改版本.

+ stringWithCapacity:容量建議值.對象會以此方法給出的建議值劃分出一塊內存作為可變字符串對象的初始大小.合理使用可以提高性能.

– appendFormat:向字符串對象追加內容.

– appendString:使用格式化方式向字符串對象追加內容.

– deleteCharactersInRange:從字符串對象的指定范圍中刪除字符串.

集合

NSArray

NSArrary是一個用來存儲對象的有序列表.

限制:只能存儲Objective-C對象.不能存儲C語言中的基本數據類型(如int,float,enum,struct,或者NSArray中的隨機指針),也不能存儲nil或者NULL值.

– count 獲得列表包含的對象數.

– objectAtIndex:獲取特定索引處的對象.索引值超出范圍時,將引發一個異常.

Cocoa框架中對象往往都是以類族的形式實現的.如:NSArray對象可能會在運行時發現其實運作的是NSCFArray(來自Core Foundation框架(C語言的實現版本),很多Cocoa對象都是如此做橋接的).

NSMutableArray

NSArray的可變版本.

+ arrayWithCapacity:以建議值作為初始化大小.

– addobject:追加對象

– removeObjectAtIndex:從指定索引處刪除對象

NSEnumerator

遍歷集合時:

使用for循環向集合對象發送objectAtIndex:消息來獲取對象.

使用枚舉對象來完成遍歷集合的工作.

Mac OS X Tiger 之前的系統,支持的語法:

NSEnumerator *enumerator;

enumerator = [array objectEnumerator];

id thingie;

while(thingie = [enumerator nextObject]){
    //something to do.
}

即向集合對象發送objectEnumerator消息,以獲取一個枚舉對象(或者NSEnumerator類或其子類的實例),然後在while循環中向此枚舉對象循環發送nextObject以依次獲取枚舉中的每個對象,直到枚舉的末尾(直到nextObject消息返回一個nil對象).

Mac OS X Leopard之後的系統可使用如下語法:

for (Nsstring *string in array){
    //to do something.
}

NSDictionary / NSMutableDictionary

字典對象(及可變型字典對象).

+ dictionaryWithObjectsAndKeys:使用所給的鍵值對建立字典.

– setobject:forKey:添加字典項

– removeObjectForKey:刪除字典項

不要擴展Cocoa框架下的類

Cocoa中許多類都是以類簇的方式實現的.即它們是一群隱藏在通用接口下的與實現相關的類.

比如:使用Nsstring對象的是個,實際上獲得的可能是NSLiteralString,NSCFString,NSSimpleCString,NSBallofString或者其他未寫入文檔的與實現相關的對象.

不要想著為以類簇方式實現的類添加子類,這可能是很痛苦的事情,想要擴展這類類的能力時,可考慮"復合"或者"類別".

數值類

NSNumber

用於包裝基本數據類型,如:int,float等.成為一個對象,以便將基本數據類型放入集合類中.

+ numberWithBool:由布爾型生成對象

+ numberWithChar:字符型

+ numberWithDouble:雙精度浮點型

+ numberWithFloat:單精度浮點型

+ numberWithInt:整型

+ numberWithInteger:ObjC整型

+ numberWithLong:長整型

+ numberWithLongLong:長長整型

+ numberWithShort:短整型

使用以下方法可以從對象中提取回基本數據型值.

– boolValue

– charValue

– decimalValue

– doubleValue

– floatValue

– intValue

– integerValue

– longLongValue

– longValue

– shortValue

都是顧名思義的方法名.

NSValue

NSValue是NSNumber的父類,可以用來包裝任意值.NSNumber不能包裝的struct,也可以用這個類來包裝.

+ valueWithBytes:objCType:第一參數傳入要包裝的數值的地址.第二個參數為數據類型的描述字符串(一般可以直接用@encode命令來生成)

NSRect rect = NSMakeRect(1,2,30,40);

NSValue *value = [NSValue  valueWithBytes:&rect  objCType:@encode(NSRect)];

[array addobject:value];

使用- getValue:方法可以從NSValue對象中提取出數據的指針(可以看到get前輟的方法約定,總是用來提取指針,而不是值).

NSValue中有一些方法,可以用來快速包裝Cocoa常用結構體數據類型:

+ valueWithPoint:快速包裝NSPoint對象

+ valueWithRange:NSRange

+ valueWithRect:NSRect

+ valueWithSize:NSSize

NSNull

空類型,由於集合類中不能添加nil值(被用來識別為集合的結尾處),所以你可以使用NSNull對象添加到集合中,以表示空(無,沒有)成員.

NSObject类现在是Objective-C运行时库的一部分(而不是Foundation组件)吗?

NSObject类现在是Objective-C运行时库的一部分(而不是Foundation组件)吗?

看着 the Mac OS X 10.8’s version of the Objective-C runtime library source code,我注意到它有一个 NSObject.mm文件.顾名思义,它有NSObject类实现,以及内置自动释放池和保留计数实现.

但是,Mountain Lion之前的ObjC运行时库的版本没有实现NSObject类(它们没有NSObject.mm文件,例如你可以在Mac OS X 10.7’s Objective-C runtime library source code看到).

那么这是否真的意味着NSObject类现在是Objective-C运行时库的一部分而不是Foundation库组件?如果是,为什么?在继承NSObject时,是否要避免连接整个Foundation库(使用-framework Foundation)?

解决方法

您可以使用nm(1)工具查看任何特定库的哪些部分.

如果你在libobjc上运行它,你会发现NSObject实际上是由libobjc提供的:

% nm /usr/lib/libobjc.dylib | grep -F NSObject
⋮
0000000000021688 t +[NSObject _isDeallocating]
0000000000021674 t +[NSObject _tryRetain]
0000000000021780 t +[NSObject allocWithZone:]
000000000002176e t +[NSObject alloc]
0000000000021699 t +[NSObject allowsWeakReference]
0000000000021712 t +[NSObject autorelease]
0000000000020fa6 t +[NSObject class]
000000000002115a t +[NSObject conformsToProtocol:]
00000000000217ea t +[NSObject copyWithZone:]
00000000000217e6 t +[NSObject copy]
000000000002178d t +[NSObject dealloc]
⋮

(“t”表示符号由此库提供;“u”表示符号未定义,表示此库使用它,但它必须来自其他位置.)

这不是他们第一次提出NSObject的实施;在Lion中,你会在CoreFoundation.framework中找到它.

我不知道为什么他们动了它.无论如何,它是一个实现细节;正式,NSObject is still part of Foundation.

Objective-C 2.0 Class类型,选择器Selector以及函数指针

Objective-C 2.0 Class类型,选择器Selector以及函数指针


5,Class类型,选择器Selector以及指针函数 

本系列讲座有着很强的前后相关性,如果你是第一次阅读本篇文章,为了更好的理解本章内容,笔者建议你最好从本系列讲座的第1章开始阅读,请点击这里。 

上一章笔者介绍了在Objective-C里面继承的概念。有了继承的知识我们可以重复的使用很多以前生效的代码,这样就大大的提高了代码开发的效率。在本章,笔者要向同学们介绍几个非常重要的概念,Class类型, 选择器Selector以及指针函数。 

我们在实际上的编程过程中,也许会遇到这样的场景,那就是我们在写程序的时候不能确切的知道我们需要使用什么类,使用这个类的什么方法。在这个时候,我们需要在我们的程序里面动态的根据用户的输入来创建我们在写程序不知道的类的对象,并且调用这个对象的实例方法。Objective-C为我们提供了Class类型, 选择器Selector以及指针函数来实现这样的需求,从而大大的提高了我们程序的动态性能。

在Objective-C里面,一个类被正确的编译过后,在这个编译成功的类里面,存在一个变量用于保存这个类的信息。我们可以通过一个普通的字符串取得这个Class,也可以通过我们生成的对象取得这个Class。Class被成功取得之后,我们可以把这个Class当作一个已经定义好的类来使用它。

Selector和Class比较类似,不同的地方是Selector用于表示方法。 在Objective-C的程序进行编译的时候,会根据方法的名字(包括参数列表)确定一个唯一的身份证明(实际上就是一个整数),不用的类里面的相同名字相同声明的方法的身份证明是一样的。这样在程序执行的时候,runtime就不用费力的进行方法的名字比较来确定是执行哪一个方法了,只是通过一个整数的寻找就可以马上定位到相应的方法,然后找到相应的方法的入口地址,这样方法就可以被执行了。

笔者在前面的章节里面叙述过,在Objective-C里面消息也就是方法的执行比C语言的直接找到函数入口地址执行的方式,从效率上来讲是比较低下的。尽管Objective-C使用了Selector等招数来提高寻找效率,但是无论如何寻找的过程,都是要消耗一定的时间的。好在Objective-C是完全兼容C的,它也有指针函数的概念。当我们需要执行效率的时候,比如说在一个很大的循环当中需要执行某个功能的时候,我们可以放弃向对某一个对象发送消息的手段,用指针函数取而代之,这样就可以获得和C语言一样的执行效率了。

说到这里,可能有的同学已经有些茫然了。这些概念有些令人难以理解,但是它们确实是Objective-C的核心的功能。掌握了这些核心的功能之后,同学们可以很轻松的看懂苹果的SDK里面的很多东西含义,甚至可以自己动手写一些苹果没有为我们提供的功能。所以建议大家仔细研读本章的内容,如果有什么问题,可以发个帖子大家可以共同探讨。

从笔者的观点上来看,对于有Java或者C++或者其他面向对象的语言的经验的同学来说,前面的从第1到第4章的内容也许有些平淡无奇。从第5章开始,我们将要逐渐的深入到Objective-C的核心部分。笔者的最终目的,虽然是向大家介绍iPhone开发的入门,但是笔者认为了解了Objective-C的基本概念以及使用方法之后,熟悉iPhone的应用程序的开发将是一件水到渠成的轻松的事情。否则如果你直接就深入到iPhone的开发的话,在绝大多数时间你也许因为一个小小的问题就会困扰你几个小时甚至几天,解决这些问题的唯一方法就是熟悉Objective-C和Cocoa Foundation的特性。

好了,说了很多我们从下面就要开始,我们的手法和前面几章是一样的,我们首先要介绍一下本章程序的执行结果。

5.1,本章程序的执行结果


 图5-1,第5章程序的执行结果

在本章里面,我们将要继续使用我们在前面几章已经构筑好的类Cattle和Bull。为了灵活的使用Cattle和Bull,我们将要构筑一个新的类,DoProxy。在DoProxy里面,我们将会引入几个我们的新朋友,他们分别是BOOL,SEL,IMP,CLASS。通过这些新的朋友我们可以动态的通过设定文件取得Cattle和Bull的类,还有方法以及方法指针。下面将要介绍如何构筑本章程序。同学们可以按照本章所述的步骤来构筑,也可以通过从这里下载。不过为了熟悉代码的写作,笔者强烈建议大家按照笔者所述的步骤来操作。

5.2,实现步骤

第一步,按照我们在第2章所述的方法,新建一个项目,项目的名字叫做05-Hello Selector。如果你是第一次看本篇文章,请到这里参看第二章的内容。

第二步,按照我们在第4章的4.2节的第二,三,四步所述的方法,把在第4章已经使用过的“Cattle.h”,“Cattle.m”,“Bull.h”还有“Bull.m” 导入本章的项目里面。如果你没有第4章的代码,请到这里下载。如果你没有阅读第4章的内容,请参看这里。

第三步,把鼠标移动到项目浏览器上面的“Source”上面,然后在弹出的菜单上面选择“Add”,然后在子菜单里面选择“New Files”,然后在新建文件对话框的左侧选择“Cocoa Touch Classes”,然后在右侧窗口选择“NSObject subclass”,选择“Next”,在“New File”对话框里面的“File Name”栏内输入“DoProxy.m”。在这里笔者没有给出图例,在这里新建文件的步骤和第3章的第二步到第四步相同,只是文件名字不一样。第一次看到本篇 文章的同学可以参照第3章。

第四步,打开“DoProxy.h”做出如下修改并且保存

复制代码

#import  < Foundation / Foundation.h >

#define  SET_SKIN_COLOR @"setSkinColor:"
 BULL_CLASS @"Bull"  CATTLE_CLASS @"Cattle"

@interface DoProxy : NSObject {
    BOOL notFirstRun;
    id cattle[
3 ];
    SEL say;
    SEL skin;
    
void ( * setSkinColor_Func) (id, SEL, Nsstring );
    IMP say_Func;
    Class bullClass;
}
-  ( ) doWithCattleId:(id) aCattle colorParam:(Nsstring ) color;
) setAllIVars;
) SELFuncs;
) functionPointers;
@end

复制代码

第五步,打开“DoProxy.m”做出如下修改并且保存

#import  " DoProxy.h "
#import 
Cattle.h Bull.h

@implementation DoProxy
) setAllIVars
{
    cattle[
0 =  [Cattle  new ];
    
    bullClass 
 NSClassFromString(BULL_CLASS);
    cattle[
1  [bullClass  ];
    cattle[
2 ];
    
    say 
 @selector(saySomething);
    skin 
 NSSelectorFromString(SET_SKIN_COLOR);
}
) SELFuncs
{
    [self doWithCattleId:cattle[
] colorParam: @" brown ];
    [self doWithCattleId:cattle[
red black ];
    [self doWithCattleId:self colorParam:
haha ];
}
) functionPointers
{
    setSkinColor_Func
)(id,0)">)) [cattle[ ] methodForSelector:skin];
    
// IMP setSkinColor_Func = [cattle[1] methodForSelector:skin];     say_Func   [cattle[ ] methodForSelector:say];
    setSkinColor_Func(cattle[
],skin, verbose );
    NSLog(
Running as a function pointer will be more efficiency! );
    say_Func(cattle[
}
) color
{
    
if (notFirstRun  ==  NO)
    {
        Nsstring 
myName   NsstringFromSelector(_cmd);
        NSLog(
Running in the method of %@ , myName);
        notFirstRun 
 YES;
    }
    
    Nsstring 
cattleParamClassName   [aCattle className];
    
([cattleParamClassName isEqualToString:BULL_CLASS]  ||  
       [cattleParamClassName isEqualToString:CATTLE_CLASS])
    {
        [aCattle setLegsCount:
4 ];
        
([aCattle respondsToSelector:skin])
        {
            [aCattle performSelector:skin withObject:color];
        }
        
else
        {
            NSLog(
Hi, I am a %@, have not setSkinColor! aramClassName);
        }
        [aCattle performSelector:say];
    }
    
    {
        Nsstring 
yourClassName   [aCattle className];
        NSLog(
    }
}
@end

复制代码

第六步,打开“05-Hello Selector.m” 作出如下修改并且保存

int  main (  argc,  const   char  argv[]) {
    NSAutoreleasePool 
 pool   [[NSAutoreleasePool alloc] init];
    DoProxy 
doProxy   [DoProxy  ];
    
    [doProxy setAllIVars];
    [doProxy SELFuncs];
    [doProxy functionPointers];
    
    [pool drain];
    
return   ;
}

复制代码

第七步,选择屏幕上方菜单里面的“Run”,然后选择“Console”,打开了Console对话框之后,选择对话框上部中央的“Build and Go”,如果不出什么意外的话,那么应该出现入图5-1所示的结果。如果出现了什么意外导致错误的话,那么请仔细检查一下你的代码。如果经过仔细检查发现 还是不能执行的话,可以到这里下载笔者为同学们准备的代码。 如果笔者的代码还是不能执行的话,请告知笔者。

5.3,BOOL类型

我们现在打开“DoProxy.h”文件。“DoProxy.h”文件的第3行到第5行是三个预定义的三个字符串的宏。我们将在程序当中使用这3个宏,为了实现代码的独立性,在实际的程序开发当中,我们也许考虑使用一个配置的文本文件或者一个XML来替代这些宏。但是现在由于笔者的主要目的是讲解Objective-C的概念,为了避免较多的代码给大家带来理解主题的困难,所以笔者没有使用配置文件或者XML来表述这些可以设定的常量。

“DoProxy.h”的第7行对同学们来说也是老朋友了,是通知编译器,我们需要声明一个DoProxy类,从NSObject继承。

我们在第8行遇到了我们的一个新的朋友,BOOL:

    BOOL notFirstRun;

我们定义了一个notFirstRun的实例变量,这个变量是布尔类型的。我们的实例方法doWithCattleId需要被执行多次,我们在第一次执行doWithCattleId的时候需要向控制输出包含doWithCattleId的方法名字的字符串,关于这个字符串的内容,请参考图5-1。

好的,我们现在需要看看在Objective-C里面BOOL是怎样定义的,我们把鼠标移动到BOOL上面,然后单击鼠标右键选择弹出菜单的“Jump to DeFinition”,然后Xcode会打开objc.h文件,我们看到下面的代码:

typedef signed               BOOL;
 BOOL is explicitly signed so @encode(BOOL) == "c" rather than "C"
 even if -funsigned-char is used.  OBJC_BOOL_DEFINED


 YES             (BOOL)1  NO              (BOOL)0

复制代码

我们看到这段代码,我们可以这样理解,在Objective-C里面,BOOL其实是signed char,YES是1,NO是0。我们可以这样给BOOL赋值:

BOOL x   YES;
BOOL y 
 NO;

 

关于BOOL,实际上就是一个开关的变量,但是我们需要注意下面2点:

第一点,从本质上来说BOOL是一个8bit的一个char,所以我们在把其他比如说short或者int转换成为BOOL的时候一定要注意。如果short或者int的最低的8位bit都是0的话,尽管除了最低的8位以外都不是0,那么经过转换之后,就变成了0也就是NO。比如说我们有一个int的值是0X1000,经过BOOL转换之后就变成了NO。 

第二点,Objective-C里面的所有的逻辑判断例如if语句等等和C语言保持兼容,如果数值不是0判断为真,如果数值是0那么就判断为假,并不是说定义了BOOL值之后就变成了只有1或者YES为真。所以下面的代码的判断都为真:

( 0X1000 )
- )

5.4,SEL类型

让我们接着看“DoProxy.h”文件的下列代码:

    id cattle[ ];
2      SEL say;
3      SEL skin;

其中id cattle[3]定义了一个数组用于存储Cattle或者Bull对象。这一行代码估计大家都很熟悉,笔者就不赘述了。像这样的传统的数组并不能完全满足我们的需求,当我们需要做诸如追加,删除等操作的时候,会很不方便。在随后的章节里面笔者将要向大家介绍传统数组的替代解决方案NSArray。

 

上一段代码的第二行和第三行是本节所关注的,就是SEL类型。Objective-C在编译的时候,会根据方法的名字(包括参数序列),生成一个用 来区分这个方法的唯一的一个ID,这个ID就是SEL类型的。我们需要注意的是,只要方法的名字(包括参数序列)相同,那么它们的ID都是相同的。就是 说,不管是超类还是子类,不管是有没有超类和子类的关系,只要名字相同那么ID就是一样的。除了函数名字和ID,编译器当然还要把方法编译成为机器可以执 行的代码,这样,在一个编译好的类里面,就产生了如下图所示方法的表格示意图(本构造属于笔者推测,没有得到官方证实,所以图5-2为示意图仅供参考,我们可以暂时认为是这样的)。

 

图5-2,方法的表格示意图 

请注意setSkinColor后面有一个冒号,因为它是带参数的。由于存在这样的一个表格,所以在程序执行的时候,我们可以方便的通过方法的名字,获取到方法的ID也就是我们所说的SEL,反之亦然。具体的使用方法如下:

    SEL 变量名   @selector(方法名字);
 NSSelectorFromString(方法名字的字符串);
    Nsstring  变量名   NsstringFromSelector(SEL参数);

其中第1行是直接在程序里面写上方法的名字,第2行是写上方法名字的字符串,第3行是通过SEL变量获得方法的名字。我们得到了SEL变量之后,可以通过下面的调用来给一个对象发送消息:

[对象 performSelector:SEL变量 withObject:参数1 withObject:参数2];

这样的机制大大的增加了我们的程序的灵活性,我们可以通过给一个方法传递SEL参数,让这个方法动态的执行某一个方法;我们也可以通过配置文件指定需要执行的方法,程序读取配置文件之后把方法的字符串翻译成为SEL变量然后给相应的对象发送这个消息。

从效率的角度上来说,执行的时候不是通过方法名字而是方法ID也就是一个整数来查找方法,由于整数的查找和匹配比字符串要快得多,所以这样可以在某种程度上提高执行的效率。 

5.5,函数指针

在讲解函数指针之前,我们先参看一下图5-2,函数指针的数值实际上就是图5-2里面的地址,有人把这个地址成为函数的入口地址。在图5-2里面我们可以通过方法名字取得方法的ID,同样我们也可以通过方法ID也就是SEL取得函数指针,从而在程序里面直接获得方法的执行地址。或者函数指针的方法有2种,第一种是传统的C语言方式,请参看“DoProxy.h” 的下列代码片断:

     );
    IMP say_Func;

其中第1行我们定义了一个C语言里面的函数指针,关于C语言里面的函数指针的定义以及使用方法,请参考C语言的书籍和参考资料。在第一行当中,值得我们注意的是这个函数指针的参数序列:

第一个参数是id类型的,就是消息的接受对象,在执行的时候这个id实际上就是self,因为我们将要向某个对象发送消息。

第二个参数是SEL,也是方法的ID。有的时候在消息发送的时候,我们需要使用用_cmd来获取方法自己的SEL,也就是说,方法的定义体里面,我们可以通过访问_cmd得到这个方法自己的SEL。 

第三个参数是Nsstring*类型的,我们用它来传递skin color。在Objective-C的函数指针里面,只有第一个id和第二个SEL是必需的,后面的参数有还是没有,如果有那么有多少个要取决于方法的声明。 

现在我们来介绍一下Objective-C里面取得函数指针的新的定义方法,IMP。

上面的代码的第一行比较复杂,令人难以理解,Objective-C为我们定义了一个新的数据类型就是在上面第二行代码里面出现的IMP。我们把鼠标移动到IMP上,单击右键之后就可以看到IMP的定义,IMP的定义如下:

typedef id                      ( IMP)(id, 

);

这个格式正好和我们在第一行代码里面的函数指针的定义是一样的。

我们取得了函数指针之后,也就意味着我们取得了执行的时候的这段方法的代码的入口,这样我们就可以像普通的C语言函数调用一样使用这个函数指针。当然我们可以把函数指针作为参数传递到其他的方法,或者实例变量里面,从而获得极大的动态性。我们获得了动态性,但是付出的代价就是编译器不知道我们要执行哪一个方法所以在编译的时候不会替我们找出错误,我们只有执行的时候才知道,我们写的函数指针是否是正确的。所以,在使用函数指针的时候要非常准确地把握能够出现的所有可能,并且做出预防。尤其是当你在写一个供他人调用的接口API的时候,这一点非常重要。

5.6,Class类型

到目前为止,我们已经知道了对应于方法的SEL数据类型,和SEL同样在Objective-C里面我们不仅仅可以使用对应于方法的SEL,对于类在Objective-C也为我们准备了类似的机制,Class类型。当一个类被正确的编译过后,在这个编译成功的类里面,存在一个变量用于保存这个类的信息。我们可以通过一个普通的字符串取得 这个Class,也可以通过我们生成的对象取得这个Class。Class被成功取得之后,我们可以把这个Class当作一个已经定义好的类来使用它。这样的机制允许我们在程序执行的过程当中,可以Class来得到对象的类,也可以在程序执行的阶段动态的生成一个在编译阶段无法确定的一个对象。

因为Class里面保存了一个类的所有信息,当然,我们也可以取得一个类的超类。关于Class类型,具体的使用格式如下:

    Class 变量名   [类或者对象  class  [类或者对象 superclass];
 NSClassFromString(方法名字的字符串);
4   NsstringFromClass(Class参数);

第一行代码,是通过向一个类或者对象发送class消息来获得这个类或者对象的Class变量。

第二行代码,是通过向一个类或者对象发送superclass消息来获得这个类或者对象的超类的Class变量。

第三行代码,是通过调用NSClassFromString函数,并且把一个字符串作为参数来取得Class变量。这个在我们使用配置文件决定执行的时候的类的时候,NSClassFromString给我们带来了极大的方便。

第四行代码,是NSClassFromString的反向函数NsstringFromClass,通过一个Class类型作为变量取得一个类的名字。

当我们在程序里面通过使用上面的第一,二或者第三行代码成功的取得一个Class类型的变量,比如说我们把这个变量名字命名为myClass,那么我们在以后的代码种可以把myClass当作一个我们已经定义好的类来使用,当然我们可以把这个变量作为参数传递到其他的方法当中让其他的方法动态的生成我们需要的对象。

5.7,DoProxy.h里面的方法定义

DoProxy.h里面还有一些实例方法,关于方法的定义的格式,同学们可以参照第三章。我们现在要对DoProxy.h里面定义的方法的做一下简要的说明。

) functionPointers;

第一行的方法,是设定aCattle,也就是Cattle或者Bull对象的属性,然后调用saySomething方法,实现控制台的打印输出。

第二行的方法,是把我们定义的DoProxy类里面的一些变量进行赋值。

第三行的方法,是调用doWithCattleId方法。

第四行的方法,是调用了函数指针的方法。

好的,我们把DoProxy.h的内容介绍完了,让我们打开DoProxy.m。

5.8,DoProxy.m的代码说明

有了DoProxy.h的说明,同学们理解DoProxy.m将是一件非常轻松的事情,让我们坚持一下把这个轻松的事情搞定。由于篇幅所限,笔者在这里的讲解将会省略掉非本章的内容。

DoProxy.m代码如下:

 1   2   3   4   5  @implementation DoProxy
 6  ) setAllIVars
 7  {
 8      cattle[  9      
10      bullClass   NSClassFromString(BULL_CLASS);
11  12  13  14      say   @selector(saySomething);
15      skin   NSSelectorFromString(SET_SKIN_COLOR);
16  }
17  ) SELFuncs
18  19      [self doWithCattleId:cattle[ 20  21  22      [self doWithCattleId:self colorParam: 23  24  ) functionPointers
25  26      setSkinColor_Func ] methodForSelector:skin];
27  28  ] methodForSelector:say];
29      setSkinColor_Func(cattle[ 30      NSLog( 31      say_Func(cattle[ 32  33  ) color
34  35   NO)
36      {
37          Nsstring   NsstringFromSelector(_cmd);
38          NSLog( 39          notFirstRun   YES;
40      }
41  42   [aCattle className];
43   
44         [cattleParamClassName isEqualToString:CATTLE_CLASS])
45  46          [aCattle setLegsCount: 47           ([aCattle respondsToSelector:skin])
48          {
49              [aCattle performSelector:skin withObject:color];
50          }
51  52  53              NSLog( aramClassName);
54  55          [aCattle performSelector:say];
56  57  58  59  60  61  62  63  @end

复制代码

第10行代码是通过一个预定义的宏BULL_CLASS取得Bull的Class变量。

第11和12行代码是使用bullClass来初始化我们的cattle实例变量数组的第2和第3个元素。

第14行是通过@selector函数来取得saySomething的SEL变量。

第15行是通过向NSSelectorFromString传递预定义的宏SET_SKIN_COLOR来取得setSkinColor的SEL变量。

第22行,笔者打算“戏弄”一下doWithCattleId,向传递了不合适的参数。

第26行,笔者取得了传统的C语言的函数指针,也是使用了第5.5节所述的第一种取得的方法。

第28行,笔者通过5.5节所述的第二种取得的方法得到了函数指针say_Func

第29行和31行分别执行了分别在第26行和28行取得的函数指针。

第35行是一个BOOL型的实例变量notFirstRun 。当对象被初始化之后,确省的值是NO。第一次执行完毕之后,我们把这个变量设定成为YES,这样就保证了花括号里面的代码只被执行一次。

第37行我们通过_cmd取得了doWithCattleId这个方法名字用于输出。当然同学们在设计方法的提供给别人使用的时候,为了防止使用方法的人把这个方法本身传递进来造成死循环,需要使用_cmd这个系统隐藏的变量判断一下。笔者在这里没有做出判断,这样写从理论上来说存在一定的风险。

第42行,我们通过向对象发送className消息来取得这个对象的类的名字。 

第43行和第44行,我们通过Nsstring的方法isEqualToString来判断取得的类的名字是否在我们事先想象的范围之内,我们只希望接受Bull或者Cattle类的对象。

第46行,本来我们想通过SEL的方式来进行这个牛股的设定,但是由于它的参数不是从NSObject继承下来的,所以我们无法使用。我们会有办法解决这个问题的,我们将在后面的章节里面介绍解决这个问题的方法。

第47行的代码,有一个非常重要NSObject的方法respondsToSelector,通过向对象发送这个消息,加上一个SEL,我们可以知道这个对象是否可以相应这个SEL消息。由于我们的Cattle无法相应setSkinColor消息,所以如果对象是Cattle类生成的话,if语句就是NO所以花括号里面的内容不会得到执行。

第59行,我们通过类的名字发现了一个假冒的Cattle,我们把这个假冒的家伙给揪出来,然后实现了屏幕打印。

5.9,本章总结

本章给同学们介绍了几个新的数据类型,以及使用方法,这些数据类型分别是BOOL,SEL,Class,IMP。

本章的内容很重要,希望同学们花一点时间仔细的理解一下。应该说,本章的内容有些令人难以理解,或者说知道了SEL,Class,IMP之后也许不知道如何使用,遇到什么情况的时候需要使用。不过在学习Objective-C的初级阶段,不知道这些也没有关系,但是SEL,Class,IMP的概念需要掌握,否则当你遇到别人写的质量比较高的代码或者苹果官方的技术文档的时候,你会觉得理解起来比较吃力。

本章内容也是理解下一章内容的基础,下一章我们将要讲述NSObject的奥秘。

Objective-C 2.0 with Cocoa Foundation - (Hello word-1)

Objective-C 2.0 with Cocoa Foundation - (Hello word-1)

  2.1,构筑Hello,World

  第一步,启动Xcode。初次启动的时候,也许会弹出一个“Welcome to Xcode”的一个对话框。这个对话框和我们的主题没有关系,我们可以把它关掉。

  第二步,选择屏幕上部菜单的“File->New Project”,出现了一个让你选择项目种类的对话框。你需要在对话框的左边选择“Command Line Utility” ,然后在右边选择“Foundation Tool”,然后选择“Choose...”按钮。如图2.1所示。

objecsive-C 2.0 with Cocoa Foundation --- 2,从Hello,World!开始

  图2-1,新建项目

  注意

  也许有人会问,你不是要讲解iphone的开发,那么为什么不选择“iPhone OS”下面的“Application”呢?

  是这样的,在这个系列当中,笔者主要侧重于objecsive-C的语法的讲解,为了使得讲解简单易懂,清除掉所有和要讲解的内容无关的东西,所以笔者在这里只是使用最简单的命令行。

  第三步,Xcode会提问你项目的名字,在“Save As”里面输入“02-Hello World”,然后选择“Save”。如图2-2所示

objecsive-C 2.0 with Cocoa Foundation --- 2,World!开始

  图2-2,输入项目的名字

  第四步,得到一个如图2-3所示的一个画面。尝试一下用鼠标分别点击左侧窗口栏里面的“02-Hello World”,“Source”.“Documentation”,“External Frameworks and Libraries”,“Products”,然后观察一下右边的窗口都出现了什么东西。一般来说,“02-Hello World”就是项目的名字下面是项目所有的文件的列表。项目下面的子目录分别是和这个项目相关的一些虚拟或者实际上的目录。为什么我说是虚拟的呢?大家可以通过Finder打开你的工程文件的目录,你会发现你的所有文件居然都在根目录下,根本就不存在什么“Source”之类的目录。

objecsive-C 2.0 with Cocoa Foundation --- 2,World!开始

  图2-3,项目浏览窗口

  第五步,选择屏幕上方菜单的“Run”然后选择“Console”,出现了如图2-4所示的画面,用鼠标点击窗口中间的“Build and Go”按钮。

objecsive-C 2.0 with Cocoa Foundation --- 2,World!开始

  图2-4,运行结果画面

  如果不出什么意外的话,大家应该看到我们熟悉得不能再熟悉的“Hello Wolrd!” 。由于我们没有写任何的代码,所以从理论上来说,这部分代码不应该出现编译错误。好的,从下面开始,笔者要开始对这个Hello World里面的一些新鲜的东西进行讲解。

我们今天的关于Objective-C 2.0 with Cocoa Foundation--- Class类型,选择器Selector以及函数指针(3)的分享就到这里,谢谢您的阅读,如果想了解更多关于Foundation Kit「Learn Objective-C on Mac、NSObject类现在是Objective-C运行时库的一部分(而不是Foundation组件)吗?、Objective-C 2.0 Class类型,选择器Selector以及函数指针、Objective-C 2.0 with Cocoa Foundation - (Hello word-1)的相关信息,可以在本站进行搜索。

本文标签: