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

Objective-C 对象与类深入探究

2024-12-055.5k 阅读

Objective-C 对象与类的基础概念

类的定义与声明

在Objective-C中,类是对象的模板,定义了对象所具有的属性(成员变量)和行为(方法)。类的声明由接口(interface)和实现(implementation)两部分组成。 接口部分声明了类的名称、继承关系、属性和方法的原型。例如,定义一个简单的 Person 类:

#import <Foundation/Foundation.h>

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

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

- (instancetype)initWithName:(NSString *)aName age:(NSInteger)anAge;
- (void)introduce;

@end

这里,@interface Person : NSObject 表明 Person 类继承自 NSObject 类,是所有Objective-C类的根类。大括号内声明了两个成员变量 nameage@property 关键字声明了两个属性,提供了更方便的访问方式。- (instancetype)initWithName:(NSString *)aName age:(NSInteger)anAge; 是一个初始化方法,- (void)introduce; 是一个实例方法。

类的实现

类的实现部分则具体实现了接口中声明的方法。对于上面的 Person 类,实现如下:

@implementation Person

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

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

@end

在初始化方法中,首先调用 [super init] 来初始化父类部分,然后设置对象自身的属性。introduce 方法则简单地打印出对象的信息。

对象的创建与初始化

使用 allocinit 创建对象

在Objective-C中,创建对象通常分两步:首先使用 alloc 分配内存,然后使用 init 方法初始化对象。例如:

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

alloc 方法为对象分配内存空间,并返回一个指向该内存的指针,但此时对象的属性还未初始化。initWithName:age: 方法对对象进行初始化,设置 nameage 属性的值。

便捷构造方法

除了使用 allocinit ,还可以定义便捷构造方法。例如,在 Person 类中添加一个类方法:

+ (instancetype)personWithName:(NSString *)aName age:(NSInteger)anAge
{
    return [[self alloc] initWithName:aName age:anAge];
}

使用时可以这样调用:

Person *person2 = [Person personWithName:@"Jane" age:25];

这种方式更简洁,并且可以在类方法中进行一些额外的操作,如对象缓存等。

对象的内存管理

引用计数机制

Objective-C 使用引用计数(Reference Counting)来管理对象的内存。每个对象都有一个引用计数,当对象被创建时,引用计数为1。每次对象被保留(retain),引用计数加1;每次对象被释放(release),引用计数减1。当引用计数为0时,对象的内存被自动释放。

例如,假设有如下代码:

Person *person = [[Person alloc] initWithName:@"Bob" age:35];
Person *anotherPerson = person;
[person release];

这里,person 创建时引用计数为1,anotherPerson = person; 使 person 的引用计数加1 。[person release]; 使 person 的引用计数减1 ,但由于 anotherPerson 还引用着该对象,所以对象不会被释放。

ARC(自动引用计数)

从iOS 5.0开始,引入了ARC(Automatic Reference Counting)。ARC自动管理对象的内存,程序员无需手动调用 retainreleaseautorelease 方法。例如,在ARC环境下:

Person *person = [[Person alloc] initWithName:@"Alice" age:28];
// 无需手动释放,ARC会在合适的时机释放对象

ARC大大简化了内存管理,减少了因手动管理不当导致的内存泄漏和悬空指针问题。但在某些复杂场景下,如与Core Foundation框架混合使用时,仍需要注意内存管理的细节。

类的继承与多态

继承

继承是面向对象编程的重要特性之一。在Objective-C中,一个类可以继承另一个类的属性和方法。例如,定义一个 Student 类继承自 Person 类:

@interface Student : Person
{
    NSString *school;
}

@property (nonatomic, copy) NSString *school;

- (instancetype)initWithName:(NSString *)aName age:(NSInteger)anAge school:(NSString *)aSchool;
- (void)study;

@end

@implementation Student

- (instancetype)initWithName:(NSString *)aName age:(NSInteger)anAge school:(NSString *)aSchool
{
    self = [super initWithName:aName age:anAge];
    if (self) {
        self.school = aSchool;
    }
    return self;
}

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

@end

Student 类继承了 Person 类的 nameage 属性以及相关的访问方法,同时添加了自己的 school 属性和 study 方法。

多态

多态是指不同类的对象对同一消息作出不同的响应。例如,假设有一个函数:

void introducePerson(Person *person)
{
    [person introduce];
}

可以这样调用:

Person *person = [[Person alloc] initWithName:@"Tom" age:40];
Student *student = [[Student alloc] initWithName:@"Jerry" age:20 school:@"ABC School"];

introducePerson(person);
introducePerson(student);

虽然 personstudent 是不同类的对象,但它们都响应 introduce 消息,分别打印出不同的信息,这就是多态的体现。

类的分类与扩展

分类(Category)

分类可以在不修改原有类代码的情况下,为类添加新的方法。例如,为 NSString 类添加一个方法来判断字符串是否为数字:

@interface NSString (NumberCheck)
- (BOOL)isNumber;
@end

@implementation NSString (NumberCheck)
- (BOOL)isNumber
{
    NSScanner *scanner = [NSScanner scannerWithString:self];
    NSNumber *number;
    return [scanner scanDouble:&number] && [scanner isAtEnd];
}
@end

使用时:

NSString *str1 = @"123";
NSString *str2 = @"abc";
BOOL isNumber1 = [str1 isNumber];
BOOL isNumber2 = [str2 isNumber];

分类只能添加方法,不能添加成员变量。如果需要添加成员变量,可以使用关联对象(Associated Objects)技术。

扩展(Extension)

扩展是分类的一种特殊形式,通常用于在类的实现文件中为类添加私有的方法和属性。例如,在 Person 类的实现文件中添加一个扩展:

@interface Person ()
@property (nonatomic, copy) NSString *privateInfo;
- (void)privateMethod;
@end

@implementation Person

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

@end

扩展中声明的方法和属性默认是私有的,只能在类的实现文件中访问。这有助于隐藏类的内部实现细节,提高代码的封装性。

对象的消息传递机制

动态绑定与消息发送

在Objective-C中,方法调用实际上是向对象发送消息。当向一个对象发送消息时,运行时系统会在该对象所属类的方法列表中查找对应的方法实现。例如:

Person *person = [[Person alloc] initWithName:@"Mike" age:32];
[person introduce];

这里 [person introduce]; 就是向 person 对象发送 introduce 消息。运行时系统首先在 Person 类的方法列表中查找 introduce 方法的实现,如果找不到,会沿着继承链向上查找,直到找到对应的方法实现或到达根类 NSObject

消息转发

当运行时系统在对象所属类及其继承链中都找不到对应的方法实现时,会启动消息转发机制。消息转发分为三个步骤:

  1. 动态方法解析:运行时系统会调用 + (BOOL)resolveInstanceMethod:(SEL)sel (实例方法)或 + (BOOL)resolveClassMethod:(SEL)sel (类方法),如果在这个方法中动态添加了方法实现,消息转发就完成了。
  2. 备用接收者:如果动态方法解析没有处理消息,运行时系统会调用 - (id)forwardingTargetForSelector:(SEL)aSelector ,如果返回一个非 nil 的对象,消息会被转发给这个对象处理。
  3. 完整转发:如果备用接收者也没有处理消息,运行时系统会创建一个 NSInvocation 对象,封装消息的参数和返回值,然后调用 - (void)forwardInvocation:(NSInvocation *)anInvocation 方法,在这个方法中可以手动处理消息或进一步转发给其他对象。

例如,在 Person 类中添加消息转发相关代码:

@implementation Person

