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

详解Objective-C类的定义与声明

2023-10-201.5k 阅读

一、Objective - C 类的基础概念

在Objective - C 编程语言中,类(Class)是面向对象编程的核心概念之一。它是一种自定义的数据类型,用于封装数据(属性)和相关的操作(方法)。可以将类看作是创建对象的蓝图,通过这个蓝图可以实例化出多个具有相同属性和行为的对象。

(一)类的作用

类在Objective - C 编程中起着至关重要的作用。它允许开发者将相关的数据和操作组织在一起,提高代码的模块化和可维护性。例如,在一个游戏开发项目中,我们可以定义一个Character类,将角色的生命值、攻击力等属性以及攻击、防御等方法封装在这个类中。这样,当需要创建多个角色对象时,只需要根据Character类进行实例化即可,每个角色对象都拥有相同的属性结构和行为方式。

(二)类与对象的关系

类和对象是紧密相关但又不同的概念。类是一种抽象的定义,它描述了对象所具有的属性和方法的集合。而对象则是类的具体实例,是在程序运行时根据类创建出来的实体。可以说类是对象的模板,对象是类的具体体现。比如,我们定义了一个Car类,它包含了颜色、速度等属性和启动、加速等方法。当我们使用Car类创建一个名为myCar的对象时,myCar就是Car类的一个实例,它具有Car类所定义的属性和方法,并且可以根据实际情况设置自己的属性值,如将颜色设置为红色,速度设置为 0 等。

二、Objective - C 类的声明

(一)声明的基本语法

在Objective - C 中,类的声明主要分为两个部分:接口(Interface)部分和实现(Implementation)部分。接口部分用于声明类的属性和方法,告知外界这个类拥有哪些成员;实现部分则用于具体实现这些方法。

接口部分的基本语法如下:

#import <Foundation/Foundation.h>

@interface ClassName : SuperClassName
// 在此声明属性
@property (nonatomic, strong) NSString *propertyName;
// 在此声明方法
- (void)methodName;
@end

在上述代码中:

  1. #import <Foundation/Foundation.h>是导入Foundation框架,这是Objective - C 开发中常用的基础框架,包含了许多基本的数据类型和工具类等。
  2. @interface关键字用于开始类的接口声明,ClassName是要声明的类的名称,需要遵循命名规范,通常采用驼峰命名法,首字母大写。
  3. SuperClassName表示该类的父类,如果没有父类(在Objective - C 中,所有类最终都继承自NSObject类,如果不显式指定父类,则默认父类为NSObject),可以省略。
  4. @property用于声明属性,(nonatomic, strong)是属性的修饰符,nonatomic表示非原子性访问,strong表示强引用。NSString *propertyName声明了一个名为propertyNameNSString类型的属性。
  5. - (void)methodName;声明了一个实例方法,-表示这是一个实例方法(如果是+则表示类方法),(void)表示方法的返回值类型为void(无返回值),methodName是方法名。

