Objective-C 编程全解-第04章 对象的类型和动态绑定

第04章
对象的类型和动态绑定

Objective-C的一个重要特征就是动态性,本章将对Objective-C的动态类型(dynamic
typing)和动态绑定(dynamic binding)进行说明。

4.1 动态绑定

4.1.1 什么是动态绑定

Objective-C中的消息是在运行时才去绑定的。运行时系统首先会确定接受者的类型(动态类型识别),然后根据消息名在类的方法列表里选择相应的方法执行,如果没有找到就到父类中去继续寻找,假如一直找到NSObject也没有找到就要调用的方法,就会报告不能识别消息的错误。

动态绑定(dynamic binding)指的就是在程序执行时才能确定对象的属性和需要相应的消息。

4.1.2 多态

在面向对象的程序设计理论中,多态(polymorphism)是指,同一操作作用不不同的类的实例时,将产生不同的执行结果。即不同的类的对象收到相同的消息时,也能得到不同的结果。

polymorphic  [?p?l?‘m?:f?k]  adj.
多形的,多态的,多形态的; 
多晶形

polymorphism  [?p?l?‘m?:f?z?m]  n.
多型现象,多态性;  多机组合形式; 
多形性

prism |?pr?z?m| noun
棱镜

多态是面向对象编程的一个重要特征,大大增强了软件的灵活性和扩展性。

例如若使用面向过程的思想,则每增加一种处理就有可能需要增加一个分支,而且可能需要在所有相关的地方增加处理。但是使用多态,就可以大大减少类似情况,可能只需要增加一个类即可。

4.2 作为类型的类

4.2.1 把类作为一种类型

我们可以把定义好的类
作为
对象的类型。如NSObject
*object;

4.2.2 空指针nil

如果给nil变量发送消息会怎么样呢?虽然这种写法是完全有效的,但是运行时不会有任何作用,消息也不会被发送。

如下例子:

if((val = [list entryForKey:”NeXT]) != nil)

{

[val increment]; //increment |???kr?m?nt| noun
增加、增值

}

给nil变量发送消息,消息也不会被发送,所以上面的程序就等价于下面的程序。

val = [list entryForKey:”NeXT”];

[val increment];

第二种写法虽然使程序变得更简洁,但却使程序变的更难理解。因为程序的本意是返回值为nil时不做任何处理。另外,不注意的话这种写法也会带来各种各样的错误,例如下面这段程序。

if ((val = [list entryForKey:”NeXT”]) != nil)

{

[val setValue:n++];

}

虽然val是nil是消息不会被发送,但是并不意味着[val
setValue:n++]不会被执行。如果上面这段代码中省略了val != nil的判断,n++就仍然会被执行,这点同判断语句中的“短路”有所不同。

那么,如果向nil发送消息,那么这个消息的返回值是什么?一般来说,如果消息对应的方法的返回值是一个对象,那么将返回nil;如果消息对应的方法的返回值是指针类型,将返回NULL;如果消息对应方法的返回值是整数类型,则将返回0。而如果返回值的类型是以上这几种类型以外的类型,例如结构体或者实数,那么返回值将是未定意的;

typedef struct

{

int one;

int two;

} MyTestStruct;

- (MyTestStruct)structTestFunc

{

MyTestStruct result = {1,2};

return result;

}

test = nil;

MyTestStruct myStruct = [test
structTestFunc];

NSLog(@"%d,%d,%lu",myStruct.one,myStruct.two,sizeof(myStruct));//0,0,8

NSLog(@"%d,%d,%lu",[test
structTestFunc].one,[test
structTestFunc].two,sizeof([test
structTestFunc]));//0,0,8

4.2.3 静态类型检查

虽然在Objective-C中id数据类型可以用来存储任何类型的对象,但绝大多数情况下我们还是将一个变量声明为特定类的对象,这种情况称为静态类型。使用静态类型时,编译器在编译时可以进行类型检查,如果类型不符合会提示警告。

在Objective-C中,id类型是一种通用的数据类型,类似于C语言的(void*),可以用来存储任何类的对象。在程序执行期间,这种数据类型真正的优势就会体现出来,使用id类型结合多态,可以使程序具备更大的灵活性和可伸缩性。但程序变灵活的同时,也需要付出代价,编译器不会对id类型的变量进行检查,这会导致程序更容易出错。

在一些复杂的程序中,使用强制类型转换时,也一定要注意类型,否则就会引发运行时错误。

对于NSObject对象,即使进行强制类型转换,实际执行时的方法也是有对象本应有的类型决定。

4.2.4 静态类型检查的总结