+ (BOOL)resolveInstanceMethod:(SEL)sel
{
    if (sel == @selector(unknownMethod)) {
        class_addMethod(self, sel, (IMP)unknownMethodIMP, "v@:");
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}

id unknownMethodIMP(id self, SEL _cmd)
{
    NSLog(@"This is a dynamically added method.");
    return nil;
}

- (id)forwardingTargetForSelector:(SEL)aSelector
{
    if (aSelector == @selector(forwardedMethod)) {
        // 返回另一个对象来处理消息
        return [[AnotherClass alloc] init];
    }
    return nil;
}

- (void)forwardInvocation:(NSInvocation *)anInvocation
{
    SEL sel = anInvocation.selector;
    if ([AnotherObject respondsToSelector:sel]) {
        AnotherObject *obj = [[AnotherObject alloc] init];
        [anInvocation invokeWithTarget:obj];
    } else {
        [super forwardInvocation:anInvocation];
    }
}

@end

通过消息转发机制,Objective-C实现了更加灵活的方法调用和动态行为。

类的属性与存取方法

属性的修饰符

在Objective-C中,@property 声明的属性有多种修饰符,用于控制属性的访问权限、内存管理等。常见的修饰符有:

  • 读写权限readwrite(默认)表示属性可读可写,readonly 表示属性只读。
  • 原子性atomic(默认)表示属性的存取方法是线程安全的,nonatomic 表示非线程安全,性能更高。
  • 内存管理assign 用于简单数据类型,如基本数据类型,不涉及内存管理;retain(ARC环境下用 strong 替代)用于对象类型,会增加对象的引用计数;copy 用于对象类型,会创建对象的副本。

例如:

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

存取方法的自定义

默认情况下,编译器会为属性自动生成存取方法。但有时需要自定义存取方法。例如,在 Person 类中自定义 name 属性的设置方法:

@implementation Person

- (void)setName:(NSString *)aName
{
    if (![aName isEqualToString:_name]) {
        _name = [aName copy];
    }
}

@end

这里,自定义了 setName: 方法,在设置 name 属性时,先检查新值与旧值是否相同,不同则进行复制操作,这样可以避免不必要的内存开销。

类的协议与代理模式

协议(Protocol)

协议定义了一组方法的声明,但不提供方法的实现。任何类都可以声明遵守某个协议,并实现协议中的方法。例如,定义一个 Flyable 协议:

@protocol Flyable <NSObject>
- (void)fly;
@end

然后定义一个 Bird 类遵守该协议:

@interface Bird : NSObject <Flyable>
@end

@implementation Bird
- (void)fly
{
    NSLog(@"The bird is flying.");
}
@end

协议常用于实现多继承的效果,一个类可以遵守多个协议,从而拥有多种行为。

代理模式

代理模式是一种设计模式,通过协议实现。例如,定义一个 ViewController 类,它有一个代理属性,用于处理某些事件:

@protocol ViewControllerDelegate <NSObject>
- (void)viewControllerDidFinish:(ViewController *)viewController;
@end

@interface ViewController : NSObject
@property (nonatomic, weak) id<ViewControllerDelegate> delegate;
- (void)doSomething
{
    // 执行某些操作
    if ([self.delegate respondsToSelector:@selector(viewControllerDidFinish:)]) {
        [self.delegate viewControllerDidFinish:self];
    }
}
@end

然后在另一个类中设置代理并实现协议方法:

@interface AppDelegate : NSObject <ViewControllerDelegate>
@end

@implementation AppDelegate
- (void)viewControllerDidFinish:(ViewController *)viewController
{
    NSLog(@"ViewController has finished.");
}
@end

AppDelegate 中设置 ViewController 的代理:

ViewController *viewController = [[ViewController alloc] init];
viewController.delegate = self;
[viewController doSomething];

通过代理模式,可以将某些功能委托给其他对象处理,提高代码的可维护性和可扩展性。

类的内存布局与对象的本质

类的内存布局

在Objective-C中,类在内存中主要由以下几部分组成:

  1. 类对象:每个类都有一个对应的类对象,存储类的元信息,如类名、父类指针、实例变量列表、方法列表等。
  2. 元类对象:用于存储类方法的相关信息。元类对象的isa指针指向根元类,根元类的isa指针指向自身。
  3. 实例对象:类的实例对象包含了实例变量的值,其isa指针指向对应的类对象。

例如,对于 Person 类,其类对象存储了 Person 类的定义信息,每个 Person 实例对象通过isa指针指向类对象,从而获取类的属性和方法。

对象的本质

从底层实现来看,Objective-C对象本质上是一个结构体,结构体的第一个成员是 isa 指针,用于指向对象所属的类。例如,在64位系统下,一个简单的对象结构体可能如下:

struct objc_object {
    Class isa;
    // 其他成员变量
};

isa 指针是对象与类之间的桥梁,通过它,运行时系统可以找到对象所属类的方法列表等信息,实现消息传递和动态绑定。理解对象的本质有助于深入掌握Objective-C的运行机制和内存管理。

通过以上对Objective-C对象与类的深入探究,我们全面了解了其基础概念、内存管理、继承多态、消息传递等重要方面,为编写高效、健壮的Objective-C代码奠定了坚实的基础。无论是开发iOS应用还是其他基于Objective-C的项目,这些知识都具有重要的指导意义。在实际编程中,我们应根据具体需求合理运用这些特性,充分发挥Objective-C语言的优势。