Objective-C 编程语言(2) 类,对象,消息 --- 对象与消息

转载请标明出处:http://blog.csdn.net/zhangxingping

对象与消息


    本小节将讨论发送消息的句法,包括消息表达式的嵌套。同时,本小节还讨论对象实例变量的作用域(或者叫做可见性),以及多态性和动态绑定的概念。

    发送消息句法

    为了让一个对象完成某件事情,我们给该对象发送一个消息,告诉他调用相应的方法。在Objective-C中,消息表达式是被括在方括号中的:
    [接收者 消息]
    其中,接收者是一个对象,消息告诉该对象要做什么。在源码中,消息就是方法的名称以及传递给方法的参数。当一个消息被发送给对象的时候,运行时会在接收者的所有功能中选择正确的方法,并调用该方法。例如,下面的消息告诉myRectangle对象完成它的display方法,也就是显示该自身的矩形框:
    [myRectangle display];
    消息表达式后面跟随了一个分号,这点和C中的语句是一样的。
    由于在上面的消息表达式中,方法的名称相当于是告诉运行时“选择”一个方法来执行,因此,消息中的方法名称通常也被称为是选择器。
    方法是可以接收参数的。带有一个参数的消息格式是在消息的后面紧跟一个分号,然后把参数放置在分号的后面:
    [myRectangle setWidth:20.0];
    对于那些需要多个参数的方法来说,Objective-C则采取了一种把参数插入到函数名称中的做法。这样做的好处是方法的名称自然地就描述了所需的参数。下面的消息告诉myRectangle对象把自己的起点设置为坐标(30.0,50.0):
    [myRectangle setOriginX:30.0 y:50.0]; //这是一个很好的需要多个参数的例子。
    选择器的名称包括名称的各个部分,也就是说包括分号,因此上面例子中选择器的名称是setOriginX:y:。名称中含有两个分号,这是因为它需要两个参数。然而选择器的名称中是不包括返回值类型以及参数类型的。    
    注意:在Objective-C中,构成选择器名称的各个部分都是必须的,而不是可选的,并且他们之间的顺序是不能变化的。在一些编程语言中,术语“命名参数”以及“关键字参数”暗含着这些参数名称在运行时是可以变化的,可以有缺省值,它们之间的顺序可以是不固定的,还有可能是有附加的命名参数。但是这些特点在Objective-C中都是不存在的。
从各个方面来看,Objective-C中方法的声明只是带有两个附加参数的简单的C函数(关于这点,可以参见:《Objective-C运行时编程指南》一书中的“消息”章节)。因此,Objective-C中方法的声明和Python等语言中的使用命名参数或者关键字参数的结果完全不同。例如下面是Python中的一个例子:
def func(a, b, NeatMode = SuperNeat, Thing = DefaultThing):

