Objective-C 苹果开发文档 03 Working with Objects

Working with Objects

在一个objective - c应用程序中,大部分的工作发生于对象生态系统中来回传递消息。一些对象是Cocoa或者Cocoa Touch类的实例,有些是自己的类的实例。

上一个章节描述了如何定义以及实现一个类的属性和方法;这一章节将向您展示如何向一个对象发送消息,包括objective - c的一些动态特性,包括动态类型,决定在运行时应该调用哪个方法的能力。

在可以使用一个对象之前,必须正确的给对象分配内存空间并且初始化。本章描述了如何使用嵌套的方法调用分配和初始化一个对象,以确保它的正确。

Objects Send and Receive Messages


虽然有几种不同的方式在objective - c对象之间发送消息,到目前为止,最常见的是使用 方括号 的基本语法,像这样:

    [someObject doSomething];

左边的引用,someObject ,是一个消息的接收器。右边的doSomething 是一个方法调用名。换句话说,当上面这句话执行时,someObject 会接收doSomething 发送的消息。

The previous chapter described how to create the interface for a class, like this:

@interface XYZPerson : NSObject
- (void)sayHello;
@end

and how to create the implementation of that class, like this:

@implementation XYZPerson
- (void)sayHello {
    NSLog(@"Hello, world!");
}
@end

Note: This example uses an Objective-C string literal, @"Hello, world!". Strings are one of several class types in Objective-C that allow a shorthand literal syntax for their creation. Specifying @"Hello, world!" is conceptually equivalent to saying “An Objective-C string object that represents the string Hello, world!.”

Literals and object creation are explained further in Objects Are Created Dynamically, later in this chapter.

假设你已经得到了一个XYZPerson对象,你可以发送sayHello消息,如下:

    [somePerson sayHello];

Sending an Objective-C message is conceptually very much like calling a C function. Figure 2-1 shows the effective program flow for the sayHello message.

发送一个objective - C的消息在概念上非常像调用一个C函数。图2 - 1显示了sayHello消息的实际的程序流程。

Figure 2-1  Basic messaging program flow

为了指定一个消息的接收者,在objective - c中,理解如何使用指针来引用对象重要的

Use Pointers to Keep Track of Objects


C和objective - C使用变量来跟踪值,就像大多数其他编程语言一样。

有许多标准C中定义的基本变量类型,包括整数、浮点数和字符,像这样声明和指定值:

    int someInteger = 42;
    float someFloatingPointNumber = 3.14f;

方法内部声明的局部变量或函数,如下:
- (void)myMethod {
    int someInteger = 42;
}

被限定在定义他们的方法中。

这个例子中, someInteger 在 myMethod里被声明为局部变量;一旦执行达到方法的括号,someInteger将不再能被访问。当一个局部变量(如int或float)消失了,价值也就消失了。

相比之下,objective - c对象分配略有不同。对象通常有一个更长的生命周期比起一个简单的方法调用。特别是,一个对象通常需要存在的时间更长相比与跟踪它的原始变量,所以对象的内存分配和释放都是动态的。

Note:如果你习惯使用属于栈或者堆,那么一个局部变量被分配在栈区,而对象被分配在堆区。


这需要你使用C指针(持有内存地址)来跟踪他们在内存中的位置,这样的:

- (void)myMethod {
    NSString *myString = // get a string from somewhere...
    [...]
}

尽管myString指针变量的范围(星号表明它是一个指针)仅限于myMethod的范围,但是实际上字符串对象在内存空间中会有更长的生命周期。它可能已经存在,或者可能需要通过对象在其他方法中调用。

You Can Pass Objects for Method Parameters


当发送消息时,如果你需要传递一个对象,你应该给一个方法参数传递一个对象的指针。前面的章节描述了声明一个方法和一个参数的语法:

- (void)someMethodWithValue:(SomeType)value;

The syntax to declare a method that takes a string object, therefore, looks like this:

- (void)saySomething:(NSString *)greeting;

You might implement the saySomething: method like this:

- (void)saySomething:(NSString *)greeting {
    NSLog(@"%@", greeting);
}

