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

Objective-C类与对象的创建与管理

2023-11-023.8k 阅读

类的定义

在Objective-C中,类是一种用户自定义的数据类型,它封装了数据(实例变量)和操作这些数据的方法。类的定义分为两部分:接口(interface)和实现(implementation)。

接口部分

接口部分声明了类的名称、继承关系(如果有)、实例变量以及方法的签名。语法如下:

#import <Foundation/Foundation.h>

@interface ClassName : SuperClassName {
    // 实例变量声明
    dataType instanceVariable1;
    dataType instanceVariable2;
}

// 方法声明
- (returnType)methodName:(parameterType)parameter;
+ (returnType)classMethodName:(parameterType)parameter;

@end

在上述代码中,@interface 关键字开始接口定义,ClassName 是类名,SuperClassName 是父类名(如果该类继承自其他类)。大括号内是实例变量的声明,实例变量是属于类的每个实例(对象)的变量。以 - 开头的方法声明是实例方法,意味着它们需要通过对象来调用;以 + 开头的方法声明是类方法,它们可以直接通过类名调用。

例如,我们定义一个简单的 Person 类:

#import <Foundation/Foundation.h>

@interface Person : NSObject {
    NSString *name;
    NSInteger age;
}

- (void)setName:(NSString *)newName;
- (NSString *)name;
- (void)setAge:(NSInteger)newAge;
- (NSInteger)age;

@end

在这个 Person 类中,我们声明了两个实例变量 nameage,分别用于存储人的名字和年龄。同时声明了四个实例方法,用于设置和获取 nameage 的值。

实现部分

实现部分提供了接口中声明的方法的具体实现。语法如下:

@implementation ClassName

// 方法实现
- (returnType)methodName:(parameterType)parameter {
    // 方法体
    return returnValue;
}

+ (returnType)classMethodName:(parameterType)parameter {
    // 方法体
    return returnValue;
}

@end

对于上面定义的 Person 类,其实现如下:

@implementation Person

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

- (NSString *)name {
    return name;
}

- (void)setAge:(NSInteger)newAge {
    age = newAge;
}

- (NSInteger)age {
    return age;
}

@end

在这些方法实现中,setName: 方法将传入的新名字赋值给 name 实例变量,name 方法返回 name 实例变量的值。同样,setAge:age 方法分别用于设置和获取 age 实例变量的值。

对象的创建与初始化

对象的创建

在Objective-C中,使用 alloc 方法为对象分配内存。allocNSObject 类的类方法,所有的类都继承自 NSObject,因此都可以调用 alloc 方法。例如,创建一个 Person 对象:

Person *person = [Person alloc];

上述代码为 Person 对象分配了内存空间,但此时对象的实例变量并没有被初始化。

对象的初始化

为了正确初始化对象的实例变量,我们需要调用初始化方法。通常,类会提供一个或多个初始化方法。在 Person 类中,我们可以添加一个自定义的初始化方法:

- (instancetype)initWithName:(NSString *)newName age:(NSInteger)newAge {
    self = [super init];
    if (self) {
        name = newName;
        age = newAge;
    }
    return self;
}

在这个初始化方法中,首先调用 [super init] 来调用父类的初始化方法。这一步非常重要,因为父类可能有自己的实例变量需要初始化。如果父类初始化成功,self 会指向一个有效的对象实例,然后我们再初始化自己的实例变量 nameage。最后返回 self

使用这个初始化方法创建并初始化 Person 对象如下:

Person *person = [[Person alloc] initWithName:@"John" age:30];

这种方式将对象的创建和初始化合并为一步,更加简洁和安全。

另外,NSObject 类提供了一个默认的初始化方法 init,如果类没有特别的初始化需求,可以直接使用这个方法。例如:

Person *person = [[Person alloc] init];

但在这种情况下,person 对象的 nameage 实例变量将保持未初始化的状态,可能导致运行时错误,所以自定义初始化方法通常是更好的选择。

类与对象的内存管理

引用计数原理

