Objective-C 面向对象初体验

面向对象简介

语言的面向对象都是换汤不换药 来来去去那几个概念

  • OOP(Object Oriented Programming) 基本概念
  • 对象 (男人,女人,程序员)
  • 抽象 - 类(人)

类和对象的关系

  • 创建类(人)、得到对象(程序员)
  • 成员变量: 内部使用,比如 大脑 眼睛 嘴
  • 属性: 给外部使用 比如 职业
  • 类的实例为对象

创建demo

  1. 征战Objective-C一样 创建一个Command Line Tool 写示例代码
  2. 创建一个文件,类型为Cocoa Class,name: People, Subclass of: NSObject, language: Objective-C 。点击Next之后会生成两个文件 People.h,People.m

引入与实例化

main.m 里面引入文件并实例化对象, 后面如果没有特别说明, 都是在 main @autoreleasepool 里面写代码

main.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#import <Foundation/Foundation.h>
#import "People.h" // 引入类

int main(int argc, const char * argv[]) {
@autoreleasepool {
// 实例化对象
// 类名 对象名(星号代表是指针类型) = [[类名/对象名] 方法名]
// People p1/p2 = [[People alloc] init]
// alloc - 为对象分配内存空间
// init - 进行初始化操作
People *p1 = [[People alloc] init];
People *p2 = [[People alloc] init];
People *p3 = [People new]; // new 在Objective-C 用得少
NSLog(@"p1 - %p", p1);
NSLog(@"p2 - %p", p2);
NSLog(@"p3 - %p", p3);
}
return 0;
}

创建成员变量与属性

People.h
1
2
3
4
5
6
7
8
9
10
// 姓名,年龄,性别
// 类内使用成员变量(m), 类外使用属性(h)
@interface People : NSObject
{
}
// 声明类的属性 声明后 main.m 就可以通过 p1.peopleName = @"Jsonz"; 访问修改属性
// 属性就是成员变量的外部接口 h 文件写。外部使用
//设置与获取Name
@property(nonatomic, strong)NSString *peopleName;
@end
People.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#import "People.h"

// 成员变量 m 文件定义 内部使用
@implementation People
{
int _peopleAge;
int _peopleSex;
}
- (instancetype) init
{
self = [super init];
if (self) {
_peopleName = @"张三"; // 成员变量 类内使用
}
return self;
}
@end

此时main.m 可以使用peopleName属性

main.m
1
2
3
4
5
// 属性的使用
p1.peopleName = @"jsonz";
p2.peopleName = @"李四";
NSLog(@"p1.peopleName - %@", p1.peopleName);
NSLog(@"p2.peopleName - %@", p2.peopleName);

函数

加号方法 减号方法 的声明与调用

People.h 声明方法

People.h
1
2
3
4
5
6
7
8
9
/**
* - 、+ (减号代表对象方法,加号代表类方法)
* 对象方法既是在实例上调用,而类方法则是在类上面调用,如: People *p1 = [[People alloc] init]
* 此时 People是类, p1是对象
* h 文件进行声明, m 进行实现
*/
-(void) report; // 减号方法
+(void) report1; // void 代表不用返回, +号方法
-(int) returnInt; // 改变返回值的函数

People.m实现该方法

People.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
- (void) report
{
NSLog(@"减号 Report");
// 如何在减号方法调加号方法
// [People report1];
}

static NSString *_peopleName1; // 静态变量,供加号方法内调用
+(void) report1
{
NSLog(@"加号 Report");
_peopleName1 = @"张三"; // 调用静态变量 不能调用成员变量
// 如何在加号方法调减号方法
// [[People alloc] report]
}
-(int) returnInt
{
// 前面是int,所以该函数必须返回一个int类型
return 0;
}

调用

main.m
1
2
3
4
5
6
People *p1 = [[People alloc] init]
[p1 report]; // 减号方法
[People report1]; // 加号方法

// [[People alloc] init]
// [People alloc]会返回一个对象,这时候才能调用 init 所以 alloc是加号方法, init是减号方法

函数参数类型

People.h 声明
1
2
3
// 函数参数问题
-(int)showWithA: (int) a; // 有一个int类型参数 函数名为 `showWithA:`
-(int)showWithA: (int)a andB:(int)b; // 有两个int类型参数 函数名为 `showWithA: andB:`
People.m 实现
1
2
3
4
5
6
7
8
9
//参数问题
-(int)showWithA:(int)a
{
return a;
}
-(int)showWithA:(int)a andB:(int)b
{
return a + b;
}
main.m 调用
1
2
3
4
5
// 函数的参数使用
int a1 = [p1 showWithA:10];
int a2 = [p1 showWithA:20 andB:30];
NSLog(@"a1 = %d", a1);
NSLog(@"a2 = %d", a2);

初始化方法

平时调用一个类的对象方法都是这么调用的 People *p1 = [[People alloc] init], 但此时这个init具体做了什么事情,我们并不知道。

我们可以在类里面重写掉这个init 方法;

People.h
1
2
3
4
// 对于初始化方法来说 id || instancetype 没有区别,对于其他方法,一般 instancetype 比 id用的多
// -(id)init; // 万能类型,可以返回各种类型对象
-(instancetype)init; // 当前类的类型,比如当前类是People类型,那么instancetype就是People类型
-(void) showPeopleProperty; // 定义一个方法来输出类的属性
People.m 重写init
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 如果此时提示 Duplicate declaration of method ‘init' 则代表你上面本来就声明了一个init 此时删除该init 或者把逻辑移到那个函数内即可
// init 固有的模式
- (instancetype) init
{
self = [super init]; // 自己继承父级的 init 此处是 NSObject
// 内部进行一些初始化的设定
if (self) {
_peopleName = @"Jsonz"; // 成员变量 类内使用
_peopleAge = 30;
_peopleSex = 1;
}
return self; // 返回自身
}

// 输出People的对象初始化值
-(void) showPeopleProperty
{
NSLog(@"peopleName = %@", _peopleName);
NSLog(@"peopleAge = %d", _peopleAge);
NSLog(@"peopleAge = %d", _peopleSex);
}

接下来我们来自定义一个初始化的方法

People.h
1
-(instancetype)initWithPeopleName:(NSString *) peopleName andPeopleAge:(int)peopleAge

People.m
1
2
3
4
5
6
7
8
9
10
11
12
// 既然我们是自定义一个初始化方法,那也要按照初始化方法的写法,把一些结构给加上
-(instancetupe)initWithPeopleName:(NSString *)peopleName andPeopleAge:(int)peopleAge
{
// 初始化方法的结构
self = [super init];
if (self) {
_peopleName = peopleName;
_peopleAge = peopleAge;
}
return self;

}
main.m
1
2
3
4
// 自己实现的初始化方法
People *pSelf = [[People alloc] initWithPeopleName:@"Jsonz" andPeopleAge: 23];
NSLog(@"自己实现的init 的属性有哪些: ---- ");
[pSelf showPeopleProperty];

面向对象三部曲

新建一个项目,避免学习太混乱

封装

新建一个类,叫MyClass

MyClass.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#import <Foundation/Foundation.h>

@interface MyClass : NSObject
{
// 成员变量访问修饰符的问题
// 默认为 protected 受保护的
@public // 公有 - 在类内 类外都可以使用,并且可以被继承
int _classInt;

@private // 私有 - 在类内可以使用, 类外无法调用 并且无法被继承
@protected // 受保护 - 默认的 在类内可以使用,类外无法调用 并且可以被继承
NSString *_classStr;
@package // 框架权限 - 在框架内相当于受保护(可被调用与继承), 在框架外相当于私有(类外无法使用与继承)
}
@property(nonatomic, strong)NSString *className;
// 方法是没有访问修饰符的, 同C语言一样。
// 如果想要一个方法可以在类外可以使用,则要在h声明,m实现。
// 如果不想在类外使用, 直接在m写实现, h 不写声明。
-(void) report;
@end

MyClass.m
1
2
3
4
5
6
7
8
9
10
#import "MyClass.h"

