VectorLu

Objectiv-C 学习笔记

虽然已经有了 Swift,但是还是有很多 Objective-C 的项目,这门语言还是有必要学一学。如果已经有 C 语言的基础,相信你能很快上手。OC 是完全兼容 C99 的,所以也会先回顾一下 C 语言的重要内容。

如果还完全不会用 Xcode 创建项目和文件,可以看看 Xcode 使用笔记 0 新司机上路

C 语言基础回顾

布尔值

Xcode 支持 C99 标准

1
_BOOL a = true;

注意在 Objective-C 中,BOOL类型使用 8 位存储空间,如果不小心将一个大于 1 字节的整型值(比如shortint)付给一个BOOL变量,那么只有低位字节会用作BOOL值,如果该低位字节刚好为 0(比如说 8960,写成十六进制为0x2300),BOOL值将会被认作是 0 ,即 NO值,故不要混用数字和BOOL类型。

函数

如果变量是“与某块数据相关联的名称”,那么函数就是“与某块代码相关联的名称”。

发现代码中的重复,提取具有共通性的部分并将其用独立的函数处理,接着添加参数处理差异部分。

局部变量、帧和栈

函数可以有很多局部变量,这些局部变量都保存在函数的帧 (frame) 中。

使用栈 (stack) 来描述帧在内存中存储的地点。执行函数时,函数的帧会在栈的顶部被创建出来。函数执行结束时,我们会说函数返回了。也就是说,其帧会退出栈,等待下一个调用它的函数继续执行。

全局变量与静态变量

在函数外声明的变量称为全局变量。复杂的程序会涉及大量的文件,包含不同的函数。这些文件中的代码都能访问全局变量。在不同的文件中共享变量不一定都是好事,有时候会引起很严重的混淆问题。

为此,C 语言引入了静态变量 (static variable)概念。只有那些声明了静态变量的文件才可以访问静态变量。详见下一小节。

存储类、链接和内存管理

C 语言中有 5 个作为存储类说明符的关键字,它们是auto register static extern以及typedef

typedef与内存存储无关,由于语法原因被归入此类。不可以在一个声明中使用一个以上存储类说明符,意味着不能将其他任一存储类说明符作为typedef的一部分。

C 使用作用域、链接和存储时期来定义 5 种存储类,见下表:

存储类 存储时期 作用域 链接 声明方式
自动 自动 代码块 代码块内
寄存器 自动 代码块 代码块内,使用 关键字 register
空链接的静态 静态 代码块 代码块内,使用关键字 static
具有外部链接的静态 静态 文件 外部 所有函数之外
具有内部链接的静态 静态 文件 内部 所有函数之外,使用关键字 static

详见:C 语言存储类详解

指针

通过*运算符,可以访问保存在某个地址中的数据。

不同数据类型的大小

sizeof()会返回一个类型为size_t的数,与之对应的格式是%zu,较少用到。示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#import <stdio.h>
int main(int argc, const char * argv[])
{
int i = 17;
int *addressOfI = &i;
printf("i stores its value at %p\n", addressOfI);
*addressOfI = 89;
printf("Now i is %d \n", i);
printf("An int is %zu bytes\n", sizeof(int));
printf("A pointer is %zu bytes\n", sizeof(int *));
return 0;
}

运行程序,如果指针长度是 4 个字节,那么该程序就是在 32 位环境下运行,8——64 位环境。

NULL

NULL就是 0(地址也是数字)。在if的条件中,很方便判断指针是否为空:

1
2
3
4
5
6
7
float *myPointer;
...
// myPointer 指针是否为空?
if(myPointer)
{
...// myPointer 非空
}

或者用较简洁的三元运算符

1
2
3
// 如果 measureGravityPtr 是空值,则计算重力值
float actualGravity = measureGravityPtr? *measuredGravityPtr : estimatedGravity(planetRadius);

通过引用传递

