您的位置:首页 > 移动开发 > Objective-C

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

2015-08-14 13:11 615 查看


WorkingwithObjects

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

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

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


ObjectsSendandReceiveMessages

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

[someObjectdoSomething];

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

Thepreviouschapterdescribedhowtocreatetheinterfaceforaclass,likethis:

@interfaceXYZPerson:NSObject

-(void)sayHello;

@end

andhowtocreatetheimplementationofthatclass,likethis:

@implementationXYZPerson

-(void)sayHello{

NSLog(@"Hello,world!");

}

@end

Note: ThisexampleusesanObjective-Cstringliteral, 
@"Hello,world!"
.
StringsareoneofseveralclasstypesinObjective-Cthatallowashorthandliteralsyntaxfortheircreation.Specifying 
@"Hello,world!"
 isconceptuallyequivalenttosaying
“AnObjective-Cstringobjectthatrepresentsthestring Hello,world!.”
Literalsandobjectcreationareexplainedfurtherin Objects
AreCreatedDynamically,laterinthischapter.

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

[somePersonsayHello];

SendinganObjective-CmessageisconceptuallyverymuchlikecallingaCfunction. Figure
2-1 showstheeffectiveprogramflowforthe 
sayHello
 message.

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

Figure
2-1  Basicmessagingprogramflow


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


UsePointerstoKeepTrackofObjects

C和objective-C使用变量来跟踪值,就像大多数其他编程语言一样。
有许多标准C中定义的基本变量类型,包括整数、浮点数和字符,像这样声明和指定值:

intsomeInteger=42;

floatsomeFloatingPointNumber=3.14f;

方法内部声明的局部变量或函数,如下:

-(void)myMethod{

intsomeInteger=42;

}

被限定在定义他们的方法中。
这个例子中, 
someInteger
 在 
myMethod里
被声明为局部变量;一旦执行达到方法的括号,someInteger将不再能被访问。当一个局部变量(如int或float)消失了,价值也就消失了。
相比之下,objective-c对象分配略有不同。对象通常有一个更长的生命周期比起一个简单的方法调用。特别是,一个对象通常需要存在的时间更长相比与跟踪它的原始变量,所以对象的内存分配和释放都是动态的。

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

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

-(void)myMethod{

NSString*myString=//getastringfromsomewhere...

[...]

}

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


YouCanPassObjectsforMethodParameters

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

-(void)someMethodWithValue:(SomeType)value;

Thesyntaxtodeclareamethodthattakesastringobject,therefore,lookslikethis:

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

Youmightimplementthe 
saySomething:
 methodlikethis:

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

NSLog(@"%@",greeting);

}

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

Note: 
NSLog()
 usesformatspecifierstoindicatesubstitution
tokens,justliketheCstandardlibrary 
printf()
 function.Thestringloggedtotheconsoleistheresultofmodifyingtheformatstring(thefirstargument)byinserting
theprovidedvalues(theremainingarguments).
ThereisoneadditionalsubstitutiontokenavailableinObjective-C, 
%@
,usedtodenoteanobject.
Atruntime,thisspecifierwillbesubstitutedwiththeresultofcallingeitherthe
descriptionWithLocale:
 method(ifitexists)orthe 
description
 method
ontheprovidedobject.The 
description
 methodisimplementedby 
NSObject
 toreturnthe
classandmemoryaddressoftheobject,butmanyCocoaandCocoaTouchclassesoverrideittoprovidemoreusefulinformation.Inthecaseof 
NSString
,the 
description
 method
simplyreturnsthestringofcharactersthatitrepresents.
Formoreinformationabouttheavailableformatspecifiersforusewith 
NSLog()
 andthe 
NSString
 class,
see String
FormatSpecifiers.


MethodsCanReturnValues

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

-(int)magicNumber;

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

-(int)magicNumber{

return42;

}

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

[someObjectmagicNumber];

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

intinterestingNumber=[someObjectmagicNumber];

Youcanreturnobjectsfrommethodsinjustthesameway.The 
NSString
 class,forexample,offers
an 
uppercaseString
 method:

-(NSString*)uppercaseString;

It’susedinthesamewayasamethodreturningascalarvalue,althoughyouneedtouseapointertokeeptrackoftheresult:

NSString*testString=@"Hello,world!";

NSString*revisedString=[testStringuppercaseString];

当这个方法又返回值的时候,
revisedString
 指针变量会指向一个NSString对象来表示字符串HELLO WORLD!
记住,当你需要实现方法返回一个对象的时候,likethis:

-(NSString*)magicString{

NSString*stringToReturn=//createaninterestingstring...


returnstringToReturn;

}

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

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


ObjectsCanSendMessagestoThemselves

