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

Objective-C类与对象的创建及生命周期管理

2022-02-024.6k 阅读

Objective-C类的创建

在Objective-C中,类是对象的蓝图,定义了对象的属性和行为。创建一个类需要两个主要部分:接口(interface)和实现(implementation)。

类的接口定义

接口部分声明了类的属性和方法,它定义了类对外提供的公共接口。接口使用@interface关键字开始,以@end结束。以下是一个简单的类接口定义示例:

#import <Foundation/Foundation.h>

@interface Person : NSObject

@property (nonatomic, strong) NSString *name;
@property (nonatomic, assign) NSInteger age;

- (void)sayHello;

@end

在上述代码中:

  1. #import <Foundation/Foundation.h>导入了Foundation框架,这是Objective-C开发中常用的基础框架,提供了许多基本的数据类型、集合类等。
  2. @interface Person : NSObject声明了一个名为Person的类,它继承自NSObject。在Objective-C中,所有的类最终都继承自NSObjectNSObject提供了一些基本的方法,如内存管理、对象比较等。
  3. @property用于声明属性。(nonatomic, strong)是属性的修饰符,nonatomic表示该属性不是线程安全的,strong表示这是一个强引用,用于对象类型的属性。NSString *name声明了一个名为name的字符串属性,NSInteger age声明了一个名为age的整数属性。
  4. - (void)sayHello;声明了一个实例方法sayHello,该方法返回值为void,即不返回任何值。方法声明以-开头表示这是一个实例方法,如果以+开头则表示类方法。

类的实现

类的实现部分提供了接口中声明的方法的具体实现。使用@implementation关键字开始,以@end结束。以下是Person类的实现:

#import "Person.h"

@implementation Person

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

@end

在上述实现中:

  1. #import "Person.h"导入了Person类的接口文件,这样编译器才能知道类的属性和方法声明。
  2. - (void)sayHello方法的实现中,使用NSLog打印出包含nameage属性的问候语。self.nameself.age用于访问当前对象的属性。

Objective-C对象的创建

创建对象是将类实例化的过程,使得我们可以使用类中定义的属性和方法。

使用allocinit方法创建对象

在Objective-C中,通常使用alloc方法为对象分配内存,然后使用init方法对对象进行初始化。以下是创建Person对象的示例:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person *person = [[Person alloc] init];
        person.name = @"John";
        person.age = 30;
        [person sayHello];
    }
    return 0;
}

在上述代码中:

  1. Person *person = [[Person alloc] init];首先使用allocPerson对象分配内存,然后使用init方法对其进行初始化。alloc返回一个未初始化的对象,init方法负责对对象进行初始化设置,比如设置默认的属性值等。
  2. person.name = @"John";person.age = 30;分别设置了person对象的nameage属性。
  3. [person sayHello];调用person对象的sayHello方法,输出问候语。

使用便利构造器创建对象

除了使用allocinit,还可以定义便利构造器来更方便地创建对象。便利构造器是类方法,通常以+开头,并且在方法内部会调用allocinit。以下是在Person类中添加便利构造器的示例:

Person.h中添加便利构造器声明:

+ (instancetype)personWithName:(NSString *)name age:(NSInteger)age;

Person.m中实现便利构造器:

+ (instancetype)personWithName:(NSString *)name age:(NSInteger)age {
    Person *person = [[self alloc] init];
    person.name = name;
    person.age = age;
    return person;
}

main函数中使用便利构造器创建对象:

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

在上述代码中:

  1. + (instancetype)personWithName:(NSString *)name age:(NSInteger)age声明了一个便利构造器,instancetype是一个类型说明符,它表示与方法所在类相同的类型。
  2. 在实现中,首先使用[[self alloc] init]创建并初始化一个Person对象,然后设置其nameage属性,最后返回该对象。
  3. main函数中,通过[Person personWithName:@"Jane" age:25]直接创建一个具有初始属性值的Person对象,并调用sayHello方法。

Objective-C对象的生命周期管理

对象的生命周期管理在Objective-C中至关重要,它涉及到对象何时创建、使用以及销毁,正确的生命周期管理可以避免内存泄漏等问题。

引用计数机制

Objective-C使用引用计数(Reference Counting)来管理对象的生命周期。每个对象都有一个引用计数,当对象被创建时,引用计数为1。每当有一个新的强引用指向该对象时,引用计数加1;当一个强引用不再指向该对象时,引用计数减1。当引用计数变为0时,对象的内存会被自动释放。

