Object C语言研究

发布于 2020-11-08  309 次阅读


综述

Objective-C是一种通用、高级、面向对象的编程语言。它扩展了标准的ANSI C编程语言,将
Smalltalk式的消息传递机制加入到ANSI C中。当前主要支持的编译器有GCC和Clang(采用LLVM作为后端)。
Objective-C的商标权属于苹果公司,苹果公司也是这个编程语言的主要开发者。苹果在开发NeXTSTEP操作系统时使用了Objective-C,之后被OS X和iOS继承下来。现在Objective-C与Swift是OS X和iOS操作系统、及与其相关的API、Cocoa和Cocoa Touch的主要编程语言。
引用:维基百科

详细内容

语法

Objective-C是C语言的严格超集--任何C语言程序不经修改就可以直接通过Objective-C编
译器,在Objective-C中使用C语言代码也是完全合法的
。Objective-C被描述为盖在C语言上的薄薄
一层,因为Objective-C的原意就是在C语言主体上加入面向对象的特性
Objective-C的面向对象语法源于Smalltalk消息传递风格。所有其他非面向对象的语法,包括变量类
型,预处理器(preprocessing),流程控制,函数声明与调用皆与C语言完全一致。但有些C语言语法
合法代码在objective-c中表达的意思不一定相同,比如某些布尔表达式,在C语言中返回值为true,
但在Objective-C若与yes直接相比较,函数将会出错,因为在Objective-C中yes的值只表示为1.
引用:维基百科

#import <Foundation/Foundation.h>

int main(int argc, char *argv[]) {

    @autoreleasepool {
        NSLog(@"Hello World!");
    }

   return 0;
}

消息传递

Objective-C最大的特色是承自Smalltalk的消息传递模型(message passing),此机制与今日C+
+式之主流风格差异甚大。Objective-C里,与其说对象互相调用方法,不如说对象之间互相传递消息更
为精确。此二种风格的主要差异在于调用方法/消息传递这个动作。C++里类别与方法的关系严格清楚,
一个方法必定属于一个类别,而且在编译时(compile time)就已经紧密绑定,不可能调用一个不存在
类别里的方法。但在Objective-C,类别与消息的关系比较松散,调用方法视为对对象发送消息,所有方
法都被视为对消息的回应。所有消息处理直到运行时(runtime)才会动态决定,并交由类别自行决定如
何处理收到的消息。也就是说,一个类别不保证一定会回应收到的消息,如果类别收到了一个无法处理的
消息,程序只会抛出异常,不会出错或崩溃。
C++里,送一个消息给对象(或者说调用一个方法)的语法如下:

obj.method(argument);

Objective-C则写成:

[obj method: argument];

此二者并不仅仅是语法上的差异,还有基本行为上的不同。
这里以一个汽车类(car class)的简单例子来解释Objective-C的消息传递特性:

[car fly];

典型的C++意义解读是“调用car类别的fly方法”。若car类别里头没有定义fly方法,那编译肯定不会通
过。但是Objective-C里,我们应当解读为“发提交一个fly的消息给car对象”,fly是消息,而car是消
息的接收者。car收到消息后会决定如何回应这个消息,若car类别内定义有fly方法就运行方法内之代
码,若car内不存在fly方法,则程序依旧可以通过编译,运行期则抛出异常。
此二种风格各有优劣。C++强制要求所有的方法都必须有对应的动作,且编译期绑定使得函数调用非常快
速。缺点是仅能借由virtual关键字提供有限的动态绑定能力。Objective-C天生即具备鸭子类型之动态
绑定能力,因为运行期才处理消息,允许发送未知消息给对象。可以送消息给整个对象集合而不需要一一
检查每个对象的类型,也具备消息转送机制。同时空对象nil接受消息后默认为不做事,所以送消息给
nil也不用担心程序崩溃。
引用:维基百科

类的定义与实现

Objective-C中强制要求将类的定义(interface)与实现(implementation)分为两个部分。
类的定义文件遵循C语言之惯例以.h为后缀,实现文件以.m为后缀

