![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=1739277162-m2vYmZfMAM6JU6Fhpd5nqXc1hqE058kF-0-b320f3368916a15e0b8af919614bb7ea)
- 消息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=1739277162-HikRzEAQ4bJTiy9YeRzN0ZzrJIUrbWbh-0-efd363880a933f9355fac136894b3506)
其中:
- 方法类型标示符(-) 即这是一个实例方法。
- 返回类型(void) 即没有返回值。
- 方法名称(insertString:atIndex:) 一个方法的实际名称是所有签名关键词的串联,包括冒号字符。
- 参数类型 该方法中包括了两个参数,两个参数的类型为NSString和NSUInteger。
- 参数名称 该方法中包含了两个参数,两个参数的名称分别为aString和loc。
注意:在定义方法时,方法名称以及参数名称需要使用驼峰法来定义。
3.方法的类型
在iOS开发中,方法一共有两种类型,分别为实例方法和类方法。
- 实例方法:消息的接收者必须为一个已经实例化的对象,实例方法在定义时以“-”开头。例如:
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-T69_8338.jpg?sign=1739277162-2VHpzC6soXR9bBWiavT22dzM0HQKNCC8-0-d184d65fa2806241e62a8486d2046eb2)
- 类方法:有时也称为工厂方法,类方法通常用于创建类的新实例。消息的接收者为一个类对象(感观上即一个类的类名),类方法在定义时以“+”开头,类方法是一般情况下是有返回值的,返回类型通常为instancetype(即返回一个本类的对象)。
示例:
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-T69_8340.jpg?sign=1739277162-Uz6yFGBceb4LnSpuMCdT5lM9LPguoldp-0-1b7ec50fc756ac6f25adbdd3f87d6986)
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=1739277162-UsWHDzxv0MPOBO5dcye9XXeW0L3gEr9S-0-ea43b4b806c4d7d3d65b39baa3c10e4a)
运行结果如图3-6所示。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-P70_8475.jpg?sign=1739277162-4rgWHzXSGATxMat8bN1o7SSDcIER19Fi-0-5e4b77c3b3ee66563b18b8fe18b9cbe3)
图3-6 运行结果
- 嵌套调用:有时为了避免声明大量的局部变量来存储临时结果,Objective-C也支持嵌套消息表达式。上面的案例中,可以不声明str2,从而对代码做如下改写:
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-T70_8479.jpg?sign=1739277162-k34dIj140NQUKVQQPPshUegp4H3V1C9R-0-ed4275f94497af73c34a3e60dd57e1c5)
- 调用父类的方法:子类可以直接调用父类的方法。如下所示:MYClass继承自NSObject,因此MYClass的对象myClass可以直接调用NSObject的copy方法。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-T70_82817.jpg?sign=1739277162-0q3s4I1CLtWalvtyPYK34oW0Urx7KHbZ-0-2887412986d2e46bf6380b00bdef46b2)
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=1739277162-THk6RjYCyJhMCm9EDbaQH6hHHVvzOoRd-0-47cebd7aba69325bd1808ccc0b594e12)
- 使用点语法对name属性进行赋值以及取值操作。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-T71_82819.jpg?sign=1739277162-T1jNb903fzS9b9b2dMSqneJEYhpCcCaF-0-5e36e9101ebae2863d8053f23382ca85)
运行结果如图3-7所示。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-P71_8602.jpg?sign=1739277162-1oVUxQMvtZcT4WdpD4Xf7xPEOy5pScet-0-9db6823b4b875e7827609e6df2e85528)
图3-7 运行结果
3.消息处理机制
为了深入理解消息、方法、接收者这些概念,必须了解消息处理的机制。在Objective-C中,消息是直到运行时才和方法进行绑定关联的。
消息机制的关键在于编译器为类和对象生成的结构。其中类的结构中包含两个基本元素:第一,指向父类的指针;第二,类的方法列表。而对象被创建时,对象的第一个实例变量是一个指向该对象的“类结构”的指针,即isa指针。通过该指针,就可以访问到该类及其父类的方法列表,如图3-8所示。
当向某个对象发送消息时:
- 首先根据isa指针,找到该对象对应的类结构的方法列表,继而即可找到具体的方法实现;当在本类的方法列表中找不到对应的方法时,会根据类结构中父类的指针去查找父类的方法列表,直至NSObject根类。
- 将对象以及参数传递给找到的方法实现。
- 执行方法中的代码,获取方法的返回值。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-P72_8646.jpg?sign=1739277162-KAxgy5T5QvvBFms5D8dbD9ZExNLIBc5c-0-90fe8638db1561218ce24c6e31c5f2d9)
图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=1739277162-JTbpoDwzbLX0CmBpmIONn9ylDEXtIfKu-0-25cb9267bd85e08bb65441a73b6a1f20)
- 在ClassA.m文件中,实现printWebSite方法的功能,即打印webSite属性的值。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-T73_82820.jpg?sign=1739277162-V6N5au0l9RQWN6AYzfb1CWTEFq1oVC94-0-327a0627544b2053ead36f4721c6c242)
- 新建一个ClassB,继承自ClassA。在ClassB.h文件中,同样添加一个printWebSite方法。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-T73_8834.jpg?sign=1739277162-18XPLuPj514bYQGoF1NSQMwYidZMbnN7-0-2e4605b7dd1f7e16054f0d9277036bc8)
- ClassB.m文件中,重写printWebSite方法,改变打印的内容。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-T73_8836.jpg?sign=1739277162-d1jJ1jKGf3DID2P5pTDHVGemGNw8GOLr-0-812c171f25624b336d14a601ab76d7b2)
- 在main()中分别调用父类和子类的printWebSite方法如下所示。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-T73_8838.jpg?sign=1739277162-JxGGWA1GxveeRN7qYDyWoxDBDjgdrI4E-0-f59799e4827bd278244f750a3489ff82)
运行结果如图3-9所示。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-P74_8905.jpg?sign=1739277162-WNxrsW4LveQMI3JGCBHILUXkjmeLIOKW-0-e79d4b1ea9166fed2a620e95e135f78f)
图3-9 运行结果
3.子类方法调用父类方法
在实际开发过程中,子类中经常会先调用一下父类的方法,然后再进行一些定制操作,例如在控制器类的viewDidLoad方法中,都需要首先执行[super viewDidLoad],然后在子类的viewDidLoad方法中进行一些额外操作。
接着上面的案例,对子类ClassB的printWebSite方法进行一些改进,使其首先调用一下父类的printWebSite方法,代码如下:
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-T74_8909.jpg?sign=1739277162-zZTECJgvcyP2nezSXbZGtxe6qEdTinbK-0-ea10f803d707426235c3298d70492823)
运行结果如图3-10所示。可以看到,当执行[classB printWebSite]时,会先调用ClassA的printWebSite方法,因此会打印出两条日志记录。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-P74_8911.jpg?sign=1739277162-pC32spwBxFG0x0StOFaTgOaqn8lZiDC0-0-fa64fdfd18cf607c50fe9702d1612154)
图3-10 运行结果