通过一个实例来学习,其中调用modf()函数并传入一个double类型数,可以得到该浮点数的整数部分和小数部分。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#import <stdio.h>
#import <math.h>
int main(int argc, const char * argv[])
{
double pi = 3.14;
double integerPart;
double fractionPart;
// 将 integerPart 的地址作为实参传入
fractionPart = modf(pi, &integerPart);
// 获取 integer 地址上的值
printf("integerPart = %.0f, fractionPart = %.2f\n", integerPart,
fractionPart);
return 0;
}
// 运行结果
// integerPart = 3, fractionPart = 0.14
// Program ended with exit code: 0

调用函数时传入某个地址,然后由函数将数据传入该地址指向的内存,这种参数传入方式称为通过引用传递 (pass-by-refrence)。

以下是一个很实用,很简单的通过引用传递参数的例子。

1
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
39
40
41
42
43
44
45
46
47
48
49
// 将公制单位米转换成非公制单位的英尺和英寸,
// 需要读取一个浮点数,返回两个浮点数。
// 调用函数时,要为 meters 传入相应的值。
// 此外,还要提供两个地址,用于保存 feet 和 inches。
#import <stdio.h>
#import <math.h>
void metersToFeetAndInches(double meters, unsigned int *ftPtr,
double *inPtr)
{
// 这个函数假定 meters 的值是非负数
// 将 meters 变量转化为 feet 的值,类型为浮点数。
double rawFeet = meters * 3.281;
// 计算类型为无符号整型 feet 变量的值
unsigned int feet = (unsigned int)floor(rawFeet);
// 将 feet 变量的值储存在提供的地址里
printf("Storing %u to the address %p \n", feet, ftPtr);
*ftPtr = feet;
// 计算英寸
double fractionalFoot = rawFeet - feet;
double inches = fractionalFoot * 12.0;
// 将 inches 变量的值储存在传入的地址里
printf("Storing %.2f to the address %p \n", inches, inPtr);
*inPtr = inches;
}
int main(int argc, const char * argv[])
{
double meters = 3.0;
unsigned int feet;
double inches;
metersToFeetAndInches(meters, &feet, &inches);
printf("%.1f meters is equal to %d feet and %.1f inches.\n",
meters, feet, inches);
return 0;
}
// 运行结果
//Storing 9 to the address 0x7fff5fbff7b4
//Storing 10.12 to the address 0x7fff5fbff7a8
//3.0 meters is equal to 9 feet and 10.1 inches.

Objective-C 基础

学习自《Objective-C 编程》第二版,《Objective-C 基础教程》第二版,iTunes 上 Stanford iOS7 的视频。

类和方法初体验

方法和消息

1
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
39
40
41
42
43
44
45
46
// main.c
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
// date 类方法 class method
// 类方法会创建类的实例
// 并创建实例变量
NSDate *now = [NSDate date];
NSLog(@"This NSDate object lives at %p", now);
NSLog(@"The date is %@", now);
// timeIntervalSince1970 实例方法 instance method
// 提供实例中实例变量的信息,或是对实例变量进行操作
double seconds = [now timeIntervalSince1970];
NSLog(@"It has been %f seconds since the start og 1970.", seconds);
// 方法带实参时,冒号时构成方法名的一个重要部分。
// 所以不存在 dateByAddingTimeInterval 方法
// 只有 dateByAdding-TimeInterval 方法
NSDate *later = [now dateByAddingTimeInterval:100000];
NSLog(@"In 100,000 seconds it will be %@.", later);
NSCalendar *cal = [NSCalendar currentCalendar];
NSLog(@"My calendar is %@", [cal calendarIdentifier]);
// 获取更多关于 NSDate 的信息
unsigned long day = [cal ordinalityOfUnit:NSCalendarUnitDay inUnit:NSCalendarUnitMonth forDate:now];
NSLog(@"This is day %lu of the month", day);
// 消息的嵌套
double secondsNest = [[NSDate date] timeIntervalSince1970];
NSLog(@"It has been %f seconds since the start of 1970", secondsNest);
// 练习:获取计算机的相关信息
// currentHost 类方法
NSHost *host = [NSHost currentHost];
// localizedName 实例方法
NSString *hostString = [host localizedName];
NSLog(@"This computer's name is %@.\n", hostString);
}
return 0;
}

