继续上一节
PS:前期为了说明白IOS开发,会截取很多书上看到的内容来更详细的说明
PS:本节中是对Object-C代码的一个应用环节,没有涉及到体现在移动端上(就是像上一节那样在模拟器上显示Hello World)
先来介绍一下类和对象
对象是面向对象编程的核心。可以通过构建类定义对象,类即为创建对象的模板。
在Objective-C中实现类与C语言类似,需要分两处进行:头文件和实现文件(具体的我就不细说了)
根据下面代码进行说明
#import <Foundation/Foundation.h>
@interface SimpleCar : NSObject {
NSString *_make;
NSString *_model;
int _year;
}
@property float fuelAmount;
- (void)configureCarWithMake: (NSString*)make
model: (NSString*)model
year: (int)year;
- (void)printCarInfo;
- (int)year;
- (NSString*)make;
- (NSString*)model;
@end
在Objective-C,类、变量和方法采用驼峰式命名法。在Objective-C中,我们会使用identifiersLikeThis
而不是identifies_like_this。类名首字母大写,而其他的名称首字母小写,例如上面代码中,SImpleCar
以大写字母开头。变量fuelAmount采用驼峰式命名法,但是以小写字母开头
- (void)printCarInfo;
@ 符号用在特定的一些关键字中。例如上述代码中的@interface和@end,这两个元素是用来话费类接口定义的开头和结尾。
@property定义变量类型是共有的,例如@property float fuelAmount;表示fuelAmount是共有的,
也就一位置fuelAmount在SimpleCar类的外部是可以使用的
Objective-C通常使用基于NSString对象的类,而不是基于字节的用char*声明类型的C字符串。因为NSString
提供的功能远远多于C字符串。
你们会注意到在有一些变量使用了下划线(_),下划线是Objective-C中区分实例变量和getter方法的一般做法,
在这里就当做是一个习惯就行(最好接受这个习惯)。
使用x=_year是直接从实例变量获得值,而x=[self year]是调用getter方法
- (int)year
可以做一些必要的计算或者在返回之前临时计算值。setter方法类似于getter,只不过是用于设置实例变量的值。当然了,setter
也可以自己重定义,这在之后用到的时候回额外说明
恩,不多说了,直接进入项目吧
首先,使用第一节中的方法创建一个CarValet应用程序项目(忘记了点此链接)
现在我们已将创建好了
现在创建一个类
右击CarValet文件夹,选择New File
选择IOS里面的Cocoa Touch Class
我们把它命名为Car
这是创建完成的后的工作区
好了,现在进入代码环节,点击Car.h,进入到Car.h文件
增加代码如下
//对于纯新手的解释://表示注释 这只能注释一行 /* 被旁边两个符号包围起来的区域全是注释 */
// Car.h
// CarValet
//
// Created by XXX on 2016/12/12.
// Copyright :emoji: 2016年 XXX. All rights reserved.
//
#import <Foundation/Foundation.h>//导入Foundation框架,在这个框架中,可以找到各种东西,从数组到日期到谓词,从URL网络连接到JSON处理,还有最最重要的对象NSObject
@interface Car : NSObject {//定义Car(NSObject的子类)对象的接口
int _year;//是汽车的生产年份,存为一个整体对象
NSString *_make;//汽车的品牌,存为一个NSString对象
NSString *_model;//汽车的型号
float _fuleAmount;//汽油,存为浮点值(也就是小数)
}
//初始化新分配的对象,并设置汽车的品牌、型号、年份和油箱中的燃料
- (id)initWithMake:(NSString*)make
model:(NSString*)model
year:(int)year
fuelAmount:(float)fuelAmount;
//向调试控制台打印出汽车的信息
- (void)printCarInfo;
//下面两行是一对方法,为_fuelAmount实例变量的getter和setter方法
- (float)fuelAmount;
- (void)setFuelAmount:(float)fuelAmount;
//其他私有实例变量的getter方法
- (int)year;
- (NSString*)make;
- (NSString*)model;
@end
以上是Car.h头文件的写法,现在要去实现Car的方法,点击Car.m
代码解释会在代码中的注释里面提到
//
// Car.m
// CarValet
//
// Created by 黄彬杨 on 2016/12/12.
// Copyright © 2016年 黄彬杨. All rights reserved.
//
#import "Car.h"
@implementation Car
- (id)init {//这是默认初始化
//在超类(NSObject)上调用init方法,这保证了NSObject要求的任何初始化逻辑,在特定于Car类的初始化逻辑之前执行完毕
self = [super init];
if (self != nil) {//确保self实际已经初始化了,如果已经初始化了,这个对象的剩余部分将被创建
_year = 1900;//默认值设置为1900
_fuleAmount = 0.0f;//默认值设置为0.0f。这里的f可以去掉,但是这是为了告诉编译器这是fload值,而不是其他类型的浮点值,建议写
}
return self;//返回self
//到此为止,Car对象已经被初始化了
}
- (id)initWithMake:(NSString *)make//分配新的对象,然后将每个值传入Car对象的属性中。这是一个自定义初始化
model:(NSString *)model
year:(int)year
fuelAmount:(float)fuelAmount {
self = [super init];//首先调用初类的初始化。如果成功,初始化对象的剩余部分;失败,返回nil
if (self !=nil) {
_make = [make copy];//为Car对象设置所有实例变量
_model = [model copy];
_year = year;
_fuleAmount = fuelAmount;
}
return self;
}
- (void)printCarInfo {//打印信息,当_make和_model不为空的时候才能打印(空的时候就是初始化没有啥意义打印也是白打)
if (!_make) return;
if (!_model) return;
//NSlog是想控制台打印的方法
NSLog(@"Car Make: %@",_make);
NSLog(@"Car Model: %@",_model);
NSLog(@"Car Year:%d",_year);
NSLog(@"Number of Gallons in Tank: %0.2f",_fuleAmount);
}
- (float)fuelAmount {
return _year;
}
- (void)setFuelAmount:(float)fuelAmount {
_fuleAmount = fuelAmount;
}
- (int)year {
return _year;
}
- (NSString*)make {
return [_make copy];
}
- (NSString*)model{
return [_model copy];
}
@end
可能会对nil这个东西有点疑惑,可以查看一下这里
还可能对copy有点疑惑,浅显一点讲,NSString类型是指针类型,如果直接返回_make或_model那么返回的是指针,同样指向了_make或_model,假设直接把_make赋值给A,那么这时候如果更改了_make后,A也会跟着变,但是A讲道理是不能变的,这时候就使用copy,就解决了这个问题。了解更多可以查看一下这里
现在说以属性这个概念
属性可以让我们定义实例变量,并让编译器访问器方法——也就是说,可以访问(get或set)变量或信息的方法。就是上面定义的
- (float)fuelAmount {
return _year;
}
- (void)setFuelAmount:(float)fuelAmount {
_fuleAmount = fuelAmount;
}
- (int)year {
return _year;
}
- (NSString*)make {
return [_make copy];
}
- (NSString*)model{
return [_model copy];
}
如何声明属性:
@property float fuelAmount;
这会让编译器创建一个实例变量和两个方法;
float _fuelAmount;
- (float)fuelAmount;
- (void)setFuelAmount:(float)fuelAmount;
会发现,这跟我们刚才写的方法一样啊这。
编译器会为我们生成下划线版本的变量。任何非汽车对象都必须使用getter和setter方法。变量和方法的实现是在编译时被添加的。而如果需要做一些特殊的事情,那么可以在.m文件总,实现特定的访问器犯法,这样,编译器就会使用我们的方法替代(虽然我们上面写的跟自动生成的一样吧。。。假装他们是不一样的)
- 现在我们把Car.m中的fuelAmount、setFuelAmount、year、make和model这下方法的实现删除
- 打开Car.h闭关移除刚才删掉的那些方法的声明
- 修改如下代码 变成下面的样子
//
// Car.h
// CarValet
//
// Created by XXX on 2016/12/12.
// Copyright © 2016年 XXX. All rights reserved.
//
#import <Foundation/Foundation.h>
@interface Car : NSObject {
int _year;
NSString *_make;
NSString *_model;
float _fuleAmount;
}
@property int year;
@property NSString *make;
@property NSString *model;
@property float fuelAmount;
- (id)initWithMake:(NSString*)make
model:(NSString*)model
year:(int)year
fuelAmount:(float)fuelAmount;
- (void)printCarInfo;
@end
PS 虽然我们删除了
- (float)fuelAmount {
return _year;
}
- (void)setFuelAmount:(float)fuelAmount {
_fuleAmount = fuelAmount;
}
- (int)year {
return _year;
}
- (NSString*)make {
return [_make copy];
}
- (NSString*)model{
return [_model copy];
}
在.m里面也没看到其他的器方法,因为我们定义了
@property int year;
@property NSString *make;
@property NSString *model;
@property float fuelAmount;
所以在编译的时候会自动生成setter和getter方法(如果我们没有自己定义的话)
现在介绍一下点表示法
点表示法允许不用方括号,就可以访问对象信息。可以使用myCar.year代替[myCar year]调用,读取year实例变量的值。看起来好像是直接访问,但是实际上不是,假设一个Car对象为my。那么my.year会自己调用[myCar year],这样有利于代码维护和可读性。
例如
用点表示法
myTableViewCell.textLabel.text=@"Hello World";
如果不用的话
[[myTableViewCell textLabel] setText:@"Hello World"];
好了,更新完代码后,Car.m中打印信息的代码也是需要更新的
- (void)printCarInfo {
if (self.make && self.model) {
NSLog(@"Car Make: %@",self.make);
NSLog(@"Car Model: %@",self.model);
NSLog(@"Car Year:%d",self.year);
NSLog(@"Number of Gallons in Tank: %0.2f",self.fuelAmount);
} else {
NSLog(@"Car undefined: no make or model specified");
}
}
这里要注意一下,有了点表示法,我们可能会在自定义的getter和setter方法中使用,但这是有风险的,最好使用“_”的版本。理由如下:
- (void) setMake:(NSString*)newMake {
if (![newMake isEqualToString:self.make) {
self.make = newMake;
}
}
这段代码是make属性的自定义setter,它检查新的make值是否与旧的make值相同,如果不同,将汽车对象的make属性设为新值牡丹石此处有一些隐藏的问题。
self.make = newMake;
可以解释为:
[self setMake:newMake];
结果即为对相同setter方法的递归调用,然后无限递归,最后程序爆炸(崩溃),正确的做法是在setter方法中使用下划线版本的变量,改为
_make = newMake;
保险的做法是让setter和getter使用它们设置或返回的实例变量的下划线版本,任何init方法或自定义初始化方法也应该使用下划线版本。
OK,现在我们把方法定义完了,也把如何实现完成了,现在开始调用吧。
打开ViewController.m文件,在最后一条import语句的下方位置,添加
#import "Car.h"
在viewDidLoad方法下面添加如下代码
- (void)viewWillAppear:(BOOL)animated {//viewWillAppear:会在ViewController的视图每次即将在屏幕上显示时被调用。
[super viewWillAppear:animated];
Car *myCar = [[Car alloc] init]; //myCar被分配,并且被初始化为Car的一个实例
[myCar printCarInfo];
myCar.make = @"Ford";
myCar.model = @"Escape";
myCar.year = 2016;
myCar.fuelAmount = 10.0f;
[myCar printCarInfo];
//采用自定义初始化
Car *otherCar = [[Car alloc] initWithMake:@"Honda"
model:@"Accord"
year:2010
fuelAmount:12.5f];
[otherCar printCarInfo];
}
运行结果如下
在这里可能会想为什么不用点方法打印
下面是点方法
虽然也能打印出来,但是会有警告
个人理解点引用是对类的成员变更的引用,包括其成员的set get方法 ,[]就是类的方法使用。所以对于类里面的方法最好还是用[]来使用。
这一节就暂时到这里了
我的另一个博客站点:Arnold-你们好啊