MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Objective-C方法声明与调用语法全解析

2022-01-034.8k 阅读

一、Objective-C 方法概述

在Objective-C 中,方法是对象能够执行的操作的实现。它是类的一部分,定义了类的行为。与其他编程语言中的函数类似,但方法与特定的对象实例或类相关联。

Objective-C 有两种类型的方法:实例方法和类方法。实例方法操作对象的实例变量,每个对象实例都可以独立执行这些方法。而类方法则与类本身相关联,不需要创建对象实例就可以调用,通常用于执行与类相关的通用操作,比如创建对象的工厂方法。

二、实例方法声明与语法

(一)实例方法声明的基本格式

在Objective-C 中,实例方法在类的接口部分(.h 文件)进行声明,其基本语法如下:

-(返回值类型)方法名:(参数类型)参数名;

这里,负号 - 表示这是一个实例方法。返回值类型指定了方法执行后返回的数据类型,如果方法不返回任何值,则使用 void。方法名是方法的标识符,遵循驼峰命名法,并且通常以小写字母开头。参数部分由参数类型和参数名组成,多个参数时以冒号 : 分隔。

例如,假设有一个 Person 类,我们要声明一个实例方法用于设置人的名字,代码如下:

#import <Foundation/Foundation.h>

@interface Person : NSObject

-(void)setName:(NSString *)name;

@end

在上述代码中,-(void)setName:(NSString *)name; 就是一个实例方法的声明。它不返回任何值(void),方法名为 setName,接受一个 NSString 类型的参数 name

(二)多个参数的实例方法声明

当一个实例方法需要多个参数时,格式如下:

-(返回值类型)方法名:(参数类型)参数名1 第二个参数名:(参数类型)参数名2;

每个参数部分都由参数名和参数类型组成,并且参数名成为方法名的一部分。例如,我们在 Person 类中添加一个设置年龄和地址的方法:

#import <Foundation/Foundation.h>

@interface Person : NSObject

-(void)setAge:(NSInteger)age address:(NSString *)address;

@end

这里的 -(void)setAge:(NSInteger)age address:(NSString *)address; 方法接受两个参数,一个是 NSInteger 类型的 age,另一个是 NSString 类型的 address。这种命名方式使得方法调用时可读性更强,从方法名就能清楚知道每个参数的含义。

(三)实例方法的实现

实例方法在类的实现部分(.m 文件)进行实现。实现的基本格式如下:

@implementation 类名
-(返回值类型)方法名:(参数类型)参数名 {
    // 方法实现代码
    // 可以访问对象的实例变量
    return 返回值;
}
@end

继续以 Person 类为例,实现 setName 方法:

#import "Person.h"

@implementation Person

-(void)setName:(NSString *)name {
    // 这里假设 Person 类有一个实例变量 _name
    _name = name;
}

-(void)setAge:(NSInteger)age address:(NSString *)address {
    // 假设 Person 类有 _age 和 _address 实例变量
    _age = age;
    _address = address;
}

@end

在上述实现中,setName 方法将传入的 name 参数赋值给对象的 _name 实例变量(假设存在)。setAge:address: 方法同理,将 ageaddress 分别赋值给对应的实例变量。

三、类方法声明与语法

(一)类方法声明的基本格式

类方法在类的接口部分声明,其语法与实例方法类似,只是使用正号 + 表示这是一个类方法,格式如下:

+(返回值类型)方法名:(参数类型)参数名;

例如,我们在 Person 类中添加一个类方法用于创建 Person 对象,代码如下:

#import <Foundation/Foundation.h>

@interface Person : NSObject

+(Person *)personWithName:(NSString *)name;

@end

这里的 +(Person *)personWithName:(NSString *)name; 是一个类方法声明,它返回一个 Person 对象,方法接受一个 NSString 类型的参数 name

(二)类方法的实现

类方法在类的实现部分实现,格式如下:

@implementation 类名
+(返回值类型)方法名:(参数类型)参数名 {
    // 方法实现代码
    // 不能直接访问实例变量
    return 返回值;
}
@end

实现上述 personWithName: 类方法:

#import "Person.h"

@implementation Person

+(Person *)personWithName:(NSString *)name {
    Person *person = [[Person alloc] init];
    [person setName:name];
    return person;
}

@end

在这个实现中,类方法 personWithName: 使用 allocinit 创建一个 Person 对象实例,然后调用实例方法 setName: 设置对象的名字,最后返回这个创建好的 Person 对象。

四、方法调用语法

(一)实例方法调用

实例方法通过对象实例来调用,语法如下:

[对象实例 方法名:参数值];

如果方法有多个参数,则每个参数部分都要按顺序写上,例如:

[对象实例 方法名:参数值1 第二个参数名:参数值2];

假设我们有如下使用 Person 类的代码:

#import <Foundation/Foundation.h>
#import "Person.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person *person = [[Person alloc] init];
        [person setName:@"John"];
        [person setAge:30 address:@"New York"];
    }
    return 0;
}

在上述代码中,首先创建了一个 Person 对象实例 person,然后通过 person 调用 setName: 方法设置名字为 John,调用 setAge:address: 方法设置年龄为 30,地址为 New York

(二)类方法调用

类方法通过类名直接调用,语法如下:

[类名 方法名:参数值];

例如,使用上述 Person 类的 personWithName: 类方法创建对象:

#import <Foundation/Foundation.h>
#import "Person.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person *person = [Person personWithName:@"Jane"];
    }
    return 0;
}

这里通过 Person 类名直接调用 personWithName: 类方法创建了一个名字为 JanePerson 对象。

五、方法的参数传递与返回值

(一)参数传递方式

在Objective-C 中,方法参数传递通常是值传递。对于基本数据类型(如 intfloat 等),传递的是实际的值。而对于对象类型,传递的是对象的指针(引用)。

例如,以下代码展示了基本数据类型和对象类型参数传递的情况:

#import <Foundation/Foundation.h>

@interface Example : NSObject

-(void)printInt:(int)num;
-(void)printString:(NSString *)str;

@end

@implementation Example

-(void)printInt:(int)num {
    NSLog(@"The integer is: %d", num);
}

-(void)printString:(NSString *)str {
    NSLog(@"The string is: %@", str);
}

@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Example *example = [[Example alloc] init];
        int number = 10;
        NSString *string = @"Hello, Objective - C";
        [example printInt:number];
        [example printString:string];
    }
    return 0;
}

printInt: 方法中,num 接收到的是 number 的值。在 printString: 方法中,str 接收到的是 string 对象的指针,这样在方法内部可以操作该对象。

(二)返回值

方法可以返回各种数据类型的值,包括基本数据类型、对象类型等。返回值使用 return 关键字。如果方法返回 void,则不需要 return 语句,或者可以使用 return; 提前结束方法执行。

例如,我们给 Person 类添加一个获取名字的方法:

#import <Foundation/Foundation.h>

@interface Person : NSObject

-(void)setName:(NSString *)name;
-(NSString *)name;

@end

@implementation Person

-(void)setName:(NSString *)name {
    _name = name;
}

-(NSString *)name {
    return _name;
}

@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person *person = [[Person alloc] init];
        [person setName:@"Bob"];
        NSString *personName = [person name];
        NSLog(@"The person's name is: %@", personName);
    }
    return 0;
}

在上述代码中,name 方法返回 _name 实例变量的值,调用者通过 [person name] 获取到对象的名字并进行输出。

六、方法重载与重写

(一)方法重载

在Objective-C 中,不存在严格意义上像C++ 或Java 那样的方法重载。因为Objective-C 方法的唯一性不仅取决于方法名,还取决于参数的个数和类型。不同参数列表的方法实际上是不同的方法。

例如,以下代码展示了两个不同参数列表的方法,它们在Objective-C 中是不同的方法:

#import <Foundation/Foundation.h>

@interface MathCalculator : NSObject

-(int)add:(int)a b:(int)b;
-(float)add:(float)a b:(float)b;

@end

@implementation MathCalculator

-(int)add:(int)a b:(int)b {
    return a + b;
}

-(float)add:(float)a b:(float)b {
    return a + b;
}

@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        MathCalculator *calculator = [[MathCalculator alloc] init];
        int intResult = [calculator add:2 b:3];
        float floatResult = [calculator add:2.5f b:3.5f];
        NSLog(@"Integer addition result: %d", intResult);
        NSLog(@"Float addition result: %f", floatResult);
    }
    return 0;
}

MathCalculator 类中,虽然两个方法都叫 add,但由于参数类型不同,它们是不同的方法,在调用时会根据传递的参数类型自动匹配相应的方法。

(二)方法重写

方法重写是指子类重新实现父类中已经定义的方法。当子类继承父类时,它可以选择重写父类的方法以提供更适合自身的行为。

要重写方法,子类在实现部分提供与父类方法相同的方法声明(包括返回值类型、方法名和参数列表)。例如,假设有一个 Animal 类和它的子类 Dog

#import <Foundation/Foundation.h>

@interface Animal : NSObject

-(void)makeSound;

@end

@implementation Animal

-(void)makeSound {
    NSLog(@"The animal makes a sound.");
}

@end

@interface Dog : Animal

-(void)makeSound;

@end

@implementation Dog

-(void)makeSound {
    NSLog(@"The dog barks.");
}

@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Animal *animal = [[Animal alloc] init];
        [animal makeSound];
        Dog *dog = [[Dog alloc] init];
        [dog makeSound];
        Animal *animalAsDog = [[Dog alloc] init];
        [animalAsDog makeSound];
    }
    return 0;
}