interface

定义部分,清楚定义了类的名称、成员变量和方法。 以关键字@interface作为开始,@end作为结
束。

@interface MyObject : NSObject {
    int memberVar1; // 实体变量
    id  memberVar2;
}

+(return_type) class_method; // 类方法

-(return_type) instance_method1; // 实例方法
-(return_type) instance_method2: (int) p1;
-(return_type) instance_method3: (int) p1 andPar: (int) p2;
@end

方法前面的 +/- 号代表函数的类型:加号(+)代表类方法(class method),不需要实例就可以调
用,与C++ 的静态函数(static member function)相似。减号(-)即是一般的实例方法
(instance method)。

这里提供了一份意义相近的C++语法对照,如下:

class MyObject : public NSObject {
protected:
    int memberVar1;  // 实体变量
    void * memberVar2;

  public:
    static return_type class_method(); // 类方法

    return_type instance_method1();    // 实例方法
    return_type instance_method2( int p1 );
    return_type instance_method3( int p1, int p2 );
}

Objective-C定义一个新的方法时,名称内的冒号(:)代表参数传递,不同于C语言以数学函数的
括号来传递参数。Objective-C方法使得参数可以夹杂于名称中间,不必全部附缀于方法名称的尾端,可
以提高程序可读性。设定颜色RGB值的方法为例:

- (void) setColorTo Red: (float)red Green: (float)green Blue:(float)blue; /* 宣告方法*/

[myColor setColorTo Red: 1.0 Green: 0.8 Blue: 0.2]; /* 呼叫方法*/

这个方法的签名是setColorTo Red:Green:Blue:。每个冒号后面都带着一个float类别的参数,分别代
表红,绿,蓝三色。

Implementation

实现区段则包含了公开方法的实现,以及定义私有(private)变量及方法。 以关键字@implementation作为区段起头,@end结尾。

@implementation MyObject {
  int memberVar3; //私有实体变量
}

+(return_type) class_method {
    .... //method implementation
}
-(return_type) instance_method1 {
     ....
}
-(return_type) instance_method2: (int) p1 {
    ....
}
-(return_type) instance_method3: (int) p1 andPar: (int) p2 {
    ....
}
@end

值得一提的是不只Interface区段可定义实体变量,Implementation区段也可以定义实体变量
两者的差别在于访问权限的不同,Interface区段内的实体变量默认权限为protected,宣告于
implementation区段的实体变量则默认为private,故在Implementation区段定义私有成员更符
合面向对象之封装原则,因为如此类别之私有信息就不需曝露于公开interface(.h文件)中。

创建对象

Objective-C创建对象需通过alloc以及init两个消息。alloc的作用是分配内存,init则是初始化对
象。 init与alloc都是定义在NSObject里的方法,父对象收到这两个信息并做出正确回应后,新对象才
创建完毕。以下为范例:

MyObject * my = [[MyObject alloc] init];

在Objective-C 2.0里,若创建对象不需要参数,则可直接使用new

MyObject * my = [MyObject new];

仅仅是语法上的精简,效果完全相同。

若要自己定义初始化的过程,可以重写init方法,来添加额外的工作。(用途类似C++ 的构造函数constructor)

- (id) init {
    if ( self=[super init] ) {   // 必须调用父类的init
        // do something here ...
    }
    return self;
}

协议(Protocol)

协议是一组没有实现的方法列表,任何的类均可采纳协议并具体实现这组方法。

Objective-C在NeXT时期曾经试图引入多重继承的概念,但由于协议的出现而没有实现之。

协议类似于Java与C#语言中的“接口”。在Objective-C中,有两种定义协议的方式:由编译器保证的
“正式协议”,以及为特定目的设定的“非正式协议”。

