![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=1738954486-SnG9BczHv30HGFILZM6zJAyp0JWJcFY7-0-78e163cd05b542c0f7e8c5a55c95e942)
- 消息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=1738954486-OfgVoJTBcfr6pNItNo6a591W07EaqaYW-0-66e6b1766dc9d0c74e22944ea8beef7f)
其中:
- 方法类型标示符(-) 即这是一个实例方法。
- 返回类型(void) 即没有返回值。
- 方法名称(insertString:atIndex:) 一个方法的实际名称是所有签名关键词的串联,包括冒号字符。
- 参数类型 该方法中包括了两个参数,两个参数的类型为NSString和NSUInteger。
- 参数名称 该方法中包含了两个参数,两个参数的名称分别为aString和loc。
注意:在定义方法时,方法名称以及参数名称需要使用驼峰法来定义。
3.方法的类型
在iOS开发中,方法一共有两种类型,分别为实例方法和类方法。
- 实例方法:消息的接收者必须为一个已经实例化的对象,实例方法在定义时以“-”开头。例如:
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-T69_8338.jpg?sign=1738954486-DITolcvylct4e2GTCA9RYcz6pXHBqdIJ-0-50b85be269211a5abca1ef391feb7a4e)
- 类方法:有时也称为工厂方法,类方法通常用于创建类的新实例。消息的接收者为一个类对象(感观上即一个类的类名),类方法在定义时以“+”开头,类方法是一般情况下是有返回值的,返回类型通常为instancetype(即返回一个本类的对象)。
示例:
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-T69_8340.jpg?sign=1738954486-vT5JU268579sX8bdefX8fxBz4Di7MojI-0-b4cfd8291997232e64188327661f2bb3)
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=1738954486-ZK2x7TrpDuTyHBnVJ4Ry8orjFS5n6IiO-0-2de54c2a4c79fcca93108802e3aff5f2)
运行结果如图3-6所示。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-P70_8475.jpg?sign=1738954486-iLbhjlj9HsLMcIoPbKhelADCrD9DQhdo-0-4f40f163c95a11a56f8fe2729c27027b)
图3-6 运行结果
- 嵌套调用:有时为了避免声明大量的局部变量来存储临时结果,Objective-C也支持嵌套消息表达式。上面的案例中,可以不声明str2,从而对代码做如下改写:
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-T70_8479.jpg?sign=1738954486-jaHbaXLuUzM5vX0LLiGwfsDROHLZcftp-0-73219466968a7ebf76fed08eead72816)
- 调用父类的方法:子类可以直接调用父类的方法。如下所示:MYClass继承自NSObject,因此MYClass的对象myClass可以直接调用NSObject的copy方法。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-T70_82817.jpg?sign=1738954486-Pid8BxrZ7TygesVAGYPNl25hoxEyKNYg-0-31fbf4b099e037dd48151bd173065a20)
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=1738954486-ZbSpKVkq8HBBz68KPuAQrsRljPnkZ5la-0-741983f7e48ca5d011cd97f6241450fe)
- 使用点语法对name属性进行赋值以及取值操作。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-T71_82819.jpg?sign=1738954486-8eqw1d0L19PFUR37WvibUxhAJ0d3RtCh-0-9ab5ce616cf4c08b52e75d34d393c06d)
运行结果如图3-7所示。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-P71_8602.jpg?sign=1738954486-SEd1Azr6zGc7pIFYi9KpfnuR5UG5LlzZ-0-ed21ce79662f137d00fff0d9d2e88205)
图3-7 运行结果
3.消息处理机制
为了深入理解消息、方法、接收者这些概念,必须了解消息处理的机制。在Objective-C中,消息是直到运行时才和方法进行绑定关联的。
消息机制的关键在于编译器为类和对象生成的结构。其中类的结构中包含两个基本元素:第一,指向父类的指针;第二,类的方法列表。而对象被创建时,对象的第一个实例变量是一个指向该对象的“类结构”的指针,即isa指针。通过该指针,就可以访问到该类及其父类的方法列表,如图3-8所示。
当向某个对象发送消息时:
- 首先根据isa指针,找到该对象对应的类结构的方法列表,继而即可找到具体的方法实现;当在本类的方法列表中找不到对应的方法时,会根据类结构中父类的指针去查找父类的方法列表,直至NSObject根类。
- 将对象以及参数传递给找到的方法实现。
- 执行方法中的代码,获取方法的返回值。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-P72_8646.jpg?sign=1738954486-qCDJGlHtpVc3hf7g5wWJQSmmpGnwPlAs-0-bd6954821a0913cefb7f1140c3b25168)
图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=1738954486-FzyWMp8BPoPmTGJ4tgGmZV8PPsI3XB08-0-b73e2c1fbc06d6f497da6ace5031d3f8)
- 在ClassA.m文件中,实现printWebSite方法的功能,即打印webSite属性的值。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-T73_82820.jpg?sign=1738954486-Ze8Bjd3x2CNs9Ba8kW2UnmF6OIiz1mhI-0-6fde37565afdfd97425a5936186d1b6c)
- 新建一个ClassB,继承自ClassA。在ClassB.h文件中,同样添加一个printWebSite方法。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-T73_8834.jpg?sign=1738954486-TUYKCDLbRl70kwns2zBRklVzlwIiLCyw-0-228003a9192ec61b7331a0e8aa085fc3)
- ClassB.m文件中,重写printWebSite方法,改变打印的内容。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-T73_8836.jpg?sign=1738954486-6CRDHrlUQYEz7xdVuEOOVPO49GSaHKEs-0-ecd905316d50e3481e987a8884eed28c)
- 在main()中分别调用父类和子类的printWebSite方法如下所示。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-T73_8838.jpg?sign=1738954486-zGBrshgoxPmhsZQfKpMVkYZYb8x8yQmL-0-a0fcf0398b21f2f53a6bfa8cf8f79acb)
运行结果如图3-9所示。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-P74_8905.jpg?sign=1738954486-ILqKu1H7Pet2uTYmvfuWX49dDBePo39z-0-274fd2ab283d8aa4078314f74777a0fc)
图3-9 运行结果
3.子类方法调用父类方法
在实际开发过程中,子类中经常会先调用一下父类的方法,然后再进行一些定制操作,例如在控制器类的viewDidLoad方法中,都需要首先执行[super viewDidLoad],然后在子类的viewDidLoad方法中进行一些额外操作。
接着上面的案例,对子类ClassB的printWebSite方法进行一些改进,使其首先调用一下父类的printWebSite方法,代码如下:
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-T74_8909.jpg?sign=1738954486-gu6yFdbk6QLoEV2UzWFUYdtHqqGktTRv-0-f54a366680aa9062c244e1a6264ccde4)
运行结果如图3-10所示。可以看到,当执行[classB printWebSite]时,会先调用ClassA的printWebSite方法,因此会打印出两条日志记录。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-P74_8911.jpg?sign=1738954486-Wbz3PNAfhZbIYlpc9iDaQxlPjXqsgu5p-0-31788d0fc6c3180c306998ddc814d30f)
图3-10 运行结果