在上述代码中,Dog 类继承自 Animal 类并重写了 makeSound 方法。当创建 Animal 对象调用 makeSound 方法时,会执行 Animal 类中的实现。而创建 Dog 对象调用 makeSound 方法时,会执行 Dog 类中重写的实现。即使将 Dog 对象赋值给 Animal 类型的变量 animalAsDog,调用 makeSound 方法时依然会执行 Dog 类的重写方法,这体现了多态性。

七、方法的默认参数与可变参数

(一)默认参数

Objective-C 本身并不支持像C++ 那样的默认参数。但是可以通过方法重载来模拟类似的效果。

例如,假设有一个 Rectangle 类,我们想要一个设置矩形大小的方法,既可以接受两个参数设置宽和高,也可以只接受一个参数设置正方形的边长(默认宽高相等)。我们可以这样实现:

#import <Foundation/Foundation.h>

@interface Rectangle : NSObject

-(void)setDimensionsWithWidth:(float)width height:(float)height;
-(void)setDimensionsWithSide:(float)side;

@end

@implementation Rectangle

-(void)setDimensionsWithWidth:(float)width height:(float)height {
    // 假设 Rectangle 类有 _width 和 _height 实例变量
    _width = width;
    _height = height;
}

-(void)setDimensionsWithSide:(float)side {
    [self setDimensionsWithWidth:side height:side];
}

@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Rectangle *rectangle = [[Rectangle alloc] init];
        [rectangle setDimensionsWithWidth:5.0f height:10.0f];
        [rectangle setDimensionsWithSide:8.0f];
    }
    return 0;
}

在上述代码中,setDimensionsWithSide: 方法通过调用 setDimensionsWithWidth:height: 方法来实现类似默认参数的效果。

(二)可变参数

Objective-C 支持可变参数,使用 va_listva_startva_argva_end 宏来处理可变参数列表。

例如,我们创建一个计算多个整数和的方法:

#import <Foundation/Foundation.h>

@interface MathUtils : NSObject

-(int)sumOfNumbers:(int)first,...;

@end

@implementation MathUtils

-(int)sumOfNumbers:(int)first,... {
    int sum = first;
    va_list args;
    va_start(args, first);
    int number;
    while ((number = va_arg(args, int))) {
        sum += number;
    }
    va_end(args);
    return sum;
}

@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        MathUtils *mathUtils = [[MathUtils alloc] init];
        int result = [mathUtils sumOfNumbers:1, 2, 3, 4, 0];
        NSLog(@"The sum is: %d", result);
    }
    return 0;
}

sumOfNumbers: 方法中,首先获取第一个参数 first 并将其加到 sum 中。然后使用 va_start 初始化可变参数列表,通过 va_arg 依次获取后续的参数并累加,直到遇到值为 0 的参数(这里约定 0 作为参数列表结束的标志),最后使用 va_end 结束可变参数的处理并返回总和。

八、方法的作用域与访问控制

(一)方法的作用域

在Objective-C 中,方法的作用域主要与其所属的类相关。实例方法只能通过对象实例来调用,其作用域局限于对象实例的生命周期内。类方法通过类名调用,作用域与类相关,在类存在期间都可以调用。

例如,对于 Person 类的实例方法 setName:,只能在 Person 对象实例上调用:

Person *person = [[Person alloc] init];
[person setName:@"Alice"];

而类方法 personWithName: 可以直接通过类名调用:

Person *person = [Person personWithName:@"Bob"];

(二)访问控制

Objective-C 提供了一些关键字来控制方法和实例变量的访问权限,主要有 @public@protected@private

  1. @public:声明为 @public 的实例变量和方法可以在类的外部直接访问。例如:
#import <Foundation/Foundation.h>

@interface PublicExample : NSObject
{
    @public
    int publicVariable;
}

-(void)printPublicVariable;

@end

@implementation PublicExample

-(void)printPublicVariable {
    NSLog(@"The public variable is: %d", publicVariable);
}

@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        PublicExample *example = [[PublicExample alloc] init];
        example->publicVariable = 10;
        [example printPublicVariable];
    }
    return 0;
}

在上述代码中,publicVariable@public 实例变量,可以在类外部通过对象指针直接访问并赋值。

  1. @protected:这是默认的访问控制级别。@protected 的实例变量和方法可以在类及其子类中访问,但在类外部不能直接访问。例如:
#import <Foundation/Foundation.h>

@interface ProtectedExample : NSObject
{
    @protected
    int protectedVariable;
}

-(void)setProtectedVariable:(int)value;
-(int)getProtectedVariable;

@end

@implementation ProtectedExample

-(void)setProtectedVariable:(int)value {
    protectedVariable = value;
}

