![iOS开发:从零基础到精通](https://wfqqreader-1252317822.image.myqcloud.com/cover/796/26793796/b_26793796.jpg)
3.3 方法
3.3.1 方法的定义
在iOS开发中,通过将一则消息(message)发送给一个对象(称为消息的接收者),可以调用该对象的一个方法,消息机制是Objective-C语言的一个重要特点。在Objective-C中,有两种类型的方法,分别是实例方法与类方法。
1.有关方法的基本概念
在Objective-C语言中,调用某个对象中定义的方法是通过向对象发送消息的方式进行的,消息的名称对应类中定义的方法名称,这种机制是Objective-C语言的区别其他编程语言的一个特性,当需要深入研究和学习Objective-C语言时,理解其消息机制是非常重要的。当然,对于初学者来说,如何去调用类中定义的方法是需要优先掌握的内容。
在Objective-C中,调用一个对象的方法采用如下形式进行。其中,会涉及一些需要大家掌握的概念。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-T68_8268.jpg?sign=1739314899-MxlRVBapwQjT4sLyoIGpZ89Mpye4JyFN-0-cf8164c7db45c2f83e5e6451d2d7d3f9)
- 消息message:在iOS开发中,调用一个方法相当于传递一个消息,这里的消息指的是方法名(选择器Selector)和参数。消息传递(Message Passing)是Objective-C最大的特色,对象不是简单的调用方法,而是相互传递消息,这与C++有很大差异。
- 接收者receiver:通常为一个对象,消息告诉接收者需要去做什么事情。当消息发送的时候,系统从接收者的方法列表中选择最合适的方法并调用。
- 方法method:一般来说,方法都包括方法声明和方法实现两部分,相关代码分别编写在.h和.m文件中。通俗来说,方法就是需要对象去完成某个工作,以实现某种功能,可以简单理解为函数(实际上和函数也有差别)。
- 发送消息:当需要调用一个方法时,通过给实现该方法的对象发送一条消息来实现,简单来说,就是通知对象去调用其定义的某个方法或者其父类的某个方法。在发送的消息中,包含方法名称以及参数。
- 选择器selector:因为方法名在消息中负责在对象的方法列表中选择一个方法执行,因此方法名在消息中通常称为选择器。
2.方法的定义
方法声明包含了以下几个部分:方法类型标示符、返回类型、方法名称、参数类型和参数名称,如下所示。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-T69_8336.jpg?sign=1739314899-UAFGrNo79I8oiv8XtGFfwlBJDOMp9M7t-0-0164eeb460d3948e9c89d9f762cc1751)
其中:
- 方法类型标示符(-) 即这是一个实例方法。
- 返回类型(void) 即没有返回值。
- 方法名称(insertString:atIndex:) 一个方法的实际名称是所有签名关键词的串联,包括冒号字符。
- 参数类型 该方法中包括了两个参数,两个参数的类型为NSString和NSUInteger。
- 参数名称 该方法中包含了两个参数,两个参数的名称分别为aString和loc。
注意:在定义方法时,方法名称以及参数名称需要使用驼峰法来定义。
3.方法的类型
在iOS开发中,方法一共有两种类型,分别为实例方法和类方法。
- 实例方法:消息的接收者必须为一个已经实例化的对象,实例方法在定义时以“-”开头。例如:
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-T69_8338.jpg?sign=1739314899-NP6HzdL1KdHLdMn4henmqNQkX4hVLSlV-0-e00fb50f2ae8b996e0216d454da690d1)
- 类方法:有时也称为工厂方法,类方法通常用于创建类的新实例。消息的接收者为一个类对象(感观上即一个类的类名),类方法在定义时以“+”开头,类方法是一般情况下是有返回值的,返回类型通常为instancetype(即返回一个本类的对象)。
示例:
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-T69_8340.jpg?sign=1739314899-BolMcMqNaixXP009TOf1fZBUWZE1faiD-0-0b91942e7a298604e4547af868ed4d9c)
3.3.2 方法的调用
在Objective-C中,调用一个方法相当于传递一个消息,这里的消息指的是方法名和参数。所有消息的分派都是动态的,所谓动态指的是所有消息处理直到执行时(runtime)才会动态决定,而不是在编译时就绑定,这也体现了Objective-C对象的多态行为(多态性是指不同类型的对象响应同一消息的能力)。
1.方法调用的方式
在Objective-C中,调用一个方法相当于传递一个消息,消息中包含方法名(也称为选择器)和参数。通常调用方法存在以下几种方式。
- 普通调用:使用方括号将消息本身与参数放到括号内,同时将接收消息的对象放在最前面,如下所示。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-T69_8343.jpg?sign=1739314899-fWq3qCP4eJdvOCzgkWBaBsfxI9TDQY6h-0-fe6444ef3b8eea117ac8fd49fa282b35)
运行结果如图3-6所示。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-P70_8475.jpg?sign=1739314899-a1aD3ggdaTsC8yJrIcvGvjFpCDpGpPxI-0-71ec973e7a1d4223dbf1e560f50bd89e)
图3-6 运行结果
- 嵌套调用:有时为了避免声明大量的局部变量来存储临时结果,Objective-C也支持嵌套消息表达式。上面的案例中,可以不声明str2,从而对代码做如下改写:
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-T70_8479.jpg?sign=1739314899-Z6xcI19VTWNbH1xcsRshn4BsFNkWHJgm-0-8dfd03d466b03ba6f0443adc1275912a)
- 调用父类的方法:子类可以直接调用父类的方法。如下所示:MYClass继承自NSObject,因此MYClass的对象myClass可以直接调用NSObject的copy方法。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-T70_82817.jpg?sign=1739314899-oarBlyvv7qwv0QLHV9uz80CD4UsvA3Ma-0-81a1904e3648dcc5a9631e85936395d5)
2.点语法
Objective-C中还提供专门用于调用存取方法(setter/getter)的点语法。开发者可以调用getter/setter方法来获取/设置对象属性的值,同样的,可以使用点语法来更加简便地获取/设置对象属性的值。
下面的示例代码中,同时使用点语法对myClass对象的name属性赋值,然后又使用点语法来获取对应的值。
- 创建一个MYClass类,并且添加一个name属性。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-T71_82818.jpg?sign=1739314899-7OjmoVkbc4Lo6KQZWg3eawqKesW9xDvO-0-a946a501cef395769f77d2d7dc3766c6)
- 使用点语法对name属性进行赋值以及取值操作。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-T71_82819.jpg?sign=1739314899-lHzVH9bsw1zx4WQgntpkO6V18BW6Cpqn-0-f9729c87bbf56faf465139a89376e576)
运行结果如图3-7所示。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-P71_8602.jpg?sign=1739314899-c8hbj8DPXoUFNW51aCk2aCvxJVopiwtu-0-d311be9069e651353c5bd1f947916df2)
图3-7 运行结果
3.消息处理机制
为了深入理解消息、方法、接收者这些概念,必须了解消息处理的机制。在Objective-C中,消息是直到运行时才和方法进行绑定关联的。
消息机制的关键在于编译器为类和对象生成的结构。其中类的结构中包含两个基本元素:第一,指向父类的指针;第二,类的方法列表。而对象被创建时,对象的第一个实例变量是一个指向该对象的“类结构”的指针,即isa指针。通过该指针,就可以访问到该类及其父类的方法列表,如图3-8所示。
当向某个对象发送消息时:
- 首先根据isa指针,找到该对象对应的类结构的方法列表,继而即可找到具体的方法实现;当在本类的方法列表中找不到对应的方法时,会根据类结构中父类的指针去查找父类的方法列表,直至NSObject根类。
- 将对象以及参数传递给找到的方法实现。
- 执行方法中的代码,获取方法的返回值。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-P72_8646.jpg?sign=1739314899-baPE3Q67vF3kVrUndMq31JvaVmzuzeE6-0-793513f7d1ad20de8d53429882b0a2b1)
图3-8 消息机制
3.3.3 方法的重写
在Objective-C中,子类不仅可以继承父类的属性,同时还可以直接继承父类中的方法,而不需要重新编写相同的方法,但有时候在子类中并不想原封不动地继承父类中的方法,而是希望在子类中实现一些特定的功能,这时可以对父类的方法进行方法重写或方法覆盖。
1.方法重写的规则
一般来说,如果希望在子类中调用父类的某个方法,实现一些特定的功能时,可以考虑对父类的方法进行重写(当然也可以考虑新增一个方法,但这样做会使程序的可读性变差)。当子类需要重写父类的方法时,必须保证重写的两个方法返回值、方法名、参数列表完全一致。
方法的重写在iOS开发中十分常见,例如,当新增一个自定义控制器类时,系统会自动添加一些有关控制器的方法,如viewDidLoad方法,以便对方法进行重写。
2.示例代码
在下方的示例代码中,创建了一个父类以及一个子类,在子类中,对父类的方法进行了重写。
- 新增一个ClassA类,在ClassA.h文件中,添加webSite属性以及printWebSite方法。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-T72_8651.jpg?sign=1739314899-NEyATwIlAUeNLwUN1eNJnK9YxeTphUqD-0-b4f175bf2c1d9f2856d4ebfd8287d8f4)
- 在ClassA.m文件中,实现printWebSite方法的功能,即打印webSite属性的值。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-T73_82820.jpg?sign=1739314899-h1koVlCUlnd31gPt1LFrSnxYHWNOLknP-0-2eb0d1c93badee1da1a57fd39830f79e)
- 新建一个ClassB,继承自ClassA。在ClassB.h文件中,同样添加一个printWebSite方法。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-T73_8834.jpg?sign=1739314899-Ij7UqPsMubvIpHzp5BVkXrE791dQ8BwX-0-ea4f135e7c800812fcab0deadddb609c)
- ClassB.m文件中,重写printWebSite方法,改变打印的内容。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-T73_8836.jpg?sign=1739314899-NGDh3LBE32xcCzcoWOIZYzXSK2HYqZkw-0-4755e91513cd604659240bf5baab6785)
- 在main()中分别调用父类和子类的printWebSite方法如下所示。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-T73_8838.jpg?sign=1739314899-cw6xR85VrZsDMEl6rMU2vRbB8fxaCq4Z-0-f9af95f7b09021257772cbab59070251)
运行结果如图3-9所示。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-P74_8905.jpg?sign=1739314899-txjK6lpc4ReA2rSmaUY3ei4orLXTkt5W-0-9e5e57dd46613e31ffecd4e58eeef114)
图3-9 运行结果
3.子类方法调用父类方法
在实际开发过程中,子类中经常会先调用一下父类的方法,然后再进行一些定制操作,例如在控制器类的viewDidLoad方法中,都需要首先执行[super viewDidLoad],然后在子类的viewDidLoad方法中进行一些额外操作。
接着上面的案例,对子类ClassB的printWebSite方法进行一些改进,使其首先调用一下父类的printWebSite方法,代码如下:
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-T74_8909.jpg?sign=1739314899-LX6GOoP9w3tL1JOTpELLtv4t0P5ktPyH-0-d94877ede44949c155ab13d05f88d914)
运行结果如图3-10所示。可以看到,当执行[classB printWebSite]时,会先调用ClassA的printWebSite方法,因此会打印出两条日志记录。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-P74_8911.jpg?sign=1739314899-CYcnyssssEZiQQ1MKs6oK4Xk8CwDbE5Q-0-69069ee0be70b037854a6d36b85ce973)
图3-10 运行结果