Objective-C 使用引用计数(Reference Counting)来管理对象的内存。每个对象都有一个引用计数,当对象被创建时,引用计数为1。每当有一个新的变量引用该对象时,引用计数加1;当一个引用对象的变量不再使用该对象(例如变量超出作用域或者被赋值为 nil)时,引用计数减1。当对象的引用计数变为0时,系统会自动释放该对象所占用的内存。

例如:

Person *person1 = [[Person alloc] initWithName:@"Alice" age:25];
Person *person2 = person1; // person1 的引用计数加1
person1 = nil; // person1 不再引用对象,对象的引用计数减1,此时引用计数为1
person2 = nil; // person2 不再引用对象,对象的引用计数减1,变为0,对象内存被释放

自动释放池(Autorelease Pool)

自动释放池是Objective-C内存管理中的一个重要概念。当一个对象发送 autorelease 消息时,它不会立即被释放,而是被添加到最近的自动释放池中。当自动释放池被销毁时,池中的所有对象都会收到 release 消息,其引用计数减1。

例如,在一个循环中创建大量临时对象时,如果不使用自动释放池,可能会导致内存峰值过高:

for (int i = 0; i < 10000; i++) {
    NSString *tempString = [[NSString alloc] initWithFormat:@"%d", i];
    // 使用 tempString
    [tempString release]; // 手动释放对象
}

如果不手动调用 release,可以使用自动释放池来管理这些对象:

@autoreleasepool {
    for (int i = 0; i < 10000; i++) {
        NSString *tempString = [[NSString alloc] initWithFormat:@"%d", i];
        // 使用 tempString
        [tempString autorelease]; // 将对象添加到自动释放池
    }
}
// 自动释放池销毁,池中的对象收到 release 消息

在ARC(自动引用计数,Automatic Reference Counting)之前,手动管理对象的 retainreleaseautorelease 操作是非常繁琐且容易出错的。ARC的出现大大简化了内存管理。

ARC(自动引用计数)

ARC是Xcode 4.2引入的一项功能,它自动管理对象的引用计数。在ARC模式下,编译器会自动在适当的地方插入 retainreleaseautorelease 代码。例如,在ARC模式下,以下代码无需手动管理内存:

Person *person = [[Person alloc] initWithName:@"Bob" age:28];
// 使用 person
// 无需手动调用 [person release]

编译器会在 person 不再被使用时自动插入释放对象的代码。ARC不仅减少了内存管理的工作量,还大大降低了内存泄漏和悬空指针的风险。

不过,在一些特殊情况下,仍然需要手动处理内存管理。例如,在使用Core Foundation框架(它不使用ARC)的函数时,可能需要进行桥接操作,在ARC管理的对象和Core Foundation对象之间转换。

类的继承与多态

继承

继承是面向对象编程的重要特性之一。在Objective-C中,一个类可以继承自另一个类,从而获得父类的属性(实例变量)和方法。子类可以重写(override)父类的方法以提供不同的实现。

例如,我们定义一个 Student 类继承自 Person 类:

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

@interface Student : Person {
    NSString *school;
}

- (void)setSchool:(NSString *)newSchool;
- (NSString *)school;

@end

Student 类继承自 Person 类,它不仅拥有 Person 类的 nameage 实例变量以及相关的访问方法,还新增了一个 school 实例变量和对应的访问方法。

Student 类的实现如下:

@implementation Student

- (void)setSchool:(NSString *)newSchool {
    school = newSchool;
}

- (NSString *)school {
    return school;
}

@end

在创建 Student 对象时,可以像使用 Person 对象一样使用继承自 Person 类的方法,同时也可以使用 Student 类特有的方法:

Student *student = [[Student alloc] initWithName:@"Tom" age:20];
[student setSchool:@"ABC University"];
NSString *studentSchool = [student school];

多态

多态性允许我们使用父类类型的变量来引用子类对象,并根据对象的实际类型来调用适当的方法。这一特性在处理对象集合或者设计可扩展的系统时非常有用。

假设 Person 类有一个 introduce 方法:

- (void)introduce {
    NSLog(@"My name is %@, and I'm %ld years old.", name, (long)age);
}