(二)属性声明

  1. 属性修饰符

    • 原子性(Atomicity)
      • atomic:默认修饰符。它保证了在多线程环境下,对属性的读写操作是原子性的,即不会被其他线程打断。例如,对于一个NSMutableArray类型的属性,如果使用atomic修饰,在一个线程对其进行添加元素操作时,其他线程不会同时对其进行读取或修改操作,从而避免数据不一致问题。但是,atomic修饰会带来一定的性能开销,因为它需要使用锁机制来保证原子性。
      • nonatomic:非原子性修饰符。它不保证在多线程环境下对属性读写操作的原子性。在单线程环境或者对性能要求较高且对数据一致性要求不是特别严格的情况下,可以使用nonatomic修饰符。比如在一个只在主线程中使用的属性,使用nonatomic可以提高性能。
    • 内存管理
      • strong:强引用修饰符。被strong修饰的属性会强持有对象,即只要该属性存在,它所指向的对象就不会被释放。例如,一个ViewController类中有一个strong修饰的UILabel属性,只要ViewController对象存在,UILabel对象就不会被释放,即使没有其他地方引用UILabel
      • weak:弱引用修饰符。weak修饰的属性不会强持有对象,当对象的强引用计数变为 0 时,对象被释放,同时weak修饰的属性会自动被设置为nil。这在解决循环引用问题时非常有用。比如在一个父子视图关系中,父视图持有子视图可以用strong,而子视图反向引用父视图时用weak,这样可以避免循环引用导致的内存泄漏。
      • assign:通常用于修饰基本数据类型(如intfloat等)。它直接赋值,不涉及引用计数。对于对象类型,使用assign可能会导致野指针问题,因为当对象被释放后,assign修饰的属性仍然指向已释放的内存地址。
      • copy:用于修饰可变对象(如NSMutableString),会创建一个对象的副本并强持有。当对copy修饰的属性进行赋值时,实际上是创建了一个新的对象,而不是简单的引用。例如,有一个NSString属性,如果传入一个NSMutableString对象,使用copy修饰可以确保属性的值不会被外部可变对象的修改所影响。
  2. 属性的类型 属性可以是基本数据类型,如intfloatBOOL等,也可以是对象类型,如NSStringNSArrayNSDictionary等。还可以是自定义的类类型。例如,我们定义一个Person类,其中可以有NSString *name(对象类型)、int age(基本数据类型)等属性。

#import <Foundation/Foundation.h>

@interface Person : NSObject
@property (nonatomic, strong) NSString *name;
@property (nonatomic, assign) int age;
@end

(三)方法声明

  1. 实例方法 实例方法是属于类的实例(对象)的方法,通过对象来调用。其声明格式为:
- (ReturnType)methodName:(ParameterType)parameterName;

例如,定义一个Dog类,其中有一个实例方法用于让狗叫:

#import <Foundation/Foundation.h>

@interface Dog : NSObject
- (void)bark;
@end

@implementation Dog
- (void)bark {
    NSLog(@"Woof!");
}
@end

在上述代码中,- (void)bark;声明了一个无参数、无返回值的实例方法bark。在@implementation部分实现了这个方法,当创建Dog类的对象并调用bark方法时,会在控制台输出Woof!

  1. 类方法 类方法是属于类本身的方法,不需要创建对象就可以调用。其声明格式为:
+ (ReturnType)methodName:(ParameterType)parameterName;

例如,在MathUtils类中定义一个类方法用于计算两个整数的和:

#import <Foundation/Foundation.h>

@interface MathUtils : NSObject
+ (int)add:(int)a b:(int)b;
@end

@implementation MathUtils
+ (int)add:(int)a b:(int)b {
    return a + b;
}
@end

调用这个类方法时,可以直接通过类名调用,如int result = [MathUtils add:2 b:3];,此时result的值为 5。

三、Objective - C 类的定义(实现)

(一)实现的基本语法

类的实现部分使用@implementation关键字,基本语法如下:

#import "ClassName.h"

@implementation ClassName
// 在此实现属性的存取方法(如果需要自定义)
// 在此实现方法
- (void)methodName {
    // 方法实现代码
}
@end

在上述代码中:

  1. #import "ClassName.h"导入类的头文件(接口文件),这一步确保在实现部分能够正确识别类的声明。
  2. @implementation ClassName开始类的实现,ClassName要与接口部分声明的类名一致。
  3. @implementation块中,可以实现属性的存取方法(如果默认的存取方法不能满足需求)以及声明的方法。

(二)属性的存取方法实现

  1. 默认存取方法 在Objective - C 中,使用@property声明属性后,编译器会自动为属性生成默认的存取方法。对于名为propertyName的属性,默认的存取方法为-(PropertyType)propertyName(获取方法,也称为 getter 方法)和-(void)setPropertyName:(PropertyType)value(设置方法,也称为 setter 方法)。例如,对于@property (nonatomic, strong) NSString *name;属性,默认的获取方法为-(NSString *)name,默认的设置方法为-(void)setName:(NSString *)value

  2. 自定义存取方法 有时候默认的存取方法不能满足需求,需要自定义存取方法。比如,在设置属性值时需要进行一些额外的逻辑判断。以下是一个自定义age属性存取方法的例子:

#import <Foundation/Foundation.h>

@interface Person : NSObject
@property (nonatomic, assign) int age;
@end

@implementation Person
- (int)age {
    return _age;
}

- (void)setAge:(int)age {
    if (age >= 0 && age <= 120) {
        _age = age;
    } else {
        NSLog(@"Invalid age value");
    }
}
@end

在上述代码中,自定义了age属性的存取方法。在setAge:方法中,对传入的年龄值进行了范围检查,如果在合理范围内则设置_age(实例变量,编译器会自动为@property声明的属性生成一个下划线开头的实例变量),否则输出错误信息。

(三)方法的实现

  1. 实例方法的实现 实例方法的实现需要在@implementation块中进行。以之前Dog类的bark方法为例,实现代码如下:
#import <Foundation/Foundation.h>

@interface Dog : NSObject
- (void)bark;
@end

@implementation Dog
- (void)bark {
    NSLog(@"Woof!");
}
@end

bark方法的实现中,简单地在控制台输出了Woof!。如果方法有参数和返回值,在实现中需要根据参数进行相应的处理并返回合适的值。例如,一个计算矩形面积的Rectangle类的实例方法:

#import <Foundation/Foundation.h>

@interface Rectangle : NSObject
@property (nonatomic, assign) float width;
@property (nonatomic, assign) float height;
- (float)area;
@end

@implementation Rectangle
- (float)area {
    return self.width * self.height;
}
@end

在上述代码中,area方法通过访问widthheight属性,计算并返回矩形的面积。

  1. 类方法的实现 类方法的实现同样在@implementation块中。以之前MathUtils类的add:方法为例:
#import <Foundation/Foundation.h>

@interface MathUtils : NSObject
+ (int)add:(int)a b:(int)b;
@end

@implementation MathUtils
+ (int)add:(int)a b:(int)b {
    return a + b;
}
@end

add:方法的实现中,直接返回两个参数的和。类方法不能访问实例变量(因为类方法是属于类本身,不是属于对象),但可以访问类变量(如果有定义)以及调用其他类方法。

四、类的继承与多态

(一)类的继承

  1. 继承的概念 在Objective - C 中,类可以继承自另一个类,继承类称为子类(Subclass),被继承类称为父类(Superclass)。子类继承了父类的属性和方法,并且可以在此基础上添加新的属性和方法,或者重写父类的方法。继承机制使得代码具有更好的复用性和层次性。例如,我们定义一个Animal类作为父类,包含name属性和eat方法。然后定义Dog类和Cat类作为Animal类的子类,Dog类和Cat类自动拥有Animal类的name属性和eat方法,并且可以各自添加独特的属性和方法,如Dog类可以添加bark方法,Cat类可以添加meow方法。

  2. 继承的语法 子类声明时通过:符号指定父类,例如:

#import <Foundation/Foundation.h>

@interface Animal : NSObject
@property (nonatomic, strong) NSString *name;
- (void)eat;
@end

@implementation Animal
- (void)eat {
    NSLog(@"%@ is eating.", self.name);
}
@end

@interface Dog : Animal
- (void)bark;
@end

@implementation Dog
- (void)bark {
    NSLog(@"%@ is barking.", self.name);
}
@end

在上述代码中,Dog类继承自Animal类,Dog类自动拥有Animal类的name属性和eat方法。并且Dog类添加了自己的bark方法。

(二)多态

  1. 多态的概念 多态是指同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果。在Objective - C 中,多态主要通过继承和方法重写来实现。例如,Animal类有一个makeSound方法,Dog类和Cat类继承自Animal类并重写makeSound方法,当通过Animal类型的指针分别指向Dog类和Cat类的对象并调用makeSound方法时,会执行不同的实现,这就是多态的体现。

  2. 多态的实现

#import <Foundation/Foundation.h>

@interface Animal : NSObject
- (void)makeSound;
@end

@implementation Animal
- (void)makeSound {
    NSLog(@"Animal makes a sound.");
}
@end

@interface Dog : Animal
- (void)makeSound {
    NSLog(@"Woof!");
}
@end