例如,以下代码展示了引用计数的变化:

Person *person1 = [[Person alloc] init]; // person1的引用计数为1
Person *person2 = person1; // person1的引用计数加1,变为2
person1 = nil; // person1的引用计数减1,变为1,此时person2仍指向该对象
person2 = nil; // person2的引用计数减1,变为0,对象的内存被释放

在ARC(自动引用计数)模式下,编译器会自动插入引用计数的相关代码,开发者无需手动管理引用计数。但在MRC(手动引用计数)模式下,开发者需要手动调用retain(增加引用计数)、release(减少引用计数)和autorelease(延迟释放)等方法。

ARC(自动引用计数)

ARC是Xcode 4.2引入的一项功能,它大大简化了内存管理。在ARC模式下,编译器会自动在适当的位置插入引用计数的相关代码,如retainreleaseautorelease

例如,在ARC模式下,以下代码无需手动管理内存:

{
    Person *person = [[Person alloc] init];
    // 使用person对象
} // 当代码块结束时,person对象超出作用域,编译器自动释放其内存

ARC还提供了一些规则来处理对象之间的引用关系,以避免循环引用。例如,对于相互引用的两个对象,通常一个对象使用strong引用,另一个对象使用weak引用。weak引用不会增加对象的引用计数,当被引用的对象释放时,weak引用会自动被设置为nil,从而避免了野指针的产生。

以下是一个可能导致循环引用的示例以及如何使用weak解决:

@interface Dog : NSObject

@property (nonatomic, strong) Person *owner;

@end

@implementation Dog

@end

@interface Person : NSObject

@property (nonatomic, strong) Dog *pet;

@end

@implementation Person

@end

// 可能导致循环引用
void testCycle() {
    Person *person = [[Person alloc] init];
    Dog *dog = [[Dog alloc] init];
    person.pet = dog;
    dog.owner = person;
}

// 使用weak解决循环引用
@interface Dog : NSObject

@property (nonatomic, weak) Person *owner;

@end

@implementation Dog

@end

@interface Person : NSObject

@property (nonatomic, strong) Dog *pet;

@end

@implementation Person

@end

void testNoCycle() {
    Person *person = [[Person alloc] init];
    Dog *dog = [[Dog alloc] init];
    person.pet = dog;
    dog.owner = person;
}

在上述代码中,testCycle方法中PersonDog对象相互使用strong引用,会导致循环引用,对象无法正常释放。而在testNoCycle方法中,Dogowner属性使用weak引用,避免了循环引用,对象可以在适当的时候正常释放。

MRC(手动引用计数)

在MRC模式下,开发者需要手动管理对象的引用计数。以下是一些常用的方法:

  • retain:增加对象的引用计数。
  • release:减少对象的引用计数。
  • autorelease:将对象放入自动释放池,在自动释放池被销毁时,对象的引用计数会减少。

例如,以下是在MRC模式下创建和管理Person对象的示例:

Person *person = [[Person alloc] init]; // person的引用计数为1
[person retain]; // person的引用计数加1,变为2
[person release]; // person的引用计数减1,变为1
[person autorelease]; // 将person放入自动释放池,在自动释放池销毁时引用计数减1

在使用MRC时,需要非常小心地平衡retainrelease的调用,确保对象在不再需要时能够正确释放内存,否则容易导致内存泄漏或野指针问题。

类的继承与多态

继承是面向对象编程的重要特性之一,它允许一个类继承另一个类的属性和方法。多态则是指不同类的对象对同一消息做出不同的响应。

类的继承

在Objective-C中,一个类可以继承自另一个类,继承使用:符号。例如,我们创建一个Student类继承自Person类:

@interface Student : Person

@property (nonatomic, strong) NSString *school;

- (void)study;

@end

@implementation Student

- (void)study {
    NSLog(@"%@ is studying at %@.", self.name, self.school);
}

@end

在上述代码中:

  1. @interface Student : Person声明Student类继承自Person类,Student类将拥有Person类的所有属性和方法。
  2. @property (nonatomic, strong) NSString *school;声明了Student类特有的属性school
  3. - (void)study;声明并实现了Student类特有的方法study

多态

