19-归档
最后更新于:2022-04-01 00:17:37
在Objective-C语言中,归档是一个过程,
即用某种格式来保存一个或多个对象,以便以后还原这些对象。
类似于Java中的序列化和反序列化。
在Mac OS X上的应用程序使用XML属性列表(或plists)
存储诸如默认参数选择、应用程序设置和配置信息这样的数据。
使用PropertyList Editor程序来创建属性列表。
使用NSPropertyListSerialization类在文件中写入或读取属性列表可以在不同的平台之间移植。
归档方法一般包括:
1)使用XML属性列表进行归档。(如果可能,尽量在程序中使用XML属性列表)
2)使用NSKeyedArchiver归档。
3)使用NSData创建自定义档案。
要归档当前没有列出的对象,必须告知系统如何归档(或编码)你的对象,以及如何解归档(或解码)它们。
这是按照协议,在类定义中添加encodeWithCoder:方法和initWithCoder:方法实现的。
每次归档程序想要根据指定的类编码对象时,都将调用encodeWithCoder:方法,
该方法告知归档程序如何进行归档。
类似地,每次从指定的类解码对象时,都会调用initWIthCoder:方法。
一般而言,编码方法应该指定如何归档想要保存的对象中的每个实例变量。
从档案文件中恢复数据很简单:所做的工作只需和归档文件相反。
首先,需要像以前那样分配一个数据空间。
其次,把档案文件中的数据读入该数据空间。
然后,需要创建一个NSKeydUnarchiver对象,并告知它从指定的空间解码数据。
必须调用解码方法来提取和解码归档的对象,做完之后,向NSKeyedUnarchiver对象发送一条finishDecoding消息。
使用归档程序复制对象:
可以使用Foundation的归档功能来创建对象的深复制。
18-复制对象
最后更新于:2022-04-01 00:17:34
将一个变量赋值给另一个对象仅仅创建另一个对这个对象的引用。
类似于Java中,赋值是一个引用。
参考C++或者C的指针来理解就容易了。这里的赋值其实就是指针地址赋值而已。
Foundation类实现了名为copy 和 mutableCopy的方法,可以使用这些方法创建对象的副本。
通过实现一个符合协议(用于制作副本)的方法来完成此任务。
注意,产生一个对象的可变副本并不要求被复制的对象本身是可变的。同样,可以创建可变对象的不可变副本。
### 浅复制和深复制:
mutableCopy方法复制数组时,在内存中为新的数组对象分配了空间,并且将单个元素复制到新数组中。
然后将原始数组中的每个元素复制到新位置意味着:仅将引用从一个数组元素复制到另一个数组元素。
这样做的结果,就是两个数组中的元素都指向内存中的同一个字符串。
若要为数组中的每个元素创建完全不同的副本,需要执行所谓的深复制。
### 实现协议:
注意,如果想要区分可变副本和不可变副本,
还需要根据协议实现mutableCopyWithZone:方法。
如果两个方法都实现,那么copyWithZone:应该返回不可变副本,
而mutableCopyWithZone:将返回可变副本。
属性并没有mutableCopy特性。
即使是可变的实例变量,也是使用copy特性,正如方法copyWithZone:的执行结果。
所以,按照约定会生成一个对象的不可变副本。
17-内存管理
最后更新于:2022-04-01 00:17:32
内存管理关心的是清理(回收)不用的内存,以便内存能够再次利用。
提供给Objective-C程序员的基本内存管理模型有以下三种:
1)自动垃圾收集。(iOS运行环境并不支持垃圾收集,在这个平台开发程序时没有这方面的选项,只能用在Mac OS X 程序上开发。这个机制挺恶心的,用mac电脑的人知道,当内存不足的时候,机器基本上就是卡死了。)
2)手工引用计数和自动释放池。(这种方式,对程序员的要求很高。自己维护嘛,)
3)自动引用计数(ARC)。
Objective-C为每个对象提供一个内部计数器, 这个计数器跟踪对象的引用次数。
所有类都继承自 NSObject 的对象。
retain和release方法:
当对象被创建或拷贝时候, 引用计数为1 。
每次保持对象时候, 就发送一条retain消息, 使其引用计数加1 ,
如果不需要这个对象就是发送一个release消息使其引用计数减1 。
当对象的引用计数为0的时候, 系统就知道不再需要这个对象了, 就会释放它内存。
一个对象的创建可以通过alloc分配内存或copy复制,
这关系到的方法有: alloc, allocWithZone:
copy, copyWithZone, mutableCopy,mutableCopyWithZone, 这些方法都可以使引用计数为1 ,
retain会使引用计数加1 ,
release会使引用计数减1 。
重写dealloc方法
当对象包含其它对象时, 就得在 dealloc中自己释放它们
~~~
#import
@interface Song : NSObject {
NSString *title;
NSString *artist;
long int duration;
}
//操作方法
- (void)start;
- (void)stop;
- (void)seek:(long int)time;
//访问成员变量方法
@property NSString *title;
@property NSString *artist;
@property(readwrite) long int duration;
//构造函数
-(Song*) initWithTitle: (NSString *) title andArtist: (NSString *) artist andDuration:( long int )duration ;
@end
~~~
实现如下:
~~~
#import "Song.h"
@implementation Song
@synthesize title;
@synthesize artist;
@synthesize duration;
//构造函数
-(Song*) initWithTitle: (NSString *) newTitle andArtist: (NSString *) newArtist andDuration:(long int)newDuration {
self = [super init];
if ( self ) {
self.title = newTitle;
self.artist = newArtist;
self.duration = newDuration;
}
return self;
}
- (void)start {
//开始播放
}
- (void)stop {
//停止播放
}
- (void)seek:(long int)time {
//跳过时间
}
-(void) dealloc {
NSLog(@"释放Song对象...");
[title release];
[artist release];
[super dealloc];
}
@end
~~~
调用的main函数
~~~
#import
#import "Song.h"
int main (int argc, const charchar * argv[]) {
Song *song1 = [[Song alloc] initWithTitle:@"Big Big World" andArtist:@"奥斯卡.艾美莉亚" andDuration:180];
Song *song2 = [[Song alloc] initWithTitle:@"It's ok" andArtist:@"atomic kitten" andDuration:280];
// print current counts
NSLog(@"song 1 retain count: %i", [song1 retainCount] );
NSLog(@"song 2 retain count: %i", [song2 retainCount] );
// increment them
[song1 retain]; // 2
[song1 retain]; // 3
[song2 retain]; // 2
// print current counts
NSLog(@"song 1 retain count: %i", [song1 retainCount] );
NSLog(@"song 2 retain count: %i", [song2 retainCount] );
// decrement
[song1 release]; // 2
[song2 release]; // 1
// print current counts
NSLog(@"song 1 retain count: %i", [song1 retainCount] );
NSLog(@"song 2 retain count: %i", [song2 retainCount] );
// release them until they dealloc themselves
[song1 release]; // 1
[song1 release]; // 0
[song2 release]; // 0
return 0;
}
~~~
代码分析:
在这个main函数中, 声明了两个Song对象, 当retain调用增加引用计数, 而release调用减少它。
调用 [obj retainCount] 来取得引用计数的 int 值。
当retainCount到达 0, 两个对象都会调用 dealloc, 所以可以看到印出了两个 “释放Song对象...” 。
在Song对象释放的时候, 先要释放它自己的对象类型成员变量title和artist, 然后再调用[super dealloc] 。
自动释放池
内存释放池(Autorelease pool ) 提供了一个对象容器,
每次对象发送autorelease消息时, 对象的引用计数并不真正变化,
而是向内存释放池中添加一条记录, 记下对象的这种要求,
直到当内存释放池发送drain或release消息时,
当池被销毁前会通知池中的所有对象, 全部发送release消息真正将引用计数减少。
~~~
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
… …
[pool release];// [pool drain];
~~~
这些语句必须要放在下面语句之间, 直到池被释放,
一个对象要想纳入内存释放池对象, 必须要发送autorelease。
~~~
#import
int main (int argc, const charchar * argv[]) {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSArray *weeksNames1 = [NSArray arrayWithObjects:
@"星期一",@"星期二",@"星期三",@"星期四"
,@"星期五",@"星期六",@"星期日",nil];
NSArray *weeksNames2 = [[NSArray alloc] initWithObjects:
@"星期一",@"星期二",@"星期三",@"星期四"
,@"星期五",@"星期六",@"星期日",nil];
//[weeksNames1 release];
//[weeksNames1 autorelease];
//[weeksNames2 release];
//[weeksNames2 autorelease];
NSLog(@" retain count: %i" , [weeksNames1 retainCount] );
NSLog(@" retain count: %i" , [weeksNames2 retainCount] );
[pool release];
return 0;
}
~~~
NSArray类是Foundation框架提供的不可变数组类,
Foundation框架中对象的创建有两类方法:
类方法(+)构造方法和实例方法(-) 构造方法。
打开NSArray ClassReference文档, 其中创建对象有关的方法如图所示。
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-12-31_5684a3f102112.jpg)
从NSArray Class Reference文档可以看出,
以+和类名开头(去掉NS, 小写第1 个字母, array),就是类级构造方法,
以-和initWith开头的就是实例构造方法。
类级构造方法不能使用 release, 可以不用 autorelease就可以自动纳入内存释放池管理。
实例构造方法, 如果发出release消息就马上释放对象,
如果发出autorelease消息可以自动纳入内存释放池管理, 不会马上释放。
在iOS开发中由于内存相对少, 因此基本上都采用实例构造方法实例化对象,
采用发送release消息立刻释放对象内存。
属性中的内存管理参数
有的时候我们声明的对象要跨越对象调用 , 内存管理就会变的更加复杂。
例如在Song类中有一对成员变量存取的方法,当然可以把它们封装成属性, 通过属性参数来管理内存。
在Song类中给成员变量设置方法如下:
~~~
- (void) setTitle:(NSString *) newTitle {
title = newTitle;
}
- (void) setArtist:(NSString *) newArtist {
artist = newArtist;
}
~~~
这段代码事实上有内存泄漏, 当设置一个新的Title时候,title = newTitle只是将指针改变了,
旧对象并没有释放, 所以我们会这样修改这些方法:
~~~
- (void) setTitle:(NSString *) newTitle {
[newTitle retain];
[title release];
title = [[NSString alloc] initWithString: newTitle];
}
- (void) setArtist:(NSString *) newArtist {
[newArtist retain];
[artist release];
artist = [[NSString alloc] initWithString: newArtist];
}
~~~
首先保留新对象, 释放旧对象, 然后使用实例构造方法实例化新的对象。
参数newTitle不要在方法中释放。
由于基本数据类型(非对象类型) 不需要释放, 因此下面的写法是没有问题的。
~~~
- (void) setDuration:(long int) newDuration {
duration = newDuration;
}
~~~
此外, 在构造方法中也必须要注意, 不能直接赋值title =newTitle, 而是要调用自身的设置方法:
~~~
//构造函数
-(Song*) initWithTitle: (NSString *) newTitle andArtist: (NSString *) newArtist
andDuration:(long int)newDuration {
self = [super init];
if ( self ) {
[self setTitle:newTitle];
[self setArtist:newArtist];
[self setDuration:newDuration];
}
return self;
}
~~~
assign 参数
assign参数代表设置时候直接赋值, 而不是复制或者保留它。
这种机制非常适合一些基本类型, 比如NSInteger和CGFloat,
或者就是不想直接拥有的类型, 比如委托。
assign相当于如下写法。
~~~
- (void) setTitle:(NSString *) newTitle {
title = newTitle;
}
~~~
retain参数
retain参数会在赋值时把新值保留(发送retain消息) 。
此属性只能用于Objective-C对象类型, 而不能用于基本数据类型或者Core Foundation。
retain相当于如下写法:
~~~
(void) setTitle:(NSString *) newTitle {
[newTitle retain];
[title release];
title = [[NSString alloc] initWithString: newTitle];
}
~~~
copy参数
copy在赋值时将新值拷贝一份, 拷贝工作由copy方法执行,
此属性只对那些实行了NSCopying协议的对象类型有效。
copy相当于如下写法:
~~~
- (void) setTitle:(NSString *) newTitle {
[newTitle copy];
[title release];
title = [[NSString alloc] initWithString: newTitle];
}
~~~
手工内存管理规则的总结:
1)如果需要保持一个对象不被销毁,可以使用retain。在使用完对象后,需要使用release进行释放。
2)给对象发送release消息并不会必须销毁这个对象,只有当这个对象的引用计数减至0时,对象才会被销毁。然后系统会发送dealloc消息给这个对象用于释放它的内存。
3)对使用了retain或者copy、mutableCopy、alloc或new方法的任何对象,以及具有retain和copy特性的属性进行释放,需要覆盖dealloc方法,使得在对象被释放的时候能够释放这些实例变量。
4)在自动释放池被清空时,也会为自动释放的对象做些事情。系统每次都会在自动释放池被释放时发送release消息给池中的每个对象。如果池中的对象引用计数降为0,系统会发送dealloc消息销毁这个对象。
5)如果在方法中不再需要用到这个对象,但需要将其返回,可以给这个对象发送autorelease消息用以标记这个对象延迟释放。autorelease消息并不会影响到对象的引用计数。
6)当应用终止时,内存中的所有对象都会被释放,不论它们是否在自动释放池中。
7)当开发Cocoa或者iOS应用程序时,随着应用程序的运行,自动释放池会被创建和清空(每次的事件都会发生)。在这种情况下,如果要使自动释放池被清空后自动释放的对象还能够存在,对象需要使用retain方法,只要这些对象的引用计数大于发送autorelease消息的数量,就能够在池清理后生存下来。
自动引用计数(ARC):
强变量,通常,所有对象的指针变量都是强变量。
如果想声明强变量,可以使用__strong Faction *fl;这样的__strong来修饰。
值得注意的是,属性默认不是strong,其默认的特性是unsafe_unretained类似assign。
所以需要声明属性strong时,可以如下:
@property (strong, nonatomic) NSMutableArray *birdNames;
编译器会保证在事件循环中通过对赋值执行保持操作强属性能够存活下来。
带有unsafe_unretained(相当于assign)或weak的属性不会执行这些操作。
弱变量,可以使用__week关键字来声明。弱变量不能阻止引用的对象被销毁。
当引用的对象释放时,弱变量会被自动设置为nil。
需要注意的是,在iOS4和Mac OS V10.6中不支持弱变量。
在这种情况下,你仍然可以为属性使用unsafe_unretained, assing特性.
ARC都会在“底层”发生,所以一般不用关心。
16-使用文件
最后更新于:2022-04-01 00:17:30
语言的设计主要是被应用于实践,
而Objective-C应用最广的地方就是Mac OS X或iOS的Foundation框架。
Foundation框架允许你利用文件系统对文件或目录执行基本操作,这些基本操作是由NSFileManager类提供的。
使用NSFileHandle类提供的方法,可以打开文件并对文件执行多次读/写操作。
NSFileHandle类的方法可以实现如下功能:
1)打开一个文件,执行读、写或更新(读取和写入)操作。
2)在文件中查找指定位置。
3)从文件中读取特定数目的字节,或将指定数目的字节写入文件。
NSFileHandle类提供的方法也可用于各种设备或套接字。
很多语言中使用的框架也有类似的FileHandle。
NSURL类允许在应用中使用URL方法。这个和Java中的URL差不多。
NSBundle类提供了允许在应用中使用包(bundle)的方法,包括搜索包中的特定资源。
这个东西更类似于Android应用中的Resource或R之类干的活。
管理文件和目录使用NSFileManager,文件或目录使用文件的路径名为唯一标识。
完整路径也称为绝对路径,以斜线(/)开始。
特殊的代字符(~)作为用户主目录的缩写。
当前目录为".";
父目录为“..”;
这些概念和Linux里面一样。
每个文件方法都是对NSFileManager对象的调用,
而NSFileManager对象是通过向类发送一条defaultManager消息创建。
iOS的设备上,程序是运行在沙盒中的,它严格限定了文件的访问。
如果在设备中运行这个程序,会看到当前目录是/,
这说明应用的根目录是在运行它的沙盒中,并不是整个iOS设备文件目录的根。
可以这么理解,每一个应用程序都是一个进程,这样它的作用范围就是进程的上下文。沙盒就是进程。
这个和Android是一样的道理。
Android中,每个应用都运行在一个进程中,每个应用都有自己的pid,即进程号。
也不能随便访问别的进程的数据或者奔溃时影响到其它应用。这就是沙盒的原理。
其实,这些相同点都是因为它们两个生态的最底层OS都是unix类似的原理所致。
熟悉Unix的同学都知道,进程,用户这些概念,其实就是沙盒的概念。
使用路径用NSPathUtilities.h
components是一个NSArray对象,它包含路径中每一部分的字符串对象,
Path是一个字符串对象,它指定文件的路径;ext是路径扩展名的字符串对象。如@“mp3”
为了保存数据直到下一次运行程序仍能够使用,可以使用Documents目录。
每个iOS应用都有自己的Documents目录供数据写入。
应用中的Caches目录也可以用来存储一些数据。
对于iOS开发说,Apple鼓励开发者存储持久化数据到云端。
对于这些Documents的理解,可以这么理解,每个应用程序都是一个用户。
在类Unix系统中,每一个用户其实都是在home目录(Linux系统的home)下的不同用户文件夹分开。
不同用户名文件夹中都有基本的文件夹,Documents,Downloads这类的。
简言之,应用程序一般都属于某一个用户(UserId),某一个进程(Pid)。
而且一般情况下不同应用程序的用户标识和进程标识都是不一样的。这就是所谓沙盒原理。
NSProcessInfo类中的argments方法返回一个字符串对象数组。
数组的第一个元素是进程名称,其余的元素是在命令行中输入的参数。
这个类主要用来记录当前进程的信息。
其实,我们都知道,unix下的进程入口都是main函数。
这也就是为什么NSProcessInfo会记录命令行中的输入参数的原因。
命令行中的输入参数都是通过main函数的入参传入的。
基本文件操作:NSFileHandle,可用于标准输入、标准输出、标准错误和控设备。
应该注意NSFileHandle类并没有提供创建文件的功能。
创建文件须使用FileManager的方法来创建。
另外,Unix系统下,应注意,打开文件进行写入并不会截断文件,需要自己完成截断。
NSURL对象并不是一个字符串(如@“http://blog.csdn.net/haomengzhu”),
但是使用URLWithString:方法可以由一个字符串对象创建出NSURL对象。
NSBundle类:
当创建一个应用时,系统存储了应用相关联的所有数据(其中包括图片、本地化字符串、图标等),
将这些内容放入一个称为应用包(application bundle)的包中。
在应用中添加一个资源(如图片或文本文件),仅需将文件拖到Xcode的左边窗格中。
mainBundle方法给出了应用包所在的目录。这个方法在Mac OS X和iOS中都适用。
15-Foundation框架
最后更新于:2022-04-01 00:17:27
### 概述
Mac OS X开发会使用 Cocoa框架,
它是一种支持应用程序提供丰富用户体验的框架,
它实际上由: Foundation和Application Kit(AppKit)和 Core Data框架组成。
iOS开发, 会使用 Cocoa Touch框架,
它实际上由: Foundation、Core Data和UIKit框架组成。
AppKit和UIKit框架都是与窗口 、 按钮、 列表等相关的类。
Foundation是Mac OS X和iOS应用程序开发的基础框架,
它包括了一些基本的类, 如: 数字、 字符串、 数组、 字典等。
快速概要:
光标放在需要搜索的类、方法或变量上,按住Option键,同时单击鼠标,你会看到所选择内容的快速概要。
在面板的右上角注意到有两个图标:第一个是一本书,第二个是一个字符h。
单击那个书的图标,Xcode会查找选择的类、协议、定义或者方法的相关文档。
单击字符h的图标,将会显示包含选择项目定义的头文件。
MAC OS X在线参考库,地址是[点击打开链接](http://developer.apple.com/library/mac/navigation/index.html)。
要使用Foundation框架中的类,要先引用头文件:
`#import `
实际上,因为Foundation.h文件导入Foundation所有的头文件,所以不必担心是否导入正确的头文件。
但使用这条语句会明显增加程序的编译时间。
所以,一般import用到的头文件。不要用太大的import。
基本数据类型:
int、 char、 float和double都不是类,
不具有方法、 成员变量和属性, 以及面向对象的特征。
为了实现“一切都是对象” 的承诺,
因此在Foundation框架中使用NSNumber类来封装这些数字类型。
这样数字就具有了面向对象的基本特征了。
也就是说:如果需要存储基本数据类型(包括char数据类型),可以使用NSNumber类,
它会依据这些数据的类型创建对象。
对于每种基本数据类型,类方法都能为它创建一个NSNumber对象,并设置为指定的值。
NSNumber类构造函数
采用 + numberWithInt: 等“+ number” 开头的类级构造方法, 可以从基本数据类型构建NSNumber对象。
而使用 –intValue方法可以从NSNumber对象获得基本数据类型。
这些方法以numberWith开头,紧接着数据的类型,如:numberWithLong:
~~~
NSNumber *intNumber= [NSNumber numberWithInteger:80];
NSNumber *floatNumber = [NSNumber numberWithFloat:
80.00];
int myint = [intNumber intValue];
float myfloat = [floatNumber floatValue];
~~~
注意,方法numberWithInt:和numberWithInteger:使用有些差别,遵循以下一些规则:
1)如果使用numberWithInt:方法创建一个整型数,需要使用intValue获取它的值,使用%i作为格式化字符串显示它的值。
2)如果使用numberWithInteger:方法创建一个整型数,需要使用integerValue获取它的值,也可以转换成long显示或使用stringWithFormat:将它格式化为字符串。使用%li作为格式化字符串。
比较两个NSNumber对象大小
可以转化成为基本数据类型比较, 当然可以使用 NSNumber的方法比较,
这就是对象的优势了, 与比较相关方法有:
isEqualToNumber: 和compare: 。
isEqualToNumber: 只是比较是否相对, compare: 可以比较大小。
~~~
if ([intNumber isEqualToNumber: floatNumber] == YES) {
NSLog(@"相等");
} else {
NSLog(@"不相等");
}
if ([intNumber compare: myNumber] ==
NSOrderedAscending) {
NSLog(@"第一个数小于第二个数");
}
~~~
NSOrderedAscending是枚举类型NSComparisonResult的其中成员, 代表第一个数小于第二个数,
此外还有NSOrderedSame成员代表第一个数等于第二个数,
NSOrderedDescending成员代表第一个数大于第二个数。
### 字符串类
在Foundation框架中字符串类有两种:
NSString不可变字符串类和NSMutableString可变字符串类。
NSString是定义固定大小的字符串,
NSMutableString是可对字符串做追加、删除、 修改、 插入和拼接等操作而不会产生新的对象。
### NSString类
NSSTring的类,用于处理字符串对象。然而C样式的字符串由char字符组成,NSString对象由unichar字符组成。
要使用Objective-C语言创建一个常量字符串对象,需要在字符串开头放置一个@字符。
创建字符串对象时,会创建一个内容不可更改的对象,这称为不可变对象。
需要制定一个范围确定子字符串,使用特殊的数据类型NSRange类创建对象。
实际上,它是结构的typedef定义,包含location和length两个成员。
如果没有找到这个字符串,则返回范围的location成员被设置为NSNotFound。
stringWithString:类级构造方法创建NSString对象,NSString构造方法还有很多;
length,返回Unicode字符的长度;
stringByAppendingString:实现了字符串的拼接,这个方法会产生下一新的对象;
isEqualToString:比较两个字符串是否相等;
compare: 比较两个字符串大小;
substringToIndex:可以获得字符串的前x个字符串;
substringFromIndex:可以截取x索引位置到尾部字符串;
rangeOfString:字符串查找;
下面来看一段示例代码:
~~~
#import
int main (int argc, const charchar * argv[]) {
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
NSString *str1 = @"aBcDeFgHiJk";
NSString *str2 = @"12345";
NSString *res;
NSComparisonResult compareResult;
NSRange subRange;
//字符个数
NSLog(@"字符串str1长度: %i",[str1 length]);
//拷贝字符串到res
res = [NSString stringWithString: str1];
NSLog(@"拷贝: %@", res);
//拷贝字符串到str1尾部
str2 = [str1 stringByAppendingString: str2];
NSLog(@"连接字符串: %@", str2);
//测试字符串相等
if ([str1 isEqualToString: res] == YES) {
NSLog(@"str1 == res");
} else {
NSLog(@"str1 != res");
}
//测试字符串 ==
compareResult = [str1 compare: str2];
if (compareResult == NSOrderedAscending) {
NSLog(@ "str1 );
} else if (compareResult == NSOrderedSame) {
NSLog(@ "str1 == str2");
} else {
NSLog(@ "str1 > str2");
}
res = [str1 uppercaseString];
NSLog(@"大写字符串:%@", res);
res = [str1 lowercaseString];
NSLog(@"小写字符串:%@", res);
NSLog(@"原始字符串: %@", str1);
//获得前三个数
res = [str1 substringToIndex: 3];
NSLog(@"字符串str1的前三个字符: %@",res);
res = [str1 substringFromIndex: 4];
NSLog(@"截取字符串,从第索引4到尾部: %@",res);
res = [[str1 substringFromIndex: 3] substringToIndex: 2];
NSLog(@"截取字符串,从第索引3到5: %@",res);
//字符串查找
subRange = [str2 rangeOfString: @"34"];
if (subRange.location == NSNotFound) {
NSLog(@"字符串没有找到");
} else {
NSLog (@"找到的字符串索引 %i 长度是 %i",
subRange.location, subRange.length);
}
[pool drain];
return 0;
}
~~~
我们解释一下主要代码:
str2 = [str1 stringByAppendingString: str2] 语句是将两个字符串对象拼接在一起,
由于NSString是不可变字符串类, 这会产生一个新的对象。
subRange = [str2 rangeOfString: @"34"] 中的rangeOfString: 方法会返回一个NSRange结构体,
它的location成员是找到的字符串的索引 , 它的length成员是找到的字符串的长度。
NSMutableString 类
NSMutableString类可以用来创建可以更改字符的字符串对象。
注意,如果替换字符串中还包括搜索字符串(如:使用字符串“ax”替换字符串“a”),
那么将会陷入无限循环。
其实,这个是和它的实现原理有关系,一般这种替换使用的方法使用了递归调用。
stringWithString: 类级构造方法创建NSString对象,NSMutableString构造方法还有很多;
insertString: 插入字符串, 不会创建新的对象;
appendString: 追加字符串, 不会创建新的对象;
deleteCharactersInRange: 在一个范围内删除字符串, 不会创建新的对象;
replaceCharactersInRange: withString: 替换字符串, 不会创建新的对象;
再看断示例代码:
~~~
#import
int main (int argc, const charchar * argv[]) {
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
NSString *str1 = @"Objective C";
NSString *search,*replace;
NSMutableString *mstr;
NSRange substr;
//从不可变的字符创建可变字符串对象
mstr = [NSMutableString stringWithString: str1];
NSLog(@" %@", mstr);
//插入字符串
[mstr insertString: @ " Java" atIndex: 9];
NSLog(@" %@", mstr);
//具有连接效果的插入字符串
[mstr insertString: @ " and C++"atIndex: [mstr length]];
NSLog(@" %@", mstr);
//字符串连接方法
[mstr appendString: @ " and C"];
NSLog(@" %@", mstr);
//使用NSRange删除字符串
[mstr deleteCharactersInRange:NSMakeRange(16, 13)];
NSLog(@" %@", mstr);
//查找字符串位置
substr = [mstr rangeOfString: @ "string B and"];
if (substr.location != NSNotFound) {
[mstr deleteCharactersInRange: substr];
NSLog(@" %@", mstr);
}
//直接设置可变字符串
[mstr setString: @ "This is string A "];
NSLog(@" %@", mstr);
[mstr replaceCharactersInRange: NSMakeRange(8, 8)
withString: @ "a mutable string "];
NSLog(@" %@", mstr);
//查找和替换
search = @"This is ";
replace = @"An example of ";
substr = [mstr rangeOfString:search];
if (substr.location != NSNotFound) {
[mstr replaceCharactersInRange:substr withString: replace];
NSLog(@" %@", mstr);
}
//查找和替换所有的情况
search = @"a";
replace = @"X";
substr = [mstr rangeOfString: search];
while (substr.location != NSNotFound) {
[mstr replaceCharactersInRange:substr withString: replace];
substr = [mstr rangeOfString: search];
}
NSLog(@" %@", mstr);
[pool drain];
return 0;
}
~~~
我们解释一下主要代码:
语句[mstr deleteCharactersInRange:NSMakeRange(16, 13)] 是删除指定范围的字符串,
函数NSMakeRange 是创建NSRange结构体, 其中第一个参数是位置, 第二个参数是长度,
语句[mstr replaceCharactersInRange: NSMakeRange(8, 8) withString:@“a mutable string ”] 替换指定访问的字符串。
[mstr deleteCharactersInRange: substr] 语句是删除指定范围的字符串。
数组类
在Foundation框架中数组被封装成为类,
数组对象:是Foundation数组是有序的对象集合。
数组有两种:
NSArray不可变数组类和NSMutableArray可变数组类。
注意,需要标记参数数组的结束,将这个数组的最后一个值定为nil,它实际上并不会存储在数组中。
数组中的元素是由它们的索引数确定的。与NSString对象类似,索引从0开始。
可以通过NSLog格式化字符串%@显示整个数组。
NSArray类
NSArray有很多方法, 下面是总结常用的方法:
count: 返回当前数组的长度;
objectAtIndex: 按照索引返回数组中的元素;
containsObject: 是否包含某一元素;
arrayWithObjects: 类级构造方法;
initWithObjects: 实例构造方法;
NSArray的实例代码如下:
~~~
#import
int main (int argc, const charchar * argv[]) {
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
NSArray *weeksNames = [NSArray arrayWithObjects:
@"星期一",@"星期二",@"星期三",@"星期四"
,@"星期五",@"星期六",@"星期日",nil];
NSLog(@"星期名字");
NSLog(@"==== ====");
int i;
for (i=0; i count];i++) {
NSLog(@"%2i %@ ", i+1, [weeksNames objectAtIndex: i]);
}
[pool drain];
return 0;
}
~~~
arrayWithObjects可以用来创建使用一列对象作为元素的数组。
在这种情况下, 按顺序列出对象并使用逗号隔开。
使用这个方法必须在列表的最后指定一个结束标志nil 。
count可以获得数组的长度。 数组的下标是从0开始的。
NSMutableArray类
NSMutableArray是NSArray的子类, NSMutableArray有很多方法,
下面是总结常用的方法:
addObject: 在数组的尾部追加一个元素;
insertObject: 在数组的添加一个元素;
atIndex: 按照索引插入一个元素;
removeObjectAtIndex: 移除特定索引的元素;
removeObject: 移除特定元素;
initWithCapacity: 实例构造方法;
~~~
#import
int main (int argc, const charchar * argv[]) {
NSMutableArray *weeksNames = [[NSMutableArray alloc] initWithCapacity: 3];
[weeksNames addObject: @"星期一"];
[weeksNames addObject: @"星期二"];
[weeksNames addObject: @"星期三"];
[weeksNames addObject: @"星期四"];
[weeksNames addObject: @"星期五"];
[weeksNames addObject: @"星期六"];
[weeksNames addObject: @"星期日"];
NSLog(@"星期名字");
NSLog(@"==== ====");
int i;
for (i=0; i count];i++)
NSLog(@"%2i %@ ", i+1, [weeksNames objectAtIndex: i]);
[weeksNames release];
return 0;
}
~~~
[[NSMutableArray alloc] initWithCapacity: 3] , 可以初始化可变数组并分配3个初始单元,
如果超过了容量会自动追加的。
addObject方法可以向数组中添加元素。
NSValue:
数组这样的Foundation集合只能存储对象,不能存储像int这样的基本数据类型。
NSValue类正好可以将结构转化为对象,并且把它存储在集合中。
这种将结构化转为对象的方式,简称为包装(wrapping),
逆向的处理是从对象中解出基本类型,简称展开(unwrapping)。
字典类
字典集合, 它是由“键-值”对构成的集合。
键集合不能重复,也就是说字典中的键必须是单值的;
值集合没有特殊要求。
键和值集合中的元素通常是字符串,但也可以是任何对象, 但是不能是nil。
词典可以使固定的,也可以是可变的。
可变词典中的记录可以动态添加和删除。
和数组对象不一样,词典对象是无序的。
这个字典类与java中的HashMap很相似,也是key-value的格式。
举例如下:
字典类数据结构模型,
如图所示的“学生与学号”集合;
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-12-31_5684a3f0d7c38.jpg)
与数组类一样, 字典类也分为NSDictionary不可变字典和NSMutableDictionary可变字典。
NSDictionary类
NSDictionary有很多方法, 下面是总结常用的方法:
count: 字典集合的长度;
objectForKey: 通过键获得值对象;
allKeys: 返回所有键集合;
arrayWithObjects: 类级构造方法;
initWithObjects: 实例构造方法;
initWithObjects:forKeys: 实例构造方法;
initWithObjectsAndKeys: 实例构造方法;
initWithContentsOfFile: 从属性文件中创建对象;
~~~
#import
int main (int argc, const charchar * argv[]) {
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
NSArray * keys = [@"one two three four five" componentsSeparatedByString:@" "];
NSArray * values = [@"alpha bravo charlie delta echo" componentsSeparatedByString:@" "];
NSDictionary * dict = [[NSDictionary alloc] initWithObjects:values forKeys:keys];
NSLog(@"%@", [dict description]);
[dict release];
[pool drain];
return 0;
}
~~~
NSArray * keys = [@“one two three four five”componentsSeparatedByString:@“ ”] 语句是将一个字符
串按照空格分割返回NSArray对象, 同理获得values对象,
然后使用 NSDictionary * dict = [[NSDictionary alloc] initWithObjects:values forKeys:keys] 语句
把keys和values放入到不可变字典对象dict中。
NSMutableDictionary类
NSMutableDictionary 是NSDictionary的子类,
NSMutableDictionary有很多方法, 下面是总结常用的方法:
setObject: forKey: 通过键和值;
removeObjectForKey: 按照键移除值;
writeToFile: atomically: 把对象写入到文件中;
~~~
#import
int main (int argc, const charchar * argv[]) {
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
NSMutableDictionary *mutable = [[NSMutableDictionary alloc] init];
// add objects
[mutable setObject: @"Tom" forKey: @"tom@jones.com"];
[mutable setObject: @"Bob" forKey: @"bob@dole.com" ];
NSLog(@"%@", [mutable description]);
[mutable release];
[pool drain];
return 0;
}
~~~
NSMutableDictionary *mutable = [[NSMutableDictionary alloc] init] 是采用默认的init构造方法创建
NSMutableDictionary对象mutable。
[mutable setObject:@"Tom" forKey: @"tom@jones.com"] 语句是按照键tom@jones.com添加Tom对象。
集合对象:
set是一组单值对象集合,它可以是变的或不可以变的。
操作包括:搜索、添加、删除集合中的成员(仅用于可变集合),比较两个集合,计算两个集合的交集和并集等。
这些集合对象包括:NSSet、NSMutableSet、NSIndexSet和NSCountedSet等。
注意,NSIndexSet没有可变版本。
14-基本的C语言特性
最后更新于:2022-04-01 00:17:25
这里介绍一些特性,在编写Objective-C程序时不一定需要了解。
事实上,这些大部分来源于相关联的基本的C语言特性,
虽然C语言是一门过程式语言,其中的一些特性与面向对象编程的思想是对立的,
但是语言是思想也是工具,在其中找到二者关联和差异,会对Objective-C有更深入的理解。
#### 数组:
定义一组有序的数据项,即数组。
如果在字符数组结尾添加一个终止空字符(‘\0’),就产生了一个通常称为字符串的变量。
和其他变量类型一样,必须在使用之前先声明数组,
数组的声明涉及声明数组所包含元素的数值类型,以及将存储在数组中的最大元素数目。
#### 函数:
如果使用了自动引用计数(ARC),那么每次调用函数(方法)时,局部对象的变量都会默认初始化为空。
在函数中(和在方法中一样)定义的变量称为自动局部变量。
因为每次调用该函数后,它们都自动“创建”,并且它们的值对于函数来说是局部的。
函数和方法:函数是指非对象的过程调用,方法是指对象的过程调用。
函数默认的返回类型与方法默认的不同。
如果没有为方法指定返回类型,编译器就会假设它返回id类型的值。
同样,应该为方法声明返回类型,不要依赖这个事实。
函数的默认返回值是整型值。
调用函数或方法时,作为参数传递的值将被复制到相应的形参中。
使用数组时,并非将整个数组的内容复制到形参数组中,
而是传递一个指针,它表示数组所在的计算机内存地址。
所以,对形参数组所作的所有更改实际上都是对原始数组而不是数组的副本执行的。
因此,函数或方法返回时,这些变化仍然有效。
块Blocks:
块对于c语言是一种扩展。
它并没有作为标准ANSI C所定义的部分,而是APPLE公司添加到语言中的。块
看起来更像是函数。
一般来说,块是不能改变外部的变量的,除非使用_ _block int num = 0;这样修饰的变量。
块的其中一个优势在于能够让系统分配给其它处理器或应用的其它线程执行。
简言之:线程安全的。
**不建议使用,不是标准的东西,当对程序进行移植时,就会显现出很不方便。**
指针:
C语言里的重要特性,面试必考,工作必用。
指针可以高效地表示复杂的数据结构,更改作为参数传递给函数和方法的值,
并且能更准确、高效的处理数组。
字符串常量:
“This is char.” 这个是C样式的字符串。它们不是对象。
@“This is char.”这个是面向对象的字符串对象。
while循环用到了空字符等于数值0这一事实,熟练的Objective-C编程人员经常这样使用。
~~~
char *from;
char *to;
while (*from)
*to++ = *from++;
*to = '\0';
~~~
函数指针:
1)常见应用之一是作为参数传递给其它函数。
2)常见应用之一是建立分派表。即函数列表。
上面这些C语言特性,要记住一个基本原则:它们都不是对象。
这意味着不能给它们传递消息,也不能利用它们获得Foundation框架提供的内存分配策略之类的最大优势。
其他语言特性:
符合字面量:
`(struct data) {.month = 7, .day = 2, .year = 2011}`
#### goto语句:
懒惰的程序员才会经常滥用goto语句,goto语句的执行导致在程序中产生一个到达特定点的直接分支。
如果你不懒惰,建议你还是不要经常使用该语句。
#### 空语句:
不做任何操作。
逗号运算符:
优先级列表里最底层的运算符,
在Objective-C语言中,所有的运算符都产生一个值,
所以逗号运算符的值是最右边的表达式值。
sizeof运算符:
Objective-C语言提供了sizeof运算符,
它可以用来确定数据类型或对象的大小,返回的是指定项的字节大小。
#### 工作原理:
关于Objective-C语言和C语言联系的4个事实。
1)实例变量存储在结构中。
定义一个类和它的实例变量时,这些实例变量实际上存储在一个结构中。
这说明了如何处理对象,对象实际上是结构,结构中的成员是实例变量。
所以继承的实例变量加上你在类中添加的变量就组成了一个结构。
使用alloc分配新对象时,系统预留了足够的空间来存储这些结构。
结构中继承的成员(从根对象中获得的)之一是名为isa的保护成员,它确定对象所属的类。
2)对象变量实际上是指针。
来创建新的实例时,是在为对象的新实例分配存储空间(即存放结构的空间),然后使用结构的指针,并将指针变量存储在其中。
3)方法是函数,而消息表达式是函数调用。
方法实际上是函数。调用方法时,是在调用与接收者类相关的函数。
传递给函数的参数接收者(self)和方法的参数。
Objective-C编译器通过类名称和方法名称的组合为每个函数产生一个唯一的名称。
4)id类型是通用指针类型。
返回id类型值的方法只是返回指向内存中某对象的指针。
在C语言中就是 void *。
看了这4个事实后,你就会发现,其实,这些事实只是透露出Objective-C的面向对象底层是如何实现的。
基本上所有的面向对象都是使用面向过程的一个扩展来实现的,C++也是这样的。
也就是说,对象这个东西的底层实现都是由过程完成的。
简而言之:
对象都是结构struct和指针的组合而已。
看到很多面向对象的语言编译时都是转换为C语言的结构和指针,使用C语言编译。
说明面向对象的底层实现都是差不多的。
所以,从技术实现上来讲,没有特别大的变革。
但是从概念或思想上来说,这是巨大的。
另外,那些像perl,python等等这些解释执行的语言,最后的实现也是C语言来完成的。
语言是工具,但同样蕴涵着一种哲理,或是一种思想的表达。
语法或细节什么的忘记了可以再查看,理解了思想,你才能说对这门语言有所了解。
13-预处理程序
最后更新于:2022-04-01 00:17:23
预处理提供了一些工具,使用这些工具更易于开发、阅读、修改程序,
也易于将程序移植到不同的系统中。
你也可以使用预处理程序定制Objective-C语言,以适应特定应用的编程或自己的编程风格。
预处理程序是Objective-C编译过程的一部分,它可以识别散布在程序中的特定语句。
预处理程序使用井号#标记,这个符号必须是一行中的第一个非空格字符。
`#define`语句:
`#define`语句的基本用途之一就是给符号名称指定程序常量。
预定义名称不是变量。因此,不能为它赋值,除非替换指定值的结果实际是一个变量。
`#define`语句经常放在程序的开始,但#import或include语句之后。
预定义的名称和变量的行为方式不同:没有局部定义之类的说法。
所有预定义的名称都用大写,这样容易区分一个名称是变量名、对象名、类名,还是预定义名称。
事实上,预定义名称一出现,预处理程序就执行文本替换,
这可以解释为什么通常不能使用分号结束#define语句的原因。
注意,重新定义底层语言语法的(#define AND &&)行为通常不是好的编程习惯,
而且不容易让他人理解你的代码。
如果需要第二行,那么上一行的最后一个字符必须是反斜杠。\
注意,在定义有参数的名称时,预定义名称和参数列表的左括号之前不允许空格。
后面使用参数的地方要用括号括起来。
~~~
#define SQUARE(x) ((x) * (*))
~~~
`#import`语句:
预处理程序允许你将所有的定义收集到一个单独文件中,然后使用#import语句把它们包含在程序中。
这个主要能区别“”,和是在系统路径查询。
条件编译:
通常用于创建可以在不同的计算机系统上编译运行的程序,它还经常用来开关程序中的各种语句。
~~~
#ifdef, #endif, #else, #ifndef和#undef这些都和c语言一样。
#ifdef DEBUG
#if defined (DEBUG)
~~~
作用是相同的。
`#undef`用于消除已经定义的名称
12-分类与协议
最后更新于:2022-04-01 00:17:21
分类与协议是OC比较有特色的部分。
从表面来看,
分类呢有点类似抽象方法在抽象类中(C++或者Java里的那个抽象类概念)。
协议类似接口(Java语言那个接口),但是又不能“一视同仁”。
分类概念
分类(Category) 允许向一个类文件中添加新的方法声明,
它不需要使用子类机制, 并且在类实现的文件中的同一个名字下定义这些方法。
其语法举例如下:
~~~
#import "ClassName.h"
@interface ClassName ( CategoryName )
// 方法声明
@end
~~~
#### 分类实例
上一篇多态性介绍中曾经使用过Vector和Scalar的例子,
下面我们为Vector增加“减”sub的方法。
Vector+sub.h文件
~~~
#import
#import "Vector.h"
@interface Vector (sub)
-(Vector *) sub: (Vector *) v;
@end
~~~
Vector+sub.m文件
~~~
#import "Vector+sub.h"
@implementation Vector (sub)
-(Vector *) sub: (Vector *) v {
Vector *result = [[Vector alloc] init];
[result setVec1: vec1 - [v vec1] andVec2: vec2 - [v vec2]];
return result;
}
@end
~~~
#### 调用的main函数
~~~
#import
#import "Vector+sub.h"
int main (int argc, const charchar * argv[]) {
Vector *vecA =[[Vector alloc] init];
Vector *vecB =[[Vector alloc] init];
id result;
//set the values
[vecA setVec1: 3.2 andVec2: 4.7];
[vecB setVec1: 32.2 andVec2: 47.7];
// print it
[vecA print];
NSLog(@" + ");
[vecB print];
NSLog(@" = ");
result = [vecA add: vecB];
[result print];
[vecA print];
NSLog(@" - ");
[vecB print];
NSLog(@" = ");
result = [vecA sub: vecB];
[result print];
// free memory
[vecA release];
[vecB release];
[result release];
return 0;
}
~~~
其中result = [vecA add: vecB] 中的add: 是Vector类原有的方法,
result = [vecA sub: vecB] 中的sub: 是Vector分类添加的方法。
分类是在Java和C++等面向对象的语言中没有的概念,
分类本质上是通过Objective-C的动态绑定而实现的。
通过分类使用能够达到比继承更好的效果。
从上面这个例子可以看到,分类提供了一种简单的方式,
用它可以将类的定义模块化到相关方法的组或分类中。
它还提供了扩展现有类定义的简便方式,并且不必访问类的源代码,也不需要创建子类。
~~~
#import "Fraction.h"
@interface Fraction (MathOps)
-(Fraction *) add: (Fraction *) f;
-(Fraction *) mul: (Fraction *) f;
-(Fraction *) sub: (Fraction *) f;
-(Fraction *) div: (Fraction *) f;
@end
~~~
注意,这既是接口部分的定义,也是现有接口部分的扩展。
因此,必须包括原接口部分,这样编译器就知道Fraction类。
按照惯例,作为分类的.h和.m文件的基本名称是由类的名称紧接着分类的名称。
例如:FractionMathOps.m;
一些程序员使用符号“+”来分隔类和分类的名称,比如Fraction+MathOps.h。不过不建议这样命名。
#### 类的扩展:
创建一个未命名的分类,且在括号“()”之间不指定名字,这是一种特殊的情况。
这种特殊的语法定义为类的扩展。
未命名类中声明的方法需要在主实现区域实现,而不是在分离的实现区域中实现。
未命名分类是非常有用的,因为它们的方法都是私有的。
如果需要写一个类,而且数据和方法仅供这个类本身使用,未命名类比较合适。
#### 关于分类的注意事项:
分类可以覆写该类中的另一个方法,但是通常认为这种做法是做虐的设计习惯。所以需要注意:
第一、覆写了一个方法后,再也不能访问原来的方法。(如果确实需要覆写方法,正确的选择可能是创建子类。)
第二、通过使用分类添加新的方法来扩展类不仅仅影响这个类,同时也会影响它的所有子类。
第三、对象/分类命名对必须是唯一的。因为大家使用的名称空间是程序代码、库、框架和插件共享的。
协议(Protocol )
与Java的Interface(接口 )或者C++的纯虚类相同,就是用来声明接口的。
协议只是定义了方法的列表,协议不负责实现方法,目的是让别的类来实现。
#### 以Graphics协议为例:
Graphics中定义了onDraw方法, 但是我们仔细分析一下onDraw方法不能实现的,
作为Graphics(几何图形)它无法知道它的子类如何绘制图形,
它只能规定绘制图名字为onDraw签名和返回值等信息, 但不能给出具体的实现,
因此Graphics(几何图形) 不应该设计成为类而应该设计成为协议。
~~~
@protocol Graphics
-(void) onDraw;
@end
~~~
协议只有接口部分, 没有实现部分, 所以没有m文件, 关键字@protocol ,
协议可以继承别的协议, 协议中不能定义成员变量。
协议实现类Ellipse
~~~
#import
#import "Graphics.h"
@interface Ellipse:NSObject {
}
@end
~~~
~~~
#import "Ellipse.h"
@implementation Ellipse
-(void)onDraw {
NSLog(@"绘制椭圆形");
}
@end
~~~
#### 协议实现类Triangle
~~~
#import
#import "Graphics.h"
@interface Triangle:NSObject {
}
@end
~~~
~~~
#import "Triangle.h"
@implementation Triangle
-(void)onDraw {
NSLog(@"绘制三角形");
}
@end
~~~
#### 代码说明:
协议的实现是在类声明的父类之后, 加上, 与类的单个继承不同,
协议可以实现多个, 表示要实现这个协议,
如果有多个协议要实现用“,” 号分隔: 。
#### 调用的main函数
~~~
#import
#import "Graphics.h"
#import "Ellipse.h"
#import "Triangle.h"
int main (int argc, const charchar * argv[]) {
id graphics;
graphics = [[Ellipse alloc] init];
[graphics onDraw];
[graphics release];
graphics = [[Triangle alloc] init];
[graphics onDraw];
[graphics release];
return 0;
}
~~~
从上面的例子可以看出:
协议是多个类共享的一个方法列表。
协议中列出的方法没有相应的实现,计划由其他人来实现。
协议提供了一种方式,用指定的名称定义一组多少有点相关的方法。
如果决定实现特定协议的所有方法,也就意味着要遵守(confirm to)或采用(adopt)这项协议。
定义一个协议很简单:只要使用@protocol指令,然后跟上你给出的协议名称。
例如:
~~~
@protocol NSCoping
- (id) copyWithZone: (NSZone *) zone;
@end
~~~
这里再提到两个重要的协议:
NSCopying
NSCoding
如果你的类采用NSCopying协议,则必须实现copyWithZone:的方法**,**
通过在@interface行的一对尖括号()内列出协议名称,可以告知编译器你正在采用的一个协议。
这项协议的名称放在类名和它的父类名称之后,
例如:
@interface AddressBook:NSObject
如果你的类采用多项协议,只需把它们都列在尖括号中,并用逗号分开,例如:
@interface AddressBook:NSObject
如果你定义了自己的协议,那么不必由自己实现它。
如果一个类遵守NSCoping协议,则它的子类也遵守NSCoping协议
(不过并不意味着对该子类而言,这些方法得到了正确的实现)。
@required和@optional关键字
@protocol MyProtocol
- (void) requiredMethod;
@optional
- (void) anOptionalMethod;
- (void) anotherOptionalMethod;
@required
- (void) anotherRequiredMethod;
@end
注意,这里使用了@optional指令。该指令之后列出的所有方法都是可选的。
@required指令之后的是需要的方法。
注意,协议不引用任何类,它是无类的(classless)。
任何类都可以遵循MyProtocol协议。
可以使用conformsToProtocol:方法检查一个对象是否遵循某些协议。
例如:
~~~
if ([currentObject conformsToProtocol: @protocol (MyProtocol)] == YES) {
...
}
~~~
这里使用的专用@protocol指令用于获取一个协议名称,并产生一个Protocol对象。
conformsToProtocol:方法期望这个对象作为它的参数。
通过在类型名称之后的尖括号中添加协议名称,可以借助编译器来检查变量的一致性。
例如:
id currentObject;
这告知编译器currentObject将包含遵守Drawing协议的对象。
如果这个变量保存的对象遵守多项协议,则可以列出多项协议 ,
如以下代码:
id myDocument;
定义一项协议时,可以扩展现有协议的定义。
如:
@protocol Drawing3D Drawing3D 协议也采用了Drawing协议。
最后要说的是,分类也是可以采用一项协议。
和类名一样,协议名必须是唯一的。
#### 代理:
协议也是一种两个类之间的接口定义。定义了协议的类可以看做是将协议定义的方法代理给了实现它们的类。
非正式(informal)协议:是一个分类,列出了一组方法但并没有实现它们。
因此,非正式分类通常是为根类定义的。有时,非正式协议也称为抽象(abstract)协议。
非正式协议实际上只是一个名称下的一组方法。
声明非正式协议的类自己并不实现这些方法,
并且选择实现这些方法中的子类需要在它的接口部分重新声明这些方法,
同时还要实现这些方法中的一个或多个。
注意:前面描述的@optional指令添加到了Objective-C 2.0语言中,用于取代非正式协议的使用。
#### 合成对象:
可以定义一个类包含其他类的一个或多个对象。
这个新类的对象就是所谓的合成(composite)对象,因为它是由其他对象组成的。
子类依赖于父类,改变了父类有可能会使得子类中的方法不能工作。
11-多态性、动态类型和动态绑定
最后更新于:2022-04-01 00:17:18
多态这个其它语言也有。动态类型有类似的,但不完全相同。
动态绑定别的语言也有类似,但没有objective-c用的这么多。
多态能够使来自不同类的对象定义相同名称的方法。
动态类型能使程序直到执行时才确定对象所属的类。
动态绑定则能使程序直到执行时才确定实际要调用的对象方法。
多态性是指在父类中定义的成员变量和方法被子类继承之后, 可以具有不同的数据类型或表现出不同的行为。
这使得同一个变量和方法在父类及其各个子类中具有不同的表现形式。
简单点说:相同的名称,不同的类,系统总是携带有关“一个对象属于哪个类”这样的信息。
该信息能使系统在运行时做出这些关键性的决定,而不是在编译时。
这种使不同的类共享相同方法名称的能力就称为多态。
我们通过一个例子理解什么是多态:
例如: “几何图形” 类的“绘图” 方法, 在它的子类“椭圆形” 和“三角形” 中也都有“绘图” 的方法,
但是“绘图” 方法功能都不同。
几何图形类图
Graphics(几何图形) 类是Ellipse(椭圆形) 类和Triangle(三角形) 类的父类,
Ellipse和Triangle重写了onDraw方法。
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-12-31_5684a3f0a265c.jpg)
Graphics.h文件
Graphics类在h文件中定义onDraw, Graphics类应该无法实现这
个onDraw, 这是因为在几何图形中是无法知道要绘制的是椭
圆形和三角形, 这个方法应该是类似Java中的抽象方法, 或者是
C++中的虚方法。
@interface Graphics : NSObject {
}
-(void) onDraw;
@end
Ellipse类的h和m文件
~~~
#import
#import "Graphics.h"
@interface Ellipse : Graphics {
}
@end
~~~
~~~
#import "Ellipse.h"
@implementation Ellipse
-(void)onDraw {
NSLog(@"绘制椭圆形");
}
@end
~~~
Triangle类的h和m文件
~~~
#import
#import "Graphics.h"
@interface Triangle : Graphics {
}
@end
~~~
~~~
#import "Triangle.h"
@implementation Triangle
-(void)onDraw {
NSLog(@"绘制三角形");
}
@end
~~~
#### 调用的main函数
~~~
#import
#import "Graphics.h"
#import "Ellipse.h"
#import "Triangle.h"
int main (int argc, const charchar * argv[]) {
Graphics *graphics;
graphics = [[Ellipse alloc] init];
[graphics onDraw];
[graphics release];
graphics = [[Triangle alloc] init];
[graphics onDraw];
[graphics release];
return 0;
}
~~~
运行结果:
绘制椭圆形
绘制三角形
动态类型和动态绑定
id 是泛类型 (generic data type), 可以用来存放各种类型的对象,
使用 id 也就是使用“动态类型”。
上面的例子改写下:
~~~
int main (int argc, const char * argv[]) {
id graphics;
graphics = [[Ellipse alloc] init];
[graphics onDraw];
[graphics release];
graphics = [[Triangle alloc] init];
[graphics onDraw];
[graphics release];
return 0;
}
~~~
把Graphics *改成id类型, 程序运行的结果没有任何影响。
由于动态类型的关系,id 在执行时,
Objective-C 的执行环境会找出该 id 所代表的原来类型,
所以根本没有所谓的转型。
id 并不是自动的转换成 Ellipse和 Triangle的父类,而是在执行期间,
由执行环境辨认出 id 实际代表的类型为Ellipse还是Triangle。
因此在这个例子中id 与Graphics没有任何关系。
务必注意,声明中并没有使用星号。
我们再举一个例子来说明动态的概念:
矢量和标量是数学和物理学应用中经常用到的两个概念;
矢量” 即有方向和大小的量, 如物理学中的“力” 。
标量为没有方向只有大小量, 如物理学中的“功”。
下面从程序的角度来实现这两个概念。
先定义两个类: “矢量” 和“标量” 类。
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-12-31_5684a3f0b7142.jpg)
Vector.h文件
~~~
#import
@interface Vector : NSObject {
double vec1;
double vec2;
}
@property double vec1,vec2;
-(void)print;
-(void)setVec1:(double)v1 andVec2:(double) v2;
-(Vector *)add:(Vector *)v;
@end
~~~
Vector.m文件
~~~
#import "Vector.h"
@implementation Vector
@synthesize vec1,vec2;
-(void) setVec1:(double) v1 andVec2:(double)v2 {
vec1 = v1;
vec2 = v2;
}
-(Vector *)add:(Vector *)v {
Vector *result = [[Vector alloc] init];
[result setVec1:vec1 + [v vec1] andVec2: vec2 + [v vec2]];
return result;
}
-(void)print {
NSLog(@"%g, %g",vec1,vec2);
}
@end
~~~
Scalar.h文件
~~~
#import
@interface Scalar : NSObject {
double scal;
}
@property double scal;
-(void)print;
-(void)setScal:(double)sval;
-(Scalar *)add:(Scalar *)s;
@end
~~~
Scalar.m文件
~~~
#import "Scalar.h"
@implementation Scalar
@synthesize scal;
-(void)print {
NSLog(@"%g", scal);
}
-(void)setScal:(double)sval {
scal = sval;
}
-(Scalar *)add:(Scalar *)s {
Scalar *result = [[Scalar alloc] init];
[result setScal:scal + [s scal]];
return result;
}
~~~
调用的main函数
~~~
#import
#import "Vector.h"
#import "Scalar.h"
int main (int argc, const charchar * argv[]) {
Scalar *scA =[[Scalar alloc] init];
Scalar *scB =[[Scalar alloc] init];
Vector *vecA =[[Vector alloc] init];
Vector *vecB =[[Vector alloc] init];
id scAandB;
id vecAandB;
[scA setScal: 10.5];
[scB setScal: 13.1];
[vecA setVec1: 3.2 andVec2: 4.7];
[vecB setVec1: 32.2 andVec2: 47.7];
[vecA print];
NSLog(@" + ");
[vecB print];
NSLog(@" = ");
vecAandB = [vecA add: vecB];
[vecAandB print];
[scA print];
NSLog(@" + ");
[scB print];
NSLog(@" = ");
scAandB = [scA add: scB];
[scAandB print];
[scA release];
[scB release];
[scAandB release];
[vecA release];
[vecB release];
[vecAandB release];
return 0;
}
~~~
运行结果:
~~~
3.2, 4.7
+
32.2, 47.7
=
35.4, 52.4
10.5
+
13.1
=
23.6
~~~
#### 代码说明:
scAandB和vecAandB对象都是动态类型, 都可调用以"print"和"add"方法。
注意:
虽然id类型可以任何类型的对象,但是不要滥用 ,
如果能够确定对象数据类型时候, 要使用“静态类型” ,
为什么要使用静态类型:
1)将一个变量定义为特定类的对象时,使用的是静态类型。
“静态”指的是对存储在变量中对象的类型进行显示声明。
这样存储在这种形态中的对象的类是预定义的,也就是静态的。
使用静态类型时,编译尽可能确保变量的用法在程序中始终保持一致。
编译器能够通过检查来确定应用于对象的方法是由该类定义的还是由该类继承的,否则它将显示警告信息。
也就是说“静态类型” 在编译阶段检查错误, 而不是在执行阶段。
2)使用静态类型的另一个原因是程序可读性好。。
动态类型的参数和返回类型:
如何使用动态类型来调用一个方法,需要注意如下规则:
如果在多个类中实现名称相同的方法,那么每个方法都必须符合各个参数的类型和返回值类型。
这样编译器才能为消息表达式生成正确的代码。编译器会对它所遇到的每个类声明执行一致性检查。
当一个方法选取对象作为它的参数,而另一个方法选取浮点数作为参数时,
或者一个方法以对象作为返回值,而另一个以整型数作为返回值。
编译器可能生成不正确的代码来向方法传递参数或处理返回值。
处理动态类型的方法:
~~~
-(BOOL) isKindOf:class-object(判断对象是否是class-object或其子类的成员) -(BOOL) isMenberOfClass:class-object(判断对象是否是class-object的成员) -(BOOL) respondsToSelector:selector(判断对象是否能够响应selector所指定的方法) +(BOOL) instancesRespondToSelector:selector(判断指定的类实例是否能响应selector所指定的方法) +(BOOL) isSubclassOfClass:class-object(判断对象是否是指定类的子类) -(id) performSelector:selector(应用selector指定的方法) -(id) performSelector:selector withObject:object(应用selector指定的方法,传递参数object) -(id) performSelector:selector withObject:object1 withObject:object2(应用selector指定的方法,传递参数object1和object2
~~~
可以对一个方法名应用@selector指令。
例如:@selector (alloc)为名为alloc的方法生成一个SEL类型的值,该方法是从NSObject类继承的。
记住,测试包含继承的方法,并不是只测试直接定义在类中的方法。
performSelector:方法和它的变体允许你向对象发送消息,这个消息可以是存储在变量中的selector。
在iOS中,respondsToSelector:方法广泛用于实现委托(delegation)的概念。
为了让系统能够检查你确实实现了特定的方法,
使用respondsToSelector:判断是否可以将事件的处理委托给你的方法。
如果你没有实现这个方法,它会自己处理该事件,按定义的默认行为来执行。
#### 使用@try处理异常
@try:如果块中的某一语句抛出异常,执行不会终止,而是立即跳到@catch块继续执行
@catch:处理异常,可行的执行顺序是记录出错信息,清除和终止执行。
@finally:使用@finally块包含是否执行抛出异常的@try块中的语句代码;
@throw:允许你抛出自己的异常,
这些概念和java的或者其它语言差不多。
一般来说,需要考虑更好的编程实践,
应该在错误发生前做系统的全面的覆盖性测试,而不是在错误发生后捕获异常。
抛出异常会使用大量的系统资源,Apple反对非必要的使用异常。
10-继承性
最后更新于:2022-04-01 00:17:16
继承性是面向对象的重要概念之一, 子类能够继承父类的某些方法和成员变量。
作用域限定符为private的成员变量是不可以被继承的。
子类还可以重写父类的方法。
当然,这一切要从根类开始:
没有父类的类,位于类层次结构的最顶层,称为根(Root)类。
NSObject是层次结构的最顶端(也就是它上面没有任何类),因此称为根类。
如果使用术语,可以将类称为子类和父类。同样,也可以将类称为子类和超类。
需要注意的是,要在子类中直接使用实例变量,必须先在接口部分声明。
在实现部分声明和合成(synthesize)的实例变量是私有的,子类中并不能够直接访问,
需要明确定义或合成取值方法,才能访问实例变量的值。
继承的概念作用于整个继承链。
一定要理解以下事实:类的每个实例都拥有自己的实例变量,即使这些实例变量是继承来的。
找出正确的方法:
首先,检查该对象所属的类,以查看在该类中是否明确定义了一个具有指定名称的方法。
如果有,就使用这个方法。如果没有定义,就检查它的父类。
如果父类中有定义,就使用这个方法,否者,继续找寻。
直到找到根类也没有发现任何方法。
通过继承来扩展:添加新方法
继承通常用于扩展一个类。
@class指令:
@class XYPoint;
或
~~~
#import "XYPoint.h"
~~~
使用@class指令提高了效率,因为编译器不需要引入和处理整个XYPoint.h文件(虽然它很小),
只需要知道XYPoint是一个类名。
如果需要引用XYPoint类的方法(在实现部分中),@class指令是不够的,因为编译器需要更多的消息。
说的通俗点:只引用了类就用@class不然就用#import。
在默认情况下,合成的设值方法只是简单地复制对象指针,而不是对象本身。
你可以合成另一种设值方法,而不是制作对象的副本。
为了了解继承性, 我们看看这样的一个场景:
一位刚学习面向对象的小菜,自从当上了班长,他就有的忙了,因为录入档案,需要描述和处理个人信息,
于是他定义了类Person:
~~~
@interface Person: NSObject {
NSString* name;
int age;
NSDate birthDate;
}
-(NSString*) getInfo;
@end
~~~
新的校花School Beauty类:
一周以后, 小菜又遇到了新的需求, 他的几个表妹非要把各自学校的校花介绍给我他,烦恼呀!
需要描述和处理校花信息, 于是他又定义了一个新的类Beauty。
~~~
@interface Beauty: NSObject {
NSString* name;
int age;
NSDate birthDate;
NSString* school;
}
-(NSString*) getInfo;
@end
~~~
#### 小结
Beauty和Person两个类的结构太接近了,
后者只比前者多出一个属性school , 却要重复定义其它所有的内容。
Objective-C提供了解决类似问题的机制, 那就是类的继承。
@interface Beauty: Person {
NSString* school;
}
方法重写或者说是覆写方法:
不能通过继承删除或减少方法,但可以利用覆写来更改继承方法的定义。
新方法必须具有相同的返回类型,并且参数的数目与覆写的方法相同。
如果在不同的类中有名称相同的方法,则根据作为消息的接收者的类选择正确的方法。
#### 为什么要创建子类?
有如下3个理由:
1)希望继承一个类的方法,也许加入一些新的方法和或实例变量。
2)希望创建一个类的特别的版本。
3)希望通过覆写一个或多个方法来改变类的默认行为。
抽象类:
有时,创建类只是为了更容易创建子类。
因此,这些类名为抽象(abstract)类,或等价地称为抽象超类(abstract superclasses)。
在该类中定义方法和实例变量,但不期望任何人从该类创建实例。
注意:
子类不能继承父类中作用域限定符为@private的成员变量。
子类可以重写父类的方法,及命名与父类同名的成员变量。
下面再通过一个矩形类和正方形类的实例说明方法重写问题:
Rectangle.h文件:
~~~
#import
@interface Rectangle: NSObject {
int width;
int height;
}
-(Rectangle*) initWithWidth: (int) w height: (int) h;
-(void) setWidth: (int) w;
-(void) setHeight: (int) h;
-(void) setWidth: (int) w height: (int) h;
-(int) width;
-(int) height;
-(void) print;
@end
~~~
Rectangle.m文件:
~~~
#import "Rectangle.h"
@implementation Rectangle
-(Rectangle*) initWithWidth: (int) w height: (int) h {
self = [super init];
if ( self ) {
[self setWidth: w height: h];
}
return self;
}
-(void) setWidth: (int) w {
width = w;
}
-(void) setHeight: (int) h {
height = h;
}
-(void) setWidth: (int) w height: (int) h {
width = w;
height = h;
}
-(int) width {
return width;
}
-(int) height {
return height;
}
-(void) print {
NSLog(@"width = %i, height = %i", width, height );
}
@end
~~~
Square.h文件:
~~~
#import "Rectangle.h"
@interface Square: Rectangle
-(Square*) initWithSize: (int) s;
-(void) setSize: (int) s;
-(int) size;
@end
~~~
Square.m文件:
~~~
#import "Square.h"
@implementation Square
-(Square*) initWithSize: (int) s {
self = [super init];
if ( self ) {
[self setSize: s];
}
return self;
}
-(void) setSize: (int) s {
width = s;
height = s;
}
-(int) size {
return width;
}
-(void) setWidth: (int) w {
[self setSize: w];
}
-(void) setHeight: (int) h {
[self setSize: h];
}
@end
~~~
调试用的main函数:
~~~
#import
#import "Square.h"
#import "Rectangle.h"
int main (int argc, const charchar * argv[]) {
Rectangle *rec = [[Rectangle alloc] initWithWidth: 10 height: 20];
Square *sq = [[Square alloc] initWithSize: 15];
NSLog(@"Rectangle: " );
[rec print];
NSLog(@"Square: " );
[sq print];
[sq setWidth: 20];
NSLog(@"Square after change: " );
[sq print];
[rec release];
[sq release];
return 0;
}
~~~
运行结果:
~~~
Rectangle:
width = 10, height = 20
Square:
width = 15, height = 15
Square after change:
width = 20, height = 20
~~~
9-类构造方法和成员变量作用域、以及变量
最后更新于:2022-04-01 00:17:14
#### 构造方法
出于初始化类中的成员变量的需要, 可以提供一个方法用于此目的,
这个方法就叫构造方法或构造方法(Constructor)。
与C++和Java不同, Objective-C命名是没有限制的, 并且有返回值本身类型指针。
以音乐类举例:
Song.h文件
~~~
@interface Song : NSObject {
NSString *title;
NSString *artist;
long int duration;
}
//操作方法
- (void)start;
- (void)stop;
- (void)seek:(long int)time;
//访问成员变量方法
@property(nonatomic,retain) NSString *title;
@property(nonatomic,retain) NSString *artist;
@property(readwrite) long int duration;
//构造方法
-(Song*) initWithTitle: (NSString *) newTitle
andArtist: (NSString *) newArtist
andDuration:( long int ) newDuration;
@end
~~~
在Song类的定义中添加了一个方法, 它一般用 init开头命名,
它的返回值很特殊, 是返回值本身类型指针。 并且有返回值本身类型指针。
实现代码如下:
~~~
@implementation Song
@synthesize title;
@synthesize artist;
@synthesize duration;
//构造方法
-(Song*) initWithTitle: (NSString *) newTitle
andArtist: (NSString *) newArtist
andDuration:(long int) newDuration {
self = [super init];
if ( self ) {
self.title = newTitle;
self.artist = newArtist;
self.duration = newDuration;
}
return self;
}
... ...
@end
~~~
#### 代码说明:
1、头文件引用规则
导入的文件要用一对引号引起来,而不是中的“"字符。双引号适用于本地文件(你自己创建的文件),而不是系统文件,这样就通知编译器在哪里能够找到指定的文件。
2、构造方法的实现代码几乎就是模式代码, 基本上都是如下写法:
~~~
-(id)init
{
self = [super init];
if (self) {
//初始化代码
}
return self;
}
~~~
其中使用 [super init] 来调用父类默认构造方法。
对象的初始化的常见的编程习惯是类中所有的初始化方法都以init开头。
如果希望在类对象初始化时做一些事情。可以通过覆写init方法达到这个目的。
这个方法返回的实例对象指派给另一新个关键词: self。
self很像 C++ 和 Java 的 this。
3、还有if ( self ) 跟 ( self != nil ) 一样, 是为了确定调用父类构造方法成功返回了一个新对象。
当初始化变量以后, 用返回self 的方式来获得自己的地址。
4、父类默认构造方法 -(id) init。
技术上来说, Objective-C中的构造方法就是一个 "init" 开头的方法,
而不像 C++ 与Java 有特殊的结构。
5、必须将父类init方法的执行结果赋值给self,
因为初始化过程改变了对象在内存中的位置(意味着引用将要改变)。
如果父类的初始化过程成功,返回的值是非空的,通过if语句可以验证,
注释说明可以在这个代码块的位置放入自定义的初始化代码。
通常可以在这个位置创建并初始化实例变量。
注意,
init被定义为返回id类型,这是编写可能会被继承的类init方法的一般规则。
程序开始执行时,它向所有的类发送initialize调用方法。
如果存在一个类及相关的子类,则父类首先得到这条消息。
该消息只向每个类发送一次,并且向该类发送其它任何消息之前,保证向其发送初始化消息。
实例成员变量作用域限定符
在接口中声明的实例变量可以通过子类进行继承。
可以把下面指令放在实例变量之前,以便更精确地控制其作用域:
即便从封装的角度出发, 实例成员变量应该定义为@private,
但作为一种面向对象的语言, Objective-C支持@public、 @private和@protected作用域限定。
如果一个实例变量没有任何的作用域限定的话, 那么缺省就是@protected。
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-12-31_5684a3f06aa20.jpg)
•@public作用域限定的实例变量,可以被本类、其他类或者模块中定义的方法直接访问,即可在任何情况下访问;
•@private作用域限定的实例变量,只能在这个类里面才可以访问;
在实现部分定义的实例变量默认属于这种作用域。
•@protected作用域限定的实例变量, 可以在这个类里面和这个类的派生子类里面可以访问这个变量,
在类外的访问是不推荐的, 但也可以访问。在接口部分定义的实例变量默认是这种作用域。
•@package,对于64位映像,可以在实现该类的映像中的任何地方访问这个实例变量。
@public指令使得其他方法或函数可以通过使用指针运算符(->)访问实例变量。
但实例变量声明为public并不是良好的编程习惯,
因为这违背了数据封装的思想(即一个类需要隐藏它的实例变量)。
以一个简单的例子说明作用域:
访问定义如下:
~~~
#import
@interface Access: NSObject {
@public
int publicVar;
@private
int privateVar;
@protected
int protectedVar;
}
@end
~~~
调用的main函数如下:
~~~
#import
#import "Access.h"
int main (int argc, const charchar * argv[]) {
Access *a = [[Access alloc] init];
a->publicVar = 5;
NSLog(@"public var: %i\n", a->publicVar);
a->protectedVar = 6;
NSLog(@"protectedVar var: %i\n", a->protectedVar);
//不能编译
//a->privateVar = 10;
//NSLog(@"private var: %i\n", a->privateVar);
return 0;
}
~~~
注意:
@public、 @private和@protected作用域限定只能修饰的实例成员变量, 不能修饰类变量, 更不能修饰方法。
什么是类变量和类方法
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-12-31_5684a3f084072.jpg)
再以一个简单的例子说明下:
ClassA.h文件
~~~
#import
static int count;
@interface ClassA: NSObject {
int aaa;
}
+(int) initCount;
+(void) initialize;
@end
~~~
ClassA.m文件
~~~
#import "ClassA.h"
@implementation ClassA
-(id) init {
self = [super init];
count++;
return self;
}
+(int) initCount {
return count;
}
+(void) initialize {
count = 0;
}
@end
~~~
#### 代码说明:
init方法是默认构造方法,
在这个构造方法中累计类变量count,
在实例方法中可以访问类变量的, 但是类方法不能访问实例变量。
initCount 方法是一个普通的类方法, 用于返回类变量count,
initialize方法是非常特殊的类方法,它是在类第一次访问时候被自动调用 ,
因此它一般用来初始化类变量的,
类似于C#中的静态构造方法。
调用的main函数
~~~
#import
#import "ClassA.h"
int main( int argc, const charchar *argv[] ) {
ClassA *c1 = [[ClassA alloc] init];
ClassA *c2 = [[ClassA alloc] init];
// print count
NSLog(@"ClassA count: %i", [ClassA initCount] );
ClassA *c3 = [[ClassA alloc] init];
NSLog(@"ClassA count: %i", [ClassA initCount] );
[c1 release];
[c2 release];
[c3 release];
return 0;
}
~~~
#### 代码说明:
在第一次实例化ClassA时候会调用两个方法:
initialize类方法和实例构造方法init,
然后再次实例化ClassA时候只是调用实例构造方法init, 而没有调用 initialize类方法。
这样类变量count被一直累加, 它隶属类;
因此c1 实例可以访问, c2和c3都可以访问。
关于属性、存储方法和实例变量:
编码规范(Xcode4已经采用的)目前的趋势是使用下划线(_)作为实例变量名的起始字符。
@synthesize window = _window;
表明合成(synthesize)属性window的取值方法和设置方法,
并将属性与实例变量_window(实例变量并没有显性声明)关联起来。
这对区别属性和实例变量的使用是有帮助的。
[window makeKeyAndVisible]; //错误
[_window makeKeyAndVisible];//正确
[self.window makeKeyAndVisible];//正确
#### 全局变量:
在程序的开始处(所有的方法、类定义)编写一下语句:
int gMoveNumber = 0;
那么这个模块中的任何位置都可以引用这个变量的值。
这种情况下,我们说gMoveNumber被定义为全局变量。
按照惯例,用小写的g作为全局变量的首字母。
外部变量是可被其他任何方法或函数访问和更改其值的变量。
在需要访问外部变量的模块中,变量声明和普通方式一样,只是需要在声明前加上关键字extern。
使用外部变量时,必须遵循下面这条重要的原则:变量必须定义在源文件中的某个位置。
即在所有的方法和函数之外声明变量,并且前面不加关键字extern,如:int gMoveNumber;
确定外部变量的第二种方式是在所有的函数之外声明变量,
在声明前面加上关键字extern,同时显式地为变量指派初始值。
记住,声明不会引起分配变量的内存空间,而定义会引起变量内存空间分配。
处理外部变量时,变量可以在许多地方声明为extern,但是只能定义一次。
注意,如果变量定义在包含访问该变量的文件中,那么不需要单独进行extern声明。
#### 静态变量:
在方法之外定义的变量不仅是全局变量,而且是外部变量。
如果希望定义全局变量且只在特定模块(文件)中是全局的,就可以使用static来修饰。
注意,重载alloc并不是好的编程实践,因为这个方法处理内存的物理分配。
#### 枚举数据类型:
枚举数据类型的定义以关键字enum开头,之后是枚举数据类型的名称,然后是标识符序列(包含在一对花括号内),它们定义了可以给该类型指派的所以的允许值。
在代码中定义的枚举类型的作用域限于块的内部。
另外,在程序的开始及所有块之外定义的枚举数据类型对于该文件是全局的。
定义枚举数据类型时,必须确保枚举标识符与定义在相同作用域之内的变量名和其他标识符不同。
8-访问成员变量和属性
最后更新于:2022-04-01 00:17:11
#### 访问成员变量
从面向对象的封装角度考虑问题, 要想访问类中的成员变量, 是要通过方法访问的,
成员变量前面要有作用域限定符(protected, public, private) ,
这些存取权限修饰符我们将在后面介绍。
成员变量的访问, 是通过读取方法(getter) 和设定方法(setter)。
访问成员属性:
可以使用点运算符.,或发送消息[]
以上一节中的音乐类为例子:
~~~
Song.h文件
@interface Song : NSObject {
NSString *title;
NSString *artist;
long int duration;
}
//操作方法
- (void)start;
- (void)stop;
- (void)seek:(long int)time;
//访问成员变量方法
- (NSString *)title;
- (void) setTitle:(NSString *) newTitle;
- (NSString *)artist;
- (void) setArtist:(NSString *) newArtist;
- (long int)duration;
- (void) setDuration:(long int) newDuration;
@end
Song.m文件
@implementation Song
- (void)start {
//开始播放
}
- (void)stop {
//停止播放
}
- (void)seek:(long int)time {
//跳过时间
}
//访问成员变量方法
- (NSString *)title {
return title;
}
- (void) setTitle:(NSString *) newTitle {
title = newTitle;
}
- (NSString *)artist {
return artist;
}
- (void) setArtist:(NSString *) newArtist {
artist = newArtist;
}
- (long int)duration {
return duration;
}
- (void) setDuration:(long int) newDuration {
duration = newDuration;
}
@end
~~~
#### 小结
采用了封装之后就可以通过存取方法访问属性,
例如[mySong title] 是取得title成员变量内容。
如果不考虑封装的问题, 单从技术上讲Objective-C, 可以直接通过对象访问成员变量的,
访问操作符是“->” ,
例如:
mySong->title, 也可以取得title成员变量的内容。
需要指出的是,也可以对自定义的方法使用点运算符,不仅仅是使用在synthesize上(即属性)。
注意:
点运算符和发消息都是可以的,但是,点运算符通常使用在属性上,用于设置或取得实例变量的值。
方法在Apple的文档中被标记为任务(Task),任务通常不是由点运算符执行的,
而是使用传统的方括号形式的消息表达式作为首选的语法。
另外,使用合成(synthesize)的存取方法,属性名称的前面不要以new、alloc、copy和init这些此开头。
这与编译器的一些假定有关,因为编译器会合成相应的方法。
#### 属性
对于成员变量的访问, 要通过读取方法(getter) 和设定方法(setter) 。
在实现部分也要实现这些读取方法和设定方法,
为了简化这些琐碎编码Objective-C2.0提出属性的概念,
使用 @property关键字在接口部分定义属性,
在实现部分使用 @synthesize关键字在组装和合成这些属性。
这种可自动生成设值方法和取值方法(统称为存取方法)。
具体步骤如下:
1)在接口部分中使用@property指令标识属性。
~~~
@interface Fraction : NSObject
@property int numerator, denominator;
@end
~~~
2)在实现部分使用@synthesize指令即可。
~~~
@inplementation Fraction
@synthesize numerator, denominator;
@end
~~~
再次实现头文件如下:
~~~
@interface Song : NSObject {
NSString *title;
NSString *artist;
long int duration;
}
//操作方法
- (void)start;
- (void)stop;
- (void)seek:(long int)time;
//访问成员变量方法
@property(copy,readwrite) NSString *title;
@property(nonatomic,retain) NSString *artist;
@property(readonly) long int duration;
@end
~~~
#### 代码说明:
声明property的语法为: @property (参数) 类型 名字; ,
这里的“参数” 主要分为3大类:
•读写属性(readwrite/readonly) ;
•内存管理(assign/retain/copy) , 这些内存管理的参数, 我们将在内存管理小节部分介绍;
•原子性atomicity(nonatomic) , 是关系线程线程安全的,
atomicity是原子性的线程安全的, 但是会影响性能。
如果确定不考虑线程安全问题可以使用 nonatomic。
> 注意:
> 如果使用了@property指令,就不需要在实现部分声明相应的实例变量。
通常,如果有称为x的属性,那么在实现部分包括以下行会导致编译器自动实现一个取值方法x和一个设置方法setX:
@synthesize x;
因为生成的存取方法是高效的,并且在使用多个核心的多台机器上,使用多线程时也可正常运行。
(这里的说法呢,就是说线程安全的)
实现.m文件如下:
~~~
@implementation Song
@synthesize title;
@synthesize artist;
@synthesize duration;
- (void)start {
//开始播放
}
- (void)stop {
//停止播放
}
- (void)seek:(long int)time {
//跳过时间
}
@end
~~~
7-类、对象和方法
最后更新于:2022-04-01 00:17:09
Objective-C作为一种面向对象的编程语言, 具有面向对象的基本特征,
即: 封装、 继承和多态。
主要介绍ObjectiveC中有关面向对象基本概念: 类、 对象、 方法和属性等。
也就是面向对象程序设计的一些关键概念,主要关注Objective-C定义类相关的语法。
OC面向对象方面的概念和其它语言差不多。相比其他语言更接近C++。
对象就是一个物件。面向对象的程序设计可以看成一个物件和你想对它做的事情。
对象(名词)----实现(动词),先定义类(Class),再定义方法(Method)。
#### C语言是典型的面向过程性语言。
在C语言中,通常是先考虑要实现什么,然后才关注对象,这几乎总是与面向对象的思考过程相反。
实现(动词)----对象(名词),先定义函数(Function),再定义模块(Module)。
类的独特存在就是一个实例,对实例执行的操作称为方法。
在某些情况下,方法可以应用于类的实例或类本身。
对象使用方法可以影响对象的状态。
关键概念:对象是类的独特表示,每个对象都包含一些通常对该对象来说是私有的信息(数据)。方法提供访问和改变这些数据的手段。
Objective-C采用特定的语法对类和实例应用方法:
[ ClassOrInstance method];
请求一个类或实例来执行某个操作时,就是向它发送一条消息,消息的接收者称为Receiver。所以,可以用另外一种方式描述:
[ receiver message];
Objective-C的Method操作执行,一种理解是发送消息,另外一种是方法调用。
前一种更贴近OC的思想。
Objective-C的类声明和实现包括两个部分:接口部分和实现部分。
~~~
@interface Song: NSObject {
… …
}
… …
@end
@implementation Song
… …
@end
~~~
同样的,程序在逻辑上就分为下面3个部分:
@interface
@implementation
program
#### 接口部分
@interface部分用于描述类和类的方法;
@implementation部分用于描述数据(类对象的实例变量存储的数据),并实现在接口中声明方法的实际代码;
主要定义了类名 、 继承的父类、 实现的协议、 成员变量和方法等信息。
举例如下:
一首好听的音乐,下面的代码是Song类的接口部分声明。
~~~
@interface Song : NSObject {
NSString *title;
NSString *artist;
long int duration;
}
- (void)start;
- (void)stop;
- (void)seek:(long int)time;
@end
~~~
program部分的程序代码实现了程序的预期目的。
@interface部分一般格式如下:
@interface NewClassName: ParentClassName
propertyAndMethodDeclarations;
@end
按照约定,类名以大写字母开头。
实例变量、对象以及方法的名称,通常以小写字母开头。
确定名称时,要遵循找到能反映变量或对象使用意图的名称。
程序具有更强的自解释性(Self-explanatory)
制定名称的规则相当简单:名称必须以字母或下划线(_)开头,
之后可以使任何大小写字母、下划线或者数字的组合。
另外像$空格等都是非法,记住不能数字开头、不能使用保留字。
再次强调,Objective-C是大小写敏感的。sum、Sum、SUM均表示不同的变量。
#### 实现部分
使用关键字@implementation, 主要实现了在接口部分定义的方法等信息。
~~~
@implementation部分的一般格式如下:
@inplementation NewClassName
{
memberDeclarations;
}
methodDefinitions;
@end
~~~
需要注意的是:使用@synthesize指令能让编译器自动为你生成一些方法。
举例如下:
下面的代码是Song类的实现部分声明。
~~~
@implementation Song
- (void)start {
//开始播放
}
- (void)stop {
//停止播放
}
- (void)seek:(long int)time {
//跳过时间
}
@end
~~~
接口和实现的要求:
接口文件包含类的公开信息,即能够与这个类的使用者共享一些信息。
另一方面,实现部分包含的是私有信息,即实例变量和代码。
#### 方法和消息
类或实例方法,开头为负号(-)表示实例方法,正号(+)表示类方法。
返回类型放在开头的负号或正号之后的圆括号中。
有参数时,在方法名后加冒号(:),再加上参数类型和参数名。
具体的如下例所示:
-(int) currentAge;
-(void) print;
-(void) setNumber: (int) n;
方法类型 (返回类型) 方法名称 方法有参数 参数类型 参数名称
可见,Objective-C中方法定义非常古怪, 它遵循了SmallTalk语法风格, 它将一个方法名字分成几个部分。
![](file:///C:/Users/Administrator/AppData/Local/YNote/Data/danielzzu@163.com/6474e70e85cb435490ae24a0705e632d/clipboard.png)
如图所示:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-12-31_5684a3f053bca.jpg)
再次解释说明:
定义了两个参数的方法,
第一个参数是anObject, 参数类型是id类型,
第二个参数是index, 参数类型是NSUInteger,
这叫做多重参数。
它的返回类型是void,
方法签名是insertObject:atIndex: 。
方法类型标识符中都“-” 代表方法是实例方法, “+” 代表方法是类方法,
关于实例方法和类方法我们将在后面内容中讨论。
如果上面的方法变成C或C++形式, 则是下面的样子的:
-(void) insertObjectAtIndex(id anObject, NSUInteger index)。
#### 消息发送
对于方法的调用通常也不称之为调用 , 而是称为发出消息,
操作符号不是“. ” 而是“[…] ” , 如下所示:
[myObject insertObject: ojb1 atIndex:0];
即向myObject对象发出一个消息insertObject:atIndex: 。
而在实际使用时候这两种叫法都会用 , 这不是严格划分。
alloc是allocate的缩写。
如果向某个类发送alloc消息,便获得该类的新实例。
这个alloc方法继承自父类。alloc方法保证对象的所有实例都变成初始状态。
当然想要适当的方法时,必须重新初始化,调用init方法。
经常地情况是把alloc和init合在一起,或者直接使用new方法。
例如:
~~~
Fraction *myFraction;
myFraction = [Fraction alloc];//类方法
myFraction = [myFraction init];//实例方法
Fraction *myFraction = [[Fraction alloc] init];//二合一
Fraction *myFraction = [Fraction new];//类方法,new包含alloc和init
~~~
记住,方法执行的上下文环境就是接收到消息的对象。
取值方法(get)和赋值方法统(set)称为访问方法(accessor)。
这就是数据封装的原则,通过使用方法来访问对“外界”隐藏的数据。
使用一个类的程序结束本章:
~~~
//
// main.m
// 3_2_class_object_method
//
// Created by haomengzhu on 14-11-01.
// Copyright (c) 2014年 haomengzhu. All rights reserved.
//
#import
//-------- @interface section ----------
@interface Fraction: NSObject
-(void) print;
-(void) setNumerator: (int) n;
-(void) setDenominator: (int) d;
@end
//-------- @implementation section ----------
@implementation Fraction
{
int numerator;
int denominator;
}
-(void) print
{
NSLog(@"%i/%i", numerator, denominator);
}
-(void) setNumerator:(int)n
{
numerator = n;
}
-(void) setDenominator:(int)d
{
denominator = d;
}
@end
//-------- program section ----------
int main(int argc, const charchar * argv[])
{
@autoreleasepool {
Fraction *myFraction;
// new object
myFraction = [Fraction alloc];
myFraction = [myFraction init];
// set
[myFraction setNumerator: 1];
[myFraction setDenominator: 3];
// print all
NSLog(@"Hello, World!");
[myFraction print];
}
return 0;
}
~~~
最后再来说说OC中的方法:
#### 关于方法:
在编写新方法时,省略参数名不是一种好的编程风格,因为它是程序很难读懂并且很不直观,
特别是当使用的方法参数特别重要时,更是如此。
举例如下:
定义了Fraction类以及新方法:
-(void)setTo:(int)n over:(int)d;
注意下面的使用方式:
[aFraction set:1 :3]这是不好的
[aFraction set:1 over:3]这是好的
#### 再添加新方法:
- (void)add:(Fraction *) f;
这条语句说明add:方法的参数是Fraction类对象的一个引用。
星号是必须的,所以声明(Fraction) f是不正确的。
#### 局部变量:
局部变量是基本的C数据类型,并没有默认的初始值,所以在使用前要先赋值。
局部对象变量默认初始化为nil。
和实例变量不同(它们在多次方法调用时保持自己的值),这些局部变量没有记忆力。
也就是说,当方法返回时,这些变量的值都消失了。
每次调用方法时,该方法中的局部变量都使用变量声明重新初始化一次。
#### 方法的参数:
方法的参数名也是局部变量。执行方法时,通过方法传递的任何参数都被复制到局部变量中。因为方法使用参数的副本,所以不能改变通过方法传递的原值。这一点很重要。
另外,如果参数是对象,可以更改其中的实例变量值。当你传递一个对象作为参数时,实际上是传递了一个数据存储位置的引用。正因为如此,你才能够修改这些数据。
#### static关键字:
在变量声明前加上关键字static,可以使局部变量保留多次调用一个方法所得的值。
和其它基本数据类型的局部变量不同。
静态变量的初始值为0。
此外,它们只在程序开始执行时初始化一次,并且在多次调用方法时保存这些数值。
记住:只能在定义静态变量和局部变量的方法中访问这些变量。
6-循环结构
最后更新于:2022-04-01 00:17:07
计算机的基本属性之一就是它能够重复执行一组语句。
循环语句的作用是反复执行一段代码, 直到满足终止循环的条件为止。
Objective-C语言中提供的循环语句有:
• while语句
• do-while语句
• for语句
while语句, 它的语法形式如下, 其中“[] ” 中的部分可以省略。
~~~
[initialization]
while (termination){
body;
[iteration;]
}
~~~
举例如下:
水仙花数是指一个 n 位数 ( n>=3 ),它的每个位上的数字的 n 次幂之和等于它本身。
(例如:1^3 + 5^3 + 3^3 = 153)
下面代码是计算1000以内的水仙花数。
~~~
int i = 100;
int r, s, t;
r = 0;s = 0;t = 0;
while (i < 1000) {
r = i / 100;
s = (i - r * 100) / 10;
t = i - r * 100 - s * 10;
if (i == r * r * r + s * s * s + t * t * t) {
NSLog(@" %i " , i );
}
i = i + 1;
}
~~~
三位的水仙花数共有4个,分别为:153、370、371、407。
do-while语句, 它的语法形式如下, 其中“[] ” 中的部分可以省略
~~~
[initialization]
do {
body;
[iteration;]
} while (termination);
~~~
计算1000以内的水仙花数
~~~
int i = 100;
int r, s, t;
r = 0;s = 0;t = 0;
do {
r = i / 100;
s = (i - r * 100) / 10;
t = i - r * 100 - s * 10;
if (i == r * r * r + s * s * s + t * t * t) {
NSLog(@" %i " , i );
}
i = i + 1;
} while (i < 1000);
~~~
for语句, 语法形式如下:
~~~
for (initialization; termination; iteration){
body;
}
~~~
举例如下:
下面代码是计算0~10平方、 立方数并输出:
~~~
int i = 10;
int r, s;
r = 0;
s = 0;
for (int j = 0; j <= i ; j++) {
r = j * j;
s = j * j * j;
NSLog(@"整数为: %i 对应的平方和%i :对应的立方和:%i ", j, i , s);
}
~~~
for语句小结for语句将按以下步骤执行:1.先求初始表达式的值。2.求循环条件的值。3.执行组成循环体的程序语句。4.求循环表达式的值。5.返回到2执行。
在for循环语句的“() ” 内部一般有3条语句,
initialization初始化语句, termination终止条件语句, iteration迭代语句,
其中这3条语句都是可以省略的。
例如把j 的初始化放在循环体的外面, 代码如下:
~~~
int j = 0;
for (; j <= i ; j++) {
r = j * j;
s = j * j * j;
NSLog(@"整数为: %i 对应的平方和%i :对应的立方和:%i ", j, i , s);
}
~~~
#### 跳转语句
与程序转移有关的跳转有break, continue, goto语句,
break和continue都主要与循环有,
goto语句在C中就有, 它是无条件跳转可以完全替代break和continue, 一般要慎用 。
break语句break语句在switch中使用过, 用来终止switch语句的执行。 break语句还可以用于循环体中, 终止当前的循环, 并紧跟该循环块的第一条语句处执行。
举例如下: break语句
~~~
for (int i = 0; i < 10; i++) {
if (i == 3)
break;
NSLog(@ " i =%i", i);
}
NSLog(@" Game Over!");
i =0
i =1
i =2
Game Over!
~~~
也就是说如果在一组嵌套循环中执行break语句,仅会推出执行break语句的最内层循环。
continue语句continue语句用来结束本次循环, 跳过循环体中下面尚未执行的语句,
接着进行终止条件的判断, 以决定是否继续循环。
对于for语句, 在进行终止条件的判断前, 还要先执行迭代语句。
举例如下:
~~~
for (int i = 0; i < 100; i ++) {
if (i % 10 == 0)
continue;
NSLog(@"%i ", i );
}
…
18
19
21
22
…
~~~
#### goto语句
goto语句是无条件跳转, 可以完全替代break和continue。如果是嵌套循环, 可以使用 goto语句指定标签, 来改变程序的流程。
举例如下:
~~~
for (int j = 0; j < 10; j++) {
for (int i = 0; i < 10; i++) {
if (i == 3)
goto label;
NSLog(@ " i =%i", i);
}
}
label:
NSLog(@" Game Over!");
~~~
在内循环中使用 break和continue都只能跳出内循环,
goto语句没有这个限制,
label: 是标签定义, goto语句后面要跟有标签。
5-选择结构
最后更新于:2022-04-01 00:17:05
Objective-C中的控制语句有以下几类:
• 分支语句: if-else, switch
• 循环语句: while, do-while, for
• 与程序转移有关的跳转语句: break, continue, goto
对于任何程序语言来说,有能力进行判断是一项基本特性。
### if-else语句
分支语句提供了一种控制机制, 使得程序的执行可以跳过某些语句不执行, 而转去执行特定的语句。
1\. 条件语句 if-else
2\. 多分支语句 switch
条件语句 if-else, 基本语法形式如下, 其中“[] ” 中的部分可以省略。
~~~
if (boolean-expression)
statement1;
[else if (boolean-expression) statement2;]
[else statement3;]
~~~
举例如下:
~~~
int number1 = 0;
int number2 = 1;
int max = 0;
if (number1 > number2) {
max = number1;
} else {
max = number2;
}
// 打印最大值max
NSLog(@"The maximum is %i ", max);
对于if语句,下面的代码很有价值:
int numerator;
int denominator;
-(double) convertToNum
{
if (denominator != 0) { //这里防止分母为0很必要
return (double) numerator /denominator;//这里double强转很有必要,否则小数位的值就丢了。
} else {
return NAN;//这里NAN表示一个数字,这个符号被定义在系统头文件math.h中。
}
}
判断整数能否整除2
remainder = number_to_test % 2;
if ( remainder == 0) {
//整除2
xxoo
}
~~~
最后对于if的条件表达式,要求用括号括起来,不要过度依赖于优先级。
尽量预测程序可能失败或产生非预期结果的情形,然后采取预防性措施应付这些情况,
是编写优秀而可靠的程序的必要部分。
其实,这个条件语句,写的好不好,主要看逻辑思维缜密不缜密。
Objective-C中有两个内置的特性,可以使Boolean变量的使用更容易。
一种特性就是特殊类型BOOL,它可以用于声明值非真即假的变量。
另外一种是预定义的值YES和NO。
非零意味着满足和零意味着不满足。
### switch语句
多分支语句 switch它的语法形式如下, 其中“[]”中的部分可以省略。
~~~
switch (expression){
case value1 : statement1;
break;
…………
case valueN : statemendN;
break;
[default : defaultStatement; ]
}
~~~
举例如下:
~~~
int score = 0;
scanf("%i", &score);
int scoreVal = score / 10;
char resChar = ' ';
switch (scoreVal ) {
case 9:
resChar = 'A';
break;
case 8:
resChar = 'B';
break;
case 7:
resChar = 'C';
break;
case 6:
resChar = 'E';
break;
default :
resChar = 'F';
}
NSLog(@"你的分数是: %c", resChar);
~~~
使用 switch语句一定要注意的是:
表达式expression的返回值类型必须是整数或能够自动转换成整数的类型都可以,
因此可以是_Bool、 char、 short int、 枚举类型、 int、 long int、 longlong以及它们的无符号类型等。
但不能是float和double等浮点类型。
case子句中的值valueN必须是常量, 而且所有case子句中的值应是不同的。
default子句是可选的。
break语句用来在执行完一个case分支后, 使程序跳出switch语句,
即终止switch语句的执行。
在一些特殊情况下, 多个不同的case值要执行一组相同的操作, 这时可以不用 break。
4-运算符和表达式
最后更新于:2022-04-01 00:17:02
### 运算符和表达式
运算符可以分成如下几种:
•算术运算符, +, ―, *, /, %, ++, ――
•关系运算符, >, =, <=, ==, !=
•布尔逻辑运算符, ! , &&, ||
•位运算符, &, | , ^, ~ , >>, <<
•赋值运算符, +=, ―=, *=, /=
•条件运算符, ? :
### 短路与和短路或
布尔逻辑运算符中有两个比较特殊的运算符号,
“&&” 和“|| ” ,
其中“&&” 为短路与,
如果对两个表达式进行计算, 若第1 个表达式的值为“假” ,
则与第2个表达式的值无关, 结果肯定为“假” ,
所以此时第2个表达式不再计算。
“|| ” 为短路或, 如果对两个表达式进行计算, 若第1 个表达式的值为“真” ,
则与第2个表达式的值无关, 结果肯定为“真” ,
所以此时第2个表达式不再计算。
举例如下:
~~~
int i = 0;
int a = 10;
int b = 9;
if ((a > b) | (i++ == 1)) {
NSLog(@" a > b");
} else {
NSLog(@" a < b");
}
NSLog(@"i = %i", i);
结果是a > b和i =1
~~~
### 位运算符
位运算符有如下几个运算符: &, | , ^, ~ , >>,
其中&是按位与, | 是按位或, ^是异或, ~是取反, >>是右位移, <<是左位移。
位运算符举例:
假设有两个二进制数16位整数(short int) ,
a=1001110110011101 和b=0011100100111001 ,
则有如下结果;
它们的运行结果如下:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-12-31_5684a3f0324db.jpg)
### 条件运算符
条件运算符的语法格式为:
布尔表达式? 返回值1: 返回值2
当布尔表达式的值为真时, 返回表达式1 的值, 否则返回表达式2的值。
举例如下:
~~~
int i = 70;
int i3 =70
NSString *res = i3 > 60 ? @"及格" : @"不及格" ;
NSLog(@"res = i3 > 60 %@ ", res);
~~~
结果是“及格”
### 表达式
表达式,是由数字、算符、数字分组符号(括号)、自由变量和约束变量等
以能求得数值的有意义排列方法所得的组合。
约束变量在表达式中已被指定数值,而自由变量则可以在表达式之外另行指定数值。
### 表达式的意义
给与自由变量一些数值指定,可以给与一个表达式数值,
即使对于一些自由变量的值,表示式或许没有定义。
因此,一个表达式代表一个函数,其输入为自由变量的定值,而其输出则为表示式因之后所产生出的数值。
举例来说:
表达式x/y,分别使自由变量x和y定值为10和5,其输出为数字2;
但在y值为0时则没有定义。
一个表达式的赋值和算符的定义以及数值的定义域是有关联的。
两个表达式若被说是等值的,表示对于自由变量任意的定值,
两个表达式都会有相同的输出,即它们代表同一个函数。
一个表达式必须是合式的。亦即,其每个算符都必须有正确的输入数量,在正确的地方。
如表达式2+3便是合式的;而表达式*2+则不是合式的,至少不是算术的一般标记方式。
表达式和其赋值曾在20世纪30年代由阿隆佐·邱奇和Stephen Kleene在其λ演算中被公式化。
λ演算对现代数学和电脑编程语言的发展都曾有过重大的影响。
### 表达式的分类
1、算术表达式:
算术表达式是最常用的表达式,又称为数值表达式。
它是通过算术运算符来进行运算的数学公式。
算术运算符
1)乘法运算符
XY 求X乘Y的值 举例:6*7=42
2) 除法运算符 /
X/Y 求X除Y的值(浮点数运算) 举例:2.76/1.2=2.3
3)整除运算符 div
X div Y 求X除Y的整数商(对整型数计算) 举例:25=5
4)取余运算符 Mod
X mod Y 求X除Y的余数(对整型数运算) 举例:25 mod 4=1
5)加法运算符 +
X+Y 加法运算 举例:32+2=34
6)减法运算符 -
- X-Y 减法运算 举例:48-21=27
2、逻辑表达式
逻辑运算的结果只有两个:True(真)和False(假)。
OC提供了六种关系运算符和三种逻辑运算符:
==(等于)、(大于)、>=(大于等于)、!=(不等于)
!(非)、&&(与)、||(或)
表达式的运算优先顺序
在进行表达式的转换过程中,必须了解各种运算的优先顺序,
使转换后的表达式能满足数学公式的运算要求。
运算优先顺序为:
~~~
括号→函数→乘方→乘、除→加、减→字符连接运算符→关系运算符→逻辑运算符
~~~
如果同级的运算是按从左到右次序进行;多层括号由里向外。
例:
~~~
(10+6)*3^2*COS(1)/2*8+7
① ④ ③ ⑤ ② ⑥ ⑦ ⑧
Sqrt(Abs(p/n-1))+1
④ ③ ① ② ⑤
~~~
3-数据类型
最后更新于:2022-04-01 00:16:59
数据类型、 运算符和表达式在任何的计算机语言中都比较重要的,
在面向对象的Objective-C语言中, 除了常规的基本类型, 还有对象类型等。
运算符和表达式完全遵守C语言规范。
Objective-C数据类型可以分为:基本数据类型、 对象类型和id类型。
> 基本数据类型有: int、 float、 double和char类型。
对象类型就是类或协议所声明的指针类型, 例如:
NSAutoreleasePool * pool, 其中NSAutoreleasePool是一个类,
NSAutoreleasePool *是它指针类型。
id类型可以表示任何类型, 也就是说id可存储任何类型的对象。
一般只是表示对象类型, 不表示基本数据类型,从某种意义上说,它是一般对象类型。
所以刚才的变量pool也可以声明为id pool。
id类型是Objective-C中十分重要的特性,它是多态和动态绑定的基础。
## 基本数据类型
### 1、int类型
int类型代表整数, 它的十六进制表示方式: 0xFFED0D,
在使用 NSLog函数中格式化字符串使用 %i表示十进制的整数,
%o(字母o) 表示8进制整数, %#x表示十六进制整数。
NSLog(@"integerVar = %i", integerVar);
整数常量由一个或多个数字的序列组成。
它的取值范围是与设备相关的, 无法一概而论。
也就是要注意设备或机器相关量。即我们通常说的int是32位还是64位取决于CPU的总线宽度。
在Mac OS X中,提供了选择应用程序是在32位还是64位下编译。
在前一种情况下,一个int占用32位;在后一种情况下,一个int占用64位。
### 2、float类型
声明为float类型的变量可以存储包含小数位的值。
float类型代表单精度浮点数, 要表示float类型浮点数, 可以在数值后面加上f或F, 例如:13.5f。
float浮点数也可以用科学计数法表示, 例如: 1.7e4。
NSLog函数中格式化字符串: %f表示浮点数, %e表示科学计数法, %g表示浮点数。
~~~
float floatingVar = 3.141592;
NSLog(@"floatingVar = %f", floatingVar);
~~~
这里的输出结果为floatingVar =3.141592
这说明实际显示的值是由具体使用的计算机系统决定的。
出现这种不准确值的原因在于,计算机内部使用了特殊的方式表示数字。
同样的不确定性也出现在:在计算机内存中不能精确地表示一些浮点值。
所以,我们在进行float或double的值比较时,一定要考虑这一点。
这也是为什么float值和0相等的比较需要写成下面表达式的原因:
if(x>0.000001&&x<-0.000001)
### 3、double类型
double类型代表双精度浮点数, 与float类型很相似, 占用的字节空间double类型大体上是float类型的两倍。
大多数计算机是用 64位表示double类型。
NSLog函数中格式化字符串, 与float的%f、 %e和 %g相同。
NSLog(@"doubleVar = %e", doubleVar);
### 4、char类型
char类型代表字符类型,char变量可存储单个字符。将字符放入一对单引号中就能得到字符常量。如:’a‘。
不要把字符常量和c语言风格的字符串混为一谈,字符常量是放在单引号中的字符,而字符串则是放在双引号中的任意个数的字符。
如果要表示一些特殊字符, 要使用转义字符“\” 。
字符常量’\n‘(即换行符)是一个合法的字符常量,尽管它似乎与前面提到的规则矛盾。
NSLog(@"charVar = %c", charVar);
下面举个例子来演示以上类型的使用:
~~~
#import
int main (int argc, const charchar * argv[]) {
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
int integerVar = 100;
float floatingVar = 331.79;
double doubleVar = 8.44e+11;
char charVar = 'W';
NSLog(@"integerVar = %i", integerVar);
NSLog(@"floatingVar = %f", floatingVar);
NSLog(@"doubleVar = %e", doubleVar);
NSLog(@"doubleVar = %g", doubleVar);
NSLog(@"charVar = %c", charVar);
[pool drain];
return 0;
}
~~~
## 数据类型限定词
Obejctive-C中数据类型可以在int、 float、 double和char类
前面加上限定词, 限定词有: long、 long long、 short、
unsigned和signed, 这些限定词从而增强了基本类型。
• long int,
如果直接把限定词long放在int声明之前,那么所声明的整型变量在某些计算机上具有扩展的值域。
long变量的具体范围也是由计算机系统决定的。
在大部分计算机中代表32位整数, 在整数后面加L(或l) 表示,
例如: long int numberOfPoints =131071100L,
NSLog函数中格式化字符串使用 %li表示;
• long long int可以指定更加宽泛的整数类型, 保证变量至少64位宽度。
NSLog函数中格式化字符串使用 %lli表示;
• long double , 可以指定更加宽泛的double类型,
要显示这个可以在尾部使用 L(大小写) 表示, 1.234e+7L。
NSLog函数中格式化字符串使用 %Lf、 %Le和%Lg表示;
• short int用来指定存放相对小的整数, 节约内存,
一般是占用 int类型的一半。 大部分计算机是16位;
• unsigned int, 告诉编译器只是接受整数,
在数值之后放字母u(或U) 表示, 例如: 0x00ffU;
编写整数时候, 可以将字母u(或U) 和l(或L) 组合起来,
例如: 20000UL;
• signed char, 代表的字符与编译器有关, 一般也作为无符合整数使用。
## 布尔类型和枚举类型
Objective-C还有两种数据类型是以int类型在计算机内部存储的, 它们是: 布尔类型和枚举类型。
•布尔类型是_Bool(别名 BOOL), 取值范围1或0,
其中1可以用TRUE和YES表示, 0可以用 FALSE和NO表示。
•枚举类型,如果需要定义一组相关常量, 可以采用枚举类型, 把这些常量定义成一个类型,
例如游戏在上、 下、 左、 右方向,
可以枚举类型:
enum direction {up,down,left,right};
其中up从0开始, down是1, 依次类推加1
枚举类型实例:
~~~
#import
int main (int argc, const charchar * argv[]) {
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
enum week {
Mon, Tue, Wed, Thu, Fri, Sat, Sun
};
int days,aweek;
//aweek = 0;
NSLog(@"Enter week number : ");
scanf("%i", &aweek);
switch (aweek) {
case Mon:
break;
case Tue:
break;
case Wed:
break;
case Thu:
break;
case Fri:
break;
case Sat:
days = 5;
break;
case Sun:
days = 6;
break;
default:
NSLog(@"bad week number");
days = -1;
}
if (days !=0) {
NSLog(@"Number of days is %i", days);
}
[pool drain];
return 0;
}
~~~
代码解释说明:
其中定义了week的枚举类型,
函数scanf("%i ", &aweek) 是C中标准函数库, 用于从终端读取键盘输入, %i 是指定接收的类型,
&aweek是传递aweek的地址给函数, 便于接收键盘输入内容。
## 数据类型转换
按照数据类型占用存储不同可以自动类型转换或强制类型转换,
总的原则是小存储容量数据类型可以自动转换成为大存储容量数据类型。
不同类型数据间按照下面关系的从左到右(从低到高) 自动转换:
_Bool 、 char、 short int、 枚举类型 -> int ->long int->long long-> float -> double -> long double。
类型转换先后顺序表:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-12-31_5684a3f019751.jpg)
数据类型转换举例:
如果有表示式f * i + l /s, 其中, f是float类型, i 为int类型, l
为long int 类型, s为short int类型, 结果是什么类型?
答案:运行结果为float类型,
这是因为f是float其它的操作数与float运算其结果就是float类型。
## 强制类型转换
如果遵守类型转换是右到左情况, 就需要强制类型转换了,
强制类型转换语法形式上很简单, 就是在数据前面加上(目标类型) ,
但是这种转换是存在风险的, 有可能造成数据的丢失, 需要谨慎进行。
例如:
~~~
long int l = 6666666666;
NSLog(@"l = %li",l );
int i = (int)l;
NSLog(@"i = %i",i );
~~~
运行结果:
~~~
l = 6666666666
i = -1923267926
~~~
~~~
int total = 3446;
int n = 6;
float average = total / n;
~~~
运行结果:
`average结果是574`
~~~
int total = 3446;
int n = 6;
float average = (float) total / n;
~~~
运行结果:
~~~
average结果是574.333
~~~
### 常量与变量
在Objective-C中,任何数字、单个字符和字符串通常都称为常量。
完全由常量值组成的表达式叫常量表达式。
Objective-C中声明常量使用关键字const:
•const double PI = 3.141592654;
Objective-C中变量可以分为成员变量、 局部变量和全局变量。
举例如下:
~~~
int gCounter;
@interface MyObject : NSObject {
int counter;
}@end
@implementation MyObject
-(void) print {
int cter = 0;
NSLog(@"%i", cter );
}@end
~~~
其中:
gCounter:全局变量;
counter : 成员变量;
cter : 局部变量。
### 更多了解:
基本数据类型表:
|类型 | 常量实例 |NSlog字符|
|---|---|---|
| Char | ‘a’,’/n’ | %c |
| Short int | -- | %hi,%hx,%ho |
| Unsigned short int | -- | %hu,%hx,%ho|
| Int | 12,-97,0xFFE0,0177 | %i,%x,%o|
| Unsigned int | 12u,100U,0xFFu | %u,%x,%o |
| Long int | 12L,-200l,0xffffL| %li,%lx,%lo |
| Unsigned long int | 12UL,100ul,0xffeeUL| %lu,%lx,%lo |
| Long long int | 0xe5e5c5e5LL,500ll | %lli,%llx,%llo|
| Unsigned long long int | 12ull,0xffeeULL | %llu,%llx,%llo|
| Float| 12.34f,3.1e-5f, | %f,%e,%g,%a|
|Double | 12.34,3.1e-5,0x.1p3 | %f,%e,%g,%a|
| Long double | 12.34l,3.1e-5l| %Lf,%Le,%Lg |
| id | nil | %p|
#### NSLog的定义
NSLog定义在NSObjCRuntime.h中,如下所示:
> ### void NSLog(NSString *format, …);
基本上,NSLog很像printf,同样会在console中输出显示结果。
不同的是,传递进去的格式化字符是NSString的对象,而不是chat *这种字符串指针。
示例:
NSLog可以如下面的方法使用:
~~~
NSLog (@"this is a test");
NSLog (@"string is :%@", string);
NSLog (@"x=%d, y=%d", 10, 20);
~~~
但是下面的写法是不行的:
~~~
int i = 12345;
NSLog( @"%@", i );
~~~
原因是, %@需要显示对象,而int i明显不是一个对象,要想正确显示,要写成:
~~~
int i = 12345;
NSLog( @"%d", i );
~~~
### NSLog格式:
NSLog的格式如下所示:
* %@ 对象
* %d, %i 整数
* %u 无符整形
* %f 浮点/双字
* %x, %X 二进制整数
* %o 八进制整数
* %zu size_t
* %p 指针
* %e 浮点/双字 (科学计算)
* %g 浮点/双字
* %s C 字符串
* %.*s Pascal字符串
* %c 字符
* %C unichar
* %lld 64位长整数(long long)
* %llu 无符64位长整数
* %Lf 64位双字
### int 类型
如果整型值的第一位是0,那么这个整数将用八进制表示,在NSlog调用的格式符号:
%o 显示的八进制值不带有前导0
%#o 显示的八进制值带有前导0
如果整型常量以0x或0X开头,那么这个整数将用十六进制表示,在NSlog调用的格式符号:
%x 显示不带前导0x,并用a-f之间的小写字符表示十六进制
%#x 显示带前导0x,并用a-f之间的小写字符表示十六进制
%X 显示不带前导0x,并用a-f之间的小写字符表示十六进制
%#X 显示带前导0x,并用a-f之间的小写字符表示十六进制
### float 类型
显示浮点值,用NSlog转换字符 %f
用科学计数法显示浮点值,用NSlog转换字符 %e
%g 充许NSlog确定使用常用的浮点计数显示还是使用科学计数法显示浮点值。(如果该值小于-4或大于5,采用%e科学计数法表示;否则采用%f浮点计数法显示)
2-Objective-C 编程
最后更新于:2022-04-01 00:16:57
编译运行Objective-C程序有两种主要方式:
1、Xcode集成开发环境。
2、使用GNU Objective-C编译器的Terminal命令行窗口。
使用Xcode
XCODE和iOS SDK下载地址:
[developer.apple.com](http://blog.csdn.net/haomengzhu/article/details/developer.apple.com)
当然也可以从App Store里面下载。
当然,你需要创建一个开发者账号,不过暂时是不需要的。
Objective-C源文件使用.m结尾,.m也就是扩展名。
Objective-C++是C++的扩展,类似于Objective-C是C的扩展。
A、使用Xcode创建新程序的操作步骤:
1、启动Xcode应用程序。
2、如果开发新项目,选择File->New->New Project...,或者在起始页选择Create a New Xcode Project。
3、选择应用程序类型,选择Application->Command Line Tool(刚开始学习,就不搞什么图形界面了先。这个和C语言差不多),然后Next。
4、为应用程序取一个名称,并且将Type设置为Foundation,确定Use Automatic Reference Counting复选框已经选中(这个是为了使用新的特性,即ARC),单击Next。
5、选择项目目录的名称,还可以选择在哪个目录中存储项目文件,然后点击Create。
6、在左上窗格中会看到文件main.m(在与项目名同名的文件夹下可以找到),突出显示该文件(就是单击那个文件)。在该窗口下面的编辑窗口中输入或编辑你的程序。
7、在工具栏中,选择位于View下方中间的图标,将显示调试区域,在这个区域会将显示输出结果。l
8、在工具栏中点击Run按钮,或者从Product菜单中选择Run,编译并运行程序。
如图示:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-12-31_5684a3efb06b8.jpg)
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-12-31_5684a3efd4441.jpg)
B、使用Terminal命令行写程序的操作步骤:
1、打开Terminal。
2、选择你的工作(工程)目录。如果没有:
~~~
$mkdir test
~~~
进入工作目录
~~~
$cd test
~~~
新建并编辑,
~~~
$vi main.m
~~~
VI的使用,请参考以下链接:
[http://en.wikipedia.org/wiki/Vi](http://en.wikipedia.org/wiki/Vi)
输入你的第一个Program:
~~~
// My first Program
#import
int main(int argc, charchar *argv[]){
@autoreleasepool{
NSLog(@"Hello World!");
}
return 0;
}
~~~
3、编译,编译链接器使用clang的LLVM Clang Objective-C。这个和Linux下的GCC使用差不多。大家不会的话,可以看看GCC那套编译。
$clang -fobjc-arc -framework Foundation main.m -o test
4、运行,这个和Linux是一样的编译的可执行文件前面加上“./”。也可以把工程目录加到PATH中(这样就可以不要"./"),一般没必要。
$./test
5、输出结果就会显示在命令行中。
需要注意的一点是:Objective-C是大小写敏感的,也就是说区分大小写,所以写代码的时候注意这点。
详解:
a、注释:使用//或者/* ... */
b、#import:告诉编译器找到并处理名为Foundation.h文件, 这是一个系统文件;
`#import表示将该文件的信息导入到程序中;`
在C和C++中采用 #include指令, 在本例中也可以采用#include,
而#import是#include改进指令, 它可以防止头文件重复包含问题;
c、int main(int argc, const char * argv[]) , 这个方法是程序的入口 。
参数argc-参数数目 , argv-参数值
d、@autoreleasepool自动释放池:它使得应用在创建新对象时,系统能够有效地管理应用所使用的内存;
或者使用下面的方式:
~~~
// First program example
#import
int main (int argc, const charchar * argv[]) {
NSAutoreleasePool * pool =
[[NSAutoreleasePool alloc] init];
// insert code here...
NSLog(@"Hello World!");
[pool drain];
return 0;
}
~~~
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]
语句是创建一个自动释放内存池对象, 使用它来维护内存释放和回收,
其中alloc方法是分配内存, init方法是初始化类, 相当于C++或Java的构造方法作用。
[pool drain]语句是用来释放内存池, 它将纳入到这个内存池中管理的对象内存全部释放掉,
关于NSAutoreleasePool问题我们将在内存管理部分详细讨论。
e:NSLog:oc库函数,仅仅显示或记录其参数,还能显示变量,使用%i;
NSLog是Foundation框架提供的Objective-C日志输出函数,
与标准C中的printf函数类似, 并可以格式化输出。
@“Hello, World!”, 常量NSString对象,是代表NSString字符串对象, 它是Objective-C常用的字符串类。
f:retrun 0:程序正常退出;
它表示要终止main的执行, 一般情况下0代表正常结束, 非0代表异常情况,
这是沿用了C语言的习惯。
Objective-C代码的文件扩展名
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-12-31_5684a3eff1850.jpg)
1-Objective-C特点
最后更新于:2022-04-01 00:16:54
Objective-C与其它面向对象有这明显的不同, 它有这自己鲜明的特色, 下面我们从这个方法介绍它的特点: 兼容性、 字符串、 类、 方法、属性、 协议和分类。
### 1、兼容性
Objective-C可以说是一种面向对象的C语言, 在Objective-C的代码中可以有C和C++语句,
它可以调用 C的函数, 也可以通过C++对象访问方法。
### 2、字符串
Objective-C通常不使用C语言风格的字符串。
大多数情况下是使用Foundation框架的NSString类型的字符串。
NSString类提供了字符串的类包装, 支持Unicode,printf风格的格式化工具等等。
它是在普通的双引号字符串前放置一个@符号,
如下面的例子所示:
~~~
NSString* myString = @"My String\n";
NSString* anotherString = [NSString stringWithFormat:@"%d %s", 1, @"String"];
~~~
### 3、类
Objective-C是一种面向对象的语言,定义类是它的基本能力。
Objective-C的类声明和实现包括两个部分:接口部分和实现部分。
### 4.方法
Objective-C是一种面向对象的语言,定义方法也是它的基本能力。
Objective-C中方法不是在“.”运算符,而是采用“[]”运算符。
有时候方法调用也称为:消息发送。
### 5、属性
属性是Objective-C 2.0提出的概念,
它是替代对成员变量访问的“读取方法(getter)”和“设定方法(setter)”的手段,
为了对类进行封装一般情况下不直接访问成员变量,而是通过属性访问。
### 6、协议
Objective-C中的协议类似于Java中的接口或C++的纯虚类,
只有接口部分定义没有实现部分,即只有h文件没有m文件。
### 7、分类
Objective-C中的分类是类似与继承机制,通过分类能够扩展父类的功能。
0-序章
最后更新于:2022-04-01 00:16:52
C语言首创于AT&T实验室,
UNIX OS的发展促进了C语言的快速普及,UNIX OS几乎完全是由C语言编写的。
Brad J.Cox在20世纪80年代早期设计了Objective-C语言,它以一种叫做SmallTalk-80的语言为基础。
SmallTalk-80可以参考:
[http://zh.wikipedia.org/wiki/Smalltalk](http://zh.wikipedia.org/wiki/Smalltalk)
Objective-C在C语言的基础上加了一层,这意味着对C进行了扩展,
从而创造出一门新的程序设计语言,支持面向对象,即对象的创建和操作。
C语言更多了解可以参考:
[http://zh.wikipedia.org/wiki/C%E8%AF%AD%E8%A8%80](http://zh.wikipedia.org/wiki/C%E8%AF%AD%E8%A8%80)
Objective-C更多了解可以参考:
[http://zh.wikipedia.org/wiki/Objective-C](http://zh.wikipedia.org/wiki/Objective-C)
1988 NeXT发布了Objective-C, 它的开发环境和类库叫“NEXTSTEP”,
1994年,NeXT计算机公司和Sun公司联合发布了一个针对NEXTSTEP系统的标准规范,名为OPENSTEP。
软件自由基金会OPENSTEP版本叫GNUStep。
1996苹果公司开始支持NeXT, 把NEXTSTEP/OPENSTEP用于Mac OS X操作系统开发,
它的版本和开发环境叫Cocoa, 使用Objective-C作为基础语言, 开发工具Xcode和Interface Builder。
2007 苹果公司推出Objective-C 2.0,Objective-C是Mac OS X和iOS开发的基础语言。
当iPhone于2007年发布时,起初,苹果公司不欢迎第三方应用程序开发。只允许他们开发基于Web的应用。
最后开发人员非常不满,于是苹果公司不久之后就宣布,开发人员能够为iPhone开发所谓的本机应用。
也就是我们所说的native应用。
IOS开发一步步走进程序员的视野,星火燎原。
C语言是面向过程的,学习Objective-C之前没必要**完整的**学习C语言。
Objective-C只是C语言的扩展,关键是它是面向对象的。所以要养成良好的面向对象的程序设计风格。
“一图胜千言,没图说个JB”,学习计算机语言也是这样的,
强烈建议运行每一个程序(必须一个字母一个字母的敲一遍)。
语言,只是工具,但这工具背后的属于自己的一套原生态思想以及开发环境,还有应用场景,
是没有孰好孰坏之分的,思想才是值得我们去探究的。
分清场合和用途,选择一种适合的语言,这样才能事半功倍。
整理电脑的时候,在一个“隐藏”的目录下无意中翻到--“Programing in Objective-C”;
打开第一页,深深着迷。
真是“不疯魔不成活”。
前几天看了“超体”,原来人的大脑如此强大!
所以说:
闻道有先后,术业有专攻。
没有什么学不会的问题,只有学的快慢和深浅,应用的娴熟和高超等个体差异而已。
古今之成大事业、大学问者,必经过三种之境界:
“昨夜西风凋碧树。独上高楼,望尽天涯路。”此第一境也。
“衣带渐宽终不悔,为伊消得人憔悴。”此第二境也。
“众里寻他千百度,蓦然回首,那人却在,灯火阑珊处。”此第三境界。
让我们一起踏上属于自己的Objective-C成魔之路。。。