Student 类可以重写这个方法:

- (void)introduce {
    NSLog(@"My name is %@, I'm %ld years old, and I study at %@.", name, (long)age, school);
}

现在,我们可以通过父类类型的变量来调用 introduce 方法,实际调用的方法会根据对象的实际类型而不同:

Person *person1 = [[Person alloc] initWithName:@"Alice" age:25];
Person *person2 = [[Student alloc] initWithName:@"Bob" age:22];

[person1 introduce];
// 输出: My name is Alice, and I'm 25 years old.

[person2 introduce];
// 输出: My name is Bob, I'm 22 years old, and I study at (null).

在这个例子中,person1Person 类型的对象,调用的是 Person 类的 introduce 方法;person2 虽然被声明为 Person 类型,但实际是 Student 类型的对象,调用的是 Student 类重写后的 introduce 方法。这就是多态的体现。

类与对象的高级特性

类别(Category)

类别允许我们在不修改类的原始代码的情况下,为现有的类添加新的方法。类别通常用于将类的实现分散到多个文件中,或者为系统类添加自定义方法。

例如,为 NSString 类添加一个计算单词数量的方法:

#import <Foundation/Foundation.h>

@interface NSString (WordCount)
- (NSUInteger)wordCount;
@end

@implementation NSString (WordCount)
- (NSUInteger)wordCount {
    NSArray *words = [self componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
    return words.count;
}
@end

在上述代码中,@interface NSString (WordCount) 定义了一个名为 WordCount 的类别,为 NSString 类添加了一个 wordCount 方法。在实现部分,我们通过将字符串按空白字符分割成数组,然后返回数组的数量来计算单词数量。

使用这个类别方法如下:

NSString *sentence = @"This is a sample sentence.";
NSUInteger count = [sentence wordCount];
NSLog(@"The number of words is %lu", (unsigned long)count);

扩展(Extension)

扩展是类的一种匿名类别,它通常用于在类的实现文件中为类添加额外的私有方法和属性。与类别不同,扩展可以声明实例变量。

例如,在 Person 类的实现文件中添加一个扩展:

#import "Person.h"

@interface Person () {
    NSString *privateInfo;
}

- (void)privateMethod;

@end

@implementation Person

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

@end

在这个扩展中,我们声明了一个私有实例变量 privateInfo 和一个私有方法 privateMethod。这些声明只能在 Person 类的实现文件中访问,外部无法直接调用 privateMethod 或者访问 privateInfo

协议(Protocol)

协议定义了一组方法的声明,但不提供实现。一个类可以声明遵循(adopt)一个或多个协议,从而承诺实现协议中的方法。协议通常用于实现类似多重继承的功能,或者定义对象之间的交互规范。

例如,定义一个 Runnable 协议:

#import <Foundation/Foundation.h>

@protocol Runnable <NSObject>
- (void)run;
@end

然后,让 Person 类遵循 Runnable 协议:

#import <Foundation/Foundation.h>

@interface Person : NSObject <Runnable> {
    NSString *name;
    NSInteger age;
}

- (void)setName:(NSString *)newName;
- (NSString *)name;
- (void)setAge:(NSInteger)newAge;
- (NSInteger)age;

@end

@implementation Person

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

- (NSString *)name {
    return name;
}

- (void)setAge:(NSInteger)newAge {
    age = newAge;
}

- (NSInteger)age {
    return age;
}

- (void)run {
    NSLog(@"%@ is running.", name);
}

@end

现在,Person 类承诺实现 Runnable 协议中的 run 方法。这样,我们可以将 Person 对象当作 Runnable 类型来处理,只要关心其 run 方法的调用,而无需关心具体的类实现细节。

通过上述内容,我们全面深入地探讨了Objective-C中类与对象的创建与管理,包括类的定义、对象的创建与初始化、内存管理、继承与多态以及一些高级特性。这些知识对于编写高效、健壮的Objective-C程序至关重要。在实际开发中,需要根据具体的需求和场景,合理运用这些特性来构建复杂的软件系统。