greeting 指针像一个局部变量一样,范围被限定在方法saySomething: 之内。即使实际的字符串对象,它指向方法被调用之前已经存在,但是方法结束时还会继续存在。

Note: NSLog() uses format specifiers to indicate substitution tokens, just like the C standard library printf() function. The string logged to the console is the result of modifying the format string (the first argument) by inserting the provided values (the remaining arguments).

There is one additional substitution token available in Objective-C, %@, used to denote an object. At runtime, this specifier will be substituted with the result of calling either thedescriptionWithLocale: method (if it exists) or the description method on the provided object. The description method is implemented by NSObject to return the class and memory address of the object, but many Cocoa and Cocoa Touch classes override it to provide more useful information. In the case of NSString, the description method simply returns the string of characters that it represents.

For more information about the available format specifiers for use with NSLog() and the NSString class, see String Format Specifiers.

Methods Can Return Values


和给方法传递一个参数一样,一个方法也可以返回一个值。在本章节中,目前方法返回一个void值,这个C语言关键字的意思是表示方法的返回值为空。

Specifying a return type of int means that the method returns a scalar integer value:

- (int)magicNumber;

方法的实现使用了一个C的返回语句来表示方法执行完成后的值应该传回, like this:

- (int)magicNumber {
    return 42;
}

忽略一个方法的返回值是完全可以接受的,在magicNumber 方法中,除了返回一个值之外并没有做什么有意义的事情,但是像下面这样调用方法完全没有任何错误:

    [someObject magicNumber];

如果你需要保留返回值,你可以声明一个变量,接受返回值:

    int interestingNumber = [someObject magicNumber];

You can return objects from methods in just the same way. The NSString class, for example, offers an uppercaseString method:

- (NSString *)uppercaseString;

It’s used in the same way as a method returning a scalar value, although you need to use a pointer to keep track of the result:

    NSString *testString = @"Hello, world!";
    NSString *revisedString = [testString uppercaseString];

当这个方法又返回值的时候,revisedString 指针变量会指向一个NSString对象来表示字符串HELLO WORLD!

记住,当你需要实现方法返回一个对象的时候,like this:

- (NSString *)magicString {
    NSString *stringToReturn = // create an interesting string...
 
    return stringToReturn;
}

当一个字符串对象被当做返回值传递之后,她还是会继续存在,尽管stringToReturn 指针已经超出范围。

在这种情况下有一些内存管理注意事项:一个返回的对象(在堆上创建)需要存在足够长的时间,由原方法调用者使用,但不是永久,因为这将造成内存泄漏。在大多数情况下,自动引用计数(ARC),objective - c编译器的特性,为你负责这些事情。

Objects Can Send Messages to Themselves


无论什么时候,当你写一个方法的实现,你都会接近一个重要的隐藏值,self,从概念上讲,self是指这个对象接受到了消息。self是一个指针,就像上面的greeting 一样,用于给当前的接收对象调用方法。

你可能会决定重构XYZPerson实现通过修改sayHello方法,使用saySomething:方法如上所示,从而把NSLog()调用另一个方法。这意味着您可以进一步添加方法,像sayGoodbye,通过saySomething:将每个调用方法以处理实际的问候过程。如果你想在一个文本字段显示每个问候用户界面,你只需要修改saySomething:方法而不必经过和单独调整每个的问候方法。

The new implementation using self to call a message on the current object would look like this:

@implementation XYZPerson
- (void)sayHello {
    [self saySomething:@"Hello, world!"];
}
- (void)saySomething:(NSString *)greeting {
    NSLog(@"%@", greeting);
}
@end

If you sent an XYZPerson object the sayHello message for this updated implementation, the effective program flow would be as shown in Figure 2-2.

Figure 2-2  Program flow when messaging self

Objects Can Call Methods Implemented by Their Superclasses


有个很重的关键字需要告诉你,她就是super 。给super传递消息是一种方法,用来调用一个实现在父类当中的方法。最常见的用法就是利用super重写方法。

就说要是你想创建一个新的person类吧,一个“shouting person”类,类中的问候语字母都是大写的。你可以复制整个XYZPerson类,修改每个字符为大写,但是最简单的方法是创建一个新类继承自XYZPerson,只要重写saySomething: 方法就好啦,像这样:

@interface XYZShoutingPerson : XYZPerson
@end
@implementation XYZShoutingPerson
- (void)saySomething:(NSString *)greeting {
    NSString *uppercaseGreeting = [greeting uppercaseString];
    NSLog(@"%@", uppercaseGreeting);
}
@end

这个例子中声明了一个额外的字符串指针,uppercaseGreeting,分配它的值是原始的greeting对象接收uppercaseString消息返回的值。正如您在前面看到的那样,这将是一个新的字符串对象由原始字符串中的每个字符转换为大写组成。

因为sayHello由XYZPerson实现,XYZShoutingPerson 继承自XYZPerson,作为一个XYZShoutingPerson 对象你也可以调用sayHello 方法。当你调用sayHello 方法时,这个方法会调用[self saySomething:...],利用重写的属性实现显示大写字母,如下图:

Figure 2-3  Program flow for an overridden method

然而,新的实现并不理想,因为如果你做了决定后修改XYZPerson类中实现的saySomething:方法显示用户界面元素的问候而不是通过NSLog(),您需要修改XYZShoutingPerson实现。

A better idea would be to change the XYZShoutingPerson version of saySomething: to call through to the superclass (XYZPerson) implementation to handle the actual greeting:

更好的想法是改变XYZShoutingPerson 中saySomething:版本,调用超类(XYZPerson)实现以处理实际的问候:

@implementation XYZShoutingPerson
- (void)saySomething:(NSString *)greeting {
    NSString *uppercaseGreeting = [greeting uppercaseString];
    [super saySomething:uppercaseGreeting];
}
@end

The effective program flow that now results from sending an XYZShoutingPerson object the sayHello message is shown in Figure 2-4.

Figure 2-4  Program flow when messaging super

Objects Are Created Dynamically


如前所述在,objective - c对象的内存是动态分配的。创建一个对象的第一步是确保足够的内存分配不仅对一个对象的类定义的属性,而且属性上定义的每个超类继承链。

NSObject根类提供了一个类方法,alloc,处理这一过程:

+ (id)alloc;

注意该方法的返回类型是id。这是一个objective - c特殊的关键字,意思是“某种类型的对象”。这是一个指向对象的指针,像(NSObject *),但很特别,因为它没有使用星号。稍后将详细介绍在这一章Objective-C Is a Dynamic Language

alloc方法还有一个重要的任务,即清除内存分配给对象,并且将属性设置为零。这避免了常见的问题,不管之前存储的内存是垃圾信息或别的什么,但并不足以完全初始化一个对象。

你需要把alloc调用与调用init相结合,另一个NSObject方法:

- (id)init;

init方法使用一个类来确保其属性在创建之处有合适的初始值,在下一章详细介绍。

注意,init方法还是返回一个id。

如果一个方法返回一个对象的指针,它可能嵌套调用该方法,从而结合多个消息调用在一个语句中。正确的分配和初始化一个对象的方法是嵌套alloc调用在init调用中,像这样:

    NSObject *newObject = [[NSObject alloc] init];

这个例子设置newObject变量以指向新创建的NSObject实例。

最内层的调用先执行,所以NSObject类调用alloc方法,它返回一个新分配的NSObject实例。这返回的对象稍后用来接收init消息,它本身将对象返回分配给newObject指针,如图2 - 5所示。

Figure 2-5  Nesting the alloc and init message

Note: init可能返回一个不同的对象和alloc方法产生的对象相比,所以最好如图所示,使用嵌套调用。

不要初始化一个对象而不重新分配任何指向该对象的指针。作为一个例子,不要这样做:

    NSObject *someObject = [NSObject alloc];
    [someObject init];
如果调用init返回其他对象,留给你的只是一个指针最初分配的对象,但从来没有初始化。

Initializer Methods Can Take Arguments


一些对象需要用所需的值初始化。例如,一个NSNumber对象,必须创建所需的数值来表示。

NSNumber类定义了几个初始化方法,包括:

- (id)initWithBool:(BOOL)value;
- (id)initWithFloat:(float)value;
- (id)initWithInt:(int)value;
- (id)initWithLong:(long)value;

