维基百科Objective-C(2)

维基百科Objective-C(2)


有兴趣的朋友可加群: 533948446 交流


协议(Protocols):

在其它的编程语言中,它被叫做接口.

NeXT曾经试图扩展Objective-C对于多重继承支持规范,但是没有

实现,后来引进了协议(protocols)。该模式可以实现类似C++的抽象的多

继承,或者像’接口’(在Java和C#)中。Objective-C 把一个称为非正式的协

议和compiler-enforced协议 的临时协议叫做正式协议。

一个非正式协议是一个可被类选择实现的方法列表。它在文档被中

特别指定说明,因为它并不在语言中出现。非正式协议在NSObject中作

为一个类别被实现同时包含可选(optional)方法,如果实现这些方法,可

以改变类的行为。例如,一个text field类,可能有一个包含可以用来自动

完成用户输入文本的非正式协议可选方法的delegate。text field检索代理

对象是否实现了该方法(通过反射),如果实现了,就调用该委托方法实现

相应的特性。

    正式协议类似于Java,C#, 和Ada2005中的接口。它是一个任何类都可以声明自己实现的方法列表。在Objective-C 2.0以前,一个类必须实现它所有protocol的所有方法,如果有协议的方法没有被实现,编译器将抛出一个错误。在Objective-C 2.0 增加了protocol的optional标记的支持,编译器将不会强制实现可选方法(optional methods)。

      必须声明一个类来实现协议的内容。这个是在runtime进行监测的。正式协议不能提供任何实现,他们只是向调用者保证提供符合协议的实现。在NeXT/Apple库中,协议经常被用于展示远程系统能力的分布式对象系统中。


语法,如下:


@protocol NSLocking

- (void)lock;

- (void)unlock;

@end


表示锁的抽象概念。协议被类实现的定义形式如下:


@interface NSLock : NSObject <NSLocking>

//...

@end


NSLock将提供对两个实例化方法的实现。


动态类型(Dynamic typing):

    Objective-C像Smalltalk一样可以利用动态类型:一个对象可以发送一个没有指定接口的消息。为了提升灵活性,它允许一个对象截取一个消息,并发送这个消息到另外一个对象,该对象可以对这个消息作出适当的响应或者也可以发送消息到另外一个对象。这种方式被称为消息的转发或者代理。另外,可以使用一个错误处理程序,以防消息转发中断。如果一个对象不转发一个消息,也不响应它,或者获取了一个错误,那么系统将产生一个runtime exception.如果消息被发送给nil(即空对象指针),他就会自动被忽略或者抛出一个通用异常,但是这取决于编译器的选项设定。

  静态类型信息也可以作为可选变量添加。这个信息会在compile time被检测。下面的四句代码中,以此越来越多的类型信息被提供。这些语句在runtime阶段是等价的,但是附加信息可以允许编译器给程序员提出传递的参数类型不匹配的警告。


  • (void)setMyValue:(id)foo;

上面代码语句中的foo可能是任何的类对象。


- (void)setMyValue:(id<NSCopying>)foo;

上面代码语句中的foo可能是任意符合NSCopying协议的类实例。


- (void)setMyValue:(NSNumber *)foo;

上面代码语句中的foo必须是一个NSNumber类实例。

- (void)setMyValue:(NSNumber<NSCopying> *)foo;


上面代码语句中的foo必须符合NSCopying协议的NSNumber类实例。


转发(Forwarding):

Objective-C允许接收消息的对象不响应该消息。与其说响应或者丢弃这个消息,不如说对象能转发这个消息到另外一个对象来响应它。转发能用于实现一些常见的设计模式,比如观察者模式,代理模式。

Objective-C在运行时(runtime)中指定一个对象中的方法。


转发方法(forwarding methods):


- (retval_t)forward:(SEL)sel args:(arglist_t)args; // with GCC

- (id)forward:(SEL)sel args:(marg_list)args; // with NeXT/Apple systems



行为方法(action methods):

- (retval_t)performv:(SEL)sel args:(arglist_t)args; // with GCC

  • (id)performv:(SEL)sel args:(marg_list)args; // with NeXT/Apple systems


一个对象希望实现转发,只需要用一个新的方法重写(覆盖)转发对象,并在该方法中定义转发的行为。行为方法”performv::” 不需要被重写,因为这样的方法只是根据selector和参数来执行的。注意:SEL类型,是Objective-C中的消息类型名。

注意:在OpenStep,Cocoa和GNUstep中,通常都是利用Objective-C的framework而不是Object class.“- (void)forwardInvocation:(NSInvocation *)anInvocation”方法被NSObject类用作转发。


例:

下面这个示例程序,演示了基本的转发:

Forwarder.h

# import <objc/Object.h>


@interface Forwarder : Object {

 id recipient; //The object we want to forward the message to.

}


//Accessor methods.

- (id)recipient;

- (id)setRecipient:(id)_recipient;


@end

Forwarder.m

# import "Forwarder.h"


@implementation Forwarder


- (retval_t)forward:(SEL)sel args:(arglist_t) args {

 /*

 * Check whether the recipient actually responds to the message.

 * This may or may not be desirable, for example, if a recipient

 * in turn does not respond to the message, it might do forwarding

 * itself.

 */

 if([recipient respondsToSelector:sel]) {

  return [recipient performv:sel args:args];

 } else {

  return [self error:"Recipient does not respond"];

 }

}


- (id)setRecipient:(id)_recipient {

 [recipient autorelease];

 recipient = [_recipient retain];

 return self;

}


- (id) recipient {

 return recipient;

}

@end

Recipient.h

# import <objc/Object.h>


// A simple Recipient object.

@interface Recipient : Object

- (id)hello;

@end

Recipient.m

# import "Recipient.h"


@implementation Recipient


- (id)hello {

 printf("Recipient says hello!\n");


 return self;

}


@end

main.m

# import "Forwarder.h"

# import "Recipient.h"


int main(void) {

 Forwarder *forwarder = [Forwarder new];

 Recipient *recipient = [Recipient new];


 [forwarder setRecipient:recipient]; //Set the recipient.

 /*

 * Observe forwarder does not respond to a hello message! It will

 * be forwarded. All unrecognized methods will be forwarded to

 * the recipient

 * (if the recipient responds to them, as written in the Forwarder)

 */

 [forwarder hello];


 [recipient release];

 [forwarder release];


 return 0;

}


注意:  以上代码中的Object类不适于 iPhone并且在Objective-C 2.0也无效,支持Mac OS X10.0以下的版本。


注意:

当用GCC编译的时候,编译日志如下:

$ gcc -x objective-c -Wno-import Forwarder.m Recipient.m main.m -lobjc

main.m: In function `main':

main.m:12: warning: `Forwarder' does not respond to `hello'

$



编译器的日志在前面提到一点儿,Forwarder类对“hello”消息无法响应。在这种情况下,在调用被实现之前都忽略这个警告。运行编译的可执行程序后的输出结果如下:

$ ./a.out

Recipient says hello!



类别(Categories):

   在设计Objective-C期间,其中一个主要的问题就是大型代码库的可维护性。根据结构化编程的经验,提供代码知道的主要方法就是降低代码的耦合性。Objective—C从Smalltalk的实现中借用和扩展了“categories”的概念来帮助我们实现这一需求。此外,类别(category)中的方法是在运行时(runtime)中被加入到响应的类中的。因此,类别(category)允许向一个现有的类中添加方法,而不需要重新编译类,甚至获取该类的源代码。例如,如果系统中不包含一个对字符串拼写检测的实现,但是它可以在不修改源代码的情况下加入该实现。

   类别(category)中的方法会在程序运行中不被察觉的情况下加入到类中。类别(category)可以访问初始化后所有的类实例的所有成员,包括私有的。

   如果类别(category) 中声明了一个方法于类中原有的方法名重复,则会采用类别(category)的方法。因此类别(category)不仅可以为现有类添加方法,还可以重写现有类的方法。这个特性可以通过重写他们的方法来修复bug,但是会导致在这程序里所有于该类相关联的改变。如果有两个类别的方法同名(方法签名不会冲突),那么不能确定哪个类别方法将被调用。其它语言都是试图以各种方法实现这项功能。TOM让Objective-C系统更进一步,也能够允许增加变量。其它语言使用”prototype oriented ”来解决提供类别(Category),其中最著名的是”Self”。

C#和Visual Basic.NET通过扩展方法的形式来实现类似的功能更,但是他们不能访问类的私有变量。Ruby和其它几种动态编程语言引入了类似”monkey patching”的技术来实现.

Logtalk实现了一个类别(category)的概念(作为一种first-class entities),其中贯穿Objective-C的类别功能。(Logtalk类别也能被用作细粒度的组成单元,比如在定义新类或者原型(prototypes)的时候;尤其是,单个Logtalk类别可以被任一数量的类核原型引入)。


类别的应用例子(Example usage of categories):

这个例子基于个建立一个Integer类,它只是实现了访问器(accessor methods ),增加了两个对于基类(即Integer)扩展的类别即Arithmetic和Display。虽然类别(category)可以访问基类的私有数据成员,但是通常安全良好的访问私有数据成员的方法是通过访问器进行访问。这样有助于类别更独立于基类(被扩展的类).实现类似的访问器是类别的典型用法。其它的就是利用类别向基类添加方法。然后,利用类别重些子类的方法实现并不是一个值得推崇的好方法,这样也被称为”monkey patching”.非正式协议是基于一个基于基类NSObject的类别实现的。通常,包含类别的扩展名头文件命名形式为”BaseClass+ExtensionClass.h.”.



Integer.h

# import <objc/Object.h>


@interface Integer : Object {

 int integer;

}


- (int) integer;

- (id) integer: (int) _integer;

@end

Integer.m

# import "Integer.h"


@implementation Integer

- (int) integer {

 return integer;

}


- (id) integer: (int) _integer {

 integer = _integer;


 return self;

}

@end

Integer+Arithmetic.h

# import "Integer.h"


@interface Integer (Arithmetic)

- (id) add: (Integer *) addend;

- (id) sub: (Integer *) subtrahend;

@end

Integer+Arithmetic.m

# import "Integer+Arithmetic.h"


@implementation Integer (Arithmetic)

- (id) add: (Integer *) addend {

 return [self integer: [self integer] + [addend integer]];

}


- (id) sub: (Integer *) subtrahend {

 return [self integer: [self integer] - [subtrahend integer]];

}

@end

Integer+Display.h

# import "Integer.h"


@interface Integer (Display)

- (id) showstars;

- (id) showint;

@end

Integer+Display.m

# import "Integer+Display.h"


@implementation Integer (Display)

- (id) showstars {

 int i, x = [self integer];

 for (i = 0; i < x; i++) {

 printf("*");

 }

 printf("\n");


 return self;

}


- (id) showint {

 printf("%d\n", [self integer]);


 return self;

}

@end

main.m

# import "Integer.h"

# import "Integer+Arithmetic.h"

# import "Integer+Display.h"


int main(void) {

 Integer *num1 = [Integer new], *num2 = [Integer new];

 int x;


 printf("Enter an integer: ");

 scanf("%d", &x);


 [num1 integer:x];

 [num1 showstars];


 printf("Enter an integer: ");

 scanf("%d", &x);


 [num2 integer:x];

 [num2 showstars];


 [num1 add:num2];

 [num1 showint];


 return 0;

}


注意:

上面例子的编译执行命令如下:

gcc -x objective-c main.m Integer.m Integer+Arithmetic.m Integer+Display.m -lobjc

实验一下,删除#import “Integer+Arithmetic.h" 和 [num1 add:num2] 这两行代码,同时在编译时去掉 Integer+Arithmetic.m 。程序依然会运行。这意味着,如果必要时的时候”mix-and-match”是可行的;如果一个类别并不提供任何功能,可以不编译它。


冒充(Posing):

Objective-C允许在程序中一个类完整的替换其他类。这个替换的类的过程被称为冒充(pose as)一个目标类。

注意:类冒充已经在Mac OS X v10.5以后被弃用了,同时在64bit的运行时也是无效的。相似的功能能通过类别的method swizzling实现,互换一个方法的实现后他们依然有着相同的方法签名。

依然支持冒充的版本中,所有发送到目的类中的消息将被冒充的类接收。但是有以下几点限制:

  • 一个类只能冒充它直接或者间接父类。
  • 冒充类不能定义任何新的和目标类中没有得实例变量。(尽可能的定义或覆盖已有的方法)。
  • 目标类接收消息的优先级不能高于冒充类。

冒充,和类别(category)相似,允许访问原有类的参数。冒充有两个特性不同于类别(category):

  • 一个冒充类能通过super调用被覆盖的方法,然后却是和目标类合并实现的。
  • 冒充类能覆盖类别中定义的方法。

例如:

@interface CustomNSApplication : NSApplication

@end


@implementation CustomNSApplication

- (void) setMainMenu: (NSMenu*) menu {

 // do something with menu

}

@end


class_poseAs ([CustomNSApplication class], [NSApplication class]);


它将拦截所有的NSApplication调用方法setMainMenu的权限。

#import


在c语言中,#include预编译指令的作用就是插入一个文件的源代码。Objective-C有#import指令,相当于每个文件只被包含一次进每一个编译单元,不需要添加include保护指令。



其他的特性:

Objective-C的特性通常很灵活,简单的解决编程中的问题。

  • 其他对象的委托(delegate)方法和远程调用都可以通过很简单的通过类别和消息转发实现。
  • Swizzling, 类的isa指针值允许在运行时(runtime)被改变。通常在调试中一个被释放的对象添加进一个固有的对象中的目的就是当有人调用他们(被释放的东西)的时候报告一个错误。Swizzling也被用于Enterprise Objects Framwork中来查找数据库的错误。Swizzling现在被用于Apple的Foundation.framework中实现Key-Value Observing.


语言变体(Language variants):

Objective-C++

Objective-C++是一个可以利用GNU Compiler Collection和Clang编译用C++和Objective-C混编的源码文件的变体语言。Objective-C++是增加了C++的扩展,Objective-C是增加了C的扩展。如果不能统一各种语言的语义特性就什么都做不成,所以有以下限制:

  • 一个C++类不能继承自一个Objective-C类,反之亦然。
  • C++的命名空间不能包含一个Objective-C的声明。
  • Objective-C的声明可以出现在全局部分,但是不能在C++的命名空间中。
  • Objective-C不能初始化为一个C++的类实例变量,因为它没有一个缺省构造函数或一个或多个虚函数,但是C++对象指针能被用作有没有严格限制的实例变量。
  • C++的值语法,不能用于Objective-C,因为Objective-C只能通过指针访问。
  • Objective-C的声明不能包含在C++模版中,反之亦然。然后Objective-C类型(例如,ClassName*)可以被用做C++的模版参数。
  • Objective-C和C++的异常处理是不同的;每个异常处理不能捕获其他的异常。为了减轻运行时(runtime)的压力要么用Objective-C异常完全替换掉C++的异常处理(Apple runtime),或者当Objective-C++的库被链接时部分取代。
  • 必须注意出现析构函数的调用约定于Objective-C和C++的异常运行时模型不匹配的情况(即当Objective-C在C++对象范围异常退出时,C++析构函数将不会被执行)。新的64BIT运行时更过引入C++异常在这个异常中的对应响应后的相互作用解决了这个问题。
  • Objective-C的block和C++11的 lambdas表达式是不同的,然而一个可传递lambda表达式的block在Mac OS X被直接创建是可行的。



Objective-C 2.0


在2006年的全球开发者大会(Worldwide Developers Conference或WWDC)上,Apple发布了Objective 2.0,该Objective-C版本的修改包括,先进的垃圾回收机制,语法增强,运行时性能优化以及对64Bit的支持。2007年10月,发布了Mac OS X v10.5,它包含了一个Objective-C 2.0编译器。GCC 4.6支持更多Objective-C 特性,比如声明和synthesized 属性,点语法,快速枚举器,可选的protocol方法,method/protocol/class属性,类扩展(class extensions)和新的GNU Objective-C 运行时(runtime)API.


垃圾回收(Garbage collection)

Objective-C 2.0提供了一个可选保守的新一代垃圾回收器。当运行向后兼容模式的时候,运行时(runtime)将开启引用计数操作,如”retain”和”release”。当启用垃圾回收器的时,所有对象都会被关联。常规的C指针不能通过添加”__strong”来获取参与编译器垃圾回收的资格。一个清零的weak子系统被提供来处理被标记为__weak的指针,当这样的对象(或者说时内存)被回收的时候指针清零。 iOS在Objective 2.0的时候,并没有实现垃圾回收。Objective-C中的垃圾回收器运行于低优先级的后台线程中,这样以至于可以做到在保证用户响应体验的前提下达到中断用户事件的目的。

垃圾回收在OS X v10.8版本后就不被推荐了,转而于更好的ARC.在运行于ARM64的iOS7的Objective-C用一个64bit中的19bit来存储标记指针的引用计数。


属性(Properties)

       Objective2.0中对作为属性的实例变量引入了新的语法(通过可选属性来产生配置访问器方法)。属性在某种意义上来说是公共成员实例变量;在声明一个实例变量属性的是同时也给它提供了外部访问的属性(如只读)。一个属性可能是只读的,同时也可能提供类似assign,copy或者retain的存储语法。默认情况下,为了防止多个线程访问造成死锁,认为属性是atomic。属性也可以被声明为移除了锁(lock)的nonatomic。


@interface Person : NSObject {

 @public

 NSString *name;

 @private

 int age;

}


@property(copy) NSString *name;

@property(readonly) int age;


-(id)initWithAge:(int)age;

@end


属性通过关键字@synthesize,自动根据属性的声明产生访问器方法(getter,setter—如果readonly则不会有)。或者,getter和setter方法,显式实现或者利用关键字@dynamic访问器方法由其他方式提供。当利用的编译版本是Clang3.0或者高于它时,所有的属性不再显式的默认声明为@dynamic,而且标记为readonly或者利用关键字@synthesize自动实现getter和setter访问器。


@implementation Person

@synthesize name;


-(id)initWithAge:(int)initAge {

 self = [super init];

 if (self) {

 age = initAge; // NOTE: direct instance variable assignment, not property setter

 }

 return self;

}


-(int)age {

 return age;

}

@end


既可以用消息传递语法访问属性,也可以利用Key-Value Coding,通过变量名利用”valueForKey:”/"setValue:forKey:"方法访问。


Person *aPerson = [[Person alloc] initWithAge: 53];

aPerson.name = @"Steve"; // NOTE: dot notation, uses synthesized setter,

 // equivalent to [aPerson setName: @"Steve"];

NSLog(@"Access by message (%@), dot notation(%@),

property name(%@) and direct instance variable access (%@)",

 [aPerson name], aPerson.name, [aPerson valueForKey:@"name"], aPerson->name);


为了利用 点 语法在一个实例方法中访问属性访问器,可以使用“self”关键字:


-(void) introduceMyselfWithProperties:(BOOL)useGetter {

 NSLog(@"Hi, my name is %@.", (useGetter ? self.name : name));

// NOTE: getter vs. ivar access

}


类和代理的属性可以被动态监测。

int i;

int propertyCount = 0;

objc_property_t *propertyList = class_copyPropertyList([aPerson class], &propertyCount);


for (i = 0; i < propertyCount; i++) {

 objc_property_t *thisProperty = propertyList + i;

 const char* propertyName = property_getName(*thisProperty);

 NSLog(@"Person has a property: '%s'", propertyName);

}


Non-fragile实例变量:


Objective-C 2.0在运行时(runtime)中加入了对non-fragile实例变量支持(在building 64bit的Mac OS X代码或者iOS代码的时候)。在现代的运行时,一般都是间接的来访问实例变量的,并且允许在运行时中动态绑定初始化设计。这个特性为Objective-C代码带来了两个重要的改变:

  • 消除了Fragile binary interface problem-父类的size改变不会影响二进制的兼容。
  • 允许类中没有声明的属性实例变量可以在运行时(runtime)被合并进来.


快速枚举器(Fast enumeration):

Objective-C 2.0提供了快速枚举器语法来替代NSEnumerator 对象或者迭代器(iterate)编译一个集合。在Objective-C 2.0中,下面的循环的功能是相同的,但是它们有不同的性能特征.


// Using NSEnumerator

NSEnumerator *enumerator = [thePeople objectEnumerator];

Person *p;


while ((p = [enumerator nextObject]) != nil) {

 NSLog(@"%@ is %i years old.", [p name], [p age]);

}

// Using indexes

for (int i = 0; i < [thePeople count]; i++) {

 Person *p = [thePeople objectAtIndex:i];

 NSLog(@"%@ is %i years old.", [p name], [p age]);

}

// Using fast enumeration

for (Person *p in thePeople) {

 NSLog(@"%@ is %i years old.", [p name], [p age]);

}


快速枚举的代码比标准枚举效率更高,因为快速枚举利用NSFastEnumeration协议取代调用对象指针算法。


类扩展(Class extensions):

   类扩展和不带类别(category)名的类别(category)有相同的语法,声明的方法和属性会被直接添加到主类中。它主要用于替换类别中没有在header中扩展的公共方法,(extensions)扩展的这个优势可以用于编译器检查私有声明的方法是否真正的被实现。


Cocoa开发的影响(Implications for Cocoa development):

在Mac OS X中利用Objective-C2.0开发的Objective-C应用不兼容 Mac OS X v10.5(Leopard)以前版本的所有操作系统。由于利用快速枚举产生的二进制与标准枚举不同,所以在OS X v10.4或者更早的版本中利用快速枚举的应用程序会crash.


闭包(Blocks):

Blocks是利用特殊语法创建闭包,但并不是一个标准的Objective-C扩展。Blocks只支持Mac OS X 10.6”Snow Leopard”或者iOS4以后的版本,同时需要附带libobjc1 1.7和clang3.1及其以后的版本。

#include <stdio.h>

#include <Block.h>

typedef int (^IntBlock)();


IntBlock MakeCounter(int start, int increment) {

__block int i = start;

return Block_copy( ^ {

int ret = i;

i += increment;

return ret;

});

}


int main(void) {

IntBlock mycounter = MakeCounter(5, 2);

printf("First call: %d\n", mycounter());

printf("Second call: %d\n", mycounter());

printf("Third call: %d\n", mycounter());

/* because it was copied, it must also be released */

Block_release(mycounter);

return 0;

}

/* Output:

First call: 5

Second call: 7

Third call: 9

*/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值