面向对象编程
# 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.2 方法的声明和实现
- 2.2.1 方法的声明
- 2.2.2 方法的实现
- 2.2.3 方法的调用
- 2.2.4 方法的嵌套调用
- 2.2.5 方法访问权限
- 2.2.6 方法的重写
- 2.3 属性定义和访问
- 2.3.1 属性的定义
- 2.3.2 属性修饰符
- 2.3.3 synthesize
- 2.3.4 属性的访问
# 2.1 类的声明和实现
# 2.1.1 类规格说明
类是 Objective-C 用来封装数据,以及操作数据的行为的基础结构。对象就是类的运行期间实例,它包含了类声明的实例变量自己的内存拷贝,以及类成员的指针。
Objective-C 的类规格说明包含了两个部分:声明interface与实现implementation。
声明(interface)部分包含了类声明和实例变量的定义,以及类相关的方法。实现(implementation)部分包含了类方法的实际代码。
类的语法由两个部分组成:接口(Interface)和实现(Implementation)。
# 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;
- (NSString *)greetWithName:(NSString *)name;
// @end 表示接口定义结束
@end
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 2.1.3 类的实现
遵循C语言的规范,类实现一般定义在对应的.m文件中。类实现包含了公开方法的实现,以及定义私有(private) 变量及方法。 以关键字@implementation作为区块起头,@end结尾。
头文件(类声明)中定义的属性默认为protected,方法为public。而类实现中定义的属性为private。当然也可以使用@public、@private等助记符来覆盖默认行为。
上述类的一个实现如下:
//@interface MyClass () 是一种类扩展(Class Extension)的语法,用于在类的实现文件(.m)中声明私有属性和方法。
@interface MyClass ()
@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;
}
- (NSString *)greetWithName:(NSString *)name {
//return name + "杨充";
return [NSString stringWithFormat:@"%@ %s", name, "打工充"];
}
- (NSInteger) age {
return 29;
}
@end
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# 2.1.4 类的实例化
实例化即创建对象。Objective-C创建对象需通过alloc以及init两个消息。alloc的作用是分配内存,init则是初始化对象。
init与alloc都是定义在NSObject里的方法,父对象收到这两个信息并做出正确回应后,新对象才创建完毕。如上述类中:
MyClass * my = [[MyClass alloc] init];
若要自己定义初始化的过程,可以重写init方法,来添加额外的工作。(用途类似C++ 的构造函数constructor),如下:
- (id) init {
if ( self=[super init] ) { // 必须调用父类的init
// do something here ...
}
return self;
}
2
3
4
5
6
# 2.1.5 类默认构造器
"默认构造器"通常指的是类的初始化方法,即init方法。默认构造器是一个类的默认初始化方法,用于创建类的实例并对其进行初始化。
构造器是一类特殊的方法,返回值通常是 id,我们可以在其中做一些类的初始化工作。默认的构造器是 init:
- (id)init {
self = [super init];
// 判断父类是否初始化成功,如果初始化失败将会返回 nil
if (self) {
_nickName = @"default";
}
return self;
}
2
3
4
5
6
7
8
# 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];
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 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;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
相关术语:
- 超类(superclass)/ 父类(parentclass):所继承的类,以上例子中的 Animal 类
- 子类(subclass) / 孩子类(childclass):实施继承的类,以上例子中的 Dog 类
- 重写(override):子类覆盖超类方法,例如 Dog 类重写了 sound 方法
继承的工作机制:
向对象发送消息时,OC 的方法调度程序将在当前对象指向的类中搜索相应的方法,如果当前类中没有,就会去超类中搜索,如果直到最高级别的超类中仍然没有所有到对应的方法,就会出现运行时错误(编译时也会有警告)。
# 2.2 方法的声明和实现
# 2.2.1 方法的声明
方法的声明包括方法的返回类型、方法名和参数列表。以下是一个示例,展示了一个方法的声明:
#import <Foundation/Foundation.h>
@interface MyClass : NSObject
// 方法,+ 表示类方法(类似静态方法),- 表示实例方法
- (void)doSomething;
- (NSString *)greetingWithName:(NSString *)name;
@end
2
3
4
5
6
7
8
9
doSomething方法没有返回值,而greetingWithName:方法返回一个NSString对象,并接受一个NSString类型的参数。
# 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
3
4
5
6
7
8
9
10
11
12
13
14
15
# 2.2.3 方法的调用
调用一个方法实际上就是传递消息到对应的对象。这里消息就是方法标识符以及传递给方法的参数信息。
发送给对象的所有消息都会动态分发,这样有利于实现Objective-C类的多态行为。
也就是说,如果子类定义了跟父类的具有相同标识符的方法,那么子类首先收到消息,然后可以有选择的把消息转发(也可以不转发)给他的父类。
消息被中括号( [ 和 ] )包括。括号中接收消息的对象在左边,消息及其参数在右边。
MyClass *my = [[MyClass alloc] init];
调用无参数的方法:
//调用void方法
[my doSomething];
2
调用带有参数的方法:
NSInteger result = [myObject calculateSumWithNumber:5 andNumber:10];
调用带有返回值的方法:
NSString *greeting = [myObject greetWithName:@"John"];
# 2.2.4 方法的嵌套调用
# 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
OC 提供了 super 关键字,可以在子类的方法中显示调用超类中的方法,向 super 发送消息等价于对超类发送消息。
# 2.3 属性定义和访问
# 2.3.1 属性的定义
属性(Property)的定义是指在类的接口部分(头文件)中声明属性的语法和规则。属性的定义包括属性的类型、访问修饰符和特性等。
每个属性的定义由@property关键字开始,后面跟着属性的类型和名称。
//@property 用于声明属性,它指定了属性的类型、访问修饰符和其他属性特性。
@property (nonatomic, strong) NSString *name;
@property (nonatomic, assign) NSInteger age;
2
3
# 2.3.2 属性修饰符
可以为类的属性设置其它修饰符 (attribute),其它这样的修饰符还有很多种。
属性(Property)是用于封装对象的实例变量,并提供对其访问和设置的方式。属性可以简化对实例变量的操作,并提供了一些额外的功能,如内存管理和线程安全等。
生成实例变量和访问方法:
- @property关键字用于声明属性。
- nonatomic:非原子性(线程不安全),不提供多线程安全保证,适用于单线程环境。。默认所有的属性都是原子性的,也就是支持跨进程赋值。
- strong、weak、copy等关键字用于指定属性的内存管理方式。
- readonly:关键字用于指定只读属性,只生成getter方法,不生成 setter 方法。其他类可以读取属性的值,但不能修改。
- readwrite:关键字用于指定可读写属性,生成getter和setter方法。其他类可以读取和修改属性的值。
- unsafe_unretained: 等同于 weak,也就是弱引用,当没有强引用的时候即释放它。主要用于防止多个对象之间的循环引用。
除了 readonly 之外,默认的 attribute 还有:
- assign: 赋值,告诉编译期生成 setter 方法。
- retain: 等同于 strong,也就是强引用,保持引用直到所有对象都释放了它,旧值被释放新值被赋值。
- atomic: 保持原子性,只有单个线程能访问它,线程安全但是效率更低。默认情况下,属性是原子性的。
strong、weak、copy等关键字用于指定属性的内存管理方式。
- strong / retain:强引用属性,增加对象的引用计数,对象不会被释放,适用于拥有关系。
- weak:弱引用属性,不增加对象的引用计数,对象释放后自动设置为 nil,避免循环引用。
- copy:复制属性,复制对象的副本,适用于不希望属性值被修改的情况。
自动生成访问方法:
- @synthesize关键字用于自动生成属性的访问方法的实现。在最新的Objective-C版本中,通常不需要显式使用@synthesize,编译器会自动合成访问方法。
默认的属性是原子的,即在访问时会加锁以避免多线程同时访问同一对象,也可以将属性声明为“nonatomic”(非原子的),避免产生锁。
# 2.3.3 synthesize
@synthesize关键字用于自动合成属性的实例变量和访问方法的实现。它会根据属性的定义自动生成对应的实例变量和访问方法。
// ViewController.h
@interface ViewController : NSObject
@property (nonatomic, strong) UITextView *textView;
@end
2
3
4
5
6
7
然后看一下如何使用属性。在类的实现文件中使用@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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
从较新的Objective-C版本开始,如果没有显式使用@synthesize,编译器会自动合成属性的实例变量和访问方法。因此,通常情况下,你不需要手动添加@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访问
2
3
4
5
6
7
8
9
10
11
12
# 参考
经典笔记:https://github.com/aJIEw/HeadFirstObjectiveC
Objective-C 内存管理——你需要知道的一切:https://segmentfault.com/a/1190000004943276