带参数的初始化方法的调用和普通init方法调用是相同的,NSNumber对象分配和初始化:

    NSNumber *magicNumber = [[NSNumber alloc] initWithInt:42];

Class Factory Methods Are an Alternative to Allocation and Initialization


如前一章中所述,一个类也可以定义工厂方法工厂方法提供了一个替代传统alloc] init]的过程,而不需要嵌套两个方法。

NSNumber类定义了一些类工厂方法匹配她的初始化,包括:

+ (NSNumber *)numberWithBool:(BOOL)value;
+ (NSNumber *)numberWithFloat:(float)value;
+ (NSNumber *)numberWithInt:(int)value;
+ (NSNumber *)numberWithLong:(long)value;

A factory method is used like this:

    NSNumber *magicNumber = [NSNumber numberWithInt:42];

这是一个和前面的示例alloc]initWithInt:]一样有效的用法。类工厂方法通常就直接调用alloc和相关的init方法,这样就提供了便利。

Use new to Create an Object If No Arguments Are Needed for Initialization


还可以使用这个new类创建一个类的实例方法。NSObject提供的这种方法,不需要覆盖在自己的子类。

实际上new也是调用alloc和init方法,不过没有参数:

    XYZObject *object = [XYZObject new];
    // is effectively the same as:
    XYZObject *object = [[XYZObject alloc] init];

Literals Offer a Concise Object-Creation Syntax


有些类允许您使用更简洁的文字语法创建实例。

你可以创建一个NSString实例比如使用一种特殊的文字符号,像这样:

    NSString *someString = @"Hello, World!";

这样是有效的分配和初始化一个NSString实例,和使用一个工厂方法类初始化一样:

    NSString *someString = [NSString stringWithCString:"Hello, World!"
                                              encoding:NSUTF8StringEncoding];

NSNumber类还允许各种各样的文字:

    NSNumber *myBOOL = @YES;
    NSNumber *myFloat = @3.14f;
    NSNumber *myInt = @42;
    NSNumber *myLong = @42L;

这些例子使用相关的初始化方法或者工厂方法是同样有效的。

You can also create an NSNumber using a boxed expression, like this:

    NSNumber *myInt = @(84 / 2);

在这个例子中,表达式计算之后,NSNumber的实例对象保存结果。

OC同样也支持创建不可变的对象如NSArray ,NSDictionary;稍后在Values and Collections 讨论。

Objective-C Is a Dynamic Language

之前提到过,你需要使用指针来跟踪内存中的对象。因为OC的动态特性,不管你使用什么样的类类型指针,当你发送消息时,总是会调用相应类型的方法在相关的对象上。

id类型是一个通用的对象指针。你可能使用id类型当你声明一个变量时,但是这样做的后果就是你失去了编译时的信息。

考虑下面的代码:

    id someObject = @"Hello, World!";
    [someObject removeAllObjects];

在这个例子中,someObject会指向一个NSString类型的实例,但是编译只是知道这个实例是一个对象。removeAllObjects是由Cocoa 或者Cocoa Touch提供的对象(例如NSMutableArray),这样编译器就不会报错,尽管这段代码在运行时会有一个异常,因为NSString对象无法响应removeAllObjects方法。

重写代码,这次试用静态对象类型:

    NSString *someObject = @"Hello, World!";
    [someObject removeAllObjects];

这意味着编译器马上会报错,因为removeAllObjects 方法没有在NSString类接口中声明过。

由于一个类的对象时在运行时决定的,这就意味着你定义什么类型的变量在你创建或者与实例交互的时候并没有什么不同。使用之前的XYZPerson 和XYZShoutingPerson 类,你可以像下面这样:

    XYZPerson *firstPerson = [[XYZPerson alloc] init];
    XYZPerson *secondPerson = [[XYZShoutingPerson alloc] init];
    [firstPerson sayHello];
    [secondPerson sayHello];

尽管firstPerson和secondPerson都是静态类型XYZPerson对象,secondPerson 会在运行时指向XYZShoutingPerson 对象。每个对象的sayHello方法被调用时,将使用正确的实现;对secondPerson而言,这意味着XYZShoutingPerson版本