@interface Cat : Animal
- (void)makeSound {
    NSLog(@"Meow!");
}
@end

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

在上述代码中,Dog类和Cat类重写了Animal类的makeSound方法。在main函数中,通过Animal类型的指针分别指向Dog类和Cat类的对象,然后调用makeSound方法,会根据对象的实际类型(DogCat)执行相应的makeSound方法,从而体现了多态性。

五、类的其他特性

(一)类别(Category)

  1. 类别概述 类别是Objective - C 中一种为已有的类添加方法的方式,不需要继承或修改原有类的代码。类别可以将一个类的实现分散到多个文件中,提高代码的可维护性和组织性。例如,对于系统类NSString,我们可以通过类别为其添加自定义的方法,如计算字符串中某个字符出现次数的方法。

  2. 类别声明与实现 类别声明的语法如下:

#import <Foundation/Foundation.h>

@interface NSString (CustomCategory)
- (NSUInteger)countOfCharacter:(unichar)character;
@end

@implementation NSString (CustomCategory)
- (NSUInteger)countOfCharacter:(unichar)character {
    NSUInteger count = 0;
    for (NSUInteger i = 0; i < self.length; i++) {
        if ([self characterAtIndex:i] == character) {
            count++;
        }
    }
    return count;
}
@end

在上述代码中,@interface NSString (CustomCategory)声明了一个名为CustomCategory的类别,为NSString类添加了一个countOfCharacter:方法。在@implementation部分实现了这个方法,用于计算字符串中指定字符出现的次数。

(二)扩展(Extension)

  1. 扩展概述 扩展是一种特殊的类别,它允许在类的实现文件中为类添加额外的私有属性和方法。与普通类别不同的是,扩展在编译时就已经知道其存在,而普通类别在运行时才被动态加载。扩展通常用于隐藏类的一些内部实现细节,只对外暴露必要的接口。

  2. 扩展声明与实现 在类的实现文件中声明扩展:

#import "MyClass.h"

@interface MyClass ()
@property (nonatomic, strong) NSString *privateProperty;
- (void)privateMethod;
@end

@implementation MyClass
- (void)privateMethod {
    NSLog(@"This is a private method.");
}
@end

在上述代码中,@interface MyClass ()声明了一个扩展,为MyClass类添加了一个私有属性privateProperty和一个私有方法privateMethod。这些私有成员只能在MyClass类的实现文件中访问,对外界是隐藏的。

(三)协议(Protocol)

  1. 协议概述 协议是一种特殊的接口,它定义了一组方法的声明,但不提供方法的实现。任何类只要实现了协议中定义的方法,就可以说这个类遵循了该协议。协议用于实现对象之间的交互和功能的复用,它可以让不同层次、不同继承体系的类具有相同的行为。例如,在一个图形绘制程序中,可以定义一个Drawable协议,包含draw方法,Circle类和Rectangle类都可以遵循这个协议并实现draw方法,这样它们都具有了绘制的能力。

  2. 协议声明与遵循 协议声明的语法如下:

#import <Foundation/Foundation.h>

@protocol Drawable <NSObject>
- (void)draw;
@end

在上述代码中,@protocol Drawable <NSObject>声明了一个名为Drawable的协议,它继承自NSObject协议(这是Objective - C 中所有协议的基协议),并定义了一个draw方法。

类遵循协议的语法如下:

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

@interface Circle : NSObject <Drawable>
@property (nonatomic, assign) float radius;
@end

@implementation Circle
- (void)draw {
    NSLog(@"Drawing a circle with radius %f.", self.radius);
}
@end

在上述代码中,@interface Circle : NSObject <Drawable>表示Circle类遵循Drawable协议,并在实现部分实现了draw方法。

通过以上对Objective - C 类的定义与声明的详细介绍,包括类的基础概念、声明、定义、继承与多态以及其他特性,希望能帮助开发者更深入地理解和运用Objective - C 类进行编程开发。在实际应用中,合理运用这些知识可以编写出结构清晰、可维护性强的Objective - C 程序。