Objective-C和Swift的转换速查手册(推荐)
更新时间:2018年06月16日 10:35:18 作者:游星啊
这篇文章主要给大家介绍了关于Objective-C和Swift的转换速查手册的相关资料,文中通过示例代码介绍的非常详细,非常推荐给大家参考学习使用,需要的朋友们下面随着小编来一起学习学习不
前言
如果你正要从Objective-C过渡到Swift,或反过来,一个在两种语言间显示等效代码的小手册会很有帮助。本文内容就是这些:苹果开发者的红宝书,包含变量,集合,函数,类等等。
下面例子中,上面是Objective-C代码,下面是等效的Swift代码。必要的地方我会给一些备注来帮助你理解。
变量与常量
创建一个变量
1 2 3 4 5 6 |
|
1 2 3 4 5 6 |
|
创建一个常量
1 2 3 4 5 6 7 |
|
1 2 3 4 5 6 7 |
|
创建一个变量数组
创建一个常量数组
1 2 3 4 |
|
1 2 3 4 |
|
向数组中添加一个值类型
1 2 3 4 5 |
|
1 2 3 4 |
|
创建一个字典
1 2 |
|
1 2 |
|
定义一个枚举
1 2 3 4 5 6 |
|
1 2 3 4 5 6 |
|
附加一串字符
1 2 3 |
|
1 2 3 |
|
增加数字
1 2 3 4 |
|
1 2 3 4 |
|
插入字符串
1 2 3 |
|
1 2 3 |
|
打印调试信息
1 2 3 |
|
1 2 3 |
|
控制流
检查状态
1 2 3 4 5 6 7 |
|
1 2 3 4 5 6 7 8 |
|
循环一定次数
1 2 3 4 |
|
1 2 3 4 |
|
在数组中循环
1 2 3 4 5 6 |
|
1 2 3 4 5 6 |
|
数值切换
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
函数
不接收参数也没有返回的函数
1 2 3 4 5 6 |
|
1 2 3 4 5 6 |
|
不接收参数,返回一个字符串的函数
1 2 3 4 5 6 |
|
1 2 3 4 5 6 |
|
接收一个字符串,返回一个字符串的函数
1 2 3 4 5 6 7 |
|
1 2 3 4 5 6 |
|
接收一个字符串和一个整数,返回一个字符串的函数
1 2 3 4 5 6 7 8 9 10 |
|
1 2 3 4 5 6 7 8 9 10 |
|
从函数返回多个值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
1 2 3 4 5 6 |
|
不接收参数没有返回的闭环
1 2 3 4 5 6 |
|
1 2 3 4 5 6 |
|
不接收参数返回一个字符串的闭环
1 2 3 4 5 6 7 |
|
1 2 3 4 5 6 7 |
|
接收一个字符串参数,返回一个字符串的闭环
1 2 3 4 5 6 7 |
|
1 2 3 4 5 6 7 |
|
类
创建空类
1 2 3 4 5 6 |
|
1 2 3 4 |
|
创建有2个属性的类
1 2 3 4 5 6 7 8 9 |
|
1 2 3 4 5 6 7 8 9 10 11 |
|
创建有一个私有属性的类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
1 2 3 4 5 6 7 8 9 10 |
|
创建有一个实例方法的类
1 2 3 4 5 6 7 8 9 10 |
|
1 2 3 4 5 6 |
|
创建有一个静态方法的类
1 2 3 4 5 6 7 8 9 10 11 |
|
1 2 3 4 5 6 7 |
|
用一种新方法扩展一个类型
1 2 3 4 5 6 7 8 9 10 11 12 |
|
1 2 3 4 5 6 |
|
检查一个对象的类
1 2 3 4 |
|
1 2 3 4 |
|
类型转换
1 2 |
|
1 2 3 4 5 |
|
GCD
在不同线程运行代码
1 2 3 4 5 6 7 8 |
|
1 2 3 4 5 6 7 8 |
|
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对脚本之家的支持。
Objective-C 简介
在iOS的开发中使用的是Objective C语言,它是一种面向对象的语言,因而对于已经掌握面向对象语言知识的编程者来说是非常简单的。
接口和实现
在Objective里完成的文件被称为界面文件,该类文件的定义被称为实现文件。
一个简单的界面文件MyClass.h将如图所示:
@interface MyClass:NSObject{ // 类变量声明 } // 类属性声明 // 类方法和声明 @end
执行MyClass.m文件,如下所示
@implementation MyClass // 类方法定义 @end
创建对象
完成创建对象,如下所示
MyClass *objectName = [[MyClass alloc]init] ;
方法(methods)
Objective C中声明的方法如下所示:
-(returnType)methodName:(typeName) variable1 :(typeName)variable2;
下面显示了一个示例:
-(void)calculateAreaForRectangleWithLength:(CGfloat)length andBreadth:(CGfloat)breadth;
你可能会想什么是andBreadth字符串,其实它的可选字符串可以帮助我们阅读和理解方法,尤其是当方法被调用的时候。
在同一类中调用此方法,我们使用下面的语句。
[self calculateAreaForRectangleWithLength:30 andBreadth:20];
正如上文所说的andBreath使用有助于我们理解breath是20。Self用来指定它是一个类的方法。
类方法(class methods)
直接而无需创建的对象,可以访问类方法。他们没有任何变量和它关联的对象。示例如下:
+(void)simpleClassMethod;
它可以通过使用类名(假设作为MyClass类名称)访问,如下所示:
[MyClass simpleClassMethod];
实例方法
可以创建的类的对象后只访问实例方法,内存分配到的实例变量。实例方法如下所示:
-(void)simpleInstanceMethod;
创建类的对象后,它可以访问它。如下所示:
MyClass *objectName = [[MyClass alloc]init] ; [objectName simpleInstanceMethod];
Objective C的重要数据类型
序号 | 数据类型 |
---|---|
1 | NSString字符串 |
2 | CGfloat 浮点值的基本类型 |
3 | NSInteger 整型 |
4 | BOOL 布尔型 |
打印日志
NSLog用于打印一份声明,它将打印在设备日志和调试版本的控制台和分别调试模式上。
如 NSlog(@"");
控制结构
除了几个增补的条款外,大多数的控制结构与C以及C++相同
属性(properties)
用于访问类的外部类的变量属性
比如:@property(非原子、强)NSString*myString
访问属性
可以使用点运算符访问属性,若要访问上一属性可以执行以下操作
self.myString = @"Test";
还可以使用set的方法,如下所示:
[self setMyString:@"Test"];
类别(categories)
类用于将方法添加到现有类。通过这种方法可以将方法添加到类,甚至不用执行文件,就可以在其中定义实际的类。MyClass的样本类别,如下所示:
@interface MyClass(customAdditions) - (void)sampleCategoryMethod; @end @implementation MyClass(categoryAdditions) -(void)sampleCategoryMethod{ NSLog(@"Just a test category"); }
数组
NSMutableArray 和 NSArray 是 ObjectiveC 中使用的数组类,前者是可变数组,后者是不可变数组。如下:
NSMutableArray *aMutableArray = [[NSMutableArray alloc]init]; [anArray addObject:@"firstobject"]; NSArray *aImmutableArray = [[NSArray alloc] initWithObjects:@"firstObject",nil];
词典
NSMutableDictionary和NSDictionary是Objective中使用的字典,前者可变词典,后者不可变词典,如下所示:
NSMutableDictionary*aMutableDictionary = [[NSMutableArray alloc]init]; [aMutableDictionary setObject:@"firstobject" forKey:@"aKey"]; NSDictionary*aImmutableDictionary= [[NSDictionary alloc]initWithObjects:[NSArray arrayWithObjects: @"firstObject",nil] forKeys:[ NSArray arrayWithObjects:@"aKey"]];
Objective-C Runtime 解析
2016.12.08 15:26:55字数 5,687阅读 247
这是一篇译文,作为一个英语水平处于半吊子的理科男,因此一定存在不尽原意的地方,翻译此文纯属个人喜好,希望能得到大家的指点和反馈,读者如有兴趣的话可以查看原文。
以下是正文:
一般而言,当人们刚接触Cocoa/Objective-C的时候,运行时机制(Objective-C Runtime)是最容易被忽视的特征之一。究其原因在于Objective-C是一门简单的语言,花费几个小时便能入门,此后,新手们通常会将大部分的时间和精力用于研究Cocoa Framework以及如何使用它。然而,每一个人至少应该清楚运行时是如何运转的,而不仅仅停留在编译方式的认知层面,如:[target doMethodWith:var];编译之后变成object_msgSend(target,@selector(doMethodWith:),var1)。了解运行时机制的工作原理可以帮助你进一步理解Objective-C这门语言以及你编写的App的运转流程。我相信各个水平层次的Mac/iPhone开发者都会在研究运行时机制的过程中有所收获。
Objective-C Runtime库是开源的
Objective-C Runtime库是开源的,你随时可以在源代码上查阅。事实上,查阅源代码是弄清楚Objective-C原理的首选途径之一,胜过阅读苹果开发文档。下载最新版本的源代码点击我。
动态 & 静态 语言
Objective-C是基于运行时的语言,意味着它会尽可能地将决定代码执行逻辑的操作从编译&链接阶段延迟到代码被执行的阶段。这将给你带来很大的灵活性,因此如果有必要的话你可以将消息重定向到合适的对象,或者你甚至可以交换两个方法实现,等等。实现上述功能需要运行时具备审查对象可以响应哪些请求和不能响应哪些请求然后准确地派发消息的能力。如果我们将Objective-C这一特性对比C语言。C语言程序运行始于main()函数,基于至上而下的设计执行你的逻辑和调用你实现的函数。C结构体不能通过发送请求到其他的结构体来执行某个函数。很可能你会编写一段C语言代码,如下所示:
#include < stdio.h >
int main(int argc, const char **argv[])
{
printf("Hello World!");
return 0;
}
上述代码经过编译器编译、优化,然后将优化后的代码转化成汇编语言:
.text
.align 4,0x90
.globl _main
_main:
Leh_func_begin1:
pushq %rbp
Llabel1:
movq %rsp, %rbp
Llabel2:
subq $16, %rsp
Llabel3:
movq %rsi, %rax
movl %edi, %ecx
movl %ecx, -8(%rbp)
movq %rax, -16(%rbp)
xorb %al, %al
leaq LC(%rip), %rcx
movq %rcx, %rdi
call _printf
movl $0, -4(%rbp)
movl -4(%rbp), %eax
addq $16, %rsp
popq %rbp
ret
Leh_func_end1:
.cstring
LC:
.asciz "Hello World!"
随后链接相关的库生成一个可执行文件。对比于Objective-C,虽然代码处理过程很相似,但是编译后的代码取决于Objective-C Runtime库。当我们最初学习Objective-C时被告知中括号里面的代码是如何被处理的,如下
[self doSomethingWithVar:var1];
被转变成
objc_msgSend(self,@selector(doSomethingWithVar:),var1);
除此之外我们并不真的知道运行时机制是如何工作的,也许很久以后会知道。
何为Runtime(运行时)
Objective-C Runtime就是一个Runtime库,主要有C语言&汇编语言编写而成,在C语言的基础上加上面向对象的功能之后就成为了Objective-C语言。这意味着运行时机制负责加载类,方法派发,方法传达等操作。本质上而言,运行时机制提供了所有的需要的结构用以支持Objective-C的面向对象编程。
Objective-C 运行时术语
在进一步深入之前,让我们扫清一些术语的障碍,这样使我们处于同一立场。就MacOS X App & iPhone OS App开发者所关心而言,这里有两种运行时机制: Modern Runtime和Legacy Runtime。Modern Runtime适用于所有64位MacOS应用和所有iPhone应用,Legacy Runtime适用于所有的32位MacOS应用。运行时机制中有两种类型的函数:实例函数(以‘-’符号开头如-(void)doFoo);类函数(以‘+’开头如+(id)alloc)。两种函数都与C函数很像,包含一组实现某个任务的代码,如下所示
-(NSString *)movieTitle
{
return @"Futurama: Into the Wild Green Yonder";
}
选择器:在Objective-C中,选择器本质上是一个C数据结构体用以标识一个对象将要执行的函数。在运行时机制中的定义如下
typedef struct objc_selector *SEL;
使用方式
SEL aSel = @selector(movieTitle);
消息调用:
[target getMovieTitleForObject:obj];
Objective-C消息就是中括号[]里面的所有东西,包括消息的接受者target,调用的函数getMovieTileForObject以及所有发送的参数obj。消息调用虽然样式上类似于c函数调用但是实现却不同。实际上,当你发送一个消息给一个对象并意味着函数会被执行。对象可能会检测谁是消息的发送者,基于此再决定执行一个不同的函数或者转送消息给其他不同的目标对象。如果你查看运行时机制里的类定义,你将会看到如下所示的内容:
typedef struct objc_class *Class;
typedef struct objc_object {
Class isa;
} *id;
这里有几个要点。首先是类Class和对象Object都有一个对应的结构体。所有的objc_object结构体都有一个类指针isa,这就是我们所说的“isa指针”。运行时机制需要通过检测一个对象的isa指针去查看对象的类别,然后查看该对象是否能响应你当前发送过来的消息。接下来是id指针,id指针默认不属于任何类别只表明指向的是一个Objective-C对象。对于id指针指向的对象,你可以获知对象的类别,查看对象是否能响应某个函数等等,然后当你具体了解了id指针指向的对象之后便可以更好的使用该对象。你同样可以查看LLVM/Clang文档中Blocks的定义:
struct Block_literal_1 {
void *isa; // initialized to &_NSConcreteStackBlock or &_NSConcreteGlobalBlock
int flags;
int reserved;
void (*invoke)(void *, ...);
struct Block_descriptor_1 {
unsigned long int reserved; // NULL
unsigned long int size; // sizeof(struct Block_literal_1)
// optional helper functions
void (*copy_helper)(void *dst, void *src);
void (*dispose_helper)(void *src);
} *descriptor;
// imported variables
};
Block结构的设计兼容于运行时机制。因此Block被视为一个Objective-C对象,所有也就可以响应消息如-retain,-release,-copy等等。
IMP:Method Implementations
typedef id (*IMP)(id self,SEL _cmd,...);
IMP是一个函数指针,由编译器生成且指向函数的实现内容。如果你目前是一个Objective-C新手则浅尝辄止,但是我们随后会了解运行时机制是如何调用你的函数的。
Objective-C类:类里面是什么?在Objective-C中,类实现基本上类似于:
@interface MyClass : NSObject {
//vars
NSInteger counter;
}
//methods
-(void)doFoo;
@end
但是类在运行时机制中定义远不如此,如下
#if !__OBJC2__
Class super_class OBJC2_UNAVAILABLE;
const char *name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list *ivars OBJC2_UNAVAILABLE;
struct objc_method_list **methodLists OBJC2_UNAVAILABLE;
struct objc_cache *cache OBJC2_UNAVAILABLE;
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE;
#endif
我们可以看到一个类中声明了一个父类的引用,类名,实例变量列表,方法列表,缓存以及协议列表。当响应发送给类或对象的消息时,运行时机制需要用到这些信息。
类定义对象同时类本身也是对象?何解?
之前我提到过在Objective-C中类本身也是对象,运行时机制通过引入元类(Meta Class)来处理类对象。当你发送一个类似于[NSObject alloc]消息的时候,实际上是发送一个消息给类对象,此时将类对象视为元类的实例对待,而元类本身也是一个根元类(Root Meta Class)的实例。While if you say subclass from NSObject, your class points to NSObject as it's superclass. However all meta classes point to the root metaclass as their superclass. (原文似乎表达观点有误,暂不翻译)。所有的元类仅有一个类函数列表(不同于类处理实例函数列表,还有变量列表和协议列表等等)。因此,当你发送一个消息给类对象时,如[NSObject alloc],objc_megSend()实际上是搜索元类的函数列表查看是否有响应的函数,如果存在则在该类对象上执行该函数。
为什么继承Apple的原生类?
在你刚开始Cocoa编程时,相关教程都是说创建一个类继承于NSObject然后开始编写自己的代码,简单地继承Apple的原生类会让你获益匪浅。其中一个你甚至意识不到的好处就是让你创建的类运行于运行时机制之上。当我们新建一个实例对象,如下:
MyObject *object = [[MyObject alloc] init];
最先被执行的消息是+alloc。如果你查阅这个文档会发现:“isa这一实例变量被初始化指向一个描述对于类的数据结构体,其他所有的实例变量都被初始化为0”。所以,通过继承Apple原始类不仅仅继承一些不错的属性,而且还能让我们轻易地创建符合于运行时机制要求的对象(包含一个指向类的isa指针)。
类缓存机制
当OC的运行时机制机制通过检视一个对象的isa指针指向的类时会发现该对象实现了很多函数。然而,你可能仅仅调用其中的一小部分也就意味没必要每一次查找某个函数时都去搜索一遍类中的函数列表。因此,类创建了缓存,将你每次搜索函数列表后找到的相应函数存入缓存中。所以,当objc_msgSend()在类中搜寻某个函数是首先会遍历缓存列表。这样做的理论依据在于如果你发送过某个消息给一个对象,你很可能回再次发送同样的消息。因此如果我们将该理论考虑在内意味着如果你有一个NSObject的子类MyObject,并运行以下代码:
MyObject *obj = [[MyObject alloc] init];
@implementation MyObject
-(id)init {
if(self = [super init]){
[self setVarA:@”blah”];
}
return self;
}
@end
接下来发生:
- [MyObject alloc]最先被执行。因为MyObject类没有实现alloc函数所以在该类自然找不到对应的函数,随后进入父类指针指向的NSObject类。
- 询问NSObject类是否响应+alloc,发现其实现了alloc函数。+alloc检测到接收类是MyObject然后分配一块响应大小的内存并在其中初始化一个isa指针指向MyObject类。现在,我们获得了一个实例对象,随后运行时机制将NSObject类的+alloc函数指针存入NSObject对象对应的类中的缓存列表中。
- 截至目前,我们发送了一个类消息,现在我们发送一个实例消息:调用-init函数或者自定义的初始化函数。显然,MyObject的实例对象能响应这个消息,因此-(id)init会被存入缓存列表中。
- 随后self=[super init]被调用。super作为一个魔法关键字指向父类对象,因此转向NSObjct类中,调用init函数。这样做是为了确保面向对象继承体系(OOP inheritance)正常运转,因为所以的父类都将会正确地初始化它们的变量,然后作为子类对象可以正确地初始化自身的变量和必要时重载父类。
在这个NSObject类的例子中,没有特别的要点出现。但是事实并不总是如此,有时候初始化很重要,如下:
#import < Foundation/Foundation.h>
@interface MyObject : NSObject
{
NSString *aString;
}
@property(retain) NSString *aString;
@end
@implementation MyObject
-(id)init
{
if (self = [super init]) {
[self setAString:nil];
}
return self;
}
@synthesize aString;
@end
int main (int argc, const char * argv[]) {
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
id obj1 = [NSMutableArray alloc];
id obj2 = [[NSMutableArray alloc] init];
id obj3 = [NSArray alloc];
id obj4 = [[NSArray alloc] initWithObjects:@"Hello",nil];
NSLog(@"obj1 class is %@",NSStringFromClass([obj1 class]));
NSLog(@"obj2 class is %@",NSStringFromClass([obj2 class]));
NSLog(@"obj3 class is %@",NSStringFromClass([obj3 class]));
NSLog(@"obj4 class is %@",NSStringFromClass([obj4 class]));
id obj5 = [MyObject alloc];
id obj6 = [[MyObject alloc] init];
NSLog(@"obj5 class is %@",NSStringFromClass([obj5 class]));
NSLog(@"obj6 class is %@",NSStringFromClass([obj6 class]));
[pool drain];
return 0;
}
如果你是Cocoa初学者,然后我问你上述代码的打印结果,你的回答可能如下:
NSMutableArray
NSMutableArray
NSArray
NSArray
MyObject
MyObject
但是运行结果却是:
obj1 class is __NSPlaceholderArray
obj2 class is NSCFArray
obj3 class is __NSPlaceholderArray
obj4 class is NSCFArray
obj5 class is MyObject
obj6 class is MyObject
这是因为在Objective-C中,调用+alloc会隐性地返回一个类的实例对象而调用-init会返回另外一个类的实例对象。
objc_msgSend的工作流程是什么?
objc_msgSend函数实现比较复杂。比如我们写了如下代码...
[self printMessageWithString:@"Hello World!"];
上述代码实际上会被编译器转化成:
objc_msgSend(self,@selector(printMessageWithString:),@"Hello World!");
随后,objc_msgSend函数根据目标对象的isa指针去查询对应的类(或者任一父类)看是否响应选择器@selector(printMessageWithString:)。假设在类的函数派发列表或者缓存中找到了对应的函数实现,那么执行该函数。如此看来,objc_msgSend函数没有返回值,它开始执行然后找到对应的目标函数并执行,因此目标函数的返回值被视为objc_msgSend函数的返回值。
Bill Bumgarner对于objc_msgSend的研究比我要表达的更为深入(part 1,part 2,part 3)。总结一下他所要表达的以及你在查阅运行时机制源代码时可能发现的内容:
- 检测屏蔽的函数和死循环,很显然如果代码运行在垃圾回收的环境下,我们可以忽略-retain,-release的调用,诸如此类。
- 检测空对象。 不同于其他编程语言,在Objective-C中发送一个消息给空对象是完全合法的。[there are some valid reasons you'd want to. Assuming we have a non nil target we go on... ]
- 然后在一个类中查找函数指针,首先是搜索缓存列表,如果找到了对应的函数指针就跳转对其实现代码段,即执行函数。
- 如果在缓存列表中没有找到对应的函数指针,便搜索类中的函数派发列表。如果找到了对应的函数指针即跳转到其实现代码段。
- 如果在缓存列表和函数列表都没有找到对应的函数,随即跳转到消息转发机制,意味着代码会被编译成c语言代码。所以一个函数如下所示:
-(int)doComputeWithNum:(int)aNum
将会被编译成:
int aClass_doComputeWithNum(aClass *self,SEL _cmd,int aNum)
此时,运行时机制通过这些函数的指针来调用这些转化后的函数,现在你已经不能直接调用这些函数,但是Cocoa库提供了一个方法来获得这些函数的函数指针。。。
//declare C function pointer
int (computeNum *)(id,SEL,int);
//methodForSelector is COCOA & not ObjC Runtime
//gets the same function pointer objc_msgSend gets
computeNum = (int (*)(id,SEL,int))[target methodForSelector:@selector(doComputeWithNum:)];
//execute the C function pointer returned by the runtime
computeNum(obj,@selector(doComputeWithNum:),aNum);
这样,你可以知道访问这些函数并在运行时中直接调用,甚至利用这种方法来绕开运行时的动态调用来确保一个指定的函数被执行。运行时机制同样可以调用你的函数,只不过是通过objc_msgSend()。
Objective-C消息传送
在Objective-C中,发送一个消息给一个不会做出响应的对象是合法的,甚至可能是有意这样设计的。苹果在其开发文档中给出的原因之一是为了模拟Objective-C不支持的多继承,或者你只是想抽象化你的设计,隐藏能处理这些消息的实例对象或类。这是运行时机制必要的功能之一。
消息传送工作流程:
- 运行时机制搜寻了对象的类和它所有父类中的缓存列表和函数列表,但是并没有找到指定的方法。
- 随后运行时机制将会调用你类中的 +(BOOL)resolveInstanceMethod:(SEL)aSEL方法给你一次机会为指定的函数提供函数实现,并告诉运行时机制你已经实现了这个方法。如果运行时机制再次搜索这个函数就能找到对应的函数实现。你可以如下所示,实现这个功能:
定义一个函数
void fooMethod(id obj, SEL _cmd)
{
NSLog(@"Doing Foo");
}
如下所示,使用class_addMethod()来实现
+(BOOL)resolveInstanceMethod:(SEL)aSEL
{
if(aSEL == @selector(doFoo:)){
class_addMethod([self class],aSEL,(IMP)fooMethod,"v@:");
return YES;
}
return [super resolveInstanceMethod];
}
class_addMethod()最后一个参数“v@:”表示函数fooMethod的返回值和参数,你可以在运行时机制指南中类型编码Type Encodings了解你可以具体的规则。
- 运行时机制随后会调用- (id)forwardingTargetForSelector:(SEL)aSelector函数,给你一次机会将运行时指向另外一个能响应目标函数的对象。这样做比触发消耗更大的函数:-(void)forwardInvocation:(NSInvocation *)anInvocation更划算。你的具体实现可能如下所示:
- (id)forwardingTargetForSelector:(SEL)aSelector
{
if(aSelector == @selector(mysteriousMethod:)){
return alternateObject;
}
return [super forwardingTargetForSelector:aSelector];
}
很显然你不想返回self指针,否则可能导致死循环。
- 此时,运行时机制尝试最后一次去获取消息的预期目标,并调用- (void)forwardInvocation:(NSInvocation *)anInvocation。如果你未曾了解NSInvocation点击查看,这是Objective-C消息中很重要的构成部分。一旦你持有一个NSInvocation对象,你基本上可以更改消息的任何内容,包括目标对象,选择器和函数参数。你可能操作如下:
-(void)forwardInvocation:(NSInvocation *)invocation
{
SEL invSEL = invocation.selector;
if([altObject respondsToSelector:invSEL]) {
[invocation invokeWithTarget:altObject];
} else {
[self doesNotRecognizeSelector:invSEL];
}
}
如果类是继承自NSObjct,- (void)forwardInvocation:(NSInvocation *)anInvocation函数的默认实现是调用-doesNotRecognizeSelector函数,如果你还想做点什么来响应这次消息转送,重载这个函数将是最后一次机会。
实例变量的无碎片化(Modern Runtime)
目前我们所了解到关于Modern Runtime的概念之一是实例变量无碎片化(Non Fragile ivars)。编译器在编译类的时候确定了实例变量的布局,决定了某个实例变量的访问位置。这属于底层细节,关乎于获得一个对象的指针,查找某个实例变量相对于对象起始位置的偏移,根据实例变量的类型读取相应数量的字节。因此,实例变量的布局可能如下所示,左侧的数字表示实例变量的字节偏移量
如上所示,NSObject对象的实例变量布局以及继承NSObject后添加了自己的变量之后的布局。这样的布局在苹果发布更新之前都能正常运行,但是苹果发布了Mac OS X 10.6之后,布局就会变成如下所示:
因为与父类的实例变量重叠,自定义的对象的实例变量被抹掉。防止这样的情况发生唯一的可能是苹果能保持更新之前的布局。但是如果苹果这样做的话,那么苹果的框架将不可能得到改进,因为这些框架的实例变量布局已经写死了。处于实例变量碎片化的情况下只能通过重新编译所有继承于苹果类的类来保证兼容新的框架。那么实例变量无碎片化的情况下会是如何处理?
实例变量无碎片化的前提下,编译器创建同实例变量碎片化情况下一样的实例变量布局。但是当运行时检测到一个重叠的父类时会调整自定义变量的偏移量,因此子类中自定义的变量得以保留。
Objective-C 关联对象
最近Mac OS X 10.6 Snow Leopard推出了一个新特性,称之为关联引用。不同于其他一些语言,Objective-C不支持动态添加实例变量到某个对象的类中。所以在此之前你不得不耗尽脑力去构建一个特定的基础架构,营造一个可以给某个对象动态添加变量的假象。现在在Mac OS X 10.6中,运行时已经支持这一功能。如果想添加一个变量到任一个已经存在的苹果原生类中,比如NSView,我们可以做如下操作:
#import < Cocoa/Cocoa.h> //Cocoa
#include < objc/runtime.h> //objc runtime api’s
@interface NSView (CustomAdditions)
@property(retain) NSImage *customImage;
@end
@implementation NSView (CustomAdditions)
static char img_key; //has a unique address (identifier)
-(NSImage *)customImage
{
return objc_getAssociatedObject(self,&img_key);
}
-(void)setCustomImage:(NSImage *)image
{
objc_setAssociatedObject(self,&img_key,image,
OBJC_ASSOCIATION_RETAIN);
}
@end
在runtime.h头文件中可以看到存储关联对象方式的可选项,作为objc_setAssociatedObject()函数的参数传入。
/* Associated Object support. */
/* objc_setAssociatedObject() options */
enum {
OBJC_ASSOCIATION_ASSIGN = 0,
OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1,
OBJC_ASSOCIATION_COPY_NONATOMIC = 3,
OBJC_ASSOCIATION_RETAIN = 01401,
OBJC_ASSOCIATION_COPY = 01403
};
这些可选值与@property语法的可选值相匹配。
混合虚函数表派发(Hybrid vTable Dispatch)
如果你查阅现代版运行时的源代码,你会看到以下内容(位于objc-runtime-new.m):
/***********************************************************************
* vtable dispatch
*
* Every class gets a vtable pointer. The vtable is an array of IMPs.
* The selectors represented in the vtable are the same for all classes
* (i.e. no class has a bigger or smaller vtable).
* Each vtable index has an associated trampoline which dispatches to
* the IMP at that index for the receiver class's vtable (after
* checking for NULL). Dispatch fixup uses these trampolines instead
* of objc_msgSend.
* Fragility: The vtable size and list of selectors is chosen at launch
* time. No compiler-generated code depends on any particular vtable
* configuration, or even the use of vtable dispatch at all.
* Memory size: If a class's vtable is identical to its superclass's
* (i.e. the class overrides none of the vtable selectors), then
* the class points directly to its superclass's vtable. This means
* selectors to be included in the vtable should be chosen so they are
* (1) frequently called, but (2) not too frequently overridden. In
* particular, -dealloc is a bad choice.
* Forwarding: If a class doesn't implement some vtable selector, that
* selector's IMP is set to objc_msgSend in that class's vtable.
* +initialize: Each class keeps the default vtable (which always
* redirects to objc_msgSend) until its +initialize is completed.
* Otherwise, the first message to a class could be a vtable dispatch,
* and the vtable trampoline doesn't include +initialize checking.
* Changes: Categories, addMethod, and setImplementation all force vtable
* reconstruction for the class and all of its subclasses, if the
* vtable selectors are affected.
**********************************************************************/
上述内容阐述的要点就是运行时会尽量存储调用最频繁的函数以达到提高软件运行速度的目的,因为通过虚函数表查找比调用objc_msgSend函数使用的指令更少。虚函数表中的16个函数调用次数远多于其他所有函数。实际上,进一步深入研究代码你会发现垃圾回收机制和无垃圾回收机制下虚函数表中默认的函数:
static const char * const defaultVtable[] = {
"allocWithZone:",
"alloc",
"class",
"self",
"isKindOfClass:",
"respondsToSelector:",
"isFlipped",
"length",
"objectForKey:",
"count",
"objectAtIndex:",
"isEqualToString:",
"isEqual:",
"retain",
"release",
"autorelease",
};
static const char * const defaultVtableGC[] = {
"allocWithZone:",
"alloc",
"class",
"self",
"isKindOfClass:",
"respondsToSelector:",
"isFlipped",
"length",
"objectForKey:",
"count",
"objectAtIndex:",
"isEqualToString:",
"isEqual:",
"hash",
"addObject:",
"countByEnumeratingWithState:objects:count:",
};
那么你如何知道是否调用了这些函数?调试模式下,你将会在栈中看到以下函数中的某一个被调用,出于调试的目的,所有的这些方法都可以视为通过objc_msgSend函数调用的。
- objc_msgSend_fixup:是当运行时正在派发一个位于虚函数表的函数时触发,即用于派发虚函数表中的函数。
- objc_msgSend_fixedup:是当调用一个本应存在于虚函数表的函数但是现在已经不存在的函数时触发(个人觉得应该是调用在objc_msgSend_fixup函数之后,并且由前者触发的)。
- objc_msgSend_vtable[0-15]:调试模式下,也许会看到某个函数调用类似于objc_msgSend_vtable5意味着正在调用虚函数表中对应序号的某个函数。
运行时可以决定是否派发这些函数,所以不要指望以下这种情况存在:objc_msgSend_vtable10在运行时的一次循环中对应的函数是-length,意味着后面任一次循环中也是同样情况。
结论
我希望你能喜欢这些内容,这篇文章基本上覆盖了我在Des Moines Cocoaheads 上谈及的内容。Objective-C运行时是一个了不起的杰作,它为我们的Cocoa/Objective-C应用提供了一个强大的平台,让很多我们正在受用的功能都成为可能。如果你还没有查阅关于如何使用Objective-C运行时的Apple开发文档,我希望你马上行动,谢谢。附上:运行时开发文档,运行时介绍文档
大部分有一点其他平台开发基础的初学者看到XCode,第一感想是磨拳擦掌,看到Interface Builder之后,第一感想是跃跃欲试,而看到Objective-C的语法,第一感想就变成就望而却步了。
如果你和我一样,对苹果相关的开发:Mac OS X或是iPhone有兴趣,但是第一时间看到Objective-C就会头疼并伴有发烧症状的话,疗效比较好的快速治疗方法是阅读本文。大概花二十分钟左右,而且绝不无聊的时间,你就会对Objective-C有那么一点点了解,至少读读例子不会那么头疼了。
不过假定你要有那么一点点C++、C#或是Java的基础,至少能看到C++、C#或是Java的源码,能够大致明白说得是什么。
这篇文章不是一篇科技文章,希望你也不要把它当做科技文章来读。文章非常不严谨,但是我相信你能看得懂。
XCode、Objective-C、Cocoa说的是几样东西?
答案:三样东西。
XCode:你可以把它看成是一个开发环境,就好像Visual Studio或者Netbeans或者SharpDevelop一样的玩意。你可以将Interface Builder认为是Visual Studio中用来画界面的那部分功能单独提出来的程序。
Objective-C:这是一种语言,就好像C++是一种语言,Java是一种语言,C#是一种语言,莺歌历史也是一种语言一样。
Cocoa:是一大堆函数库,就好像MFC、.NET、Swing这类玩意,人家已经写好了一堆现成的东西,你只要知道怎么用就可以了。
有些人会比较容易混淆Objective-C和Cocoa,就好像有些人会混淆c#和.NET一样。这两个东西真的是两个不一样的东西。
Objective-C是什么?
你可以把它认为是语法稍稍有点不一样的c语言。虽然第一眼望上去你可能会认为它是火星语,和你所认知的任何一种语言都不一样。
先简单列出一点差别:
问题一:我在程序中看到大量的减号、中括号和NS****这种东西,他们是什么玩意儿?
1 减号(或者加号)
减号表示一个函数、或者方法、或者消息的开始,怎么说都行。
比如c#中,一个方法的写法可能是:
- private void hello(bool ishello)
- {
- //OOXX
- }
用Objective-C写出来就是
- -(void) hello:(BOOL)ishello
- {
- //OOXX
- }
挺好懂的吧?
不过在Objective-C里面没有public和private的概念,你可以认为全是public。
而用加号的意思就是其他函数可以直接调用这个类中的这个函数,而不用创建这个类的实例。
2 中括号
中括号可以认为是如何调用你刚才写的这个方法,通常在Objective-C里说“消息”。
比如C#里你可以这么写:
- this.hello(true);
在Objective-C里,就要写成:
- [self hello:YES];
- 3 NS****
老乔当年被人挤兑出苹果,自立门户的时候做了个公司叫做NextStep,里面这一整套开发包很是让一些科学家们喜欢,而现在Mac OS用的就是NextStep这一套函数库。
这些开发NextStep的人们比较自恋地把函数库里面所有的类都用NextStep的缩写打头命名,也就是NS****了。比较常见的比如:
◆NSLog
◆NSString
◆NSInteger
◆NSURL
◆NSImage
…
你会经常看到一些教学里面会用到:
- NSLog (@”%d”,myInt);
这句话主要是在console里面跟踪使用,你会在console里面看到myInt的值(在XCode里面运行的时候打开dbg窗口即可看到)。而我们在其他开发环境里面可能会比较习惯使用MessageBox这种方式进行调试。
你还可以看到其他名字打头的一些类,比如CF、CA、CG、UI等等,比如
◆CFStringTokenizer 这是个分词的东东
◆CALayer 这表示Core Animation的层
◆CGPoint 这表示一个点
◆UIImage 这表示iPhone里面的图片
CF说的是Core Foundation,CA说的是Core Animation,CG说的是Core Graphics,UI说的是iPhone的User Interface……还有很多别的,等你自己去发掘了。
问题二、#import、@interface这类玩意说的是什么?
1、#import
你可以把它认为是#include,一样的。但是最好用#import,记住这个就行了。
2、@interface等等
比如你在c#中写一个抓孩子类的定义:
- public class Kids : System
- {
- private string kidName=”mykid”;
- private string kidAge=“15”;
- private bool isCaughtKid()
- {
- return true;
- }
- }
当然,上面的写法不一定对,就是个用于看语法的举例。
在Objective-C里就得这么写:
先写一个kids.h文件定义这个类:
- @interface Kids: NSObject {
- NSString *kidName;
- NSString *kidAge;
- }
- -(BOOL) isCaughtKid:;
- @end
再写一个kids.m文件实现:
- #import “kids.h”
- @implementation Kids
- -(void) init {
- kidName=@”mykid”;
- kidAge=@”15”;
- }
- -(BOOL) isCaughtKid:{
- return YES;
- }
- @end
这个写法也不一定对,主要是看看语法就行了。
问题三、一个方法如何传递多个参数?
一个方法可以包含多个参数,不过后面的参数都要写名字。
多个参数的写法
(方法的数据类型) 函数名: (参数1数据类型) 参数1的数值的名字 参数2的名字: (参数2数据类型
) 参数2值的名字 …. ;
举个例子,一个方法的定义:
- -(void) setKids: (NSString *)myOldestKidName secondKid: (NSString *) mySecondOldestKidName thirdKid: (NSString *) myThirdOldestKidName;
实现这个函数的时候:
- -(void) setKids: (NSString *)myOldestKidName secondKid: (NSString *) mySecondOldestKidName thirdKid: (NSString *) myThirdOldestKidName{
- 大儿子 = myOldestKidName;
- 二儿子 = mySecondOldestKidName;
- 三儿子 = myThirdOldestKidName;
- }
调用的时候:
- Kids *myKids = [[Kids alloc] init];
- [myKids setKids: @”张大力” secondKid: @”张二力” thirdKid: @”张小力”];
而如果你用c#写这个方法,大致的写法可能是
- public void setKids( string myOldestKidName, string mySecondOldestKidName, string myThirdOldestKidName)
- {
- …
- }
调用的时候大概的写法可能是:
- Kids myKids = new Kids();
- myKids.setKids (“张大力”, “张二力”, “张小力”);
明白了吧?其实不怎么难看懂。
基本上,如果你能了解下面这段代码的转换关系,你Objective-C的语法也就懂了八成了:
- [[[MyClass alloc] init:[foo bar]] autorelease];
转换成C#或者Java的语法也就是:
- MyClass.alloc().init(foo.bar()).autorelease();
其他的一些东西
其实这些本站之前的文章有所提及,这里再详细解释一下。
1、 id:
Objective-C有一种比较特殊的数据类型是id。你可以把它理解为“随便”。
在Objective-C里,一切东西都是指针形式保存,你获取到的就是这个对象在内存的位置。那么id就是你知道这个位置,但是不知道里面是啥的时候的写法。
2、 同一个数组可以保存不同的对象:
比如一个数组NSArray,这种数组里面可以保存各种不同的对象,比如这个数组里:
myArray <—-|
0: (float) 234.33f
1: @”我是个好人”
2: (NSImage *)
3: @”我真的是好人”
这是一个由4个东西组成的数组,这个数组包括一个浮点数,两个字符串和一个图片。
3、BOOL,YES,NO:
你可以认为YES表示C#或者Java里的true,NO表示false。而实际上YES是1,NO是0,BOOL本身就是个char。
4、IBOutlet、IBAction是啥玩意,总能看到。
这两个东西其实在语法中没有太大的作用。如果你希望在Interface Builder中能看到这个控件对象,那么在定义的时候前面加上IBOutlet,在IB里就能看到这个对象的outlet,如果你希望在Interface Builder里控制某个对象执行某些动作,就在方法前面加上(IBAction)。
而这两个东西实际上和void是一样的。
5、nil。
Objective-C里的NULL(空)就这么写,表示空指针。
6、为什么是@”字符串”而不是”字符串”
前面加上@符号,编译器在编译的时候会在程序中给你留出位置,这样才能保证这个字符串不会丢失。反正记住,如果你要想把某些字符串写死在程序里,就要用@”字符串”,如果忘了用@,程序应该会出错。
superzhou大侠指正:
6、为什么是@”字符串”而不是”字符串”
”字符串”是C的字符串,@””是把C的字符串转成NSString的一个简写.
在需要NSString的地方才需要这个转化,例如NSLog里面.
在需要C string的地方,还是用”字符串”的.
另外,@””这个转换是不支持中文的.例如NSLog(@”字符串”); 是一定输出不了中文的.
Objective-C 2.0
Objective-C 2.0是Leopard新增加的一门语言,其实和原来的Objective-C是一样的。主要是增加了属性。详细的内容这里不写了,可以参阅Allen Dang的这篇文章,写的很明白。
总结
现在来总结一下怎么看Objective-C的代码和怎么开始学Objective-C吧。
1、记住Objective-C就是C,不是火星语,这个很关键。
2、记住你自己看不懂不表示脑子迟钝,大部分人第一次看Objective-C的代码可能比你还要迟钝。
3、把CocoaChina.com加入收藏夹,看不明白代码就来再看一遍这篇开宗明义的好文。
4、文档很关键,当你看不懂某些东西说的是什么的时候,先查Cocoachina,再看英文文档里面的API说明,尤其这个类是以NS开头的时候。再不行就去google搜,直接把你要查的方法贴进google,通常能找到不少人也在问同样的问题,自然也有热心人活雷锋帮助回答。
5、可以看hello world例子,但是不能总看,看多了真的会晕。另外,千万要放弃苹果官方的Currency Converter货币转换的例子,那个例子是毒药,刚学的时候越看越蒙。
6、学习一门语言最好的方法是先用,和学外语一样,当你会说的时候自然会读。给自己设立一个简单的目标,比如做一个简单的程序,然后一点点解决问题。这样学习起来比只看例子快得多。
这是一篇初学者写的文章,希望对同样是初学者的你有一点点帮助:)虽然只是很肤浅的一点点内容,但是应该对你迈入Objective-C的大门有一点帮助。看懂了这篇文章,回过头看Cocoachina的其他文章,你就会觉得很顺眼了。记得天天来哦。
另外,这篇Objective-C的参考资料也比较好,如果你有兴趣可以一读。http://www.otierney.net/objective-c.html.zh-tw.big5
四. 类与结构
(1) 简单的类:
在Object-C中, 一个类分为接口与实现两个部分,分别使用@interface与@implementation来表示。
@interface用于描述类、类的数据成分以及类的方法;@implementation部分包括实现这些方法的实际代码。
@interface的一般格式类似于:
@interface ClassName : ParentClassName [ < protocol , .. > ]
{
[@protected | @private | @public | @package ]
memberDeclarations
}
methodDeclarations ;
@end;
其中,使用-表示这是一个实例方法, 而使用+表示这是一个类方法。
默认情况下,类的成员属性都是protected的。在类的声明中也可以使用特定指定其属性。
@implement的一般格式类似于:
@implement ClassName
methodDefinitions
@end
(2) 两个类中的特殊引用:
在Object-C中,在类中提供了两个特殊的引用,一个是self, 一个是super 。
self引用 C语言中的this指针一样,指向该类本身,而super则是指向其父类。
(3) 类继承与扩展
a) 类继承
Object-C中定义的所有类都必须继承自同一个父类-- NSObject,也可以继承自某个已经继承了NSObject的类,成为其子类。关于类继承的一些规则与C++语言类似,在这里就不详细描述了。
b) 类扩展
Object-C也提供了另外一种在原有类上进行扩展的机制,叫做类扩展。使用这种机制可以在不修改原有类代码的基础上进行类的扩展。经过扩展后的类拥有新的属性或者方法
(4) 动态特性
Object-C中一个强大的特性就是它的动态因素。每个对象都保存着它所拥有的一些类属性,包括属于哪个类, 哪个父类,是否响应某个函数等等,因此 Object-C也支持一些处理动态类型的方法。
也因为Object-C 的动态特性,导致了一些常规的编译时性能优化方法都不用能于Object-C,这也是导致了 Object-C的运行性能劣于类似的对象抽象语言(如C++) ,因此一些底层的操作应使用C++或类似的语言进行封装,由Object-C负责高层逻辑的封装。
(5) 多重继承的偏方
由于Object-C不支持多重继承,有一些逻辑类的封装可能无法方便地使用,因此Object-C中提供了协议的机制,一个类可以实现若干的协议,从这个角度实现了类的多重继承。
五. 基本的 C 语言特性
a) import 与 include
对于Objecy-C与C语言两大阵营的一个分歧在于,Object-C更偏向于使用import并且性能比include更高。
b) define
Object-C支持所有的预编译操作
c) 指针
Object-C支持C中的指针操作
六.Object-C的一些原理
(1)类对象的结构
每个对象的结构中保存着该类的所有成员属性与一个名为isa的保护成员属性,其记录着该对象所属于的类,当该对象调用函数的时候则通过这个isa的保护成员属性确定其所属的类,从而知道如何调用这个方法。
(2)类对象的指针属性
在Object-C中,所有的对象变量都必须以指针的形式定义,并使用alloc函数进行内存分配。而在Foundation框架中,Object-C提供了自动释放池与自动垃圾回收机制去进行内存管理。在Object-C 中, 类对象更像是一种引用,采用引用计数进行自动释放是iphone sdk中一个重要的内容。
(3)函数调用
在Object-C中,函数调用都是使用一种消息机制,向特定的类对象发送消息,以达到函数调用的目的。