pass

    在上面的Python示例中,Thing和NeatMode在调用的时候可以被省落或者是被传入不同的值。
    从原则上来讲,Rectangle类可以在实现setOrigin::方法的时候不为第二个参数指定标签,如下:
    [ myRectangle setOrigion:30.0 :50.0]; //这种写法不是很好
    从句法上来说,这种写法完全是正确的。但是这种写法没有把参数穿插在函数的名称当中,读者不能很好的理解到每个参数的具体含义。
    Objective-C中是允许实现参数数量不固定的方法的,尽管需要这样做的情况可能会很少见。这些参数写在函数名称的后面,用逗号间隔。和分号不同的是,这些用逗号间隔的参数不会被看作是函数名称的一部分。例如,在下面的示例中,假定方法makeGroup:需要一个必选参数,而其他参数都是可选的:
    [ receiver makeGroup:groupnemberOne, memberTwo, memberThree];
    和标准的C函数一样,方法也可以返回一定的值。下面的示例中使用了方法isFilled方法的返回值来为变量isFilled赋值。方法isFilled在矩形框被作为实心框而填充绘制的情况下返回YES,在没有被填充的情况返回NO:
    BOOL isFilled;
    isFilled = [myRectangle isFilled];
    从上面的示例中我们可以看出,变量和函数是可以同名的。
    消息表达式还可以进行嵌套。下面的示例中,把一个矩形框的颜色设置称另外一个矩形框的颜色:
    [ myRectangle setPrimaryColor: [otherRectprimaryColor ] ] ;
    Objective-C中提供了点(.)运算符,以便提供一种兼容的方便的调用对象访问方法的方法。这种点号运算符通常是和对象的属性一起使用的(更多信息,请参见“声明属性”以及“点号的句法”)。


    向nil发送消息

    在Objective-C中向nil发送消息是完全有效的——只是在运行时不会有任何作用。Cocoa中的几种模式就利用到了这一点。发向nil的消息的返回值也可以是有效的:
    • 如果一个方法返回值是一个对象,那么发送给nil的消息将返回0(nil)。例如:Person * motherInlaw = [ aPerson spouse] mother]; 如果spouse对象为nil,那么发送给nil的消息mother也将返回nil。
    • 如果方法返回值为指针类型,其指针大小为小于或者等于sizeof(void*),float,double,long double 或者long long的整型标量,发送给nil的消息将返回0。
    • 如果方法返回值为结构体,正如在《Mac OS X ABI 函数调用指南》,发送给nil的消息将返回0。结构体中各个字段的值将都是0。其他的结构体数据类型将不是用0填充的。
    • 如果方法的返回值不是上述提到的几种情况,那么发送给nil的消息的返回值将是未定义的。
    下面的代码段就是一个有效地向nil发送消息的示例:
    id anObjectMybeNil = nil;
    //这种写法是有效的
    if ( [ anObjectMaybeNil methordThatReturnADouble] == 0.0 )
    {
        //其他的实现代码
    }
   注意:在Mac OS X v10.5版本中,向nil发送消息的结果与上面的描述会稍有不同。在Mac OS X v10.4以及更以前的版本中,向nil发送消息是完全有效的,只要消息的返回值是对象,任意类型的指针,void,或者是其他大小小于或者等于sizeof(void*)的整型标量。此时,发送给nil的消息将返回nil。如果发送nil的消息的返回值不是上述几种类型(比如说返回的类型是结构体,或者是浮点类型,或者是向量类型的),其返回值则是未定义的。因此,在Mac OS X v10.4以及更老的版本中,我们不应该依赖于发送给nil对象的消息的返回值,除非该消息的返回值是一个对象,任意类型的指针,或者是任意大小小于或者是等于sizeof(void *)的整型标量。


    消息接收者的实例变量

    消息自动地可以访问其接收者对象的实例变量,不需要把他们作为参数传递给消息。例如,前面示例中的primaryColor方法不需要参数,但是我们可以得到对象otherRect的主体颜色,并返回这个颜色。因此,所有的方法都是可以访问其接收者的实例变量的,而不用把他们作为参数进行声明。
   这种习惯可以简化Objetive-C代码。同时,也符合面向对象编程人员对对象,消息的认知。消息发送给接收者就像是信件被发送到自己家里一样。消息的参数是把信息从外界带给消息的接收者;而不需要把消息接收者自身的信息带给接收者本身。
   消息自动地只能访问其接收者的实例变量。如果需要存储在别的对象中的信息,就必须向这个对象发送消息,请求其把相应变量的内容展示给自己。前面示例中的primaryColor以及isFilled方法正是基于这样的原因。

   更多关于引用对象的示例变量,请参阅“定义类”章节。

    

    多态性

    从前面的示例中可以看出,Objective-C中的消息在语法上和标准C语言中的函数调用是一样的。但是,由于方法是“属于”对象的,所以消息和函数调用还是有一些不同的。

    不同之一就是:对象只能被为之定义的方法操作。即使两个对象有着同名的方法,向这两个不同类型的发送同样的消息,这两个对象的处理也是不会混淆的。也就是说他们对这个消息的响应是不一样的。例如,每一个接收到display消息的图形对象都能以自己的方式来显示自身的形状。圆形和矩形会根据鼠标对相同的指令做出不同的反应。
    这种特性被称之为多态性。多态性在面型对象的程序设计中起着举足轻重的作用。多特性和动态绑定一起使得我们可以编写适用于多个不同类型对象的代码。这些对象甚至可以是以后才会别的程序员在别的项目中开发的对象。如果代码中项一个id类型的变量发送消息,任何有display方法的对象都可以是消息接收者。

    

    动态绑定

    函数调用和发送消息的一个重要不同:对于函数调用,其参数和函数是在编译时就绑定在一起的;但是消息和其接收者却不是。要等到程序运行时,发送消息的时候,其接收者才和消息进行绑定的。因此,具体调用哪个方法来响应一个消息是在运行时才进行裁决的,而不是在编译时。

    当发送一个消息时,运行时的一段例行程序会查看消息的接收者以及消息中的方法名称,并定位与消息匹配的方法的实现,然后“调用”这个方法;并在此时把指向接收者实例变量的一个指针传递给这个方法。(关于这个例行程序的更多信息,请参见《Objective-C运行时编程指南》“消息”章节。)
    这种把消息和方法动态绑定的机制以及多态性是紧密联系在一起的。正是他们使得面向对象的编程具有了更多的灵活性,也使得面向对象的编程更加强大。由于每个对象都可以有自己的针对同一个方法的不同实现版本,因此,一条Objective-C语句是可以得到不同结果的。这种不同结果不是通过发送不同消息而得到的,而是通过接收消息的不同对象来实现的。消息接收者是在程序运行的时候才决定的。这样一来消息的接收者就可以由诸如用户动作等因素来决定(也就用户的同一个动作会由于动作对象的不同而使得消息的接收者也不同,而不是在编译时就确定下来的,译者注。)。
    例如,当我们执行基于“Cocoa应用程序框架”开发的程序时,是由用户来决定那些个对象来响应菜单命令“剪贴”,“复制”,“粘贴”的消息的。这些消息是被发送给任何当前被选中的对象的。用来显示文本的对象和用来显示扫描图像的对象对“复制”消息的响应是不同的。代表一堆形状的对象和一个矩形对象对“复制”消息的响应也是不同的。由于消息对应的方法是直到运行时才被确定的,因此,这种行为上的不同和方法本身是不相干的。发送消息的代码没有必被考虑这些,甚至都不用考虑类似上面提到的哪些不同的可能性。应用程序中的对象可以对“复制”消息有自己的响应方式。
    Objective-C进一步实现了上面提到的这种动态绑定。其甚至允许被发送的消息都是可变的,直到运行时才确定的。这种机制将在《Objective-C运行时编程》一书的“消息”章节中有所讨论。


    动态方法解析

    Objective-C允许我们使用动态方法解析来在运行时提供类及实例方法的实现。更多关于动态方法解析的信息请参阅《Objective-C运行时编程》一书中关于“动态方法解析”的相关章节。

    

    点号(.)运算符

    Objective-C提供了一种和方括号([ ])作用相同的运算符,那就是点号(.)运算符。通过点号运算符我们可以调用访问方法。使用点号运算符的方法和C语言中使用点号来访问结构体中的成员是一样的:

    myInstance.value = 10;
    printf("myInstance value: %d", myInstance.value);
    当使用点号运算符来对一个对象进行操作的时候,点号运算符的作用就是使得程序的编写更加方便,更加具有可读性。(译者注:原文中关于这种语法现象有一个词汇叫做“syntax sugar”,或者“synatacic sugar”,译为“语法方糖”?)。编译器会把点号翻译成是对对象的访问方法的调用。使用点号运算符并不是直接获取或者是设置实例变量的值。下面的代码段和前面的代码是等价的:
    [myInstance setValue:10];
    printf("myInstance value: %d",[myInstance value]);
    当然,如果我们想使用这种点号运算符来方法自身的实例变量,那么我们就必须显示地使用self关键字,例如:
    self.age = 10;
    或者是:
    [self setAge:10];
    如果没有使用self.,那就是直接访问自身的实例变量了。对应的访问方法是不会被调用的,如下:
    age = 10;
    和使用方括号的方式相比,使用点号运算符的一个好处就是这种表达方式更紧凑,更具有可读性。特别是当我们需要访问一个对象的属性,而该属性又是另外对象的属性的时候。使用点号运算符还有一个好处就是当我们在代码中企图对一个只读属性进行写操作的时候,编译器会报告错误。而如果我们使用的是方括号的方式,编译器最多是在编译时报告“没有声明的方法”,这样一个告警,而程序则会是在运行时会失败。
    
    点号运算符的一般用法
    当使用点号运算符来获取值的时候,系统会调用对应的getter访问方法。缺省情况下,getter访问方法和点号后面的标示符是同名的。当使用点运算符来设置值的时候,系统会调用对应的setter访问方法。缺省情况下,setter访问方法的名称点号后面标示符名称的第一个字母大写,然后加上前缀set。如果我们不想使用这些缺省情况下的getter和setter访问方法名称,我们可以通过声明属性的方式来修改访问方法的名称(具体参见“声明属性”这一章节)。
    列表1-1中给出了几种使用点号运算符的情形:
    Graphic *graphic = [[graphic alloc] init];
    NSColor *color = graphi.color;
    CGFloat xLoc = graphic.xLoc;
    BOOL hidden = graphic.hidden;
    int textCharacterLength = graphic.text.length;
    
    if ( graphic.textHidden != YES )
    {
        graphic.text = @"Hello"; //@"Hello" 是一个NSString类的常量对象
    }   
    graphic.bounds = NSMakeRect(10,0, 10.0, 20.0, 120.0);

 列表1-1

    列表1-2和列表1-1中使用点号运算符的代码段是完全等价的,只不过使用的是方括号的方式:
    Graphic *graphic = [[Graphic alloc] init];
    NSColor *color= [graphic color];
    CGFloat xLoc = [graphic xLoc];
    BOOL hidden = [graphic hidden];
    int textCharacterLength = [[graphic text] length];


    if ([graphic isTextHidden] != YES )
    {
         [graphic setText:@"Hello"];
    }
    [graphic setBounds:NSMake(10.0, 10.0, 20.0, 120.0)];

        列表1-2


    对于那些C语言中定义的类型的属性来说,Objective-C中也对其构成的复合语句进行了很好的定义。例如,假设我们有一个NSMutableData类的实例:
    NSMutableData *data =[NSMutableData dataWithLength:1024];
    那么我们可以使用点号运算符以及复合赋值语句来修改这个实例的length属性:
    data.length += 1024;
    data.length *= 2;
    data.length /= 4;
    这种写法和下面的写法是等价的:
    [data setLength:[data length] + 1024];
    [data setLength:[data length] * 2];
    [data setLength:[data length] / 4];

         nil值
    在使用属性的时候如果遇到的对象为nil,其结果就相当于是给nil对象发送对应的消息。例如:下面的每两行代码是等价的:
    //属性路径中的每一个成员都是一个对象
    x = person.address.street.name;
    x = [[[person address] street] name ];


    //属性路径中含有一个C结构体
    y = window.contentView.bounds.origin.y;
    y = [[window contentView] bounds].origin.y;

    //一个使用setter的示例
    person.address.street.name = @"Oxford Road";
    [[[person address] street] setName:@"Oxford Road"]; 
    
        关于性能和线程
        无论我们使用方括号还是点号来调用访问方法,编译器生成的代码是一样的。因此,这两种不同的编码技巧在性能上完全相同的。由于使用点号运算符只是一种简单的调用访问方法的技巧,因此这样做也不会引入额外的线程。 
        
        点号运算符用法小结 
        点号运算符只是Objective-C中在调用访问方法时,使用方括号的一种替代方式。
     ·下面的代码将调用aProperty属性的getter方法,并将返回值赋值给变量aVariable:
       aVariable = anObject.aProperty;
       其中属性aProperty的类型和aVariable的类型必须兼容;否则会产生编译告警。
     ·下面的代码将调用队形anObject的setName:方法,并把@"New Name"作为参数传递给该方法:
        anObject.name = @"New Name";
        如果setName:方法不存在或者setName:方法的返回值不是void,编译时会报告警告。
     ·下面的代码调用aView的bounds方法。然后将其返回值NSRect结构中的origin.x赋值给变量xOrigin:
        xOrigin = aView.bounds.origin.x;
     ·下面的代码将值11赋值给两个属性:anObject的integerProperty属性以及anotherObject的floatProperty属性:
        NSInteger i = 10;
        anObject.integerProperty = anotherObject.floatProperty = ++i;
       也就是说,先对最右侧的表达式的值进行计算,然后将其结果传递给setIntegerProperty:和setFloatProperty:这两个setter方法。最先被评估的表达式的值在赋值的时候会被强制转换成所需的类型。
    
    点号运算符的不正确用法
    下面的写法是强烈推荐不要使用的,因为这些写法都是不符合点号运算符的本意的,也就是调用访问方法。
     ·下面的代码在编译时会产生警告(warning: value returen from property not used.):
        anObject.retain;
     ·下面的代码在编译时会报告"setFooIfYouCan: does not appear to be a setter method because it dose not return (void)(setFooIfYouCan::貌似不是一个正确的setter方法,因为他得返回值不是void类型的).":        
        /*方法的声明*/
        -(BOOL) setFooIfYouCan:(MyClass *)newFoo;
     
        /* 代码段*/
        anObject.foolIfYouCan = myInstance;
     ·下面的语句调用lockFocusIfCanDraw方法,并将其返回只赋值给flag。如果flag的类型和其返回值的类型是匹配的,编译时不会产生告警。然而,我们强烈建议不要采用这种写法:
       flag = aView.lockFocusIfCanDraw;
     ·下面的代码在编译时会出现警告。因为属性readOnlyProperty是只读的(warning: assignment to readonly property 'readonlyProperty').
        /*属性的声明*/
        @property(readonly) NSInteger readonlyProperty;
        /*方法的声明*/
        -(void) setReadpnlyProperty:(NSInteger)newValue;


        /*代码段*/
        self.readonlyProperty = 5;
        由于这样的代码提供了属性的setter方法,因此代码是可以运行的。但是这种方式是强烈推荐不要采用的。仅通过为属性增加setter方法并不意味着属性的访问权限就是readwrite了。我们要确保的是在声明属性的时候对其的访问权限进行了正确的描述。

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值