-(int)getProtectedVariable {
    return protectedVariable;
}

@end

@interface SubProtectedExample : ProtectedExample

-(void)printProtectedVariable;

@end

@implementation SubProtectedExample

-(void)printProtectedVariable {
    NSLog(@"The protected variable in subclass is: %d", protectedVariable);
}

@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        ProtectedExample *example = [[ProtectedExample alloc] init];
        [example setProtectedVariable:20];
        int value = [example getProtectedVariable];
        NSLog(@"The protected variable value is: %d", value);

        SubProtectedExample *subExample = [[SubProtectedExample alloc] init];
        [subExample setProtectedVariable:30];
        [subExample printProtectedVariable];
    }
    return 0;
}

在上述代码中,protectedVariable@protected 实例变量,在 ProtectedExample 类及其子类 SubProtectedExample 中可以访问,但在类外部不能直接访问,只能通过类的公共方法来操作。

  1. @private:声明为 @private 的实例变量和方法只能在类内部访问,即使是子类也无法访问。例如:
#import <Foundation/Foundation.h>

@interface PrivateExample : NSObject
{
    @private
    int privateVariable;
}

-(void)setPrivateVariable:(int)value;
-(int)getPrivateVariable;

@end

@implementation PrivateExample

-(void)setPrivateVariable:(int)value {
    privateVariable = value;
}

-(int)getPrivateVariable {
    return privateVariable;
}

@end

@interface SubPrivateExample : PrivateExample

-(void)printPrivateVariable;

@end

@implementation SubPrivateExample

-(void)printPrivateVariable {
    // 这里不能直接访问 privateVariable,会编译错误
    // NSLog(@"The private variable in subclass is: %d", privateVariable);
}

@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        PrivateExample *example = [[PrivateExample alloc] init];
        [example setPrivateVariable:40];
        int value = [example getPrivateVariable];
        NSLog(@"The private variable value is: %d", value);
    }
    return 0;
}

在上述代码中,privateVariable@private 实例变量,只能在 PrivateExample 类内部通过其公共方法 setPrivateVariable:getPrivateVariable 来访问,子类 SubPrivateExample 无法直接访问。

通过合理使用这些访问控制关键字,可以更好地封装类的实现细节,提高代码的安全性和可维护性。

九、选择器(Selector)与动态方法调用

(一)选择器(Selector)

在Objective-C 中,选择器(Selector)是一个表示方法名的标识符。它是一个 SEL 类型,通过 @selector 指令来获取。选择器用于在运行时动态地指定要调用的方法。

例如,对于 Person 类的 setName: 方法,我们可以获取其选择器:

SEL selector = @selector(setName:);

选择器的主要用途之一是在动态方法调用中。

(二)动态方法调用

动态方法调用允许在运行时根据选择器来决定调用哪个方法,而不是在编译时就确定。这为程序带来了很大的灵活性。

Objective-C 提供了 NSObject 类的一些方法来支持动态方法调用,比如 performSelector:performSelector:withObject:performSelector:withObject:withObject: 等。

例如,假设我们有一个 Person 类,并且想在运行时根据用户输入动态调用不同的方法:

#import <Foundation/Foundation.h>

@interface Person : NSObject

-(void)sayHello;
-(void)sayGoodbye;

@end

@implementation Person

-(void)sayHello {
    NSLog(@"Hello!");
}

-(void)sayGoodbye {
    NSLog(@"Goodbye!");
}

@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person *person = [[Person alloc] init];
        NSString *userInput = @"hello";
        SEL selector;
        if ([userInput isEqualToString:@"hello"]) {
            selector = @selector(sayHello);
        } else if ([userInput isEqualToString:@"goodbye"]) {
            selector = @selector(sayGoodbye);
        }
        if (selector) {
            [person performSelector:selector];
        }
    }
    return 0;
}

在上述代码中,根据用户输入的字符串,获取相应方法的选择器,然后通过 performSelector: 方法在运行时动态调用对应的方法。如果方法需要参数,可以使用 performSelector:withObject:performSelector:withObject:withObject: 等方法,将参数传递给动态调用的方法。

动态方法调用在很多场景下都非常有用,比如实现插件机制、根据配置文件动态调用不同的功能等。但同时也需要注意,过度使用动态方法调用可能会降低代码的可读性和可维护性,因为方法调用在编译时不明确,调试起来相对困难。所以在使用时需要权衡利弊,确保代码的合理性和健壮性。

通过以上对Objective-C 方法声明与调用语法的全面解析,相信读者对Objective-C 的这一核心特性有了更深入的理解和掌握。在实际编程中,合理运用这些知识将有助于编写出高效、可读且易于维护的Objective-C 代码。无论是开发iOS 应用还是其他基于Objective-C 的项目,扎实的方法声明与调用基础都是必不可少的。