无论什么时候,当你写一个方法的实现,你都会接近一个重要的隐藏值,self,从概念上讲,self是指这个对象接受到了消息。self是一个指针,就像上面的
greeting
 一样,用于给当前的接收对象调用方法。
你可能会决定重构XYZPerson实现通过修改sayHello方法,使用saySomething:方法如上所示,从而把NSLog()调用另一个方法。这意味着您可以进一步添加方法,像sayGoodbye,通过saySomething:将每个调用方法以处理实际的问候过程。如果你想在一个文本字段显示每个问候用户界面,你只需要修改saySomething:方法而不必经过和单独调整每个的问候方法。
Thenewimplementationusing 
self
 tocallamessageonthecurrentobjectwouldlooklikethis:

@implementationXYZPerson

-(void)sayHello{

[selfsaySomething:@"Hello,world!"];

}

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

NSLog(@"%@",greeting);

}

@end

Ifyousentan 
XYZPerson
 objectthe 
sayHello
 message
forthisupdatedimplementation,theeffectiveprogramflowwouldbeasshownin Figure
2-2.
Figure
2-2  Programflowwhenmessagingself



ObjectsCanCallMethodsImplementedbyTheirSuperclasses

有个很重的关键字需要告诉你,她就是super。给super传递消息是一种方法,用来调用一个实现在父类当中的方法。最常见的用法就是利用super重写方法。
就说要是你想创建一个新的person类吧,一个“shoutingperson”类,类中的问候语字母都是大写的。你可以复制整个XYZPerson类,修改每个字符为大写,但是最简单的方法是创建一个新类继承自XYZPerson,只要重写
saySomething:
 方法就好啦,像这样:

@interfaceXYZShoutingPerson:XYZPerson

@end

@implementationXYZShoutingPerson

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

NSString*uppercaseGreeting=[greetinguppercaseString];

NSLog(@"%@",uppercaseGreeting);

}

@end

这个例子中声明了一个额外的字符串指针,uppercaseGreeting,分配它的值是原始的greeting对象接收uppercaseString消息返回的值。正如您在前面看到的那样,这将是一个新的字符串对象由原始字符串中的每个字符转换为大写组成。
因为sayHello由XYZPerson实现,
XYZShoutingPerson
 继承自XYZPerson,作为一个
XYZShoutingPerson
 对象你也可以调用
sayHello
 方法。当你调用
sayHello
 方法时,这个方法会调用[self
saySomething:...],利用重写的属性实现显示大写字母,如下图:
Figure
2-3  Programflowforanoverriddenmethod


然而,新的实现并不理想,因为如果你做了决定后修改XYZPerson类中实现的saySomething:方法显示用户界面元素的问候而不是通过NSLog(),您需要修改XYZShoutingPerson实现。
Abetterideawouldbetochangethe 
XYZShoutingPerson
 versionof 
saySomething:
 to
callthroughtothesuperclass(
XYZPerson
)implementationtohandletheactualgreeting:
更好的想法是改变XYZShoutingPerson中saySomething:版本,调用超类(XYZPerson)实现以处理实际的问候:

@implementationXYZShoutingPerson

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

NSString*uppercaseGreeting=[greetinguppercaseString];

[supersaySomething:uppercaseGreeting];

}

@end

Theeffectiveprogramflowthatnowresultsfromsendingan 
XYZShoutingPerson
 objectthe 
sayHello
 message
isshownin Figure
2-4.
Figure
2-4  Programflowwhenmessagingsuper



ObjectsAreCreatedDynamically

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

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

+(id)alloc;

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

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

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

-(id)init;

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

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

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

NSObject*newObject=[[NSObjectalloc]init];

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

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

Figure
2-5  Nestingtheallocandinitmessage


Note: init可能返回一个不同的对象和alloc方法产生的对象相比,所以最好如图所示,使用嵌套调用。
不要初始化一个对象而不重新分配任何指向该对象的指针。作为一个例子,不要这样做:

NSObject*someObject=[NSObjectalloc];

[someObjectinit];

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

InitializerMethodsCanTakeArguments

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

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

-(id)initWithBool:(BOOL)value;

-(id)initWithFloat:(float)value;

-(id)initWithInt:(int)value;

-(id)initWithLong:(long)value;

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

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


ClassFactoryMethodsAreanAlternativetoAllocationandInitialization

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

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

+(NSNumber*)numberWithBool:(BOOL)value;

+(NSNumber*)numberWithFloat:(float)value;

+(NSNumber*)numberWithInt:(int)value;

+(NSNumber*)numberWithLong:(long)value;

Afactorymethodisusedlikethis:

NSNumber*magicNumber=[NSNumbernumberWithInt:42];

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


UsenewtoCreateanObjectIfNoArgumentsAreNeededforInitialization

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

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