Determining Equality of Objects


如果你需要确定两个对象是否相同,记住他们的指针很重要。

标准C中使用==判断两个变量是否值相等:

    if (someInteger == 42) {
        // someInteger has the value 42
    }

当处理对象时,==操作符用来判断两个指针是否指向同一个对象:

    if (firstPerson == secondPerson) {
        // firstPerson is the same object as secondPerson
    }

如果你需要判断两个对象是否表示同一个值,你需要调用一个方法,比如isEqual,在NSObject中可用:

    if ([firstPerson isEqual:secondPerson]) {
        // firstPerson is identical to secondPerson
    }

如果你需要比较一个对象的值是否大于或者小于另一个,你不可以使用标准C中的>和<操作符,相反,基础框架类型如NSNumberNSString and NSDate提供了一个比较方法:

    if ([someDate compare:anotherDate] == NSOrderedAscending) {
        // someDate is earlier than anotherDate
    }

Working with nil


当你在声明一个纯数值的变量同时初始化她,是一个好的想法,否则她们会初始为垃圾值:

    BOOL success = NO;
    int magicNumber = 42;

这对于指针对象并不是必须的,因为编译器会自动的设置指针指向nil类型值,如果你没有指定初始值:

    XYZPerson *somePerson;
    // somePerson is automatically set to nil

如果你没有一个值给指针用,那么一个nil类型值是一个安全的方法用来初始化指针对象,因为在OC中给nil发消息是可以接受的。如果你给nil发消息,很明显,啥事没有,放心吧,明白了吧... ...nil就是个木头类型,你怎么逗适她,她也不会鸟你的,呵呵,想多了。

Note: 如果你期望给nil发消息会有返回值,对于对象来说返回值也只会是nil,0是数字类型,NO为BOOL类型。返回结构体类型,所有成员都会被初始化为0.

If you need to check to make sure an object is not nil (that a variable points to an object in memory), you can either use the standard C inequality operator:

如果你需要检查以确保一个对象不是nil(在内存中一个变量指向一个对象),你可以任意使用标准C中的!=操作符:

    if (somePerson != nil) {
        // somePerson points to an object
    }

or simply supply the variable:

    if (somePerson) {
        // somePerson points to an object
    }

如果somePerson 变量是nil类型的,它的逻辑值便为0(false假)。如果她包含一个地址,就不是0,就是真。

同样,如果你需要检查一个变量是nil类型的,你可以任性的使用==操作符:

    if (somePerson == nil) {
        // somePerson does not point to an object
    }

or just use the C logical negation operator:逻辑非

    if (!somePerson) {
        // somePerson does not point to an object
    }

Exercises 练习

  1. Open the main.m file in your project from the exercises at the end of the last chapter and find the main() function. As with any executable written in C, this function represents the starting point for your application.

    Create a new XYZPerson instance using alloc and init, and then call the sayHello method.

    Note: If the compiler doesn’t prompt you automatically, you will need to import the header file (containing the XYZPerson interface) at the top of main.m.

  2. Implement the saySomething: method shown earlier in this chapter, and rewrite the sayHello method to use it. Add a variety of other greetings and call each of them on the instance you created above.

  3. Create new class files for the XYZShoutingPerson class, set to inherit from XYZPerson.

    Override the saySomething: method to display the uppercase greeting, and test the behavior on an XYZShoutingPerson instance.

  4. Implement the XYZPerson class person factory method you declared in the previous chapter, to return a correctly allocated and initialized instance of the XYZPerson class, then use the method in main() instead of your nested alloc and init.

    Tip: Rather than using [[XYZPerson alloc] init] in the class factory method, instead try using [[self alloc] init].

    Using self in a class factory method means that you’re referring to the class itself.

    This means that you don’t have to override the person method in the XYZShoutingPerson implementation to create the correct instance. Test this by checking that:

        XYZShoutingPerson *shoutingPerson = [XYZShoutingPerson person];
    creates the correct type of object.

  5. Create a new local XYZPerson pointer, but don’t include any value assignment.

    Use a branch (if statement) to check whether the variable is automatically assigned as nil.

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值