Objective-C 苹果开发文档 03 Working with Objects
2015-08-14 13:11
615 查看
WorkingwithObjects
在一个objective-c应用程序中,大部分的工作发生于对象的生态系统中来回传递消息。一些对象是Cocoa或者CocoaTouch类的实例,有些是自己的类的实例。
上一个章节描述了如何定义以及实现一个类的属性和方法;这一章节将向您展示如何向一个对象发送消息,包括objective-c的一些动态特性,包括动态类型,决定在运行时应该调用哪个方法的能力。
在可以使用一个对象之前,必须正确的给对象分配内存空间并且初始化。本章描述了如何使用嵌套的方法调用分配和初始化一个对象,以确保它的正确。
ObjectsSendandReceiveMessages
虽然有几种不同的方式在objective-c对象之间发送消息,到目前为止,最常见的是使用方括号的基本语法,像这样:[someObjectdoSomething]; |
someObject,是一个消息的接收器。右边的doSomething
是一个方法调用名。换句话说,当上面这句话执行时,
someObject会接收
doSomething发送的消息。
Thepreviouschapterdescribedhowtocreatetheinterfaceforaclass,likethis:
@interfaceXYZPerson:NSObject |
-(void)sayHello; |
@end |
@implementationXYZPerson |
-(void)sayHello{ |
NSLog(@"Hello,world!"); |
} |
@end |
@"Hello,world!".
StringsareoneofseveralclasstypesinObjective-Cthatallowashorthandliteralsyntaxfortheircreation.Specifying
@"Hello,world!"isconceptuallyequivalenttosaying
“AnObjective-Cstringobjectthatrepresentsthestring Hello,world!.”
Literalsandobjectcreationareexplainedfurtherin Objects
AreCreatedDynamically,laterinthischapter.
假设你已经得到了一个XYZPerson对象,你可以发送sayHello消息,如下:
[somePersonsayHello]; |
2-1 showstheeffectiveprogramflowforthe
sayHellomessage.
发送一个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... |
[...] |
} |
YouCanPassObjectsforMethodParameters
当发送消息时,如果你需要传递一个对象,你应该给一个方法参数传递一个对象的指针。前面的章节描述了声明一个方法和一个参数的语法:-(void)someMethodWithValue:(SomeType)value; |
-(void)saySomething:(NSString*)greeting; |
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
descriptionmethod
ontheprovidedobject.The
descriptionmethodisimplementedby
NSObjecttoreturnthe
classandmemoryaddressoftheobject,butmanyCocoaandCocoaTouchclassesoverrideittoprovidemoreusefulinformation.Inthecaseof
NSString,the
descriptionmethod
simplyreturnsthestringofcharactersthatitrepresents.
Formoreinformationabouttheavailableformatspecifiersforusewith
NSLog()andthe
NSStringclass,
see String
FormatSpecifiers.
MethodsCanReturnValues
和给方法传递一个参数一样,一个方法也可以返回一个值。在本章节中,目前方法返回一个void值,这个C语言关键字的意思是表示方法的返回值为空。Specifyingareturntypeof
intmeansthatthemethodreturnsascalarintegervalue:
-(int)magicNumber; |
this:
-(int)magicNumber{ |
return42; |
} |
magicNumber方法中,除了返回一个值之外并没有做什么有意义的事情,但是像下面这样调用方法完全没有任何错误:
[someObjectmagicNumber]; |
intinterestingNumber=[someObjectmagicNumber]; |
NSStringclass,forexample,offers
an
uppercaseStringmethod:
-(NSString*)uppercaseString; |
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
selftocallamessageonthecurrentobjectwouldlooklikethis:
@implementationXYZPerson |
-(void)sayHello{ |
[selfsaySomething:@"Hello,world!"]; |
} |
-(void)saySomething:(NSString*)greeting{ |
NSLog(@"%@",greeting); |
} |
@end |
XYZPersonobjectthe
sayHellomessage
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 |
因为sayHello由XYZPerson实现,
XYZShoutingPerson继承自XYZPerson,作为一个
XYZShoutingPerson对象你也可以调用
sayHello方法。当你调用
sayHello方法时,这个方法会调用[self
saySomething:...],利用重写的属性实现显示大写字母,如下图:
Figure
2-3 Programflowforanoverriddenmethod
然而,新的实现并不理想,因为如果你做了决定后修改XYZPerson类中实现的saySomething:方法显示用户界面元素的问候而不是通过NSLog(),您需要修改XYZShoutingPerson实现。
Abetterideawouldbetochangethe
XYZShoutingPersonversionof
saySomething:to
callthroughtothesuperclass(
XYZPerson)implementationtohandletheactualgreeting:
更好的想法是改变XYZShoutingPerson中saySomething:版本,调用超类(XYZPerson)实现以处理实际的问候:
@implementationXYZShoutingPerson |
-(void)saySomething:(NSString*)greeting{ |
NSString*uppercaseGreeting=[greetinguppercaseString]; |
[supersaySomething:uppercaseGreeting]; |
} |
@end |
XYZShoutingPersonobjectthe
sayHellomessage
isshownin Figure
2-4.
Figure
2-4 Programflowwhenmessagingsuper
ObjectsAreCreatedDynamically
如前所述在,objective-c对象的内存是动态分配的。创建一个对象的第一步是确保足够的内存分配不仅对一个对象的类定义的属性,而且属性上定义的每个超类继承链。NSObject根类提供了一个类方法,alloc,处理这一过程:
+(id)alloc; |
IsaDynamicLanguage。
alloc方法还有一个重要的任务,即清除内存分配给对象,并且将属性设置为零。这避免了常见的问题,不管之前存储的内存是垃圾信息或别的什么,但并不足以完全初始化一个对象。
你需要把alloc调用与调用init相结合,另一个NSObject方法:
-(id)init; |
注意,init方法还是返回一个id。
如果一个方法返回一个对象的指针,它可能嵌套调用该方法,从而结合多个消息调用在一个语句中。正确的分配和初始化一个对象的方法是嵌套alloc调用在init调用中,像这样:
NSObject*newObject=[[NSObjectalloc]init]; |
最内层的调用先执行,所以NSObject类调用alloc方法,它返回一个新分配的NSObject实例。这返回的对象稍后用来接收init消息,它本身将对象返回分配给newObject指针,如图2-5所示。
Figure
2-5 Nestingtheallocandinitmessage
Note: init可能返回一个不同的对象和alloc方法产生的对象相比,所以最好如图所示,使用嵌套调用。
不要初始化一个对象而不重新分配任何指向该对象的指针。作为一个例子,不要这样做:
NSObject*someObject=[NSObjectalloc]; |
[someObjectinit]; |
InitializerMethodsCanTakeArguments
一些对象需要用所需的值初始化。例如,一个NSNumber对象,必须创建所需的数值来表示。
NSNumber类定义了几个初始化方法,包括:
-(id)initWithBool:(BOOL)value; |
-(id)initWithFloat:(float)value; |
-(id)initWithInt:(int)value; |
-(id)initWithLong:(long)value; |
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; |
NSNumber*magicNumber=[NSNumbernumberWithInt:42]; |
UsenewtoCreateanObjectIfNoArgumentsAreNeededforInitialization
还可以使用这个new类创建一个类的实例方法。NSObject提供的这种方法,不需要覆盖在自己的子类。实际上new也是调用alloc和init方法,不过没有参数:
XYZObject*object=[XYZObjectnew]; |
//iseffectivelythesameas: |
XYZObject*object=[[XYZObjectalloc]init]; |
LiteralsOfferaConciseObject-CreationSyntax
有些类允许您使用更简洁的文字语法创建实例。你可以创建一个NSString实例比如使用一种特殊的文字符号,像这样:
NSString*someString=@"Hello,World!"; |
NSString*someString=[NSStringstringWithCString:"Hello,World!" |
encoding:NSUTF8StringEncoding]; |
NSNumber*myBOOL=@YES; |
NSNumber*myFloat=@3.14f; |
NSNumber*myInt=@42; |
NSNumber*myLong=@42L; |
Youcanalsocreatean
NSNumberusingaboxedexpression,likethis:
NSNumber*myInt=@(84/2); |
OC同样也支持创建不可变的对象如NSArray,NSDictionary;稍后在Values
andCollections 讨论。
Objective-CIsaDynamicLanguage
之前提到过,你需要使用指针来跟踪内存中的对象。因为OC的动态特性,不管你使用什么样的类类型指针,当你发送消息时,总是会调用相应类型的方法在相关的对象上。id类型是一个通用的对象指针。你可能使用id类型当你声明一个变量时,但是这样做的后果就是你失去了编译时的信息。
考虑下面的代码:
idsomeObject=@"Hello,World!"; |
[someObjectremoveAllObjects]; |
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]; |
secondPerson会在运行时指向
XYZShoutingPerson对象。每个对象的sayHello方法被调用时,将使用正确的实现;对secondPerson而言,这意味着XYZShoutingPerson版本。
DeterminingEqualityofObjects
如果你需要确定两个对象是否相同,记住他们的指针很重要。标准C中使用==判断两个变量是否值相等:
if(someInteger==42){ |
//someIntegerhasthevalue42 |
} |
if(firstPerson==secondPerson){ |
//firstPersonisthesameobjectassecondPerson |
} |
if([firstPersonisEqual:secondPerson]){ |
//firstPersonisidenticaltosecondPerson |
} |
NSNumber,
NSStringand
NSDate提供了一个比较方法:
if([someDatecompare:anotherDate]==NSOrderedAscending){ |
//someDateisearlierthananotherDate |
} |
Workingwithnil
当你在声明一个纯数值的变量同时初始化她,是一个好的想法,否则她们会初始为垃圾值:BOOLsuccess=NO; |
intmagicNumber=42; |
XYZPerson*somePerson; |
//somePersonisautomaticallysettonil |
Note: 如果你期望给nil发消息会有返回值,对于对象来说返回值也只会是nil,0是数字类型,NO为BOOL类型。返回结构体类型,所有成员都会被初始化为0.
Ifyouneedtochecktomakesureanobjectisnot
nil(thatavariablepointstoanobjectin
memory),youcaneitherusethestandardCinequalityoperator:
如果你需要检查以确保一个对象不是nil(在内存中一个变量指向一个对象),你可以任意使用标准C中的!=操作符:
if(somePerson!=nil){ |
//somePersonpointstoanobject |
} |
if(somePerson){ |
//somePersonpointstoanobject |
} |
somePerson变量是nil类型的,它的逻辑值便为0(false假)。如果她包含一个地址,就不是0,就是真。
同样,如果你需要检查一个变量是nil类型的,你可以任性的使用==操作符:
if(somePerson==nil){ |
//somePersondoesnotpointtoanobject |
} |
if(!somePerson){ |
//somePersondoesnotpointtoanobject |
} |
Exercises练习
Openthe main.mfileinyourprojectfromtheexercisesattheendofthelast
chapterandfindthe
main()function.AswithanyexecutablewritteninC,thisfunctionrepresentsthestartingpointforyourapplication.
Createanew
XYZPersoninstanceusing
allocand
init,
andthencallthe
sayHellomethod.
Note: Ifthecompilerdoesn’tpromptyouautomatically,youwillneedtoimporttheheaderfile(containingthe
XYZPersoninterface)
atthetopof
main.m.
Implementthe
saySomething:methodshownearlierinthischapter,andrewrite
the
sayHellomethodtouseit.Addavarietyofothergreetingsandcalleachofthemontheinstanceyoucreatedabove.
Createnewclassfilesforthe
XYZShoutingPersonclass,settoinheritfrom
XYZPerson.
Overridethe
saySomething:methodtodisplaytheuppercasegreeting,andtestthebehavior
onan
XYZShoutingPersoninstance.
Implementthe
XYZPersonclass
personfactory
methodyoudeclaredinthepreviouschapter,toreturnacorrectlyallocatedandinitializedinstanceofthe
XYZPersonclass,thenusethemethodin
main()instead
ofyournested
allocand
init.
Tip: Ratherthanusing
[[XYZPersonalloc]init]intheclass
factorymethod,insteadtryusing
[[selfalloc]init].
Using
selfinaclassfactorymethodmeansthatyou’rereferringtotheclassitself.
Thismeansthatyoudon’thavetooverridethe
personmethodinthe
XYZShoutingPersonimplementation
tocreatethecorrectinstance.Testthisbycheckingthat:
XYZShoutingPerson*shoutingPerson=[XYZShoutingPersonperson]; |
Createanewlocal
XYZPersonpointer,butdon’tincludeanyvalueassignment.
Useabranch(
ifstatement)tocheckwhetherthevariableisautomaticallyassignedas
nil.
相关文章推荐
- Large Object Support大对象支持
- python-可变迭代对象在for循环中的风险Risk in FOR loop while looping mutable iterable object
- 【iOS】objective-c2.0之基本数据类型
- 错误: g_dbus_connection_register_object: assertion 'G_IS_DBUS_CONNECTION (connection)' failed
- 【Objective-C】代码块(Block)
- Objective-c 中 nil, Nil, NULL和NSNull的区别
- Objective-C中的方法重载与初始化方法
- 《Objective-C基础教程》第10章 对象初始化
- 黑马程序员———API之Object、String和Scanner
- Objective-C 苹果开发文档--02 Defining Classes
- objective-c学习笔记之属性特性(assign , retain , copy , readonly , readwrite , atomic , nonatomic)
- Objective-C 苹果开发文档 01 Introduction
- Objective-C中一个方法如何传递多个参数的理解
- object-c的内存管理
- TypeError:'stepUp' called on an object that does not implement interface HTMLInputElement.
- 深入JavaScript(12)变量对象(Variable Object)
- NSObject的load和initialize方法
- Objective-C马路成魔【12-分类和协议】
- Objective-C:ARC自动释放对象内存
- Objective-C:MRC(引用计数器)在OC内部的可变对象是适用的,不可变对象是不适用的(例如 NSString、NSArray等)