alloc 和 init

唯一必须以嵌套的形式连续发送的消息是allocinit

1
2
3
4
// NSDate *now = [NSDate date];
NSDate *now = [[NSDate alloc] init];
// 两种方法等价,使用 date 方法可以用最少的代码获取一个 NSDate 实例
// 称这种方法为便利方法 convenience method

向 nil 发送信息

nil是不指向任何对象的指针,多数面向对象的语言不允许向nil发送消息,但是在 Objective-C 中,可以向nil中发送消息,什么事情也不会发生。

重点1:
如果程序向某个对象发送了消息,但是没有得到预期的结果,请检查消息接收方是否为nil

重点2:
nil发送消息,得到的返回值没有意义。

id

id 是可以指向任意类型对象的指针
id 已经隐含了星号的作用。

获取从出生到当前时刻的时间间隔

(单位:秒)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSDateComponents *comps = [[NSDateComponents alloc] init];
[comps setYear:1997];
[comps setMonth:5];
[comps setDay:1];
[comps setHour:10];
[comps setMinute:10];
[comps setSecond:0];
NSCalendar *g = [[NSCalendar alloc] initWithCalendarIdentifier:
NSCalendarIdentifierGregorian];
NSDate *dateOfBirth = [g dateFromComponents:comps];
NSDate *now = [NSDate date];
double secondsSinceEarlierDate = [now timeIntervalSinceDate:dateOfBirth];
NSLog(@"I have been living for %f seconds", secondsSinceEarlierDate);
}
return 0;
}

对象和内存

内存管理

栈是由有序的帧构成的。函数的帧在函数结束后会自动释放。而堆里的对象不会自动释放。管理堆很重要,因为程序占用的堆大小是有限的。而每个对象被创建后都会占用一顶的堆内存。如果系统内存不足,Mac 应用的运行效果就会变差,而 iOS 应用则会崩溃。因此,必须释放不需要的对象,将内存交还给系统,继续重新使用。

1
2
3
4
5
6
7
NSDate *currentTime = [NSDate date];
NSLog(@"currentTime's value is %p", currentTime);
sleep(2);
currentTime = [NSDate date];
NSLog(@"currentTime's value is now %p", currentTime);

ARC

第一个NSDate实例对象怎么样了?

我们已经丢失了这个对象以及其包含的信息。因为我们丢失了指向该对象的指针,即使它仍然在堆上,我们也再不能获得这个对象的信息。如果要保留第一个 NSDate实例,需要在currentTime指向第一个实例时,创建新指针从currentTime那里获取第一个实例对象的地址,此时第一个实例对象的引用数为 2(该实例被 2 个指针所指向)。

将指向实例对象的指针设置为nil,实例对象就会失去一个引用,如果开启了 ARC,当一个实例对象的引用数为 0 时,就会自动销毁该实例对象,释放内存。

ARC automatic reference counting 自动引用计数

如果指针变量被销毁,它所指向的对象会失去一个引用。当某个对象的实例变量指向其他对象时,事情会变得更复杂—— todo

NSString

创建 NSString 实例

1
NSString *lament = @"Why me?";

创建动态字符串,字符串的内容要等到程序运行时才能知道,可以使用stringWithFormat:类方法来创建动态字符串:

1
NSString *datastring = [NSString stringWithFormat:@"The date is %@", now];

使用官方文档

打开 Xcode 的 “Help -> Documentation and API Reference”,或者用快捷键 “Command + Shift + 0” ,搜索想了解的资料,注意语言的切换,可以先在顶部的搜索框输入某个类,再用 “Command + f” 在页面内搜索这个类的某个方法。

NSArray

创建数组和遍历

