编程进阶网 编程进阶网
首页
  • 在线工具
  • JSON工具
  • 文本工具
  • 图片处理
  • 文档转化
  • 代码压缩
  • 加解密
  • 时间日期
  • 网络工具
  • 颜色设计
  • 二维码
  • 开发实用
  • 计算机的原理
  • 操作系统原理
  • 网络协议原理
  • 数据库的原理
  • 序卷导读
  • 数据本质
  • 运行模型
  • 并发设计
  • 内存真相
  • 交互系统
  • 面向对象
  • 设计原则
  • 设计模式
  • 系统架构
  • 技能之旅
  • 体系建设
  • 代码品质
  • 方案设计
  • 稳定可靠
  • 工程运维
  • 性能优化
  • 数据结构导论
  • 线性结构详解
  • 树哈希结构论
  • 容器设计实战
  • 经典算法思想
  • 工程案例剖析
  • 算法题库精练
  • C语言入门
  • C综合案例
  • C专栏博客
  • C标准集库
  • C++入门教程
  • C++综合案例
  • C++专栏博客
  • C++编程技巧
  • Java入门教程
  • Java综合案例
  • Java专栏博客
  • Go入门教程
  • Go综合案例
  • Go专栏博客
  • Go开发技巧
  • JavaScript入门
  • JavaScript案例
  • JavaScript高级
  • Kotlin精通
  • Android库解读
  • Android专栏
  • iOS ObjC入门
  • iOS Swift入门
  • iOS入门精通
  • Web之Html手册
  • Web之TypeScript
  • Web之Vue高级进阶
  • Linux之QML入门
  • Linux之QT核心库
  • Python教程
  • Shell&Bash教程
  • 工具脚本
  • 自动化脚本
  • 质量保障
  • 产品思考
  • 软实力
  • 开发流程
  • Git应用
  • 技术模版
  • 技术规范
  • Markdown
  • Mermaid
  • 开源协议
  • 毛选解读
  • 自我精进
  • 关于我
  • 自我精进
  • 职场管理
  • 职场面试
  • 心情杂货
  • 友情链接

杨充

专注编程 · 终身学习者
首页
  • 在线工具
  • JSON工具
  • 文本工具
  • 图片处理
  • 文档转化
  • 代码压缩
  • 加解密
  • 时间日期
  • 网络工具
  • 颜色设计
  • 二维码
  • 开发实用
  • 计算机的原理
  • 操作系统原理
  • 网络协议原理
  • 数据库的原理
  • 序卷导读
  • 数据本质
  • 运行模型
  • 并发设计
  • 内存真相
  • 交互系统
  • 面向对象
  • 设计原则
  • 设计模式
  • 系统架构
  • 技能之旅
  • 体系建设
  • 代码品质
  • 方案设计
  • 稳定可靠
  • 工程运维
  • 性能优化
  • 数据结构导论
  • 线性结构详解
  • 树哈希结构论
  • 容器设计实战
  • 经典算法思想
  • 工程案例剖析
  • 算法题库精练
  • C语言入门
  • C综合案例
  • C专栏博客
  • C标准集库
  • C++入门教程
  • C++综合案例
  • C++专栏博客
  • C++编程技巧
  • Java入门教程
  • Java综合案例
  • Java专栏博客
  • Go入门教程
  • Go综合案例
  • Go专栏博客
  • Go开发技巧
  • JavaScript入门
  • JavaScript案例
  • JavaScript高级
  • Kotlin精通
  • Android库解读
  • Android专栏
  • iOS ObjC入门
  • iOS Swift入门
  • iOS入门精通
  • Web之Html手册
  • Web之TypeScript
  • Web之Vue高级进阶
  • Linux之QML入门
  • Linux之QT核心库
  • Python教程
  • Shell&Bash教程
  • 工具脚本
  • 自动化脚本
  • 质量保障
  • 产品思考
  • 软实力
  • 开发流程
  • Git应用
  • 技术模版
  • 技术规范
  • Markdown
  • Mermaid
  • 开源协议
  • 毛选解读
  • 自我精进
  • 关于我
  • 自我精进
  • 职场管理
  • 职场面试
  • 心情杂货
  • 友情链接
  • README
  • Android提升进阶

  • iOS开发和进阶

    • README
    • ObjC编程入门

      • README
      • 1.基础语法快速入门
      • 面向对象编程
        • 2.1 类的声明和实现
          • 2.1.1 类规格说明
          • 2.1.2 类的声明
          • 2.1.3 类的实现
          • 2.1.4 类的实例化
          • 2.1.5 类默认构造器
          • 2.1.6 自定义构造器
          • 2.1.7 类继承和实现
          • 2.1.8 alloc + init 两阶段原理
        • 2.2 方法的声明和实现
          • 2.2.1 方法的声明
          • 2.2.2 方法的实现
          • 2.2.3 方法的调用
          • 2.2.4 方法的嵌套调用
          • 2.2.5 方法访问权限
          • 2.2.6 方法的重写
          • 2.2.7 实例方法 vs 类方法
        • 2.3 属性定义和访问
          • 2.3.1 属性的定义
          • 2.3.2 属性修饰符
          • 2.3.3 synthesize
          • 2.3.4 属性的访问
        • 2.4 属性的深入理解
          • 2.4.1 copy 属性的陷阱
          • 2.4.2 atomic vs nonatomic 性能之争
          • 2.4.3 @dynamic 的使用场景
        • 2.5 继承的反模式与替代
          • 2.5.1 滥用继承的典型场景
          • 2.5.2 用组合替代继承
          • 2.5.3 面向协议编程
        • 2.6 综合案例:学生成绩管理系统
      • ObjC高级语法
      • ObjC内存管理
    • Swift编程入门

    • 专栏博客

  • Web开发和进阶

  • Linux应用开发

  • IoT智能硬件开发

  • Apps
  • iOS开发和进阶
  • ObjC编程入门
杨充
2025-07-22
目录

面向对象编程

# 02.面向对象编程

# 目录介绍

  • 2.1 类的声明和实现
    • 2.1.1 类规格说明
    • 2.1.2 类的声明
    • 2.1.3 类的实现
    • 2.1.4 类的实例化
    • 2.1.5 类默认构造器
    • 2.1.6 自定义构造器
    • 2.1.7 类继承和实现
    • 2.1.8 alloc + init 两阶段原理
  • 2.2 方法的声明和实现
    • 2.2.1 方法的声明
    • 2.2.2 方法的实现
    • 2.2.3 方法的调用
    • 2.2.4 方法的嵌套调用
    • 2.2.5 方法访问权限
    • 2.2.6 方法的重写
    • 2.2.7 实例方法 vs 类方法
  • 2.3 属性定义和访问
    • 2.3.1 属性的定义
    • 2.3.2 属性修饰符
    • 2.3.3 synthesize
    • 2.3.4 属性的访问
  • 2.4 属性的深入理解
    • 2.4.1 copy 属性的陷阱
    • 2.4.2 atomic vs nonatomic 性能之争
    • 2.4.3 @dynamic 的使用场景
  • 2.5 继承的反模式与替代
    • 2.5.1 滥用继承的典型场景
    • 2.5.2 用组合替代继承
    • 2.5.3 面向协议编程
  • 2.6 综合案例:学生成绩管理系统

# 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等关键字用于指定属性的内存管理方式。

  1. strong / retain:强引用属性,增加对象的引用计数,对象不会被释放,适用于拥有关系。
  2. weak:弱引用属性,不增加对象的引用计数,对象释放后自动设置为 nil,避免循环引用。
  3. 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]
上次更新: 2026/07/05, 14:50:16
1.基础语法快速入门
ObjC高级语法

← 1.基础语法快速入门 ObjC高级语法→

最近更新
01
audit
07-27
02
C++入门教程全章思考题汇编
07-24
03
12.技术团队建设能力
07-21
更多文章>
Theme by Vdoing | Copyright © 2019-2026 杨充 | MIT License | 鄂ICP备2024073355号-1 | 鄂ICP备2024073355号
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式