@implementation MyClass
-(void)report
{
_classStr = @"ClassStr bilibilii";
NSLog(@"ClassName - %@", _className);
NSLog(@"classInt - %d", _classInt);
}
@end
main.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#import <Foundation/Foundation.h>
#import "MyClass.h"

int main(int argc, const char * argv[]) {
@autoreleasepool {
MyClass *mc = [[MyClass alloc] init];
mc.className = @"我的类";

// 类外使用 public 成员变量
mc->_classInt = 1001; // 使用指向来调用类中的公有成员变量

[mc report];

}
return 0;
}

继承

创建两个类

  • 父类 ParentClass
  • 子类 ChildClass, 创建的时候记得选上继承 ParentClass, 如果忘了选也没关系 后面手动修改一下就可以
ParentClass.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#import <Foundation/Foundation.h>

// NSObject - 基类 此处的:(冒号)是代表继承关系
@interface ParentClass : NSObject
{
// 受保护变量 可继承 不可外部调用
int _classInt;

// 私有变量
@private
NSString *_classStr;

}
@property(nonatomic, strong)NSString *className;
-(void)report; // 如果此处不声明,则类外不能调。 子类也不继承
@end
ParentClass.m
1
2
3
4
5
6
7
8
9
10
11
12
#import "ParentClass.h"

@implementation ParentClass
-(void)report
{
_classInt = 1002;
_classStr = @"Jsonz's 私有变量";
NSLog(@"ClassName - %@", _className);
NSLog(@"classInt - %d", _classInt);
NSLog(@"classStr - %@", _classStr); // 此处在 ChildClass 中也会被打印出来,因为继承了该方法,所以会被打印
}
@end
ChildClass.h
1
2
3
4
5
#import "ParentClass.h"

@interface ChildClass : ParentClass
-(void)show;
@end
ChildClass.m
1
2
3
4
5
6
7
8
9
10
#import "ChildClass.h"

@implementation ChildClass
-(void)show
{
_classInt = 1003;
NSLog(@"show 此处_classInt 变化了 - %d", _classInt);
// NSLog(@"show 打印父类的私有方法NSString %@", _classStr); 此处因为是父级的私有变量,所以外部无法访问
}
@end
main.m
1
2
3
4
5
6
7
8
9
10
11
// 调用
// 父类
ParentClass *pc = [[ParentClass alloc] init];
pc.className = @"parentClass ClassName";
[pc report];

// 子类
ChildClass *cc = [[ChildClass alloc] init];
cc.className = @"ChildClass ClassName"; // 此处为父类继承过来的属性
[cc show];
[cc report]; // 此处还是1002 因为 cc中的 report 继承 pc 的report,此处重新复制了并打印。所以是1002

多态

  • 方法重写 基于父类方法继承重写,返回值,函数名,参数等都一致
  • 方法重载 (OC 不支持) 函数名一致,返回值 参数 参数类型等都不一致

创建一个类ColorPrint 用于演示多态,后面会基于 ParentClass, ChildClassColorPrint 来说明多态。

父类实现一个打印机方法

ParentClass.h
1
-(void) print;

ParentClass.m
1
2
3
4
-(void) print
{
NSLog(@"普通打印机");
}

子类重写不需要声明,直接在m文件去重写实现即可

ChildClass.m
1
2
3
4
-(void)print
{
NSLog(@"我是黑白打印机");
}

ColorPrinter.m
1
2
3
4
5
6
7
8
-(void)print
{
// 如果此处要调用父类的方法可以这么写:
[super print];
// 调用当前类的方法可以用 self 父类可以用 super
// [self print];
NSLog(@"我是彩色打印机");
}
main.m
1
2
3
4
5
6
7
8
9
10
// 调用
ChildClass *cc = [[ChilClass alloc] init];
[cc print]; // 黑白
ColorPrinter *cc2 = [[ColorPrinter alloc] init];
[cc2 print]; // 彩色打印机

// 如果已经引入了子类的头文件,默认父类h文件(子类头文件所引入的头文件)也会被引入了。
// 可以这么写
ParentClass *color = [[ColorPrinter alloc] init];
[color print]; // 此处也是才是打印机

demo