1
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
39
40
41
42
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[])
{
@autoreleasepool
{
// 创建 3 个 NSDate 对象
NSDate *now = [NSDate date];
NSDate *tomorrow = [now dateByAddingTimeInterval:24.0 * 60.0 * 60.0];
NSDate *yesterday = [now dateByAddingTimeInterval:-24.0 * 60.0 * 60.0];
// 创建 1 个数组包含这 3 个对象
NSArray *dateList = @[now, tomorrow, yesterday];
// NSArray 实例一旦创建就无法改变,
// 无法添加删除数组中的元素,也无法改变它们的位置
// 输出其中的两个对象
NSLog(@"The first date is %@", dateList[0]);
NSLog(@"The third date is %@", dateList[2]);
// 用 count 方法获取 dateList 指向的 NSArray
// 对象的元素个数
NSLog(@"There are %lu dates", [dateList count]);
// 遍历数组
NSUInteger dateCount = [dateList count];
for(int i = 0; i < dateCount; i++)
{
NSData *d = dateList[i];
NSLog(@"Here is a date: %@", d);
}
// 快速枚举
for(NSDate *d in dateList)
{
NSLog(@"Here is a date: %@", d);
}
}
return 0;
}

可变数组 NSMutableArray

1
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
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[])
{
@autoreleasepool
{
// 创建 3 个 NSDate 对象
NSDate *now = [NSDate date];
NSDate *tomorrow = [now dateByAddingTimeInterval:24.0 * 60.0 * 60.0];
NSDate *yesterday = [now dateByAddingTimeInterval:-24.0 * 60.0 * 60.0];
// 创建空数组
NSMutableArray *dateList = [NSMutableArray array];
// 将 2 个 NSDate 对象加入新创建的数组
[dateList addObject:now];
[dateList addObject:tomorrow];
// 将 yesterday 指针插入到数组的起始位置
[dateList insertObject:yesterday atIndex:0];
// 快速枚举
for(NSDate *d in dateList)
{
NSLog(@"Here is a date: %@", d);
}
// 删除 yesterday 指针
[dateList removeObjectAtIndex:0];
NSLog(@"Now the first date is %@", dateList[0]);
}
return 0;
}

旧式数组方法

1
NSArray *dateList = [NSArray arrayWithObjects:now, tomorrow, yesterday, nil];

结束处的nil 告诉方法停止运行,所以,这个NSDate数列拥有三个指针对象。如果没有

练习

购物清单

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[])
{
@autoreleasepool
{
NSString *bread= @"Loaf of bread";
NSString *milk = @"Container of milk";
NSString *butter = @"Stick of butter";
NSMutableArray *shoppingList = [NSMutableArray array];
[shoppingList addObject:bread];
[shoppingList addObject:milk];
[shoppingList addObject:butter];
for(NSString *s in shoppingList)
{
NSLog(@"Don't forget to buy %@", s);
}
}
return 0;
}

里程碑 1:学会从问题出发查找 OC 文档

在专有名词和常见名词表单中找到含”aa”的专有名词(即首字母大写)。

在给出的程序基础上,思考:

如何判断首字母是否是大写,在NSString的界面搜索capital,发现没有相关方法,于是搜索index,想着应该有用index获取某个字符的方法,搜索得

1
- (unichar)characterAtIndex:(NSUInteger)index;

于是将获取的字符与'A''Z'比较一下就好了。

读入文件并将数据保存在字符串中的方法

1
2
3
4
5
// 读入文件并将数据保存在字符串中
// 没有处理可能发生的错误
NSString *nameString = [NSString stringWithContentsOfFile:
@"/usr/share/dict/words" encoding:NSUTF8StringEncoding
error:NULL];

然后贴一下完整的源码,打开的文件是在任意一台有 Mac OS 系统的人都有的文件,不必修改任何代码就可以运行。