多态通过方法重写和动态绑定来实现。当子类继承自父类并实现了与父类相同方法签名的方法时,称为方法重写。在运行时,根据对象的实际类型来决定调用哪个类的方法,这就是动态绑定。

以下是多态的示例:

Person *person1 = [[Person alloc] init];
person1.name = @"Tom";
Student *student1 = [[Student alloc] init];
student1.name = @"Alice";
student1.school = @"ABC School";

NSArray *people = @[person1, student1];

for (Person *person in people) {
    if ([person isKindOfClass:[Student class]]) {
        Student *student = (Student *)person;
        [student study];
    } else {
        [person sayHello];
    }
}

在上述代码中:

  1. 创建了一个Person对象person1和一个Student对象student1
  2. 将这两个对象放入一个数组people中。
  3. 通过遍历数组,使用isKindOfClass方法判断对象的实际类型。如果是Student类型,则将其转换为Student对象并调用study方法;如果是Person类型,则调用sayHello方法。这体现了多态,不同类型的对象对不同的消息做出了相应的响应。

类的初始化方法与自定义初始化

类的初始化方法在对象创建时起到关键作用,它确保对象在使用前处于一个正确的初始状态。除了默认的init方法,我们还可以定义自定义的初始化方法。

初始化方法的重要性

初始化方法负责设置对象的初始属性值,为对象的正常使用做好准备。例如,在Person类中,默认的init方法可能如下:

- (instancetype)init {
    self = [super init];
    if (self) {
        _name = @"Unknown";
        _age = 0;
    }
    return self;
}

在上述代码中:

  1. self = [super init];调用父类的init方法进行初始化。在Objective-C中,子类的初始化方法通常需要先调用父类的初始化方法,以确保父类的部分被正确初始化。
  2. if (self)检查初始化是否成功。如果[super init]返回nil,说明初始化失败,此时应返回nil,不再进行后续的初始化操作。
  3. _name = @"Unknown";_age = 0;设置了nameage属性的默认值。

自定义初始化方法

除了默认的init方法,我们可以定义自定义的初始化方法来满足特定的初始化需求。例如,在Person类中定义一个接受姓名和年龄作为参数的初始化方法: 在Person.h中声明:

- (instancetype)initWithName:(NSString *)name age:(NSInteger)age;

Person.m中实现:

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

main函数中使用自定义初始化方法:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person *person = [[Person alloc] initWithName:@"Bob" age:28];
        [person sayHello];
    }
    return 0;
}

在上述代码中:

  1. - (instancetype)initWithName:(NSString *)name age:(NSInteger)age声明并实现了一个自定义初始化方法,该方法接受姓名和年龄作为参数。
  2. 在实现中,同样先调用[super init]进行父类初始化,然后设置nameage属性。
  3. main函数中,通过[[Person alloc] initWithName:@"Bob" age:28]使用自定义初始化方法创建Person对象,并调用sayHello方法。

类的分类与扩展

类的分类(Category)和扩展(Extension)是Objective-C中两个重要的特性,它们提供了在不修改类的源代码的情况下为类添加方法和属性的能力。

类的分类

类的分类允许在运行时为已有的类添加新的方法。分类的定义使用@interface关键字,后跟类名和分类名,用()括起来。例如,为NSString类添加一个计算单词数量的分类:

@interface NSString (WordCount)

- (NSUInteger)wordCount;

@end

@implementation NSString (WordCount)