(6)若要判断到底是哪个类的方法被执行了,不要看变量所声明的类型,而要看实际执行时这个变量的类型。

(7)id类型并不是(NSObject*)类型,id类型和其他类之间没有继承关系。

C++和Java的多态是基于类层次结构的,可以通过子类重写父类中的方法来实现多态。这种类型的语言无法实现那种没有继承关系的多态,而OC中结合id类型可以做到这一点。

4.3 编程中的类型定义

4.3.1 签名不一致时的情况

消息选择器(message selector)中并不包含参数和返回值的类型的信息,消息选择器和这些类型信息结合起来构成签名(signature),签名被用于在运行时标记一个方法。接口文件中方法的定义也叫做签名。

NSArray * arry =
@[@1,@2];

[self
signature:(NSString *)arry
number:2];

- (void)signature:(NSString*)str number:(NSInteger)num

{

NSLog(@"%@,%p,%u",str,str,num);

/*

2016-06-01 17:12:53.207 Test[1153:80130] (

1,

2

),0x7d276fa0,2

*/

}

Cocoa提供了类NSMethodSignature,以面向对象的方式来记录方法的参数个数、参数类型和返回值类型等信息。这个类的实例也叫做方法签名。

重载(over loading)指的就是一个函数、运算符或方法定义有多种功能,并根据情况来选择合适的功能。

OC是动态语言,参数的类型是在运行时确定的,所以不支持这种根据参数类型的不同来调用不同函数的重载。OC可以通过动态绑定让同一个消息选择器执行不同的功能来实现重载。

4.3.2 类的前置声明

通过编译指令@class告知编译器某个符号是一个类名。这种写法称为类的前置声明(forward declaration)

class指令后面可以一次接很多个类,用“,”号分割,用“;”表示结束。前置声明也可以声明多次。如:

@class NSString,NSArry,NSMutableArry;

@class Volume;

通过使用@class可以提升程序整体的编译速度。但是要注意的是,如果新定义的类中要使用原有类的具体成员或方法,就一定要引入原有类的头文件。

@class的另外一个好处是,当多个接口出现类的嵌套定义时,如果只是相互包含对方的头文件无法解决,则只能通过类的前置声明来解决。

4.3.3 强制类型转换的使用示例

虽然强制类型转换的功能很强大,但会让编译器的类型检查变得没有意义,所以要尽量少用。不得不使用的情况下,要重新思考设计是否合理。

4.4 实例变量的数据封装

4.4.1 实例变量的访问权限

ClassA.h

//

//  ClassA.h

//  Test

//

//  Created by ranzhou on 16/6/1.

//  Copyright ? 2016年 ranzhouee. All rights reserved.

//

#import <Foundation/Foundation.h>

@interface ClassA : NSObject

{

NSString *A_default;   
//默认为protected

@public

NSString *A_public;

@protected

NSString *A_protected;

@private

NSString *A_private;

}

-(void)accessTestFun_A;

@end

@interface ClassA_Inline :
ClassA

-(void)accessTestFun_A_Inline;

@end

ClassA.m

//

//  ClassA.m

//  Test

//

//  Created by ranzhou on 16/6/1.

//  Copyright ? 2016年 ranzhouee. All rights reserved.

//

#import "ClassA.h"

@interface ClassA()
//在匿名类别中也能声明实例变量

{

NSString *A_category_default;//默认为private

@public

NSString *A_category_public;

@protected

NSString *A_category_protected;

@private

NSString *A_category_private;

}

@end

@implementation ClassA

-(void)accessTestFun_A

{

A_default = @"A_default";

A_public = @"A_public";

A_protected = @"A_protected";

A_private = @"A_private";

NSLog(@"%@\t%@\t%@\t%@",A_default,A_public,A_protected,A_private);

A_category_default = @"A_category_default";

A_category_public = @"A_category_public";

A_category_protected =
@"A_category_protected";

A_category_private =
@"A_category_private";

NSLog(@"%@\t%@\t%@\t%@",A_category_default,A_category_public,A_category_protected,A_category_private);

}

@end

@implementation ClassA_Inline

-(void)accessTestFun_A_Inline

{

A_default =
@"A_Inline_default";

A_public =
@"A_Inline_public";

A_protected =
@"A_Inline_protected";

//A_private = @"A_Inline_private";//Instance varible ‘A_private‘ is private.

NSLog(@"%@\t%@\t%@",A_default,A_public,A_protected);

//A_category_default = @"A_Inline_category_default";//Instance varible ‘A_category_default‘ is private.

A_category_public =
@"A_Inline_category_public";

A_category_protected =
@"A_Inline_category_protected";

//A_category_private = @"A_Inline_category_private";//Instance varible ‘A_category_private‘ is private.

NSLog(@"%@\t%@",A_category_public,A_category_protected);

}