XYZObject*object=[XYZObjectnew];

//iseffectivelythesameas:

XYZObject*object=[[XYZObjectalloc]init];


LiteralsOfferaConciseObject-CreationSyntax

有些类允许您使用更简洁的文字语法创建实例。
你可以创建一个NSString实例比如使用一种特殊的文字符号,像这样:

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

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

NSString*someString=[NSStringstringWithCString:"Hello,World!"

encoding:NSUTF8StringEncoding];

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

NSNumber*myBOOL=@YES;

NSNumber*myFloat=@3.14f;

NSNumber*myInt=@42;

NSNumber*myLong=@42L;

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

NSNumber*myInt=@(84/2);

在这个例子中,表达式计算之后,NSNumber的实例对象保存结果。
OC同样也支持创建不可变的对象如NSArray,NSDictionary;稍后在Values
andCollections 讨论。


Objective-CIsaDynamicLanguage

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

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

考虑下面的代码:

idsomeObject=@"Hello,World!";

[someObjectremoveAllObjects];

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

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

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

[someObjectremoveAllObjects];

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

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

XYZPerson*firstPerson=[[XYZPersonalloc]init];

XYZPerson*secondPerson=[[XYZShoutingPersonalloc]init];

[firstPersonsayHello];

[secondPersonsayHello];

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


DeterminingEqualityofObjects

如果你需要确定两个对象是否相同,记住他们的指针很重要。
标准C中使用==判断两个变量是否值相等:

if(someInteger==42){

//someIntegerhasthevalue42

}

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

if(firstPerson==secondPerson){

//firstPersonisthesameobjectassecondPerson

}

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

if([firstPersonisEqual:secondPerson]){

//firstPersonisidenticaltosecondPerson

}

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


if([someDatecompare:anotherDate]==NSOrderedAscending){

//someDateisearlierthananotherDate

}


Workingwithnil

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

BOOLsuccess=NO;

intmagicNumber=42;

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

XYZPerson*somePerson;

//somePersonisautomaticallysettonil

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

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

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

if(somePerson!=nil){

//somePersonpointstoanobject

}

orsimplysupplythevariable:

if(somePerson){

//somePersonpointstoanobject

}

如果
somePerson
 变量是nil类型的,它的逻辑值便为0(false假)。如果她包含一个地址,就不是0,就是真。
同样,如果你需要检查一个变量是nil类型的,你可以任性的使用==操作符:

if(somePerson==nil){

//somePersondoesnotpointtoanobject

}

orjustusetheClogicalnegationoperator:逻辑非

if(!somePerson){

//somePersondoesnotpointtoanobject

}


Exercises练习

Openthe 
main.m
 fileinyourprojectfromtheexercisesattheendofthelast
chapterandfindthe 
main()
 function.AswithanyexecutablewritteninC,thisfunctionrepresentsthestartingpointforyourapplication.
Createanew 
XYZPerson
 instanceusing 
alloc
 and 
init
,
andthencallthe 
sayHello
 method.

Note: Ifthecompilerdoesn’tpromptyouautomatically,youwillneedtoimporttheheaderfile(containingthe 
XYZPerson
 interface)
atthetopof 
main.m
.

Implementthe 
saySomething:
 methodshownearlierinthischapter,andrewrite
the 
sayHello
 methodtouseit.Addavarietyofothergreetingsandcalleachofthemontheinstanceyoucreatedabove.

Createnewclassfilesforthe 
XYZShoutingPerson
 class,settoinheritfrom 
XYZPerson
.
Overridethe 
saySomething:
 methodtodisplaytheuppercasegreeting,andtestthebehavior
onan 
XYZShoutingPerson
 instance.

Implementthe 
XYZPerson
 class 
person
 factory
methodyoudeclaredinthepreviouschapter,toreturnacorrectlyallocatedandinitializedinstanceofthe 
XYZPerson
 class,thenusethemethodin 
main()
 instead
ofyournested 
alloc
 and 
init
.

Tip: Ratherthanusing 
[[XYZPersonalloc]init]
 intheclass
factorymethod,insteadtryusing 
[[selfalloc]init]
.
Using 
self
 inaclassfactorymethodmeansthatyou’rereferringtotheclassitself.
Thismeansthatyoudon’thavetooverridethe 
person
 methodinthe 
XYZShoutingPerson
 implementation
tocreatethecorrectinstance.Testthisbycheckingthat:

XYZShoutingPerson*shoutingPerson=[XYZShoutingPersonperson];

createsthecorrecttypeofobject.

Createanewlocal 
XYZPerson
 pointer,butdon’tincludeanyvalueassignment.
Useabranch(
if
 statement)tocheckwhetherthevariableisautomaticallyassignedas 
nil
.

NextPrevious
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: