您的位置:首页 > 其它

第四章:面向对象(上)

2015-12-24 13:29 288 查看
Objective-C是面向对象的程序设计语言,提供了定义类,成员变量和方法的基本功能。也支持面向对象的三大特征:封装,继承和多态。


一、类和对象

1、定义类

面向对象的程序设计过程中两个重要概念:类(class)和对象(object,也称为实例,instance)。

类:一批对象的抽象,可以把类理解成某种概念,抽象的,不占用内存;

对象:是类的具体实例,是具体的,占用内存。

定义类的两个步骤:

(1)、接口部分:定义该类包含的成员变量和方法;

#import <Foundation/Foundation.h>
@interface FKPerson : NSObject
{
//下面定义两个成员变量
NSString* _name;
int _age;
}
//下面定义一个setName:andAge方法
- (void)setName:(NSString *)name andAge:(int)age;
//下面定义一个say:方法,并不提供实现
- ( void)say:(NSString *)content;
//下面定义了一个不再形参的info方法
- (NSString *)info;
//定义一个类方法
+ (void)foo;
@end


上面的语法中:

@interface用于声明定义类的接口部分,@end用于定义结束。其中紧跟该类的一对花括号用于声明该类的成员变量;花括号后面的部分用于声明该类的方法。

概念一:成员变量:用于描述该类的对象的状态数据。

概念二:方法:用于描述该类的行为;

定义成员变量的语法格式:

类型 成员变量名;


类型:可以是Objective-C语言允许的任何数据类型,包括基本类型、构造类型和各种指针类型类型。

成员变量名:从语法角度来讲,成员变量名只是一个合法的标识符即可。

注意:一般情况下成员变量名第一个单词首字母小写,后面每个单词首字母大写,其他字母全部小写,单词与单词至今不需要任何分隔符,并建议成员变量以下划线(_)开头(Xcode4开始)。


方法声明的语法格式:

方法类型标识 方法返回值类型 方法签名关键字(形参标签)形参名;


例如:

- ( void)say:(NSString *)content;


方法类型标识:该标识符不是+就是-。+代表类方法,直接用类名即可调用。-代表是实例方法,必须用对象才能调用。

方法返回值类型:返回值类型可以是Objective-C允许的任何数据类型,包括基本数据类型,构造类型和各种指针类型。

方法签名关键字:Objective-C的方法签名关键字由方法名、形参标签和冒号组成。

(2)、实现部分:为该类的方法提供实现。

#import "FKPerson.h"
@implementation FKPerson
{
//定义一个只能在实现部分使用的成员变量(被隐藏的成员变量)
int _testAttr;
}
//下面定义了一个setName:andAge:方法
- (void)setName:(NSString *)name andAge:(int)age
{
_name = name;
_age = age;
}
//下面定义一个say方法
- (void)say:(NSString *)content
{
NSLog(@"%@",content);
}
//下面定义一个不带形参的info方法
- (NSString *)info
{
[self test];
return [NSString stringWithFormat:@"我是一个人,名字为:%@,年龄为%d",_name,_age];
}
//定义一个只能在实现部分使用的方法(被隐藏的方法)
- (void)test
{
NSLog(@"--只在实现部分定义的test方法--");
}
//定义一个类方法
+ (void)foo{
NSLog(@"FKPerson类的类方法,通过类名调用");
}
@end


类实现部分的类名必须与接口部分的类名相同,用于表示这是同一个类的接口部分和实现部分。

类实现部分也可以在类名后使用“:父类”来表示继承了某个父类,但一般没有必要。

类实现部分也可声明自己的成员变量,但这些成员变量只能在当前类内访问,因此在类实现部分声明成员变量相当于定义隐藏的成员变量。

类实现部分必须为类声明部分的每个方法提供方法定义。方法定义由方法签名和方法体组成

2、对象的产生和使用

定义变量;

创建对象;

调用类方法;

(1)、定义变量语法格式:

类名* 变量名;


(2)、创建对象

[[类名 alloc]初始化方法];


或者也可以

[类名 new];


(3)、调用方法的语法格式

[调用者 方法名:形参 形参标签:参数值…];


注意:实例方法(以-声明的方法)必须用实例来调用;而类方法(以+声明的方法)则必须使用类调用。

例如:上面的FKPerson

另外讲一下成员变量访问控制符:

@perivate:只有当前类的内部才有权访问;

@public:所有人都有权访问;

@protected:只限于当前类以及他的子类才可以访问;默认是受保护的。

@package:让那些受它控制的成员变量不仅可以在当前类中访问,也可以在相同映像的其他程序中访问。

#import "FKPersonTest.h"
#import "FKPerson.h"
@implementation FKPersonTest
-(void)start
{
//创建FKPerson对象,赋给person变量
FKPerson* person = [[FKPerson alloc]init];
//在访问权限允许(@public)的情况下,通过对象访问成员变量
person->_name = @"坏孩子";
NSLog(@"person.name = %@",person->_name);//person.name = 坏孩子

//通过类来调用类方法
[person say:@"Hello,I Love iOS"];
[person setName:@"孙悟空" andAge:500];
//方法中有参数放回,定义一个类型匹配的变量接收
NSString* info = [person info];
NSLog(@"info = %@",info);//info = 我是一个人,名字为:孙悟空,年龄为500
//通过类名来调用类方法
[FKPerson foo];
}
@end


3、对象和指针

FKPerson*类型的变量本质就是一个指针变量,也就是说person变量紧紧保存了FKPerson对象在内存中的首地址。形象的说,FKPerson*类型的变量指向实际的对象。

(1)、程序中定义FKPerson*类型只是存放了一个地址值,它被保存在该main()函数的动态存储区中,它指向实际的FKPerson对象。

(2)、FKPerson对象存在堆(heap)中,当一个对象被创建成功以后,这个对象将保存在堆内存中,只能通过该对象的指针就行访问该对象。

注意:在Xcode4.2的发布,引入了自动引用计数(Automatic Prference Counting,ARC)。

4、self关键字

作用:总代表当前类的对象,只能出现在实例方法中。让类中一个方法访问该类的另一个方法或者成员变量。

当局部变量和成员变量重名的情况下,局部变量会隐藏成员变量,这时候可用self来进行区分。

//声明部分
#import <Foundation/Foundation.h>
@interface FKDog : NSObject
{
@public
NSString* _name;
int _age;
}
//定义一个setName: andAge:方法
- (void)setName:(NSString *)_name andAge:(int) _age;
//定义一个jump方法
- (void)jump;
//定义一个run方法,run方法需要借助jump方法
- (void)run;
//定义一个info方法
- (void)info;
@end


//实现部分
#import "FKDog.h"
@implementation FKDog
//实现setName: andAge:方法
- (void)setName:(NSString *)_name andAge:(int)_age{
self->_name = _name;
self->_age = _age;
}
//实现一个jump方法
- (void)jump
{
NSLog(@"正在执行jump方法");
}
//实现一个run方法
- (void)run{
[self jump];
NSLog(@"正在执行run方法");
}
//实现info方法
- (void)info{
NSLog(@"我的名字是%@,年龄是%d",_name,_age);
}
@end


5、id类型

id类型可以代表所有对象的类型,也就是说,任意类的对象都可赋值给id类型的变量。

当通过id类型的变量来调用方法时,Objective-C将会执行动态绑定,所谓动态绑定,是指:Objective-C将会跟踪对象所属的类,它会在运行时判断该对象所属的类,并在运行时确定需要动态调用的方法,而不是在编译时确定要调用的方法。

二、方法详解

1、方法的所属性

Objective-C方法不能独立存在,所有的方法都必须定义在类中,方法在逻辑上属于类或者对象。同样是值传递。

(1)、方法不能独立定义,只能在类体里定义。

(2)、从逻辑意义上看,方法要么属于该类本身,要么属于该类的一个对象。

(3)、永远不能独立执行方法,执行方法必须使用类或对象作为调用者。使用+标志的方法属于这个类本身,因为只能用类作为调用者来调用该方法;使用-标志方法属于该类的实例,必须用实例作为调用者来调用。

2、形参个数可变的方法:可以直接用数组实现

如果在发定义参数方法时,在最后一个形参后增加逗号和三点(,…),则表明该形参可以接受多个参数值。但是在一个方法中最多只能有一个长度可变的形参。

例如:

接口部分

#import <Foundation/Foundation.h>
@interface Varargs : NSObject
- (void)test:(NSString *)name,...;
@end


va_list:这是一个类型,用于定义指向可变参数列表的指针变量;

va_start:这是一个函数,该函数指定开始处理可变形参的列表,并让指针变量指向可变形参列表的第一个参数;

va_end:结束处理可变形参,释放指针变量;

var_arg:该函数返回获取指针当前指向的参数的值,并将指针移动到指向下一个参数。

实现部分:

#import "Varargs.h"
@implementation Varargs
-(void)test:(NSString *)name, ...{
//使用va_List定义一个argList指针变量,该指针变量指向可变参数列表
va_list argList;
//如果第一个name参数存在 才需要处理后面的参数
if (name)
{
//由于name参数并不在可变参数列表中,因此先处理name参数
NSLog(@"%@",name);
//让argList指向第一个可变参数列表的第一个参数 开始提取可变参数列表的参数
va_start(argList, name);
//va_arg用于提取argList指针当前指向的参数 并将指针移动到指向下一个参数
//arg变量用于保存当前获取的参数 如果该参数不为nil 进入循环体
NSString* arg = va_arg(argList, id);
while (arg) {
//打印出每一个参数
NSLog(@"%@",arg);
//再次提取下一个参数 并将指针移动到指向下一个参数
arg = va_arg(argList, id);
}
//释放argList指针 结束提取
va_end(argList);
}
}


注意:调用的时候在后面需要添加nil

例如:

Varargs *var = [[Varargs alloc]init];
[var test:@"nihao",@"buhao",@"weishenm",nil];


三、成员变量

根据定义变量位置的不同,可将变量分为三大类:成员变量,局部变量,全局变量。

1、成员变量及其运行机制

成员变量:在类接口部分或实现部分定义的变量。实例变量是从该类的实例被创建开始起存在的,指导系统完全被销毁这个实例,实例变量的作用域与对应实例的生存范围相同。

实例—>实例变量

成员变量无须显示初始化,只要为一个类定义了实例变量,系统就会为实例变量执行默认初始化,基本类型的实例变量默认被初始化为0;指针类型的成员变量默认被初始化为nil。

2、模拟类变量

static关键字:不能用于修饰成员变量,只能修饰局部变量、全局变量和函数。

static修饰局部变量表示将该局部变量存储到静态存储区;

static修饰全局变量用于限制该全局变量只能在当前源文件中访问;

static修饰函数用于限制函数只能在当前源文件中调用。

例如:

FKUser的接口部分

#import <Foundation/Foundation.h>
@interface FKUser : NSObject
+ (NSString *)nation;
+ (void)setNation:(NSString *)newNation;
@end


FKUser的实现部分

#import "FKUser.h"
static NSString* nation = nil;
@implementation FKUser
+ (NSString *)nation{
return nation;//返回nation全局变量
}
+ (void)setNation:(NSString *)newNation{
//对nation全局变量进行赋值
if (![nation isEqualToString:newNation]) {
nation = newNation;
}
}
@end


main调用

[FKUser setNation:@"中国"];
NSLog(@"%@",[FKUser nation]);//中国


3、单例(singleton)模式

单例类:一个类始终只能创建一个实例。通过static全局变量来实现的。

例如:

Singleton的接口部分

#import <Foundation/Foundation.h>
@interface Singleton : NSObject
+ (id)instance;
@end


Singleton的实现部分

#import "Singleton.h"
static id instance = nil;
@implementation Singleton
+ (id)instance
{
//如果instance为nil
if (!instance)
{
//创建一个Singleton实例 并将该实例赋给instance全局变量
instance = [[super alloc]init];
}
return instance;
}
@end


mian函数部分

NSLog(@"%d",[Singleton instance] == [Singleton instance]);//1


四、隐藏和封装

1、理解封装

面向对象的三大特征:封装,继承,多态。

封装:将对象的状态信息隐藏在对象内部,不允许外部程序直接访问对象内部信息,而是通过该类所提供的方法实现对内部信息的操作和访问。

对一个类或对象实现良好的封装,可以实现一下目的:

隐藏类的实现细节;

让使用者只能通过事先预定的方法来访问数据,从而可以在该方法里加入控制逻辑限制对成员变量的不合理访问;

可进行数据检查,从而有利于保证对象信息的完整性;

便于修改,提高代码的可维护性;

实现良好的封装没需要从以下两个方面考虑

将对象的成员变量和实现细节隐藏起来,不允许外部直接访问;

把方法暴露出来,让方法来控制对这些成员变量进行安全的访问和操作。

2、使用访问控制符

4个控制符:@private,@package,@protected和@public。

@private(当前类访问权限):如果类的成员变量是使用@private访问控制符来限制,则这个成员只能在当前类的内部被访问。在类的实现部分定义的成员变量相当于默认使用这种访问权限。

@package(与映像访问权限相同):如果类的成员变量使用@package访问控制来限制,则这个成员可以子啊当前类以及当前类实现的同一个映像的任意地方访问。

@protected(子类访问权限):如果类的成员变量使用@protected访问控制来限制,则这个成员变量可以在当前类、当前类的子类的任意地方访问。在类的接口部分定义的成员变量默认使用这种访问权限。

@public(公共访问权限):如果类的成员变量使用@public限制,这个成员变量可以在任意地方访问。

例如:

FKPerson的接口部分

#import <Foundation/Foundation.h>
@interface FKPerson : NSObject
{
//使用@private限制成员变量
@private
NSString* _name;
int _age;
}
//提供方法来操作name Field
- (void)setName:(NSString *)name;
//提供方法来获取_name成员变量的值
- (NSString *)name;
//提供方法来获取age成员变量的值
- (void)setAge:(int)age;
//提供方法来获取_age成员变量的值
- (int)age;
@end


注意:访问控制符相当于开关,从他们出现的位置开始,到下一个权限控制符或右花括号之间的成员变量,都受该访问权限控制符控制。

FKPerson的实现部分

#import "FKPerson.h"
@implementation FKPerson
//提供方法来设置_name成员变量
- (void)setName:(NSString *)name{
//执行合理性校验 要求用户名必须在2~6位之间
if ([name length] > 6 || [name length] < 2) {
NSLog(@"您设置的人名不符合要求");
return;
}else{
_name = name;
}
}
//提供方法来获取_name成员变量的值
- (NSString *)name{
return _name;
}
//提供方法来设置age成员变量
- (void)setAge:(int)age{
if (_age != age) {
//执行合理性校验 要求用户年龄必须在0~100之间
if (age > 100 || age < 0){
NSLog(@"您设置的年龄不合法");
return;
}else{
_age = age;
}
}
}
//提供方法来获取_age成员变量的值
- (int)age{
return _age;
}
@end


注意:在FKPerson类之外的只能通过各自对应的setter和getter方法来操作和访问他们。

main函数部分

FKPerson* p = [[FKPerson alloc]init];
//应为age成员变量已经被隐藏,所以下面的语句将出现编译错误
//p->_age = 1000;
//下面的语句编译不会出现错误 但运行时将提示age变量不合法
//程序不会修改p的age成员变量
[p setAge:1000];
//访问p的age成员变量也必须通过其对应的getter方法
//因为上面从未成功设置p的age成员变量 故此处输出0
NSLog(@"未能设置age成员变量时:%d",[p age]);
//成功修改p的age成员变量
[p setAge:30];
//因为上面成功设置了p的age成员变量 故此处输出30
NSLog(@"成功设置age成员变量后:%d",[p age]);
//不能直接操作p的name成员变量 只能通过其对应的setter方法
//因为“李刚”字符串的长度满足2~6 所以可以成功设置
[p setName:@"李刚"];
NSLog(@"成功设置name成员变量后:%@",[p name]);


3、理解@package访问权限

同一映像:编译后生成的同一个框架或同一个执行文件

4、合成存取方法

让系统自动生成setter方法和getter方法如下两步:

首先,在类接口部分使用@property指定定义属性。使用@property定义属性时无需放在类接口部分的花括号里,而是直接放在@interface、@end之间定义。@property指示符放在属性定义的最前面。

其次 在类实现部分使用@synthesize指令声明该属性即可,

@synthesize的语法格式如下:

@synthesize property名 [= 成员变量名];


如果使用如下的代码

@synthesize window = _window;用于告诉系统合成的property对应的成员变量为_window,而不是window。


例如:

FKUser接口部分

#import <Foundation/Foundation.h>
@interface FKUser : NSObject
//使用@property定义3个property
@property (nonatomic)NSString* name;
@property NSString* pass;
@property NSDate* brith;
@end


FKUser实现部分

#import "FKUser.h"
@implementation FKUser
//为3个property合成setter和getter方法
//指定name property底层对应的成员变量名为_name
@synthesize name = _name;
@synthesize pass;
@synthesize brith;
//实现自定义的setName:方法 添加自己的控制逻辑
- (void)setName:(NSString *)name{
self->_name = [NSString stringWithFormat:@"+++%@",name];
}
@end


注意:生成了三个成员变量分别是_name、pass、brith。

main函数部分

//创建FKUser对象
FKUser* user = [[FKUser alloc]init];
//调用setter方法修改user成员变量的值
[user setName:@"admin"];
[user setPass:@"1234"];
[user setBrith:[NSDate date]];
//访问user成员变量的值
NSLog(@"管理员账号为:%@,密码为:%@,生日为:%@",[user name],[user pass],[user brith]);//管理员账号为:+++admin,密码为:1234,生日为:2014-11-08 09:12:08 +0000


当使用@property定义property时还可在@property和类型之间用括号添加一些额外的指示符

指示符如下

assign:该提示符制定对属性只是进行简单赋值,不更改对所赋的值的引用计数。这个指示符主要使用于NSInteger等基础类型,以及short、float、double、结构体等各种C数据类型。

atomic(nonatomic):指定合成的存取方法是否为原子操作。所谓原子操作,主要指是否线程安全。如果使用atomic,那么合成的存、取方法都是线程安全的——当一个线程进入存、取方法的方法体之后,其他线程无法进入该存、取方法,atomic是默认值。但atomic的线程安全会造成性能下降,大多数单线程环境下,我们都会考虑使用nonatomic来提高存取方法的访问性能。

copy:如果使用copy指示符,当调用setter方法对成员变量赋值时,会将被赋值的对象复制一个副本,再将该副本赋值给成员变量。copy指示符将原成员变量所引用对象的引用计数减1。当成员变量的类型是可变类型,或其子类是可变类型时,被赋值的对象有可能在赋值之前被修改,如果程序不需要这种修改影响setter方法设置的成员变量的值,此时就可以考虑使用copy指示符。

例如:

FKBookTest的接口部分

#import <Foundation/Foundation.h>
@interface FKBookTest : NSObject
@property (nonatomic)NSString* name;
@end


FKBookTest的实现部分

#import "FKBookTest.h"
@implementation FKBookTest
@synthesize name;
@end


main函数部分

FKBookTest* book = [[FKBookTest alloc]init];
NSMutableString* str = [NSMutableString stringWithString:@"iOS"];
[book setName:str];//对book的name属性赋值
NSLog(@"book的name属性为:%@",[book name]);// book的name属性为:iOS
[str appendString:@"我很喜欢"];//修改str的字符串
//该代码将会看到book的name属性也会被修改
NSLog(@"book的name属性为:%@",[book name]);// book的name属性为:iOS我很喜欢


getter、setter:这两个指示符用于合成的getter方法、setter方法指定自定义方法名。

例如:

FKItemTest的接口部分

#import <Foundation/Foundation.h>
@interface FKItemTest : NSObject
//使用@property定义1个property,并指定自定义getter、setter方法名
@property (assign,nonatomic,getter=wawa,setter=nana:)int price;
@end


FKItemTest的实现部分

#import "FKItemTest.h"
@implementation FKItemTest
@synthesize price;
@end


main函数部分

FKItemTest* item = [[FKItemTest alloc]init];
//设置price属性
[item nana:30];
//访问price属性
NSLog(@"item的price为:%d",[item wawa]);//tem的price为:30


注意:setter方法需要带参数,所以后面需要加一个冒号。

readonly,readwrite:

readonly指示系统只合成getter方法,不在合成setter方法;

readwrite指示系统需要合成getter和setter方法;

retain:当把某个对象赋值给该属性时,该属性原来所引用的对象的引用计数减1,被赋值对象的引用计数加1。(未启用ARC机制的情况下,当一个对象大的引用计数大于1时,该对象不会被回收,但启用ARC时,一般少用retain指示符)。

例如:

FKItemTest接口部分

#import <Foundation/Foundation.h>
@interface FKItemTest : NSObject
@property (nonatomic,retain) NSDate* date;
@end


main的部分

FKItemTest* item = [[FKItemTest alloc]init];
NSDate* date = [[NSDate alloc]init];
//第一次赋值时,data的引用计数为1
NSLog(@"date的引用计数为:%ld",date.retainCount);
//由于使用了retain指示符,赋值时导致data的引用计数+1
[item setDate:date];
//下面的引用计数为2
NSLog(@"date的引用计数为:%ld",[item date].retainCount);
//释放data的引用计数,data的引用计数-1
[date release];
//下面输出的引用计数为1
NSLog(@"date的引用计数为:%ld",[item date].retainCount);


strong,weak:

strong指示符指定该属性被赋值对象持有强引用,强引用:只要该强引用指向被赋值的对象,那么该对象就不会自动回收。

weak指示符指定该属性被赋值对象持有若引用,若引用:即使该若引用指向被赋值的对象,该对象也可能被回收,当weak指针所引用的对象被回收后,weak指针会被赋为nil,不会导致程序崩溃。

unsafe_unretained:与weak类似,不过对于unsafe_unretained指针所指向的对象,该对象也可能被回收,当unsafe_unretained指针所引用的对象被回收后,unsafe_unretained指针不会被赋为nil,可能导致程序崩溃。

5、使用点语法访问属性

程序调用点语法获取指定对象的属性时,本质上就是返回该对象的getter方法的返回值。

程序调用点语法设置对象的属性值时,本质上就是返回该对象的setter方法进行设置。

五、键值编码(KVC)与键值监听(KVO)

KVC:Key Value Coding
KVO:Key Value Observing


1、简单的KVC

setValue:属性值 forKey:属性名:为指定属性设置值;

搜索顺序:(1)setName:;(2)_name成员变量;(3)name成员变量。

valueForKey:属性名:获取指定属性的值;

搜索顺序:(1)name方法;(2)_name成员变量;(3)name成员变量。

例如:

FKUser的接口部分

#import <Foundation/Foundation.h>
@interface FKUser : NSObject
//使用@property定义3个property
@property (nonatomic,copy)NSString* name;
@property (nonatomic,copy)NSString* pass;
@property (nonatomic,copy)NSDate* brith;
@end


mian的部分

//创建FKUser对象
FKUser* user = [[FKUser alloc]init];
//使用KVC方式为name属性设置属性值
[user setValue:@"孙悟空" forKey:@"name"];
//使用KVC方式为pass属性设置属性值
[user setValue:@"125" forKey:@"pass"];
//使用KVC方式为brith属性设置属性值
[user setValue:[[NSDate alloc]init] forKey:@"brith"];
//使用KVC获取FKUser对象的属性
//user的name为:孙悟空
NSLog(@"user的name为:%@",[user valueForKey:@"name"]);
//user的pass为:125
NSLog(@"user的pass为:%@",[user valueForKey:@"pass"]);
//user的brith为:2014-11-10 02:56:57 +0000
NSLog(@"user的brith为:%@",[user valueForKey:@"brith"]);


对于“setValue:属性值 forKey:@“name”;”代码,底层的执行机制如下:

程序优先考虑调用setName:方法,代码通过setter方法完成设置;

如果没有setName:方法,KVC机制会搜索该类名为_name的成员变量,无论该成员变量在类接口部分定义,还是在类实现部分定义,也无论用哪个访问控制符修饰,该KVC代码底层实际上就是对_name成员变量赋值。

如果该类既没有setName:方法,也没有定义_name成员变量,KVC机制会搜索该类名为name的成员变量,无论该成员变量在类接口部分定义,还是在类实现部分定义,也无论用哪个访问控制符修饰,该KVC代码底层实际上就是对name成员变量赋值。

如果上面3条都没有找到,系统将会执行该对象的setValue:forUndefinedKey:方法。

提示:setValue:forUndefinedKey:方法实现就是引发一个异常,这个异常将会导致系统因为异常结束。


对于“valueForKey:@“name”;”代码,底层的执行机制如下:

程序优先考虑调用“name;”代码来获取该getter方法的返回值;

如果该类没有name方法,KVC机制会搜索该类名为_name的成员变量,无论该成员变量实在类接口部分定义还是在类实现部分定义,也无论用哪个访问控制符修饰,这条KVC代码底层实际就是返回_name成员变量的值。

如果该类既没有name方法,也没有定义_name的成员变量,KVC机制会搜索该类名为name的成员变量,无论该成员变量实在类接口部分定义还是在类实现部分定义,也无论是哪个访问控制符修饰,这条KVC代码底层实际就是返回name成员变量的值。

如果上面3条都没有找到,系统将会执行该对象的valueForUndefinedKey:方法。

提示:默认的valueForUndefinedKey:方法实现就是一个异常,这个异常将会导致系统因为异常结束。


2、处理不存在的key

(1)、使用setValue:属性值 forKey:@“name”方法时,若没有setName:方法或者_name成员变量或者name成员变量的错误提示为

* Terminating app due to uncaught exception ‘NSUnknownKeyException’, reason: ‘[ setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key name.’

此时重写该方法:

- (void)setValue:(id)value forUndefinedKey:(NSString *)key{

NSLog(@”您尝试设置的key[%@]不存在”,key); //您尝试设置的key[name]不存在

}

(2)、使用valueForKey:@”name”方法,若没有name方法或者_name成员变量或者name成员变量的错误提示为

*** Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[<FKUser 0x100202e20> valueForUndefinedKey:]: this class is not key value coding-compliant for the key name.'


此时重写该方法:

- (id)valueForUndefinedKey:(NSString *)key{
NSLog(@"您尝试访问的key[%@]不存在",key); //您尝试访问的key[name]不存在
return nil;
}


3、处理nil值

为不能为nil的成员变量赋值为nil的时候,出现的错误提示为

Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '[<FKUser 0x10010a0f0> setNilValueForKey]: could not set nil as the value for the key price.'


此时重写方法

例如:

FKUser的接口部分

#import <Foundation/Foundation.h>
@interface FKUser : NSObject
//使用@property定义2个property
@property (nonatomic,copy)NSString* name;
@property (nonatomic,assign)int price;
@end


FKUser的实现部分

- (void)setNilValueForKey:(NSString *)key{
//如果尝试将key为price的属性设置为nil
if ([key isEqualToString:@"price"]) {
//将price设置为0
_price = 0;
}else{
//回调父类的setNilValueForKey,执行默认行为
[super setNilValueForKey:key];
}
}


main的部分

//创建FKUser对象
FKUser* user = [[FKUser alloc]init];
//使用KVC方式尝试为name,price设置属性值为nil
[user setValue:nil forKey:@"name"];
[user setValue:nil forKey:@"price"];
//使用KVC获取FKUser对象的属性
//user的name为:nil
//user的price为:0
NSLog(@"user的name为:%@",[user valueForKey:@"name"]);
NSLog(@"user的price为:%@",[user valueForKey:@"price"]);


4、key路径

复合属性:KVC机制中称其为Key路径,比如,FKOrder对象内包含一个FKUser类型的user属性,而FKUser对象又包含了name属性和price属性,那么KVC可以通过user.name、user.price这种Key路径来支持操作FKOrder对象的name、price属性。

KVC协议中为操作Key路径的方法如下:

setValue:forKeyPath:根基Key路径设置属性值;
valueForKeyPath:根据key路径获取属性值;


例如:

FKOrder的接口部分

#import <Foundation/Foundation.h>
#import "FKUser.h"
@interface FKOrder : NSObject
@property (nonatomic ,strong)FKUser* user;
@property (nonatomic ,assign)int amount;
@end


main的实现

//创建FKOrder对象
FKOrder* order = [[FKOrder alloc]init];
//使用KVC方式为amount设置属性值
[order setValue:@"12" forKey:@"amount"];
//order.user = [[FKUser alloc]init];
[order setValue:[[FKUser alloc]init] forKey:@"user"];
//使用setValue:forKeyPath设置user属性的name属性
[order setValue:@"鼠标" forKeyPath:@"user.name"];
//使用valueForKeyPath来获取复合属性值
[order setValue:[NSNumber numberWithInt:20] forKeyPath:@"user.price"];
NSLog(@"订单包含:%@个,总价为:%@",[order valueForKeyPath:@"user.name"],[order valueForKeyPath:@"user.price"]);//订单包含:鼠标个,总价为:20


5、键值监听(KVO)

addObserver:forKeyPath:options:context::注册一个监听器用于监听指定的key路径

removeObserver:forKeyPath::为key路径删除指定的监听器。

removeObserve:forKeyPath:context::为key路径删除指定的监听器,只是多了一个参数。

当数据模型组件的key路径对应属性值发生改变时,作为监听器的视图组件将会被激发,激发时调用监听器本身,方法如下:

observeValueForKeyPath:ofObject:change:context:

KVO步骤:

为被监听对象(通常是数据模型组件)注册监听器;

重写监听器的observeValueForKeyPath:ofObject:change:context:方法。

例如:

在FKItemView的接口部分

#import <Foundation/Foundation.h>
#import "FKItem.h"
@interface FKItemView : NSObject
@property (nonatomic ,weak)FKItem* item;
- (void)showItemInfo;
@end


其中item中又两个属性name(NSString *)和price(int);


在FKItemView的实现部分

#import "FKItemView.h"
@implementation FKItemView
@synthesize item = _item;
- (void)showItemInfo{
NSLog(@"item物品名为:%@,物品价格:%d",self.item.name,self.item.price);
}
//自定义setItem:方法
- (void)setItem:(FKItem *)item{
self -> _item = item;
//为item添加监听器self,监听item的name属性的改变
[self.item addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:nil];
//为item添加监听器self,监听item的price属性的改变
[self.item addObserver:self forKeyPath:@"price" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:nil];
}
//重写该方法,当被监听的数据模型发生改变时,就会毁掉监听器的该方法
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{
NSLog(@"--observeValueForKeyPath--方法被调用");
NSLog(@"被修改的keyPath为:%@",keyPath);
NSLog(@"被修改的对象为:%@",object);
NSLog(@"被修改的旧值为:%@",[change objectForKey:@"old"]);
NSLog(@"新被修改的属性值为:%@",[change objectForKey:@"new"]);
NSLog(@"被修改的上下文为:%@",context);
}
- (void)dealloc{
[self.item removeObserver:self forKeyPath:@"name"];
[self.item removeObserver:self forKeyPath:@"price"];
}
@end


main的实现部分

//创建FKItem对象
FKItem* item = [[FKItem alloc]init];
//设置item的属性
item.name = @"疯狂讲义";
item.price = 109;
//创建FKItemView对象
FKItemView* itemView = [[FKItemView alloc]init];
//将itemView的item属性设为item
itemView.item = item;
[itemView showItemInfo];//item物品名为:疯狂讲义,物品价格:109
//再次更改item的对象的属性,将会激发监听器的方法
item.name = @"疯狂iOS讲义";
item.price = 111;
[itemView showItemInfo];//item物品名为:疯狂iOS讲义,物品价格:111


输出部分

--observeValueForKeyPath--方法被调用
被修改的keyPath为:name
被修改的对象为:<FKItem: 0x100103640>
被修改的旧值为:疯狂讲义
新被修改的属性值为:疯狂iOS讲义
被修改的上下文为:(null)
--observeValueForKeyPath--方法被调用
被修改的keyPath为:price
被修改的对象为:<FKItem: 0x100103640>
被修改的旧值为:109
新被修改的属性值为:111
被修改的上下文为:(null)


六、对象的初始化

[[类名 alloc]init];

[类名 new];

1、为对象分配空间

当程序通过某个类的alloc类方法时,系统帮我们完成如下事情:

- (1)、系统为该对象的多有实例变量分配内存空间。

- (2)、将每个实例变量的内存空间都重置为0。具体的说,所有的整型变量所在的空间都重置为0;所有的浮点型变量所在的空间都重置为0.0;所有的BOOL型变量都重置为NO;所有的指针类型变量都重置为nil。

2、初始化方法与对象初始化

就行初始化可以重写父类的init方法:

例如:

FKUser的接口部分

#import <Foundation/Foundation.h>
@interface FKUser : NSObject
@property (nonatomic ,copy)NSString* name;
@property (nonatomic ,copy)NSString* address;
@property (nonatomic ,assign)int age;
@end


FKUser的方法实现部分

#import "FKUser.h"
@implementation FKUser
@synthesize name;
@synthesize age;
@synthesize address;
//重写init方法,完成自定义初始化
- (id)init{
//调用父类的init方法执行初始化,将初始化的得到的对象赋值给self对象
//如果self不为nil,表明父类init方法初始化成功
if (self = [super init]) {
//对该对象的name,age,address赋初始值
self.name = @"孙悟空";
self.age = 500;
self.address = @"花果山,水帘洞";
}
return self;
}


mian函数的实现部分

FKUser* user = [[FKUser alloc]init];
NSLog(@"user的name为:%@",user.name);//user的name为:孙悟空
NSLog(@"user的age为:%d",user.age);//user的age为:500
NSLog(@"user的address为:%@",user.address);//user的age为:500


3、便利的初始化方法

例如:

FKCar的接口部分

#import <Foundation/Foundation.h>
@interface FKCar : NSObject
@property (nonatomic ,copy)NSString* brand;
@property (nonatomic ,copy)NSString* model;
@property (nonatomic ,copy)NSString* color;
//定义initWithBrand: model:方法 完成自定义初始化
- (void)initWithbrand:(NSString *)name model:(NSString *)model;
//定义initWithBrand: model: color:方法,完成自定义初始化
- (void)initWithbrand:(NSString *)name model:(NSString *)model color:(NSString *)color;
@end


FKCar的方法实现部分

#import "FKCar.h"
@implementation FKCar
@synthesize brand =_brand;
@synthesize model = _model;
@synthesize color = _color;
//重写init方法,完成自定义初始化
- (id)init{
//调用父类init方法执行初始化,将初始化得到的对象赋值给self对象
//如果self不为nil,表明父类的init方法初始化成功
if (self = [super init]) {
self.brand = @"奥迪";
self.model = @"Q5";
self.color = @"黑色";
}
return self;
}
//实现initWithBrand: model:方法,完成自定义初始化
- (id)initWithbrand:(NSString *)brand model:(NSString *)model{
//调用父类init方法执行初始化,将初始化得到的对象赋值给self对象
//如果self不为nil,表明父类的init方法初始化成功
if (self = [super init]) {
//对该对象的brand,model,color赋初始值
self.brand = brand;
self.model = model;
self.color = @"黑色";
}
return self;
}
- (id)initWithbrand:(NSString *)brand model:(NSString *)model color:(NSString *)color{
//调用父类init方法执行初始化,将初始化得到的对象赋值给self对象
//如果self不为nil,表明父类的init方法初始化成功
if (self = [self initWithbrand:brand model:model]) {
self.color = color;
}
return self;
}
@end


七、类的继承

1、类的继承

Objective-C里子类继承父类的语法格式如下:

@interface SubClass:SuperClass
{
//成员变量定义
}
//方法定义部分
@end


当子类扩展父类时,子类可以继承得到父类的如下东西:

(1)、全部成员变量。

(2)、全部方法(包括初始化方法)。

从子类的角度看,子类扩展(extends)了父类;但从父类的角度来看,父类派生(derive)了子类。

2、重写父类的方法

当子类重写父类的方法时,子类接口部分并不需要重新声明要重写的方法,只要在类实现部分直接重写该方法即可。方法重写也叫方法覆盖。但重写了父类的方法,子类在运行该方法时会现在子类中查找此方法,若找到就运行;若没有,在从父类方法中寻找。

3、super关键字

super是Objective-C提供一个关键字,super用于限定该对象调用它从父类继承得到的属性或方法。

super跟self一样,不能出现在类方法中。

注意:无论父类接口部分的成员变量使用任何种访问控制符限制,子类接口部分定义的成员变量都不能与父类接口部分定义的成员变量重名。但是父类在类实现部分定义的成员变量将被定义在该类的内部,因此,父类在类实现部分定义的成员变量对子类没有任何影响。无论是在接口部分还是在是实现部分定义的成员变量,子类都可以与父类实现部分定义的成员变量重名。

八、多态

1、多态性

Objective-C指针类型的变量有两个:一个是编译时的类型,一个是运行时的类型,编译时的类型由声明该变量时使用的类型决定,运行时的类型由实际赋给该变量的对象决定。如果编译时类型和运行时类型不一致,就可能出现所谓的多态。

例如:

父类FKBase的接口部分

#import <Foundation/Foundation.h>
@interface FKBase : NSObject
- (void)base;
- (void)test;
@end


父类FKBase的实现部分

#import "FKBase.h"
@implementation FKBase
- (void)base{
NSLog(@"父类的普通base方法");
}
- (void)test{
NSLog(@"父类的将被覆盖的test方法");
}
@end


子类FKSubClass的接口部分

#import "FKBase.h"
@interface FKSubClass : FKBase
- (void)sub;
@end


子类FKSubClass的实现部分

#import "FKSubClass.h"
@implementation FKSubClass
- (void)test{
NSLog(@"子类的覆盖父类的test方法");
}
- (void)sub{
NSLog(@"子类的方法");
}
@end


mian的实现部分

FKBase* bc = [[FKBase alloc]init];
//下面两次调用将执行BaseClass的方法
[bc base];//父类的普通base方法
[bc test];//父类的将被覆盖的test方法
//下面编译时类型和运行时类型完全一样,因此不存在多态
FKSubClass* sc = [[FKSubClass alloc]init];
//下面调用将执行从父类继承到得base方法
[sc base];//父类的普通base方法
//下面调用将执行子类重写的test方法
[sc test];//子类的覆盖父类的test方法
//下面调用将执行子类定义的sub方法
[sc sub];//子类的方法
//下面编译时类型和运行时类型不一样,多态发生
FKBase* ploymophicBc = [[FKSubClass alloc]init];
//下面将执行从父类继承到的base方法
[ploymophicBc base];//父类的普通base方法
//下面将执行子类重写的test方法
[ploymophicBc test];//子类的覆盖父类的test方法
//FKBase类没有提供sub方法,所以下面代码编译时会出现错误
//[ploymophicBc sub];
//可以将任何类型的指针变量赋值给id类型的变量
id dyna = ploymophicBc;
[dyna sub];//子类的方法


注释:当把一个子类对象直接赋给父类指针变量,例如上面的FKBase* ploymophicBc = [[FKSubClass alloc]init];这个ploymophicBc变量在编译时类型时FKBase,而运行时类型时FKSbuclass,当运行时调用该指针变量的方法时,其方法行为总是变现出子类方法的行为特征,而不是父类的方法行为特征,这就可能出现:相同类型的变量在调用同一个方法时呈现出多种不同的行为特征,这就是多态。


注意:编写Objective-C代码时,指针变量只能调用声明时多用类中包含的方法。这就出现了id类型,程序可以对任意对象或任何类型的指针变量赋值为id类型的变量,而且使用id类型的变量可以调用该变量实际所指对象的方法。

2、指针变量的强制类型转换

类型强制转换运算符用法:(type*)variable;可以使variable变量转化成type类型的变量,但是这种类型转换只是改变该指针变量的编译时类型,该变量所指向对象的实际类型并不会发生任何改变。

注意:当把子类对象赋给父类指针变量时,被称为向上转型(upcasing),这种转型只是表明这个指针变量的编译类型是父类,但实际执行它的方法时,依然变现出子类对象的行为方式。

3、判断指针变量的实际类型

两种方法:

(1)、- (BOOL)isKindOfClass:class:判断该对象是否为class或其子类的实例。(实例方法)

(2)、+ (BOOL)isSubclassOfClass:class:判断该对象是否为class的子类的实例。(类方法)

//声明hello时使用NSObject类,则hello编译时类型时NSObject
//NSObject是所有类的父类,当hello变量的实际类型时NSString
NSObject* hello = @"hello";
//使用
NSLog(@"字符串是否是NSObject类的实例:%d",[hello isKindOfClass:[NSObject class]]);//1
NSLog(@"字符串是否是NSString类的实例:%d",[hello isKindOfClass:[NSString class]]);//1
NSLog(@"NSObject是否是NSObject的子类%d",[NSObject isSubclassOfClass:[NSObject class]]);//1
NSLog(@"NSString是否是NSObject的子类%d",[NSObject isSubclassOfClass:[NSString class]]);//0
NSLog(@"字符串是否是NSDate类的实例:%d",[hello isKindOfClass:[NSDate class]]);//0
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: