面向对象编程
# 02.面向对象编程
# 目录介绍
# 2.1 类的声明和实现
# 2.1.1 类规格说明
类是 Objective-C 用来封装数据,以及操作数据的行为的基础结构。对象就是类的运行期间实例,它包含了类声明的实例变量自己的内存拷贝,以及类成员的指针。
Objective-C 的类规格说明包含了两个部分:声明interface与实现implementation。
声明(interface)部分包含了类声明和实例变量的定义,以及类相关的方法。实现(implementation)部分包含了类方法的实际代码。
类的语法由两个部分组成:接口(Interface)和实现(Implementation)。
OC 类与 C++ 类的结构差异:
| Objective-C | C++ | |
|---|---|---|
| 声明文件 | .h(@interface) | .h(class 声明) |
| 实现文件 | .m(@implementation) | .cpp(class 实现) |
| 私有方法 | 放在 .m 的类扩展中 | private: 区 |
| 构造器 | init 系列(普通方法) | 构造函数(特殊语法) |
| 析构 | dealloc | 析构函数 ~ClassName() |
| 根类 | NSObject(强制) | 无强制根类 |
| 多继承 | ❌ 不支持 | ✅ 支持 |
# 2.1.2 类的声明
遵循C语言的规范,类声明一般定义在.h头文件中。类声明以关键字@interface作为开始,@end作为结束。
接口部分(Interface)通常位于头文件(.h)中,用于声明类的属性和方法。它定义了类的公共接口,其他类可以通过引入头文件来访问这些接口。
其中类方法前的+号表示类方法,-号表示实例方法。一个对应的C++类定义如下:
//@interface 是用于声明类的接口的关键字。它用于定义类的属性、方法和其他成员。
//在 OC 中所有的类都继承自 NSObject
@interface MyClass : NSObject
//@property 用于声明属性,它指定了属性的类型、访问修饰符和其他属性特性。
@property (nonatomic, strong) NSString *name;
@property (nonatomic, assign) NSInteger age;
//可以使用构造方法(Constructor)来创建具有参数的对象。构造方法是一种特殊的方法,用于初始化对象并设置其属性。
- (instancetype)initWithName:(NSString *)name age:(NSInteger)age;
//方法声明:- (返回类型)方法名:(参数类型)参数名;
// - 代表是实例方法,+ 代表是类方法或者静态方法
- (void)doSomething;
- (NSInteger)calculateSumWithNumber:(NSInteger)number1 andNumber:(NSInteger)number2;
// @end 表示接口定义结束
@end
OC 方法签名的命名哲学——"自我描述":
// OC 风格:方法名就是文档
[tableView insertRowsAtIndexPaths:paths withRowAnimation:UITableViewRowAnimationFade];
// 等价 C++ 风格:需要查文档才知道每个参数的含义
tableView->insertRows(paths, UITableViewRowAnimationFade);
OC 的方法名通过参数标签(atIndexPaths: / withRowAnimation:)让每个参数的作用一目了然——读代码即读文档。
# 2.1.3 类的实现
遵循C语言的规范,类实现一般定义在对应的.m文件中。类实现包含了公开方法的实现,以及定义私有(private) 变量及方法。 以关键字@implementation作为区块起头,@end结尾。
头文件(类声明)中定义的属性默认为protected,方法为public。而类实现中定义的属性为private。当然也可以使用@public、@private等助记符来覆盖默认行为。
上述类的一个实现如下:
//@interface MyClass () 是一种类扩展(Class Extension)的语法,用于在类的实现文件(.m)中声明私有属性和方法。
@interface MyClass ()
@property (nonatomic, strong) NSString *privateSecret; // 私有属性
@end
@implementation MyClass
- (instancetype)initWithName:(NSString *)name age:(NSInteger)age {
self = [super init];
if (self) {
self.name = name;
self.age = age;
}
return self;
}
- (void)doSomething {
NSLog(@"Doing something...");
}
- (NSInteger)calculateSumWithNumber:(NSInteger)number1 andNumber:(NSInteger)number2 {
return number1 + number2;
}
@end
.h vs .m 的分工设计——OC 最大的特色之一:
.h (公共) .m (私有)
─────── ───────
@property (公开) ←→ @interface MyClass () { 私有属性 }
- (void)publicMethod; - (void)privateMethod; (不对外暴露)
@implementation { 实现 }
接口与实现物理隔离 = 头文件就是 API 契约!
其他类只需 import .h,永远看不到实现细节。
# 2.1.4 类的实例化
实例化即创建对象。Objective-C创建对象需通过alloc以及init两个消息。alloc的作用是分配内存,init则是初始化对象。
init与alloc都是定义在NSObject里的方法,父对象收到这两个信息并做出正确回应后,新对象才创建完毕。如上述类中:
MyClass * my = [[MyClass alloc] init];
alloc 和 init 为什么要分开? 这是 OC 的"两阶段创建"模式——先分配清零内存、再初始化。分开的好处是可以灵活组合不同的 alloc 和 init:
// 同一个 alloc,不同的 init
MyClass *obj1 = [[MyClass alloc] init]; // 默认初始化
MyClass *obj2 = [[MyClass alloc] initWithName:@"Alice" age:25]; // 自定义初始化
若要自己定义初始化的过程,可以重写init方法,来添加额外的工作。(用途类似C++ 的构造函数constructor),如下:
- (id) init {
if ( self=[super init] ) { // 必须调用父类的init
// do something here ...
}
return self;
}
# 2.1.5 类默认构造器
"默认构造器"通常指的是类的初始化方法,即init方法。默认构造器是一个类的默认初始化方法,用于创建类的实例并对其进行初始化。
构造器是一类特殊的方法,返回值通常是 id,我们可以在其中做一些类的初始化工作。默认的构造器是 init:
- (id)init {
self = [super init];
// 判断父类是否初始化成功,如果初始化失败将会返回 nil
if (self) {
_nickName = @"default";
}
return self;
}
为什么 init 返回 id 而不是具体类型? OC 传统上用 id 作为返回类型,允许子类的 init 返回不同对象(如类簇模式中 NSString 的 init 可能返回 NSPlaceholderString)。现代 OC 推荐用 instancetype 让编译器做类型检查。
# 2.1.6 自定义构造器
自定义构造器:指开发者自己定义的初始化方法,用于创建类的实例并对其进行初始化。
@interface Person : NSObject
@property (nonatomic, strong) NSString *name;
@property (nonatomic) NSInteger age;
- (instancetype)initWithName:(NSString *)name age:(NSInteger)age;
@end
@implementation Person
- (instancetype)initWithName:(NSString *)name age:(NSInteger)age {
self = [super init];
if (self) {
self.name = name;
self.age = age;
}
return self;
}
@end
// 使用自定义构造器创建对象
Person *person = [[Person alloc] initWithName:@"Alice" age:30];
自定义构造器的命名约定——以 init 开头 + 参数标签描述:
- (instancetype)initWithName:(NSString *)name; // 只有一个参数
- (instancetype)initWithName:(NSString *)name age:(NSInteger)age; // 两个参数
- (instancetype)initWithFrame:(CGRect)frame; // 带结构体参数
# 2.1.7 类继承和实现
Objective-C 是一种面向对象的语言,支持单继承。子类可以从父类继承属性和方法,同时也可以重写父类的方法或添加新的方法和属性。
#import <Foundation/Foundation.h>
// 父类
@interface Animal : NSObject
@property (nonatomic, strong) NSString *name;
- (void)sound;
@end
@implementation Animal
- (void)sound {
NSLog(@"Animal makes a sound");
}
@end
// 子类
@interface Dog : Animal
@end
@implementation Dog
// 重写父类方法
- (void)sound {
NSLog(@"%@ says Woof!", self.name);
}
// 新增方法
- (void)bark {
NSLog(@"Barking loudly!");
}
@end
int main(int argc, const char * argv[]) {
Dog *dog = [[Dog alloc] init];
dog.name = @"Buddy";
[dog sound]; // 调用重写后的方法
[dog bark]; // 调用子类新增的方法
return 0;
}
相关术语:
- 超类(superclass)/ 父类(parentclass):所继承的类,以上例子中的 Animal 类
- 子类(subclass) / 孩子类(childclass):实施继承的类,以上例子中的 Dog 类
- 重写(override):子类覆盖超类方法,例如 Dog 类重写了 sound 方法
继承的工作机制:
向对象发送消息时,OC 的方法调度程序将在当前对象指向的类中搜索相应的方法,如果当前类中没有,就会去超类中搜索,如果直到最高级别的超类中仍然没有所有到对应的方法,就会出现运行时错误(编译时也会有警告)。
# 2.1.8 alloc + init 两阶段原理
两阶段创建是 OC 对象模型的核心:
阶段一:alloc(内存分配)
├─ 计算实例变量总大小(包括父类的)
├─ 调用 calloc 分配堆内存
├─ 全部清零(isa 指针除外)
└─ 设置 isa 指针指向类对象
阶段二:init(初始化)
├─ 调用父类 init(链式调用直到 NSObject)
├─ 设置实例变量的初值
└─ 返回 self(可能被替换)
alloc 底层做了多少事?
[MyClass alloc]
→ +[NSObject alloc]
→ _class_createInstanceFromZone()
→ calloc(1, instanceSize) // 分配内存 + 全部清零
→ obj->isa = MyClass.class // 设置 isa 指针
→ return obj // 返回未初始化的对象
为什么 alloc 不直接调 init? 因为这给了你灵活选择初始化方式的自由——同一个 alloc 可以和不同的 init 组合。这也是为什么 [[MyClass alloc] init] 是两步而非一步。
# 2.2 方法的声明和实现
# 2.2.1 方法的声明
方法的声明包括方法的返回类型、方法名和参数列表。以下是一个示例,展示了一个方法的声明:
#import <Foundation/Foundation.h>
@interface MyClass : NSObject
// 方法,+ 表示类方法(类似静态方法),- 表示实例方法
- (void)doSomething;
- (NSString *)greetingWithName:(NSString *)name;
@end
doSomething方法没有返回值,而greetingWithName:方法返回一个NSString对象,并接受一个NSString类型的参数。
OC 方法签名的结构:
返回值 参数标签 参数类型 参数名
│ │ │ │
- (instancetype) initWithName: (NSString *)name age:(NSInteger)age;
│ │ │
实例方法 方法名片段1 片段2(连续构成选择器)
选择器 = initWithName:age:
# 2.2.2 方法的实现
方法的实现部分包含了方法的具体代码逻辑。以下是一个示例,展示了方法的实现:
#import "MyClass.h"
@implementation MyClass
- (void)doSomething {
// 实现方法的逻辑
NSLog(@"Doing something...");
}
- (NSString *)greetingWithName:(NSString *)name {
NSString *greeting = [NSString stringWithFormat:@"Hello, %@!", name];
return greeting;
}
@end
# 2.2.3 方法的调用
调用一个方法实际上就是传递消息到对应的对象。这里消息就是方法标识符以及传递给方法的参数信息。
发送给对象的所有消息都会动态分发,这样有利于实现Objective-C类的多态行为。
也就是说,如果子类定义了跟父类的具有相同标识符的方法,那么子类首先收到消息,然后可以有选择的把消息转发(也可以不转发)给他的父类。
消息被中括号( [ 和 ] )包括。括号中接收消息的对象在左边,消息及其参数在右边。
MyClass *my = [[MyClass alloc] init];
调用无参数的方法:
//调用void方法
[my doSomething];
调用带有参数的方法:
NSInteger result = [myObject calculateSumWithNumber:5 andNumber:10];
调用带有返回值的方法:
NSString *greeting = [myObject greetWithName:@"John"];
两种消息发送语法:
// 标准语法:[receiver message:argument]
[person setName:@"Alice"];
// 嵌套调用:返回值作为另一个消息的接收者
NSString *name = [[person getFriend] getName];
// 点语法(仅对 @property 有效——本质仍是消息发送)
person.name = @"Alice"; // 等价于 [person setName:@"Alice"]
# 2.2.4 方法的嵌套调用
OC 允许多层嵌套的消息发送,从内向外逐层求值:
NSString *result = [[[NSString alloc] initWithFormat:@"%d + %d = %d", 1, 2, 3] uppercaseString];
// 等价于分步:
// NSString *temp1 = [NSString alloc];
// NSString *temp2 = [temp1 initWithFormat:@"%d + %d = %d", 1, 2, 3];
// NSString *result = [temp2 uppercaseString];
嵌套调用增强了表达力,但超过 2 层后应考虑拆分为临时变量提升可读性。
# 2.2.5 方法访问权限
Objective-C 并没有严格的方法访问控制机制,但可以通过以下方式间接实现:
将方法声明在 .m 文件中,而不暴露在 .h 文件中。使用类别(Category)隐藏方法。
// Person.h
@interface Person : NSObject
- (void)sayHello;
@end
// Person.m
@interface Person ()
- (void)internalMethod; // 内部方法,不对外暴露
@end
@implementation Person
- (void)sayHello {
NSLog(@"Hello!");
[self internalMethod];
}
- (void)internalMethod {
NSLog(@"This is an internal method.");
}
@end
OC 的方法"权限"完全由头文件控制:声明在 .h → 公开;声明在 .m 的类扩展 → 私有。没有 C++ 的 public:/private:/protected: 关键字——这是一种"约定优于配置"的体现。
# 2.2.6 方法的重写
向 Circle 实例发送 draw 消息时,根据方法调度机制会执行 Circle 重写的 draw 方法,不会执行 Shape 定义的 draw 方法。
@implementation Shape
- (void) setBounds: (ShapeRect) _bounds {
bounds = _bounds;
};
- (void) setFillColor: (ShapeColor) _fillColor {
fillColor = _fillColor;
};
// 可以看作抽象方法
- (void) draw {
}
@end
@implementation Circle
// 重写方法(具体实现在这里)
- (void) draw {
NSLog(@"绘制一个 (%d %d %d %d) %@色的圆",
bounds.x, bounds.y,
bounds.width,bounds.height,
colorName(fillColor));
}
@end
OC 提供了 super 关键字,可以在子类的方法中显示调用超类中的方法,向 super 发送消息等价于对超类发送消息。
self vs super 的本质区别:
[self doSomething]; // → objc_msgSend(self, @selector(doSomething))
// 从当前类开始查找方法
[super doSomething]; // → objc_msgSendSuper(self, @selector(doSomething))
// 从父类开始查找方法——跳过当前类
// self 仍然是同一个对象,但方法搜索起点不同
# 2.2.7 实例方法 vs 类方法
实例方法(-) | 类方法(+) | |
|---|---|---|
| 调用方式 | [object method] | [ClassName method] |
| 可访问实例变量 | ✅ 可以 | ❌ 不能(没有 self 指向实例) |
| 等价 C++ | 成员函数 | 静态成员函数 |
| 典型用途 | 操作对象数据 | 工厂方法、工具函数 |
类方法的两个经典用法:
// 1. 工厂方法(替代 alloc + init)
NSArray *arr = [NSArray arrayWithObjects:@1, @2, @3, nil];
// 2. 单例访问
UIApplication *app = [UIApplication sharedApplication];
# 2.3 属性定义和访问
# 2.3.1 属性的定义
属性(Property)的定义是指在类的接口部分(头文件)中声明属性的语法和规则。属性的定义包括属性的类型、访问修饰符和特性等。
每个属性的定义由@property关键字开始,后面跟着属性的类型和名称。
//@property 用于声明属性,它指定了属性的类型、访问修饰符和其他属性特性。
@property (nonatomic, strong) NSString *name;
@property (nonatomic, assign) NSInteger age;
@property 一行代码 = 编译器自动生成三样东西:
@property NSString *name;
编译器自动生成:
1. 实例变量:NSString *_name;
2. Getter: - (NSString *)name { return _name; }
3. Setter: - (void)setName:(NSString *)name { _name = name; }
这就是 OC 最核心的"语法糖"——一行声明替代 C++ 中的属性声明 + getter + setter 约 10 行代码。
# 2.3.2 属性修饰符
可以为类的属性设置其它修饰符 (attribute),其它这样的修饰符还有很多种。
属性(Property)是用于封装对象的实例变量,并提供对其访问和设置的方式。属性可以简化对实例变量的操作,并提供了一些额外的功能,如内存管理和线程安全等。
属性修饰符全景表:
| 修饰符 | 类别 | 含义 | 生成代码 |
|---|---|---|---|
atomic | 原子性 | 默认:getter/setter 加自旋锁 | 线程安全但慢 |
nonatomic | 原子性 | 不加锁 | 99% 的场景用这个 |
strong | 内存 | 强引用,持有对象 | [newValue retain]; [_old release]; |
weak | 内存 | 弱引用,对象释放后自动 nil | 不增加引用计数 |
copy | 内存 | 复制对象(不可变副本) | _name = [newValue copy]; |
assign | 内存 | 直接赋值(基本类型用) | _age = newValue; |
readonly | 读写 | 只生成 getter | 无 setter |
readwrite | 读写 | 默认:生成 getter + setter | 全生成 |
strong、weak、copy等关键字用于指定属性的内存管理方式。
- strong / retain:强引用属性,增加对象的引用计数,对象不会被释放,适用于拥有关系。
- weak:弱引用属性,不增加对象的引用计数,对象释放后自动设置为 nil,避免循环引用。
- copy:复制属性,复制对象的副本,适用于不希望属性值被修改的情况。
默认的属性是原子的,即在访问时会加锁以避免多线程同时访问同一对象,也可以将属性声明为"nonatomic"(非原子的),避免产生锁。
# 2.3.3 synthesize
@synthesize关键字用于自动合成属性的实例变量和访问方法的实现。它会根据属性的定义自动生成对应的实例变量和访问方法。
// ViewController.h
@interface ViewController : NSObject
@property (nonatomic, strong) UITextView *textView;
@end
然后看一下如何使用属性。在类的实现文件中使用@synthesize关键字为textView属性生成了一个名为_textView的实例变量。这样,我们就可以通过_textView实例变量来访问和设置textView属性的值。
// ViewController.m
#import "ViewController.h"
@implementation ViewController
//@synthesize textView = _textView;
- (void)viewDidLoad {
[super viewDidLoad];
_textView = [[UITextView alloc] init];
_textView.text = @"学习oc语言demo案例";
_textView.font = [UIFont systemFontOfSize:12];
_textView.textColor = [UIColor blackColor];
[self.view addSubview:_textView];
}
@end
从较新的Objective-C版本开始,如果没有显式使用@synthesize,编译器会自动合成属性的实例变量和访问方法。因此,通常情况下,你不需要手动添加@synthesize。
@synthesize 的三个历史阶段:
2011 前: 必须手写 @synthesize name = _name;
2011: Xcode 4.4+ 开始自动合成(大多数情况不用写)
现在: 只需关注特殊情况——同时重写 getter 和 setter 时,需手动加 @synthesize
# 2.3.4 属性的访问
属性可以利用传统的消息表达式、点表达式或"valueForKey:"/"setValue:forKey:"方法对来访问。如下:
//创建有参数的对象
MyClass *person = [[MyClass alloc] initWithName:@"John" age:25];
//修改属性
person.name = @"yangchong";
[person setName:@"yangchong"];
//读取属性
NSString *tmp;
tmp = [person name]; // 消息表达式
tmp = person.name; // 点表达式
//tmp = person->name; // 直接访问成员变量
tmp = [person valueForKey:@"name"]; // property访问
三种访问方式的本质:
person.name → 编译为 objc_msgSend(person, @selector(name))
[person name] → 编译为 objc_msgSend(person, @selector(name))
[person valueForKey:] → 运行时 KVC 查找(会尝试 getName/name/isName/_name)
点语法是消息发送的语法糖——不是 C++ 的成员访问!
# 2.4 属性的深入理解
# 2.4.1 copy 属性的陷阱
copy 修饰符用于保护可变对象被外部修改。当外部传入 NSMutableString 时,copy 会产生不可变副本,防止外部修改影响内部状态。
@interface Person : NSObject
@property (nonatomic, copy) NSString *name; // ✅ 防御可变对象注入
@end
NSMutableString *mutableName = [@"Alice" mutableCopy];
person.name = mutableName;
[mutableName appendString:@" Surprise"]; // person.name 仍为 "Alice"
如果上例中 name 声明为 strong,则 person.name 会变成 "Alice Surprise"——你的私有数据被外部静默修改了。
copy 适用清单:NSString、NSArray、NSDictionary、NSData、NSSet——总之所有有可变子类(NSMutable*)的不可变类型,都用 copy。
# 2.4.2 atomic vs nonatomic 性能之争
atomic 保证 getter/setter 的原子性(线程安全),但代价是加锁开销。在 iOS 开发中,UI 操作都在主线程,绝大多数属性使用 nonatomic 即可。
atomic 并不能保证线程安全——它只保证单次读写是原子的,不保证连续操作(先读后写、先判断后修改)的安全性。真正的线程安全需要配合 @synchronized、NSLock 或串行队列。
atomic 生成的代码 vs nonatomic:
// atomic 的 getter(简化)
- (NSString *)name {
@synchronized(self) { return _name; } // 加锁!
}
// nonatomic 的 getter
- (NSString *)name {
return _name; // 直接返回
}
# 2.4.3 @dynamic 的使用场景
@dynamic 告诉编译器"不要自动合成 getter/setter,我会在运行时动态提供"。主要用于 Core Data 的 NSManagedObject 子类和需要消息转发机制的属性。
@dynamic name; // 不在编译时生成 _name 实例变量和 setter/getter
# 2.5 继承的反模式与替代
# 2.5.1 滥用继承的典型场景
当一个类只是为了"复用父类的几个方法"而继承时,就掉进了继承陷阱。继承意味着"是一个"的关系——Dog is an Animal 成立,但"NetworkManager 继承自 BaseViewController 只为了复用 loading 方法"就不成立。
继承滥用的问题:子类被迫继承父类所有不需要的方法和属性;父类修改会影响所有子类;多层继承让代码难以追踪。
# 2.5.2 用组合替代继承
组合(Composition)是继承的最佳替代方案。将需要复用的功能封装成独立对象,通过持有引用来使用。
// ❌ 继承滥用
@interface UserListVC : BaseTableVC // 为了复用刷新逻辑而继承
@end
// ✅ 组合
@interface UserListVC : UIViewController
@property (nonatomic, strong) TableViewHelper *tableHelper; // 组合
@end
组合原则:问自己"这个类有这个功能,还是是这个功能"。前者用组合,后者才用继承。
# 2.5.3 面向协议编程
Swift 推广的面向协议编程(POP)在 ObjC 中同样适用。通过协议定义能力接口,类遵循协议来获得能力,比继承更灵活。
@protocol Refreshable <NSObject>
- (void)startRefresh;
- (void)stopRefresh;
@end
// 任何需要刷新能力的类都可以遵循这个协议
@interface UserListVC : UIViewController <Refreshable>
@end
继承 vs 组合 vs 协议的决策树:
需要复用代码?
├─ "是一个"关系 → 继承
├─ "有一个"关系 → 组合
└─ "能做什么"(能力) → 协议
# 2.6 综合案例:学生成绩管理系统
用一个完整案例串联本章所有知识点:
// ── Student.h ──
#import <Foundation/Foundation.h>
@interface Student : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) NSInteger score;
- (instancetype)initWithName:(NSString *)name score:(NSInteger)score;
- (NSString *)grade; // 根据分数返回 A/B/C/D/F
@end
// ── Student.m ──
#import "Student.h"
@implementation Student
- (instancetype)initWithName:(NSString *)name score:(NSInteger)score {
self = [super init];
if (self) {
_name = [name copy];
_score = score;
}
return self;
}
- (NSString *)grade {
if (self.score >= 90) return @"A";
if (self.score >= 80) return @"B";
if (self.score >= 70) return @"C";
if (self.score >= 60) return @"D";
return @"F";
}
- (NSString *)description {
return [NSString stringWithFormat:@"%@ (%@)", self.name, [self grade]];
}
@end
// ── ScoreManager.h ──
#import <Foundation/Foundation.h>
#import "Student.h"
@interface ScoreManager : NSObject
@property (nonatomic, strong, readonly) NSArray<Student *> *students; // readonly 防止外部修改
- (void)addStudent:(Student *)student;
- (double)averageScore;
- (NSDictionary<NSString *, NSNumber *> *)gradeDistribution;
@end
// ── ScoreManager.m ──
#import "ScoreManager.h"
@interface ScoreManager ()
@property (nonatomic, strong) NSMutableArray<Student *> *mutableStudents; // 内部可变
@end
@implementation ScoreManager
- (instancetype)init {
self = [super init];
if (self) {
_mutableStudents = [NSMutableArray array];
}
return self;
}
- (NSArray<Student *> *)students {
return [_mutableStudents copy]; // 防御性拷贝
}
- (void)addStudent:(Student *)student {
[_mutableStudents addObject:student];
}
- (double)averageScore {
if (self.mutableStudents.count == 0) return 0;
double total = 0;
for (Student *s in self.mutableStudents) {
total += s.score;
}
return total / self.mutableStudents.count;
}
- (NSDictionary<NSString *, NSNumber *> *)gradeDistribution {
NSMutableDictionary *dist = [NSMutableDictionary dictionary];
for (Student *s in self.mutableStudents) {
NSString *g = [s grade];
NSNumber *count = dist[g] ?: @0;
dist[g] = @(count.integerValue + 1);
}
return [dist copy];
}
@end
// ── main ──
int main() {
@autoreleasepool {
ScoreManager *mgr = [[ScoreManager alloc] init];
[mgr addStudent:[[Student alloc] initWithName:@"Alice" score:92]];
[mgr addStudent:[[Student alloc] initWithName:@"Bob" score:78]];
[mgr addStudent:[[Student alloc] initWithName:@"Carol" score:55]];
[mgr addStudent:[[Student alloc] initWithName:@"Dave" score:88]];
NSLog(@"平均分: %.1f", [mgr averageScore]);
NSLog(@"分数段: %@", [mgr gradeDistribution]);
for (Student *s in mgr.students) {
NSLog(@"%@", s); // Alice (A) ...
}
}
return 0;
}
这个案例用到的知识点:
@property修饰符选择:copy保护字符串、readonly保护数组、assign用于基本类型- 类扩展中声明私有可变数组,公开只暴露只读版本 —— 封装
- 自定义
initWithName:score:构造器 —— 两阶段创建 - 重写
description方法 —— 多态 for-in遍历集合 + 防御性拷贝[_mutableStudents copy]