@end

ClassB.h

//

//  ClassB.h

//  Test

//

//  Created by ranzhou on 16/6/1.

//  Copyright ? 2016年 ranzhouee. All rights reserved.

//

#import "ClassA.h"

@interface ClassB : ClassA

-(void)accessTestFun_B;

@end

ClassB.m

//

//  ClassB.m

//  Test

//

//  Created by ranzhou on 16/6/1.

//  Copyright ? 2016年 ranzhouee. All rights reserved.

//

#import "ClassB.h"

@implementation ClassB

-(void)accessTestFun_B

{

A_default =
@"A_default_In_B";

A_public =
@"A_public_In_B";

A_protected =
@"A_protected_In_B";

//A_private = @"A_private_In_B";//Instance varible ‘A_private‘ is private.

NSLog(@"%@\t%@\t%@",A_default,A_public,A_protected);

//A_category_public = @"A_category_public_In_B";//Use of undefintifier ‘A_category_public‘.

}

@end

ViewController.m中的测试代码

//----------------------------------------------------------------------------------

ClassA *testA = [[ClassA
alloc]init];

//testA->A_default = @"A_default";//Instance varible ‘A_default‘ is protected.

//testA->A_protected = @"A_protected";//Instance varible ‘A_protected‘ is protected.

//testA->A_private = @"A_private";//Instance varible ‘A_private‘ is private.

testA->A_public =
@"A_public";

//testA->A_category_default = @"A_category_default";//ClassA does not have a member named‘A_category_default‘;

[testA accessTestFun_A];

/*

2016-06-01 19:17:15.446 Test[1424:104376] A_default
A_public A_protected
A_private

2016-06-01 19:17:19.668 Test[1424:104376] A_category_default
A_category_public A_category_protected
A_category_private

*/

//----------------------------------------------------------------------------------

ClassB *testB = [[ClassB
alloc]init];

testB->A_public =
@"A_public_In_B";//同上仅仅有此实例变量可以访问。

[testB accessTestFun_A];

/*

2016-06-01 19:17:42.794 Test[1424:104376] A_default
A_public A_protected
A_private

2016-06-01 19:17:45.114 Test[1424:104376] A_category_default
A_category_public A_category_protected
A_category_private

*/

[testB accessTestFun_B];

/*

2016-06-01 19:17:57.460 Test[1424:104376] A_default_In_B
A_public_In_B A_protected_In_B

*/

//----------------------------------------------------------------------------------

ClassA_Inline *testA_Inline = [[ClassA_Inline
alloc]init];

testA_Inline->A_public =
@"A_public_Inline";//同上仅仅有此实例变量可以访问。

[testA_Inline accessTestFun_A_Inline];

/*

2016-06-01 19:25:17.490 Test[1519:108504] A_Inline_default
A_Inline_public A_Inline_protected

2016-06-01 19:25:19.001 Test[1519:108504] A_Inline_category_public
A_Inline_category_protected

*/

//----------------------------------------------------------------------------------

4.4.2 访问器

就是getter与setter方法。

一切都是为了封装。类中包含哪些实例变量以及怎么使用这些实例变量都和类的实现密切相关。如果是直接访问类的实例变量,那么当类的实现发生变化时就要做大量修改,如果是通过getter/setter接口,那么只需要修改接口即可。

4.5 类对象

4.5.1 什么是类对象

面向对象的语言中对类有两种认识,一种认为类知识作为类型的定义,程序运行时不作为实体存在;另外一种认为类本省也作为一个对象存在。我们把后一种定义中类的对象叫做类对象(class
object)。这种情况下类定义分为两个部分,一部分定义所生成的实例的类型,另外一部分则定义类自身的行为。OC把对作为对象看待,而在C++中,类只是作为类型定义使用。

类对象有自己的方法和变量,分别称为类方法和类变量。OC中只有类方法的概念,没有类变量的概念。另外,OC中类对象也称为factory,类方法也称为factory
method。

类方法中一个典型操作就是创建类的实例对象。类对象收到alloc这种消息后会生成类的实例。通过向类发送消息可以生成实例对象,那么类对象自身是什么时候生成的呢?类对象是程序执行时自动生成的,每个类只有一个类对象,不需要手动生成。

4.5.2 类对象的类型

假设把类对象也作为对象来处理,那么可以为类对象进行赋值操作吗?答案是肯定的。

id类型可以表示任何对象,类对象也可以用id类型来表示。Objective-C中还专门定义了一个Class类型用来表示类对象,所有的类对象都是Class类型。Class和id一样都是指针类型,只是一个地址,并不需要了解实际指向的内容。Nil被用来表示Class类型的空指针,实际的值是0.

//typedef struct objc_class *Class;

Class theClass = test ? [NSString
class]:[NSArray
class];

id testClass = [[theClass
alloc]init];

将类名定义为消息接收者是类对象特有的功能,除此之外类名只能在类型定义时使用。

类NSObject有一个class实例方法。所有的实例对象都可以使用class实例方法,这个方法返回的是对象所属的类对象。

4.5.3 类方法的定义

类方法的名字可以和实例方法甚至和实例变量的名字一样。

类方法中不能访问类中定义的实例变量和实例方法。类对象只有一个,类的实例对象可以有任意个。所以,如果类对象可以防问实例变量,就会不清楚访问的到底是哪个实例对象的变量。类方法中也不能访问实例方法。

其次,类方法在执行时用self代表了类对象自身,因此可以通过给self发送消息的方式来调用类中的其他类方法。同实例方法一样,也要注意self实际指向的类对象。

调用父类的类方法是,可以使用super。

4.5.4 类变量

OC中不支持类变量,但通过静态变量(static variables)可以模仿类变量。

4.5.6 类对象的初始化

由于类对象在程序执行的时候就已经生成了,因此不能通过发送消息的的方法来进行初始化。

OC的根类NSObject中存在一个initialize类方法,可以通过这个方法来为各类对象进行初始化。在每个类接收到消息之前,为这个类调用一次initialize,调用之前要先调用父类的initialize方法。每个类的initialize方法只能被调用一次。

因为在初始化的过程中会自动调用父类的initialize方法,所以子类的initialize方法中不用显示调用父类的initialize方法。

如果一个类中没有实现initialize方法,其父类的initialize方法就会被调用两次,面向自己一次,面向子类一次。所以实现initialize方法时要能确保该方法可以被重复调用。以免其子类没有实现initialize方法。

4.5.6 初始化方法的返回值

初始化方法的返回值一般都应该是id类型,起始写成类本身的类型也没啥么问题。只不过容易引起误解。

如下代码:

#import <Foundation/Foundation.h>

@interface ClassA :
NSObject

- (ClassA*)init;

@end

#import "ClassA.h"

@implementation ClassA

- (ClassA*)init

{

self = [super
init];

NSLog(@"%@",NSStringFromClass([self
class]));

return
self;

}

@end

#import "ClassA.h"

@interface ClassB :
ClassA

-(ClassB*)init;

@end

#import "ClassB.h"

@implementation ClassB

-(ClassB*)init

{

self = [super
init];

NSLog(@"%@",NSStringFromClass([self
class]));

return
self;

}

@end

ViewController 中有测试代码如下:

ClassA *testA = [[ClassA
alloc]init];

/*

2016-06-01 22:24:39.087 Test[2278:197930] ClassA

*/

ClassB *testB = [[ClassB
alloc]init];

/*

2016-06-01 22:24:43.651 Test[2278:197930] ClassB

2016-06-01 22:25:12.178 Test[2278:197930] ClassB

*/

再看一个增加代码灵活性的例子:Volume类中有:

[[Volume alloc] initWithMin:a max:b step:s];

比起该写法,一种更好的写法是

[[[self class]alloc] initWithMin:a max:b step:s];

新写法中使用[self class]代替了具体的类名Volume。class是类方法,返回self所属的类对象。这样修改后,子类可以原封不动地使用这行代码。volume
|?v?lju?m, American -j?m| n 容积、音量

时间: 2024-10-12 12:16:19

Objective-C 编程全解-第04章 对象的类型和动态绑定的相关文章

Java IO编程全解(六)——4种I/O的对比与选型

转载请注明出处:http://www.cnblogs.com/Joanna-Yan/p/7804185.html 前面讲到:Java IO编程全解(五)--AIO编程 为了防止由于对一些技术概念和术语的理解或者叫法不一致而引起歧义,这里对涉及到的专业术语或者技术用语做下声明:如果它们与其他一些地方的称呼不一致,请以本解释为准. 异步非阻塞I/O 很多人喜欢将JDK1.4提供的NIO框架成为异步非阻塞I/O,但是,如果严格按照UNIX网络编程模型和JDK的实现进行区分,实际上它只能被称为非阻塞I/

Java IO编程全解(三)——伪异步IO编程

转载请注明出处:http://www.cnblogs.com/Joanna-Yan/p/7723174.html 前面讲到:Java IO编程全解(二)--传统的BIO编程 为了解决同步阻塞I/O面临的一个链路需要一个线程处理的问题,后来有人对它的线程模型进行了优化,后端通过一个线程池来处理多个客户端的请求接入,形成客户端个数M:线程池最大线程数N的比例关系,其中M可以远远大于N,通过线程池可以灵活的调配线程资源,设置线程的最大值,防止由于海量并发接入导致线程耗尽. 下面,我们结合连接模型图和源

《Java并发编程实战》第三章 对象的共享 读书笔记

一.可见性 什么是可见性? Java线程安全须要防止某个线程正在使用对象状态而还有一个线程在同一时候改动该状态,并且须要确保当一个线程改动了对象的状态后,其它线程能够看到发生的状态变化. 后者就是可见性的描写叙述即多线程能够实时获取其它线程改动后的状态. *** 待补充   两个工人同一时候记录生产产品总数问题 1. 失效数据 可见性出现故障就是其它线程没有获取到改动后的状态,更直观的描写叙述就是其它线程获取到的数据是失效数据. 2. 非原子64位操作 3. 加锁与可见性 比如在一个变量的读取与

PHP面向对象编程详解:类和对象

PHP面向对象编程详解:类和对象 从OOP的视角看,不应区分语言.无论是C++.无论是Java.无论是.net还有更多面向对象的语言,只要你了解了OO的真谛,便可以跨越语言,让你的思想轻松的跳跃.便没有对于Java..net.PHP 之间谁强谁弱的争执了. 希望这个介绍PHP5面向对象编程(OOP)的资料能让初学者受益,能让更多的PHPer开始转向OO的编程过程. 相对PHP4,PHP5在面向对象方面改变了很多.我们将只介绍PHP5环境下的面向对象.而我们必须改变自己来跟随PHP5的发展.如果代

c#高级编程第七版 学习笔记 第三章 对象和类型

第三章 对象和类型 本章的内容: 类和结构的区别 类成员 按值和按引用传送参数 方法重载 构造函数和静态构造函数 只读字段 部分类 静态类 Object类,其他类型都从该类派生而来 3.1 类和结构 类和结构都是创建对象的模板,每个对象都包含数据,并提供了处理和访问数据的方法 结构和类的区别是他们在内存中的存储方式.访问方式(类是存储在堆上的引用类型,而结构是存储在栈上的值类型)和他们的一些特征(如结构不支持继承).较小的数据类型使用结构可提高性能.但在语法上,结构和类非常相似,主要的区别是使用

&lt;&lt;JavaScript编程全解&gt;&gt;阅读笔记之函数与闭包

1.通过函数声明语句声明的函数,可以在进行声明的代码行之前调用 function doit() { fn(); function fn() { alert("ok"); } } doit(); //ok 2.匿名函数不能再进行声明代码行之前调用 function doit() { fn(); fn= function () { alert("ok"); } } doit(); //Uncaught ReferenceError: fn is not defined

【1】JavaScript编程全解笔记(一)

1.概述 本书涵盖了 JavaScript 各个方面的主题,从客户端以及服务端 JavaScript 等基础内容,主要讲了  HTML5.Web API.Node.js 与 WebSocket 等技术. 本书前半部分对 JavaScript 基础进行解说. 本书后半部分主要介绍包括客户端 JavaScript.HTML5.Web API 以及服务器 JavaScript 等与 JavaScript 相关的应用领域. ing!!!

JAVA编程思想学习笔记——第一章 对象导论

搞了一年多java,野路子出身,发现java基础这块还是相当的薄弱!故决定学习<Java编程思想>这本书.在此把学习的知识点记录下! 面向对象的五大特性 1.万物皆为对象 2.程序是对象的集合,它们通过发送消息来告诉彼此所要做的 3.每个对象都由自己的由其它对象所构成的存储 4.每个对象都拥有其类型 5.某一特定类型的所有对象都可以接收同样的信息  单根继承结构 所有的类都继承自单一的基类,Object.在单根集成结构中的所有对象都具有一个公用接口,所以他们归根到底都是相同的基本类型.单根集成

《Java并发编程实战》第四章 对象的组合 读书笔记

一.设计线程安全的类 在设计线程安全类的过程中,须要包括下面三个基本要素: . 找出构成对象状态的全部变量. . 找出约束状态变量的不变性条件. . 建立对象状态的并发訪问管理策略. 分析对象的状态,首先从对象的域開始. 变量按作用域划分: . 全局变量 . 局部变量 . 方法行參 . 异常处理參数 1. 收集同步需求 假设不了解对象的不变性条件与后验条件,那么就不能确保线程安全性.要满足在状态变量的有效值或状态转换上的各种约束条件.就须要借助原子性和封装性. 说的更简略些是Java线程安全都是