1
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
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[])
{
@autoreleasepool
{
// 读入文件并将数据保存在字符串中
// 没有处理可能发生的错误
NSString *nameString = [NSString stringWithContentsOfFile:
@"/usr/share/dict/words" encoding:NSUTF8StringEncoding
error:NULL];
// 将所得字符串按"\n"进行分割,并保存在数组中
NSArray *names = [nameString componentsSeparatedByString:@"\n"];
// 枚举数组,一次处理一个字符串
for (NSString *n in names)
{
// 查找字符串 "aa"(忽略大小写)
NSRange r = [n rangeOfString:@"AA" options:NSCaseInsensitiveSearch];
// 是否找到
if (r.location != NSNotFound)
{
// 获取每个单词的第一个字符
unichar firstChar = [n characterAtIndex:0];
if (firstChar >= 'A' && firstChar <= 'Z')
{
NSLog(@"%@", n);
}
}
}
}
return 0;
}

里程碑2:第一个自定义类

创建新类的步骤,Command + n,选择 macOS 区域中的 Cocoa class
图片

图片

点击 Next,填入类名,确定 Group 是存放源代码的文件夹,而不是整个项目,如下图

图片

快捷键

“Ctrl-Command-上箭头”,在 .m 文件及其对应的 .h 文件中切换。

在头文件中声明方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// BNRPerson.h
#import <Foundation/Foundation.h>
@interface BNRPerson : NSObject
{
// Step 0: BNRPerson 拥有两个实例变量
float _heightInMeters;
int _weightInKilos;
}
// Step1: 在花括号外声明 5 个实例方法
- (float)heightInMeters;
- (void)setHeightInMeters:(float)h;
- (int)weightInKilos;
- (void)setWeightInKilos:(int)w;
// BNRPerson 类拥有计算 Body Mass Index 方法
- (float)bodyMassIndex;
@end

在 .m 文件中实现方法

1
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
// BNRPerson.m
#import <Foundation/Foundation.h>
#import "BNRPerson.h"
@implementation BNRPerson
// getter method
- (float)heightInMeters
{
return _heightInMeters;
}
// setter method
- (void)setHeightInMeters:(float)h
{
_heightInMeters = h;
}
- (int)weightInKilos
{
return _weightInKilos;
}
- (void)setWeightInKilos:(int)w
{
_weightInKilos = w;
}
- (float)bodyMassIndex
{
return _weightInKilos / (_heightInMeters *_heightInMeters);
}
@end

在 main()添加使用 BNRPerson 类的代码

首先记得在 main.m 中导入头文件,当你打出 BNR… 还没有自动补全的提示,那么很可能是忘记了导入头文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// main.c
#import <Foundation/Foundation.h>
#import "BNRPerson.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
// 创建 BNRPerson 实例
BNRPerson *mikey = [[BNRPerson alloc] init];
// 使用 setter 方法为实例变量赋值
[mikey setWeightInKilos:96];
[mikey setHeightInMeters:1.8];
// 使用 getter 方法打印出实例变量的值
float height = [mikey heightInMeters];
int weight = [mikey weightInKilos];
NSLog(@"mikey is %.2f meters tall and weights %d kilograms", height, weight);
// 使用定制方法打印出 bmi 的值
float bmi = [mikey bodyMassIndex];
NSLog(@"mikey has a BMI of %f", bmi);
}
return 0;
}

存取方法及命名

getter 和 setter 方法被统称为存取方法 accessor method 或 accessor。

实例变量的命名方法是:开头是下划线,然后是小写开始的驼峰命名法。

方法不以下划线开头,存方法以 set 开头,取方法名和实例变量名相同(只是没有下划线)。

特别需要注意的是,Objective-C 中没有命名空间,所以如果你写了一个称为 Person 的类,而它连接到另一个库,其中有其他人声明的 Person 类,编译器无法区别这两个类,就会出现编译器错误。

为了避免这样的冲突,苹果公司推荐使用三个或三个以上字母作为类的前缀,让类的名字独一无二,就不会和其他人的类名起冲突。

self

Objective-C 的方法都包含一个隐含的局部变量self,它是一个指向当前方法的对象的指针。当某个对象要向自己发送消息的时候,就需要使用self。有些人坚持不直接存取实例变量(个人认为是为了避免不小心修改实例变量)的信条,他们会在类的方法中调用对象自身的存取方法。改写一些上文中的bodyMassIndex方法:

1
2
3
4
5
- (float)bodyMassIndex
{
float h = [self heightInMeters];
return [self weightInKilos]/(h*h);
}

Properties

可简化存取方法的过程。

Declaring properties

在 BNRPerson.h 中删除实例变量,然后用下面两个属性heightInMetersweightInKilos重写存取方法的声明。

1
2
@property (nonatomic) float heightInMeters;
@property (nonatomic) int weightInKilos;

声明属性的时候,编译器还会帮你声明存取方法。不过有一些例外情况,需要自己调整属性声明或实现存取方法。

Property attributes

  • nonatomic
  • atomic (default)
  • readonly
  • readwrite

例如:

1
2
3
4
5
6
@property (nonatomic, readonly) double circumferenceOfEarth;
// 属性要么是只读属性
// 要么是读/写 (readwrite) 属性
@property (nonatomic, readwrite) double humanPopulation;

Dot notation

使用 dot notation 的时候是在发送消息。

1
2
3
mikey.weightInKilos = 96;
// ...
float height = mikey.heightInMeters;

Inheritance

创建一个新类,命名为 BNREmployee,还记得怎么创建新类吗?如果不记得,查看左边的目录,点击“里程碑2:第一个自定义类”再复习一下(・ω・)ノ

在 BNREmployee.h,导入 BNRPerson.h,将父类从 NSObject 改为 NSRPerson

1
2
3
4
5
6
7
8
9
10
11
12
13
// BNREmployee.h
#import "BNRPerson.h"
@interface BNREmployee : BNRPerson
@property (nonatomic) unsigned int employeeID;
@property (nonatomic) unsigned int officeAlarmCode;
@property (nonatomic) NSDate *hireDate;
- (double)yearOfEmployment;
@end

在 BNREmployee.m 文件中,实现 yearOfEmployment 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#import "BNREmployee.h"
@implementation BNREmployee
- (double)yearOfEmployment
{
// 是否拥有一个非 nil 的 hireDate ?
if (self.hireDate)
{
NSDate *now = [NSDate date];
NSTimeInterval secs = [now timeIntervalSinceDate:self.hireDate];
return secs / 31557600.0; // 每年的秒数
}
else
{
return 0;
}
}
@end

在 main.m 中导入 BNREmployee.h,创建一个 BNREmployee 的实例。让 mikey 变量指向一个 BNRPerson 对象。

1
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
//
// main.m
// BMI
//
// Created by rooooooot on 12/10/16.
// Copyright © 2016 VectorLu. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "BNRPerson.h"
#import "BNREmployee.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
// 创建 BNRPerson 实例
BNREmployee *mikey = [[BNREmployee alloc] init];
// 使用 setter 方法为实例变量赋值
mikey.weightInKilos = 96;
mikey.heightInMeters = 1.8;
mikey.employeeID = 12;
mikey.hireDate = [NSDate dateWithNaturalLanguageString:@"Aug 2nd, 2010"];
// 使用 getter 方法打印出实例变量的值
float height = mikey.heightInMeters;
int weight = mikey.weightInKilos;
NSLog(@"mikey is %.2f meters tall and weights %d kilograms", height, weight);
NSLog(@"Employee %u hired on %@", mikey.employeeID, mikey.hireDate);
float bmi = [mikey bodyMassIndex];
double years = [mikey yearOfEmployment];
NSLog(@"BMI of %.2f, has worked with us for %.2f years", bmi, years);
}
return 0;
}

Overriding methods

通常,子类会与父类有所不同。假设,员工的 BMI 值都是 19.在这种情况下,可以覆盖BNREmployeebodyMassIndex方法。

可以编写一个新的实现来覆盖继承下来的方法。在BNREmployee.m方法中,覆盖bodyMassIndex

1
2
3
4
- (float)bodyMassIndex
{
return 19.0;
}

Inheritance hierarchy

NSObject

您的支持将鼓励我继续创作!

热评文章