非正式协议为一个可以选择性实现的一系列方法列表。非正式协议虽名为协议,但实际上是挂于
NSObject上的未实现分类(Unimplemented Category)的一种称谓,Objetive-C语言机制上并没有非
正式协议这种东西,OSX 10.6版本之后由于引入@optional关键字,使得正式协议已具备同样的能力,
所以非正式协议已经被废弃不再使用。

正式协议类似于Java中的"接口",它是一系列方法的列表,任何类都可以声明自身实现了某个协议。在
Objective-C 2.0之前,一个类必须实现它声明符合的协议中的所有方法,否则编译器会报告错误,表
明这个类没有实现它声明符合的协议中的全部方法。Objective-C 2.0版本允许标记协议中某些方法为
可选的(Optional),这样编译器就不会强制实现这些可选的方法。

协议经常应用于Cocoa中的委托及事件触发。例如文本框类通常会包括一个委托(delegate)对象,该对
象可以实现一个协议,该协议中可能包含一个实现文字输入的自动完成方法。若这个委托对象实现了这个
方法,那么文本框类就会在适当的时候触发自动完成事件,并调用这个方法用于自动完成功能。

Objective-C中协议的概念与Java中接口的概念并不完全相同,即一个类可以在不声明它符合某个协议
的情况下,实现这个协议所包含的方法,也即实质上符合这个协议,而这种差别对外部代码而言是不可见
的。正式协议的声明不提供实现,它只是简单地表明符合该协议的类实现了该协议的方法,保证调用端可
以安全调用方法。

协议语法

协议以关键字@protocol作为区段起始,@end结束,中间为方法列表。

@protocol Locking
- (void)lock;
- (void)unlock;
@end

这是一个协议的例子,多线程编程中经常要确保一份共享资源同时只有一个线程可以使用,会在使用前给
该资源挂上锁 ,以上即为一个表明有“锁”的概念的协议,协议中有两个方法,只有名称但尚未实现。

下面的SomeClass宣称他采纳了Locking协议:

@interface SomeClass : SomeSuperClass <Locking>
@end

一旦SomeClass表明他采纳了Locking协议,SomeClass就有义务实现Locking协议中的两个方法。

@implementation SomeClass
- (void)lock {
  // 實現lock方法...
}
- (void)unlock {
  // 實現unlock方法...
}
@end

由于SomeClass已经确实遵从了Locking协议,故调用端可以安全的发送lock或unlock消息给
SomeClass实体变量,不需担心他没有办法回应消息。

插件是另一个使用抽象定义的例子,可以在不关心插件的实现的情况下定义其希望的行为。

动态类型

类似于Smalltalk,Objective-C具备动态类型:即消息可以发送给任何对象实体,无论该对象实体的公
开接口中有没有对应的方法。对比于C++这种静态类型的语言,编译器会挡下对(void*)指针调用方法
的行为。但在Objective-C中,你可以对id发送任何消息(id很像void*,但是被严格限制只能使用在对
象上),编译器仅会发出“该对象可能无法回应消息”的警告,程序可以通过编译,而实际发生的事则取决
于运行期该对象的真正形态,若该对象的确可以回应消息,则依旧运行对应的方法。

一个对象收到消息之后,他有三种处理消息的可能手段,第一是回应该消息并运行方法,若无法回应,则
可以转发消息给其他对象,若以上两者均无,就要处理无法回应而抛出的例外。只要进行三者之其一,该
消息就算完成任务而被丢弃。若对“nil”(空对象指针)发送消息,该消息通常会被忽略,取决于编译器
选项可能会抛出例外。

虽然Objective-C具备动态类型的能力,但编译期的静态类型检查依旧可以应用到变量上。以下三种声明
在运行时效力是完全相同的,但是三种声明提供了一个比一个更明显的类型信息,附加的类型信息让编译
器在编译时可以检查变量类型,并对类型不符的变量提出警告。

下面三个方法,差异仅在于参数的形态:

- setMyValue:(id) foo;

id形态表示参数“foo”可以是任何类的实例。

- setMyValue:(id <aProtocol>) foo;

id<aProtocol>表示“foo”可以是任何类的实例,但必须采纳“aProtocol”协议。

- setMyValue:(NSNumber*) foo;

该声明表示“foo”必须是“NSNumber”的实例。

动态类型是一种强大的特性。在缺少泛型的静态类型语言(如Java 5以前的版本)中实现容器类时,程
序员需要写一种针对通用类型对象的容器类,然后在通用类型和实际类型中不停的强制类型转换。无论如
何,类型转换会破坏静态类型,例如写入一个“整数”而将其读取为“字符串”会产生运行时错误。这样的
问题被泛型解决,但容器类需要其内容对象的类型一致,而对于动态类型语言则完全没有这方面的问题。

转发

Objective-C允许对一个对象发送消息,不管它是否能够响应之。除了响应或丢弃消息以外,对象也可以
将消息转发到可以响应该消息的对象。转发可以用于简化特定的设计模式,例如观测器模式或代理模式。

Objective-C运行时在Object中定义了一对方法:

  • 转发方法
- (retval_t) forward:(SEL) sel :(arglist_t) args; // with GCC
- (id) forward:(SEL) sel :(marg_list) args; // with NeXT/Apple systems
  • 响应方法
- (retval_t) performv:(SEL) sel :(arglist_t) args;  // with GCC
- (id) performv:(SEL) sel :(marg_list) args; // with NeXT/Apple systems

希望实现转发的对象只需用新的方法覆盖以上方法来定义其转发行为。无需重写响应方法performv::,
由于该方法只是单纯的对响应对象发送消息并传递参数。其中,SEL类型是Objective-C中消息的类型。

转发例子

头文件:Forwarder.h

#import <objc/Object.h>

@interface Forwarder : Object
{
    id recipient; //该对象是我们希望转发到的对象。
}

@property (assign, nonatomic) id recipient;

@end

m文件:Forwarder.m

#import "Forwarder.h"

@implementation Forwarder

@synthesize recipient;

- (retval_t) forward: (SEL) sel : (arglist_t) args
{
    /*
     *检查转发对象是否响应该消息。
     *若转发对象不响应该消息,则不会转发,而产生一个错误。
     */
    if([recipient respondsTo:sel])
       return [recipient performv: sel : args];
    else
       return [self error:"Recipient does not respond"];
}

头文件:Recipient.h

#import <objc/Object.h>

// A simple Recipient object.
@interface Recipient : Object
- (id) hello;
@end

m文件:Recipient.m

#import "Recipient.h"

@implementation Recipient

- (id) hello
{
    printf("Recipient says hello!\n");

    return self;
}

@end

m文件:main.m

#import "Forwarder.h"
#import "Recipient.h"

int main(void)
{
    Forwarder *forwarder = [Forwarder new];
    Recipient *recipient = [Recipient new];

    forwarder.recipient = recipient; //Set the recipient.
    /*
     *转发者不响应hello消息!该消息将被转发到转发对象。
     *(若转发对象响应该消息)
     */
    [forwarder hello];

    return 0;
}
转发编译
$ 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'
$ ./a.out
Recipient says hello!

类别 (Category)

在Objective-C的设计中,一个主要的考虑即为大型代码框架的维护。结构化编程的经验显示,改进代码
的一种主要方法即为将其分解为更小的片段。Objective-C借用并扩展了Smalltalk实现中的“分类”概
念,用以帮助达到分解代码的目的。

一个分类可以将方法的实现分解进一系列分离的文件。程序员可以将一组相关的方法放进一个分类,使程
序更具可读性。举例来讲,可以在字符串类中增加一个名为“拼写检查”的分类,并将拼写检查的相关代码
放进这个分类中。

进一步的,分类中的方法是在运行时被加入类中的,这一特性允许程序员向现存的类中增加方法,而无需
持有原有的代码,或是重新编译原有的类。例如若系统提供的字符串类的实现中不包含拼写检查的功能
,可以增加这样的功能而无需更改原有的字符串类的代码。

在运行时,分类中的方法与类原有的方法并无区别,其代码可以访问包括私有类成员变量在内的所有成员
变量。

若分类声明了与类中原有方法同名的函数,则分类中的方法会被调用。因此分类不仅可以增加类的方法
,也可以代替原有的方法。这个特性可以用于修正原有代码中的错误,更可以从根本上改变程序中原有类
的行为。若两个分类中的方法同名,则被调用的方法是不可预测的。

其它语言也尝试了通过不同方法增加这一语言特性。TOM在这方面走的更远,不仅允许增加方法,更允许
增加成员变量。也有其它语言使用面向声明的解决方案,其中最值得注意的是Self语言。

C#与Visual Basic.NET语言以扩展函数的与不完全类的方式实现了类似的功能。Ruby与一些动态语言则
以"monkey patch"的名字称呼这种技术。

使用分类的例子

这个例子创建了Integer类,其本身只定义了integer属性,然后增加了两个分类Arithmetic与
Display以扩展类的功能。虽然分类可以访问类的私有成员,但通常利用属性的访问方法来访问是一种更
好的做法,可以使得分类与原有类更加独立。这是分类的一种典型应用—另外的应用是利用分类来替换原
有类中的方法,虽然用分类而不是继承来替换方法不被认为是一种好的做法。

头文件:Integer.h

#import <objc/Object.h>

@interface Integer : Object
{
@private
    int integer;
}

@property (assign, nonatomic) integer;

@end

m文件:Integer.m

#import "Integer.h"

@implementation Integer

@synthesize integer;

@end

头文件:Arithmetic.h

#import "Integer.h"

@interface Integer(Arithmetic)
- (id) add: (Integer *) addend;
- (id) sub: (Integer *) subtrahend;
@end

m文件:Arithmetic.m

#import "Arithmetic.h"

@implementation Integer(Arithmetic)
- (id) add: (Integer *) addend
{
    self.integer = self.integer + addend.integer;
    return self;
}

- (id) sub: (Integer *) subtrahend
{
    self.integer = self.integer - subtrahend.integer;
    return self;
}
@end

头文件:Display.h

#import "Integer.h"

@interface Integer(Display)
- (id) showstars;
- (id) showint;
@end

m文件:Display.m

#import "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

m文件:Recipient.m

#import "Integer.h"
#import "Arithmetic.h"
#import "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 Arithmetic.m Display.m -lobjc

扮演

Objective-C允许一个类在程序中完全取代另一个类,这种行为称为前者“扮演”(Posing)目标类。

注意:类的扮演在Mac OS X v10.5中被废弃,在64位运行时中不可用。

#import

在C语言中,#include预处理指令总是使被包含的文件内容被插入指令点。在Objective-C中,类似的指
令#import保证一个文件只会被包含一次,类似于一般头文件中的

#ifndef XXX
#define XXX ...
#endif

惯用法,或MSVC中的

#pragma once

Note

Objective-C++是GCC的一个前端,它可以编译混合了C++与Objective-C语法的源文件。
Objective-C++是C++的扩展,类似于Objective-C是C的扩展。由于在融合C++与Objective-C两种语言
的特性方面没有做特别的工作,因此有以下限制:

  • C++类不能从Objective-C类继承,反之亦然。
  • Objective-C定义内部不能定义C++名字空间。
  • Objective-C类的成员变量不能包括不含默认构造函数和/或含有虚方法的C++类对象,但使用C++类
    指针并无如此限制(可以在 -init方法中对之进行初始化)。
  • C++“传递值”的特性不能用在Objective-C对象上,而只能传递其指针。
  • Objective-C声明不能存在在C++模板声明中,反之亦然。但Objective-C类型可以用在C++模板的参数中。
  • Objective-C和C++的错误处理语句不同,各自的语句只能处理各自的错误。
  • Objective-C错误使得C++对象被退出时,C++析构函数不会被调用。新的64位运行时解决了这个问题。

朝闻道,夕死可矣