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 字节的整型值(比如short或int)付给一个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

唯一必须以嵌套的形式连续发送的消息是alloc和init。

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 中删除实例变量,然后用下面两个属性heightInMeters和weightInKilos重写存取方法的声明。

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.在这种情况下,可以覆盖BNREmployee的bodyMassIndex方法。

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

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

Inheritance hierarchy

NSObject

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

热评文章