- (NSUInteger)wordCount {
    NSArray *words = [self componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
    return words.count;
}

@end

在上述代码中:

  1. @interface NSString (WordCount)声明了一个名为WordCount的分类,用于NSString类。
  2. - (NSUInteger)wordCount;声明了一个新的方法wordCount,用于计算字符串中的单词数量。
  3. 在实现部分,[self componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]将字符串按空白字符和换行符分割成数组,然后返回数组的数量,即单词数量。

在使用时,可以直接对NSString对象调用wordCount方法:

NSString *string = @"Hello world, this is a test.";
NSUInteger count = [string wordCount];
NSLog(@"The word count is %lu.", (unsigned long)count);

类的扩展

类的扩展是一种特殊的分类,它可以为类添加属性和方法,但这些属性和方法只能在定义扩展的文件中访问。扩展通常定义在类的实现文件(.m文件)中。

例如,为Person类添加一个扩展,添加一个私有属性和方法:

@interface Person ()

@property (nonatomic, strong) NSString *privateInfo;
- (void)privateMethod;

@end

@implementation Person

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

@end

在上述代码中:

  1. @interface Person ()声明了一个匿名扩展,用于Person类。
  2. @property (nonatomic, strong) NSString *privateInfo;声明了一个私有属性privateInfo
  3. - (void)privateMethod;声明并在实现部分实现了一个私有方法privateMethod

这些私有属性和方法只能在Person.m文件中访问,外部无法直接调用,从而实现了一定程度的封装。

类与对象在内存中的布局

了解类与对象在内存中的布局对于深入理解Objective-C的运行机制非常重要。

对象的内存布局

在Objective-C中,对象在内存中由一个结构体表示,这个结构体至少包含一个指向类的指针(isa指针)。对于包含属性的对象,结构体还会包含属性的值。

例如,对于Person类的对象,其内存布局可能如下:

struct Person_IMPL {
    Class isa;
    NSString *name;
    NSInteger age;
};

其中,isa指针指向Person类的元类,通过isa指针,对象可以找到其所属的类以及类中定义的方法。nameage是对象的属性。

类的内存布局

类在内存中也有特定的布局,它包含了类的元数据,如类名、超类、属性列表、方法列表等。类的元数据存储在一个称为类对象的数据结构中。

类对象的结构大致如下:

struct objc_class {
    Class isa;
    Class superclass;
    cache_t cache;
    class_data_bits_t bits;
};

其中:

  1. isa指针指向类对象的元类,元类用于存储类方法。
  2. superclass指针指向父类,如果该类没有父类,则为nil
  3. cache用于缓存最近调用的方法,以提高方法调用的效率。
  4. bits包含了类的属性列表、方法列表等重要信息。

通过了解类与对象在内存中的布局,我们可以更好地理解对象的创建、方法调用以及内存管理等底层机制。

类与对象的高级特性

除了上述基本特性,Objective-C的类与对象还有一些高级特性,如协议(Protocol)、关联对象(Associated Objects)等。

协议

协议定义了一组方法的声明,但不提供方法的实现。一个类可以声明遵循某个协议,从而表明它提供了协议中定义的方法。

例如,定义一个Runnable协议:

@protocol Runnable <NSObject>

- (void)run;

@end

然后让Person类遵循Runnable协议:

@interface Person : NSObject <Runnable>

@end

@implementation Person

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

@end

在上述代码中:

  1. @protocol Runnable <NSObject>声明了一个Runnable协议,它继承自NSObject协议。NSObject协议定义了一些基本的方法,如descriptionhash等。
  2. @interface Person : NSObject <Runnable>声明Person类遵循Runnable协议。
  3. Person类实现了Runnable协议中定义的run方法。

协议的作用在于实现多态和代码的解耦,不同的类可以遵循同一个协议,从而对相同的消息做出不同的响应。

关联对象

关联对象允许在运行时为对象动态添加属性。这在一些情况下非常有用,比如为系统类添加额外的属性而无需继承。

使用关联对象需要导入objc/runtime.h头文件,并使用objc_setAssociatedObjectobjc_getAssociatedObject函数。

例如,为NSString对象添加一个自定义属性:

#import <objc/runtime.h>

@interface NSString (ExtraProperty)

@property (nonatomic, strong) NSString *extraInfo;

@end

@implementation NSString (ExtraProperty)

- (void)setExtraInfo:(NSString *)extraInfo {
    objc_setAssociatedObject(self, @selector(extraInfo), extraInfo, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (NSString *)extraInfo {
    return objc_getAssociatedObject(self, @selector(extraInfo));
}

@end

在上述代码中:

  1. @interface NSString (ExtraProperty)声明了一个分类,为NSString类添加一个extraInfo属性。
  2. 在实现部分,setExtraInfo:方法使用objc_setAssociatedObject函数将extraInfoNSString对象关联起来,OBJC_ASSOCIATION_RETAIN_NONATOMIC表示使用非原子性的强引用。
  3. extraInfo方法使用objc_getAssociatedObject函数获取与NSString对象关联的extraInfo

使用时:

NSString *string = @"Hello";
string.extraInfo = @"This is some extra information.";
NSLog(@"Extra info: %@", string.extraInfo);

通过这些高级特性,开发者可以更加灵活地使用Objective-C的类与对象,实现复杂的功能和设计模式。