《企业级ios应用开发实战》一3.2 面向对象的C

简介: 本节书摘来自华章出版社《企业级ios应用开发实战》一 书中的第3章,第3.2节,作者:杨宏焱,更多章节内容可以访问云栖社区“华章计算机”公众号查看。

3.2 面向对象的C

从现在开始,我们开始介绍期待已久的Objective-C的面向对象特性。

3.2.1 类和对象

面向对象最重要的概念就是类。通过类,我们可以实现面向对象的两个主要特性:继承和聚合。
在Cocoa框架中,NSObject是所有类的根类,其他所有类从此开始继承。
1.类的定义
类的定义在接口.h文件中进行,典型的类定义如下面的代码所示:

@interface MyClass:NSObject
{
    NSString name;
    NSArray array;
    ...
}
@property(nonatomic,retain)NSString name;
@property(nonatomic,retain)NSArray array;
-(id)initWithName:(NSString)string;
...
@end

从上面的例子中,我们可以看出以下几点:
类的定义由@interface开始,到@end结束;
类名后面紧跟冒号和所继承的父类名;
花括号中定义类的成员变量;
属性由@property关键字声明;
方法声明位于成员声明之后,@end之前。
2.方法
方法类似于函数,虽然形式上有区别,但仍然由返回值、方法名和参数列表构成。其中,返回值和参数的类型说明使用圆括号括住,方法名分为多个部分,有几个参数,方法名就被分成几个部分,每个部分都有一个冒号分隔,冒号后面才是参数类型和参数名。此外,方法一般由一个方法类型符(+号或者号)修饰,+号表示方法为类方法,号表示方法为实例方法。例如:

-(id)initWithName:(NSString)string withArray:(NSArray)arr;

方法开头的号表示这是一个实例方法。实例方法和类方法不同,实例方法属于实例对象所拥有,必须通过类的实例来调用;而类方法属于类所有,它直接通过类来调用,甚至不需要创建实例对象(等同于C++/Java中的static方法)。
这个方法有两个参数(数一数参数列表中冒号的个数,一个冒号代表一个参数),因此方法名由两部分组成,两个带冒号的单词:initWithName:和withArray:。
记住,方法名实际上由“方法名+参数名”两部分构成。而参数名是后面带冒号的单词,如果该方法没有参数,则方法名仅包含第1部分(即只有方法名,而没有参数名)。而且方法的第1个参数的参数名就是方法名。因此有多少个参数,方法名就会由多少个带冒号的单词组成。记住这一点,因为这跟现存的许多语言都不一样。在Objective-C中,总是用initWithName: withArray: 这样的形式表示一个方法名,而不是像Java一样用init来表示方法名。
Objective-C吸收了C和C++的一些令人称道的优点,比如可变参数。如果你在使用方法中总是拿不定总共需要多少个参数,那么你可以使用可变参数,例如NSString的stringWithFormat:方法:
+(id)stringWithFormat: (NSString)format, ...;
这个方法的第一个参数是NSString类型的参数format,但第二个参数就是一个可变参数。这样的参数不仅没有确切的参数数目,而且也无法得知其具体类型。对于stringWithFormat:方法而言,这些不确定因素只能通过已确定的参数format来确定。因此这个方法需要通过format字符串中的%@、%d等字符串来确定第2个可变参数的数目和类型。如果可变参数的类型和数目与format参数中的格式字符串不相匹配,stringWithFormat:方法将无法正常工作。这样的例子还有Objective-C程序中经常用到的NSLog函数。
3.属性
属性用于封装对成员变量的访问,是面向对象语言中的一个重要特性,Objective-C也不例外。也许你已经在C++和Java中熟悉了属性的概念,那么我们不再对此进行过多强调。
在将实例变量声明为属性的过程中,首先需要声明实例变量,然后使用@property关键字。例如前面的代码中,首先用:

NSString name;

声明了实例变量name,然后用@property将name声明为属性:

@property(nonatomic,retain)NSString name;

提示:实例变量的声明不是必须的。如果省略实例变量声明的话,编译器会自动提供一个与属性名同名的实例变量。
然后在.m文件的implementation语句后使用@synthesize<属性名>;语句。
当你这样做完之后,编译器会自动声明变量name的get、set方法,get方法就是变量名,而set方法是单词set加上首字母被大写的变量名,例如:

-(NSString) name;
-(void)setName:(NSString)newValue;

虽然这些声明在源代码中不可见,但确实是存在的,由编译器在编译时产生。
在其中比较复杂的部分是nonatomic和retain关键字的真实含义。在声明属性时,我们可以在@property后面的圆括号中使用多个关键字修饰属性,以指定属性的可访问性、线程管理、内存管理等。由编译器根据这些修饰词产生不同的get、set方法。
nonatomic关键字的意思是非原子的,意指对属性进行存取操作时是线程不安全的,如果在多线程环境下,该属性很可能是不同步的,一个线程读取属性值时,另一个属性却修改了属性值,这样两个线程对同一个属性进行操作的情况下,属性的值是不一致的。我们在属性中使用nonatomic的原因是,该属性不会在多线程环境下使用,使用非原子特性能得到较好的性能。而在iOS编程中,性能始终是程序员首先要考虑的问题。
retain关键字是我们用得最多的属性修饰符之一,它和属性的内存管理有关。这样在对这个属性进行赋值操作时,在编译器自动生成(如果你使用了@synthesize关键字的话)的Set方法代码中,会对实例变量进行retain操作。对于Objective-C对象类型的实例变量而言,使用retain操作使得属性在赋值后一直到对象被销毁之前始终可用。如果实例变量或属性并不是Objective-C对象类型,而是一个简单类型,如BOOL、int、id、float,则用asign关键字替换retain关键字。这样,属性在赋值时不会被持有,这样导致的直接后果是:刚对一个属性赋值后,再访问这个属性,这个属性就变成空了。关于Objective-C的内存管理,更多内容会在后面介绍。
属性还可以用readonly、readwrite进行修饰,分别用于创建只读的属性和可读可写属性。
属性声明之后,接下来就是在实现文件(.m文件)中实现属性的get、set方法。这些方法你可以选择自己去实现,也可以让Objective-C替你实现,只需要使用@synthesize或者@dynamic编译器指令。
也许你已经习惯书写传统C++和Java的 get、set方法,虽然实际上那是一件相当枯燥的事情。在Objective-C中,你完全不需要这样做,除非你真的需要。在实现的.m文件中,使用@synthesize关键字,可以自动产生属性的get、set方法代码。而这一切不是在源代码中进行的,而是编译器在编译时产生的,因此你不会在源代码中看到自动产生的代码。@dynamic指令则是在运行时才产生这些代码。不管使用@synthesize还是@dynamic,这些自动产生的代码均不可见,但它们是存在的。

3.2.2 消息机制

消息是Objective-C区别于其他语言的最大的地方,它其实是Objective-C特有的方法调用语法。同C++和Java的方法调用一样:
1)消息能够接受参数;
2)消息可以嵌套调用;
3)消息既可以发送Objective-C对象,也可以发送给类。
1.消息的参数
一个没有参数的消息是这样的:

[object methodWithoutParameter];

如果要调用的方法带有参数,则这个消息是这样的:

[object methodWithOneParameter: value];

注意,当方法没有参数时,其方法名后没有冒号。当方法有多个参数时,其方法名如我们前面所述,会有多个带冒号的参数:

[object methodWithFirstParameter:value1 withSecondParameter:value2];

2.消息的嵌套
当要进行一个嵌套的方法调用时,会使用嵌套消息:

[textView setTextColor:[UIColor whiteColor]];

内层的消息[UIColor whiteColor]首先调用并返回一个UIColor,然后将这个临时的UIColor对象作为外层消息的参数传入。
即第1个消息的输出作为第2个消息的输入。
3.接收者
如前面所举的例子中,消息的第一个元素object和textView都是消息的接收者,即我们要将消息发给的对象。
除了接收者可以是对象以外,接收者还可以是一个类。因为在方法的种类中,不仅仅有实例方法,还有类方法。使用类作为接收者,就能调用类方法:

NSMutableString mstring=[NSMutableString stringWithString:@””];

3.2.3 Objective-C 的内存管理

内存管理是资源管理的重要部分,在iPhone中尤其如此(因为其内存有限)。相对于Java语言的垃圾回收机制而言,Cocoa的内存管理相当粗糙。这意味着对Objective-C程序员而言,需要做更多的工作,即使是相当有经验的程序员,在这方面也很难不犯错,尤其是不使用ARC的情况下。
本书不讲述ARC(即iOS SDK 5.0以后的自动引用计数),因为在iOS企业应用中会大量使用第三方框架,这些框架大部分不使用ARC,这给我们在项目中应用ARC带来许多困难——要么你要单独设置每个.m文件的ARC选项,要么你得放弃使用该框架,这在很多时候给我们带来困惑。因此,本书不推荐使用ARC。
1.对象的生命周期
每个对象都有生命周期,程序员要知道一个对象的生命周期是什么,就需要仔细回忆一下这个对象是什么时候创建的。每当对象创建出来,它的生命就已经开始了,一直到操作系统释放了该对象,对象的生命才结束。但操作系统怎么知道一个对象应该释放了呢?Objective-C采用了两种内存管理模式:基于计数器的内存管理和基于自动释放池的内存管理。这两种内存管理模式会采用不同的方式来通知操作系统,何时应该释放一个内存对象。此外,Objective-C 2.0以后也支持基于垃圾回收的内存管理,但垃圾回收只限于Mac,而无法在iOS中使用。
基于计数器的内存管理和基于自动释放池的内存管理我们在下面介绍。而基于垃圾回收的内存管理则不属于本书讨论的范围。
2.基于计数器的内存管理
在这种模式下,iOS对内存对象的生命周期管理是通过引用计数来进行的。每个对象都有一个引用计数器,它记录了对象被使用的情况。如果计数器的值为0,表示该对象不再被使用,对象的dealloc方法被调用, dealloc方法是对象的销毁方法,于是对象被销毁。
要知道一个对象的计数器值是多少,可以发送retainCount方法:

NSLog(@”%d”,[object retainCount]);

当使用alloc、copy、new三种方法之中的任一种方法创建对象时,对象计数器会被自动设置为1。此外,如果向对象发送retain消息,对象计数器会自动加1。而向对象发送release消息,对象计数器会自动减1。
因此,在使用计数器情况下的内存管理应当遵循以下原则:
alloc、copy或者new总是和release配套使用。如果你通过alloc、copy或者new方法创建了对象,那么必然跟随着一个release。如果这个对象不是使用这3种方法之一创建的,则不需要一个配套的release。
retain总是和release配套使用。如果对象创建后发送了1个retain消息,则需要配套1个release消息。
release的使用时机。对于临时对象,如果使用过alloc、copy、new以及retain之后,当不再使用该临时对象时,就可以使用release消息。如果该对象不是临时对象,而是类的成员,则在类的dealloc方法中release对象。
对于不是使用alloc、copy、new方法创建的对象,如果该对象是一个临时对象,则不要对该对象进行retain操作,也不要对该对象进行release;如果该对象是类的成员,则需要在获取该对象时retain,在dealloc方法中release。
3.基于自动释放池的内存管理
如前面讲述,使用基于计数器的内存管理十分麻烦,程序员必须对自己创建的每一个对象是否使用了alloc、copy、new、retain方法了如指掌,同时依据一系列规则适时地并在恰当的地方使用release消息。而且有时候要明白一个对象什么时候不再使用并不是一件容易的事情。
如果你不想使用对象计数器进行内存管理,则自动释放池是你的另外一个选择。
Cocoa引入了自动释放池(autorelease pool)的概念。自动释放池实际是一个对象集合或对象容器的概念。如果你把所有的对象在创建时,都放到这个池中,则自动释放池被销毁时,池中的对象都会接收到release消息。
要把对象加入到自动释放池中,只需在创建对象的同时调用autorelease方法。该方法由NSObject提供:

-(id) autorelease;

例如下面的代码,当对象string一创建就被放到了自动释放池里:

NSString string=[[[NSString alloc]initWithString:@”I am a string”] autorelease];

那么,这个自动释放池是什么时候创建的?我们并没有创建它,它是Foundation框架自动为我们创建的。当我们使用Xcode新建任何一个iPhone应用程序时,Foundation框架创建的模板代码中总是有这样的代码,你可以在应用程序委托的实现文件中找到它:

NSAutoreleasePool pool;
pool=[[NSAutoreleasePool alloc]init];
...
[pool release];

其实我们的HelloWorld程序中也有类似的代码。原来,应用程序一开始就创建了这个自动释放池,并立即成为当前活动的池。我们使用对象的autorelease消息就是把对象放入这个池中。当程序退出时,最后的[pool release]或[pool drain]代码会向池中对象发送release消息。
这样,对象会从创建开始,一直存在到应用程序结束。这样无疑会造成一定的内存浪费,整个应用程序运行期间,随着对象的不断创建,内存只见增加不见减少,容易造成内存资源的紧张。很显然,并不适合内存使用率较高的应用程序。
本书的建议,程序员主要使用基于计数器的内存管理模式,自动释放池模式的使用则适可而止。
提示:[pool release]方法适用于Mac OS的所有版本,而[pool drain]方法只适用于Mac OS 10.4(Tiger)以上版本。

3.2.4 类别和协议

1.类别
类别是另一种为现有类添加新行为的方法。不同于子类,类别实际上是Objective-C的一种动态行为,它利用了运行时分配机制。因此,类别甚至不需要拥有原类的源代码。此外,类别不能向现有的类中添加实例变量。
要声明一个类别,你需要使用类声明时使用的@interface关键字,如:

@interface NSString(NumberConvenience)
-(NSNumber)lengthAsNumber;
@end

注意,这很像是一个类的声明。首先它使用@interface 类名开始,以@end结束。但实际上,NSString的类名加上其后的圆括号,表明这是一个对现有类NSString的补充或扩充。圆括号说明扩充方式为通过类别扩充而非通过继承来扩充。圆括号中的单词是类别的名称,本例中的类别名称是NumberConvenience。
另外,第2行的方法声明语句也很像是NSString的成员方法。但实际上NSString类中根本没有这个方法。这个方法是类别为Cocoa类NSString新增加的。
此外,由于类别不能扩充实例变量这一限制,类别的声明中不会出现用于声明实例变量的{}符号。
类别主要是定义对现有类的扩充方法,因此类别的实现中(.m文件)就需要对这些新方法一一进行实现:

@implementation NSString(NumberConvenience)
-(NSNumber)lengthAsNumber{
    ...
}
@end

这个实现文件跟类的实现文件没有多大区别,除了圆括号中的类别名外。接下来,你可以像这样调用这个新方法lengthAsNumber(假设NumberConvenience类别声明是定义在NSStringCategory.h头文件里):

#import “NSStringCategory.h”
...
NSNumber number=[@”hello” lengthAsNumber];
NSLog(@”length by NSNumber:%d”, [number intValue]);

就好像lengthAsNumber本来就是NSString中已经存在的方法一样。
类别有许多好处,比如把类的实现分散在多个implementation文件里(如果使用类,你做不到这点,这就好像C#中的分部类),或者用于创建非正式协议(如下面有关协议的内容所述)。
但是,类别仍然有一些限制,在类别的使用中一定要注意以下两点:
类别仅仅是一堆方法的集合,你无法为类添加实例变量。
类别不能解决命名冲突,如果类别中的方法与类原有的方法重名,则类中的方法被覆盖。
2.协议
Objective-C的协议等同于Java中接口的概念。下面我们来讨论协议的声明:

@protocol NSCopying
-(id)copyWithZone: (NSZone)zone;
...
@end

协议看起来就像类别的声明,还是一堆方法声明的集合。但@protocol关键字的出现说明了这是一份正式协议。
正式协议的意思是,每个采用(Java中用实现implements这个词)这份协议的类必须实现这份协议中的所有方法。这种说法一直持续到Objective-C 2.0。
从Objective-C 2.0开始,协议中的方法可以有选择地由类实现。对于需要实现的方法,使用@required关键字修饰,对于可选择性地实现的方法,使用@optional关键字修饰,比如:

@protocol TheProtocol
@required
-(void)firstMethod;
-(void)secondMethod;
@optional
-(void)thirdMethod;
@end

这个协议规定,第1、2个方法是必须实现的,而第3个方法可以实现,但不要求一定实现。
如果一个类要采用(或实现)这个协议,则需要在@interface中这样声明:

@interface MyClass:NSobject<TheProtocol>

而MyClass类的实现中,则应当实现该协议规定的2个必选方法及1个可选的方法。
除了正式协议外,我们也可以采用非正式协议。非正式协议不需要采用@protocol关键字声明,但需要创建一个类别,例如MyCategory:

@interface NSObject(MyCategory)
-(void)doSomething;
...
@end

由于NSObject是所有Cocoa类的根类,这个类别实际上指明了任何类都可以实现这些方法。从而可以向任何对象发送这些消息,而不需要在类的声明中做任何特别的说明。

3.2.5 反射机制

同其他高级语言一样,Objective-C也提供了运行时支持——即Java所谓的反射机制,这充分体现了Objective-C的动态性特征。尤其在Mac OS X 10.5以后,苹果对Objective-C运行时API进行了重要的升级,以提供对64位模式的支持。
Objective-C运行时API包含了众多函数和结构体的定义,然而,我们不准备在此介绍庞大的Objective-C 运行时库。如果要全面了解这些API,请翻阅苹果运行时库参考。
通常,我们不需要直接调用Objective-C运行时API,Cocoa框架已经把它的许多有用函数进行了封装,从而使我们更容易调用。
1.获取类信息
首先,我们知道NSArray等集合对象中不限制所存储的对象类型,只要它是一个NSObject就行。但有时候,我们想知道我们刚刚放进去的一个对象到底是什么类型(程序员总是那么健忘),是一个字符串?还是别的什么?
我们可以发送class消息:

id class=[[array objectAtIndex:0]class];

id类型(即对象)其实是一个objc_object结构:

typedef struct objc_object {
    Class isa;
} id;

NSObject的class方法实际是上返回了这个结构中的isa成员。isa指向了一个Class对象,因此除了发送class消息外,我们也可以使用isa成员来访问Class对象。Class对象指向了该对象所属的类(即“类对象”,Objective-C把类也看成是对象,以贯彻“万物皆对象”的原则)。
Class对象实际上是一个objc_class结构:

typedef struct objc_class Class;
struct objc_class {
    Class isa;
    Class super_class;
    / followed by runtime specific details... /
};

注意:这个结构很象是objc_object,但多了一个指向父类的super_class成员。也就是说,对象跟类的区别仅在于,对象中不保存继承关系,而类(或“类对象”)保存了继承关系。因此我们是通过类而不是对象来追溯类的“父类”、“祖父类”等。
我们有时需要比较一个对象是否属于某个类,可以使用类似的代码:

if (obj->isa ==[NSString class]){
    ...
}

而super_class成员指向了父类对象,通过它我们可以访问父类的信息。
在Cocoa中还有一个“元类”的概念,即“类对象的类”。因此完整的类信息称为“类对”,即由“类对象”和“元类”构成。
如果向一个对象发送class方法,我们可以得到“类对象”,而如果再向“类对象”发送class消息,则返回的就是“元类”(meta class)。每个类只能有一个元类,其包含了类方法列表。而类对象不同,它包含了对象的方法列表。
2.选择器
选择器实际上是一个方法名称,用@selector关键字来指定一个选择器,选择器用于查询对象的某个方法,例如:

@selector(setResponse:)

通过NSObject的responseToSelector方法,我们可以动态地查询某个对象是否存在指定的方法。该方法需要指定一个选择器参数:

if([object responseToSelector:@selector(setResponse:)]){
    ...
}

如果object对象能够响应setResponse:方法,则返回YES,否则返回NO。
如果确定某对象能响应指定方法,则可以通过performSelector:方法进行调用:

[anObject performSelector:@selector(method)];

如果该方法带有参数,则使用performSelector: withObject:方法传递参数:

[anObject performSelector:@selecor(method) withObject:obj];

如果有2个参数,则使用performSelector: withObject: withObject:。更多的参数或者有返回值,则使用NSInvocation。
除了向对象发送 performSelector 消息之外,Objective-C还提供了objc_msgSend 函数,你可以用它向任何对象发送一条消息:
objc_msgSend( anObject,@selector(method),obj);
要使用objc_msgSend 函数,需要导入头文件,其完整定义如下:

id objc_msgSend(id theReceiver, SEL theSelector, ...)

函数的第1个参数指向消息的接收者(即该方法的对象),第2个参数是一个选择器(即方法),第3个参数是一个可变参数,是该方法的1个或多个参数,如果该方法没有参数,用一个nil代替。
方法的返回值通过函数的返回值返回。
3.类的动态创建
要在代码中创建类,而不是通过静态的.h和.m文件定义类,可以使用Object C运行时库API(需要#include ):

Class newClz=objc_allocateClassPair([NSError class], "RuntimeErrorSubclass", 0);
class_addMethod(newClz, @selector(report), (IMP)ReportFunction, "v@:");
objc_registerClassPair(newClz);

首先,使用objc_allocateClassPair动态函数创建了一个类,并在参数中指明该类的父类和类名。用class_addMethod函数为该类增加了一个方法report,这个方法是由函数ReportFunction实现的,由于该函数至少应包含两个参数self和_cmd,因此定义了该方法有3个参数,类型分别为v、@、:(一个返回值,self,_cmd)。
v代表void,指定了方法的返回值;@代表了id类型(对象),指定了方法的固定参数self;: 表示选择器类型(SEL),指定了固定参数_cmd。因此函数ReportFunction应当实现为:

void ReportFunction(id self, SEL _cmd)
{
    // 实现代码
}

最后,新类被注册为类对(Class Pair)。
类对注册后,即可在代码中这样使用类newClz:

id obj =[[newClz alloc] init];
[obj performSelector:@selector(report)];
[obj release]; 

4.类的动态加载
Cocoa的Foundation框架提供的NSClassFromString函数类似于Java的Class.forName()方法:

Class clz=NSClassFromString(@"MyClass");

返回对象为“类对象”Class。通过这种方法,我们从字符串构建类实例就不再是什么问题:

id obj=[[clz alloc]init]
或者
id obj=[[NSClassFromString(@”MyClass”) alloc]init];

这使得我们可以在运行时而不是编译时加载类,同时,不需要#import “MyClass”。
5.方法的动态调用
通过@selector关键字我们已经可以在一定程度上实现方法的动态调用。然而更动态的方式是通过Fundation框架的NSSelectorFromString函数,它可以直接从字符串获得一个选择器:

SEL sel = NSSelectorFromString(@"doSomethingMethod:") 
if([object respondsToSelector:sel]) { 
    [object performSelector:sel withObject:color]; 
} 

3.2.6 谓词

Cocoa提供NSPredicate类,用于描述条件和计算对象是否匹配指定条件。类似于SQL语句,通过NSPredicate,可以将条件的计算从代码中分离出来,从而在比较对象时避免使用硬编码。
1.创建谓词
首先需要创建一个NSPredicate对象:

NSPredicate predicate=[NSPredicate redicateWithFormat:@”name==’Herbie’”];

类方法predicateWithFormat常用于创建一个NSPredicate对象。仅需要提供一个代表计算条件的字符串。字符串@"name=='Herbie'"代表了需要进行计算的C条件表达式。事实上,除了==运算符外,谓词的条件表达式支持括号和C语言的比较运算符和逻辑运算符。
下面我们来看看谓词是如何进行计算的。
2.谓词的计算
要对谓词进行计算,需要将谓词应用在某个对象身上。例如:

BOOL match=[predicate evaluateWithObject: car];

谓词predicate的evaluateWithObject方法实际上对car对象进行了计算,并返回BOOL值YES或NO。前面的条件表达式@"name=='Herbie'",表明将用car的name属性与字符串Herbie进行比较,如果相等,则返回YES,否则返回NO。
谓词可以用任何对象作为参数进行计算,包括数组或集合。如果persons是一个包含了多个person实例的NSArray数组,那么我们可以用谓词计算或过滤出其中gender(性别)属性为男性的对象:

NSPredicate predicate=[NSPredicate predicateWithFormat: @”gender==’M’”];
NSArray result=[persons filteredArrayUsingPredicate: predicate];

谓词将对persons中的所有对象进行条件计算,把gender等于M的对象加到结果集数组中返回。
3.在条件中使用变量
有时候,在构建谓词时,使用变量组成条件表达式将更方便。这就类似于JDBC中的预编译语句,Where条件子句中的表达式可以使用可变参数。只需在SQL语句中使用问号?来代表可变参数即可。这样的好处是显而易见的,我们就可以重用预编译语句,节省数据库编译上SQL语句的时间。我们只需在执行预编译语句时,提供不同的可变参数值,即可执行不同的SQL查询。
同样,我们也可以在条件表达式中使用格式化字符串或占位符,来作为条件表达式中的可变部分。例如:

NSPredicate predicate=[NSPredicate predicateWithFormat:@”name==%@”,@”Herbie”];

这和原来没有任何区别。占位符%@表示这里将被一个对象(id类型)所替换。如果想在表达式左边使用变量,可以使用%K作为占位符,例如:

predicate=[NSPredicate predicateWithFormat:@”%K==%@”,@”name”,@”Herbie”];

这与前面一条语句是等效的。
提示:%K中的字母K代表的是keypath(键路径)。在后面介绍。
除此之外,我们还可以使用NSLog中使用的各种占位符,如%d等。当然,不使用占位符,还可以使用命名的键-值对(NSDictionary)作为条件变量。例如:

predicate=[NSPredicate predicateWithFormat:@”name==$NAME”];

这里$NAME就代表了一个变量(而不是对象),这个变量值未指定。如果要指定这个变量的值,需要在NSDictionary中加入一个Key为Name的对象,同时用这个NSDictionary (Cocoa将这个NSDictionary称为环境变量SubstitutionVariances)创建新的predicate:

NSPredicate* predicate=[NSPredicate predicateWithFormat: @"name=$NAME"];
predicate=[predicate predicateWithSubstitutionVariances: dic];

然后使用新的predicate去进行计算。也就是说,通过这种方式,我们提交一个NSDictionary作为谓词变量。
4.BETWEEN和IN
BETWEEN运算符用于表示位于某个区间中的取值。例如:

predicate=[NSPredicate predicateWithFormat:@”age BETWEEN{16,50}”];

该条件表达式表示age属性值在16到50之间的对象。
IN用于测试某个值处于一个集合范围内的情况。例如:

predicate=[NSPredicate predicateWithFormat:
    @”name IN {‘Herbie’,’Elvis’,’Phoenix’}”];

该条件表达式表示name属性等于这三者之一的对象。
5.字符串匹配
谓词常用于判断字符串的匹配,除使用比较运算符外,谓词表达式还支持多种对字符串进行计算的运算符:
BEGINSWITH用于判断一个字符串的开头是否包含另一个字符串。
ENDSWITH用于判断一个字符串的结尾是否包含另一个字符串。
CONTAINS用于判断一个字符串中是否包含另一个字符串。
LIKE运算符用于进行字符串的匹配。如同SQL语法一样,谓词表达式中的LIKE运算符支持两种通配符:和?,号匹配任意个数的任意字符,?号匹配1个任意字符。
此外,[c]选项用于表示忽略大小写,[d]选项用于表示忽略发音符号,[cd]选项用于表示同时忽略大小写和发音符号。例如:

predicate=[NSPredicate predicateWithFormat: @”name BEGINSWITH[cd] ‘HER’”];

提示:如果你想在谓词表达式中使用正则式,则需要使用MATCHES运算符。
6.KeyPath和SELF
Cocoa中经常引用KeyPath的概念。键路径KeyPath实际上和文件系统中路径概念无关,KeyPath是面向对象语言如Java中“.”语法的概念,比如:

indexPath.row

这就是一个KeyPath。
用前面的例子,我们想匹配persons数组中名称长度超过10的对象,那么在谓词表达式中不能使用name作为主语了(假设我们把谓词表达式分为“主语+运算符+宾语”结构,位于运算符左边的操作数是主语,而位于运算符右边的操作数就是宾语)。因为name(具体地说,指数组中某个对象的name属性)只是一个字符串,它的长度应该用name.length表示。如前面所说,name.length就是一个KeyPath,用它作为表达式的主语是恰当的。那么,这个谓词应该这样构建:

predicate=[NSPredicate predicateWithFormat:@”name.length > 10”];

然后再对persons数组中的每个对象进行计算:

NSArray result=[persons filteredArrayUsingPredicate: predicate];

其实KeyPath“name.length”还可以是“SELF.name.length”,这二者是完全等效的。SELF用来表示应用了谓词的对象。SELF经常可以省略,但在一种情况下不能被省略。让我们来假设这样的情况:

predicate=[NSPredicate predicateWithFormat:@”SELF ==@’Herbie’];
BOOL match=[predicate evaluateWithObject:@”Rubby”];

先看第2句,我们把谓词应用到一个字符串对象@"Rubby"。那么第1句中的SELF就不能省略了。该谓词的KeyPath使用SELF,表示我们要用它(而不是它的属性)直接和另一个字符串@"Herbie"进行比较,这是可以理解的。在这种情况下,你不能省略SELF,否则谓词表达式就不完整了,因为它缺乏了“主语”这个关键的语法元素。

相关文章
|
1月前
|
开发框架 前端开发 Android开发
Flutter 与原生模块(Android 和 iOS)之间的通信机制,包括方法调用、事件传递等,分析了通信的必要性、主要方式、数据传递、性能优化及错误处理,并通过实际案例展示了其应用效果,展望了未来的发展趋势
本文深入探讨了 Flutter 与原生模块(Android 和 iOS)之间的通信机制,包括方法调用、事件传递等,分析了通信的必要性、主要方式、数据传递、性能优化及错误处理,并通过实际案例展示了其应用效果,展望了未来的发展趋势。这对于实现高效的跨平台移动应用开发具有重要指导意义。
160 4
|
2月前
|
设计模式 安全 Swift
探索iOS开发:打造你的第一个天气应用
【9月更文挑战第36天】在这篇文章中,我们将一起踏上iOS开发的旅程,从零开始构建一个简单的天气应用。文章将通过通俗易懂的语言,引导你理解iOS开发的基本概念,掌握Swift语言的核心语法,并逐步实现一个具有实际功能的天气应用。我们将遵循“学中做,做中学”的原则,让理论知识和实践操作紧密结合,确保学习过程既高效又有趣。无论你是编程新手还是希望拓展技能的开发者,这篇文章都将为你打开一扇通往iOS开发世界的大门。
|
2月前
|
搜索推荐 IDE API
打造个性化天气应用:iOS开发之旅
【9月更文挑战第35天】在这篇文章中,我们将一起踏上iOS开发的旅程,通过创建一个个性化的天气应用来探索Swift编程语言的魅力和iOS平台的强大功能。无论你是编程新手还是希望扩展你的技能集,这个项目都将为你提供实战经验,帮助你理解从构思到实现一个应用的全过程。让我们开始吧,构建你自己的天气应用,探索更多可能!
77 1
|
1月前
|
安全 Swift iOS开发
Swift 与 UIKit 在 iOS 应用界面开发中的关键技术和实践方法
本文深入探讨了 Swift 与 UIKit 在 iOS 应用界面开发中的关键技术和实践方法。Swift 以其简洁、高效和类型安全的特点,结合 UIKit 丰富的组件和功能,为开发者提供了强大的工具。文章从 Swift 的语法优势、类型安全、编程模型以及与 UIKit 的集成,到 UIKit 的主要组件和功能,再到构建界面的实践技巧和实际案例分析,全面介绍了如何利用这些技术创建高质量的用户界面。
33 2
|
1月前
|
JSON 前端开发 API
探索iOS开发之旅:打造你的第一个天气应用
【10月更文挑战第36天】在这篇文章中,我们将踏上一段激动人心的旅程,一起构建属于我们自己的iOS天气应用。通过这个实战项目,你将学习到如何从零开始搭建一个iOS应用,掌握基本的用户界面设计、网络请求处理以及数据解析等核心技能。无论你是编程新手还是希望扩展你的iOS开发技能,这个项目都将为你提供宝贵的实践经验。准备好了吗?让我们开始吧!
|
1月前
|
Swift iOS开发 UED
如何使用Swift和UIKit在iOS应用中实现自定义按钮动画
本文通过一个具体案例,介绍如何使用Swift和UIKit在iOS应用中实现自定义按钮动画。当用户点击按钮时,按钮将从圆形变为椭圆形,颜色从蓝色渐变到绿色;释放按钮时,动画以相反方式恢复。通过UIView的动画方法和弹簧动画效果,实现平滑自然的过渡。
59 1
|
2月前
|
Swift iOS开发 UED
如何使用Swift和UIKit在iOS应用中实现自定义按钮动画
【10月更文挑战第18天】本文通过一个具体案例,介绍如何使用Swift和UIKit在iOS应用中实现自定义按钮动画。当用户按下按钮时,按钮将从圆形变为椭圆形并从蓝色渐变为绿色;释放按钮时,动画恢复原状。通过UIView的动画方法和弹簧动画效果,实现平滑自然的动画过渡。
62 5
|
3月前
|
存储 IDE 开发工具
移动应用开发之旅:打造你的首个iOS应用
【9月更文挑战第23天】在数字化浪潮中,移动应用已成为连接用户与数字世界的关键桥梁。本文将带领读者踏上开发属于自己的第一个iOS移动应用的旅程,从理解移动操作系统的核心概念出发,逐步深入到实际的应用构建过程中。通过简洁明了的语言和具体的代码示例,我们将一起探索如何在苹果的iOS平台上实现一个简单的“待办事项列表”应用,让读者不仅能够学习到编程知识,还能体会到将想法转化为现实产品的成就感。无论你是编程新手还是希望扩展技能的开发者,这篇文章都将为你提供一个实用的指南,帮助你迈出成为移动应用开发者的第一步。
|
3月前
|
开发框架 Android开发 iOS开发
探索安卓与iOS开发的差异:构建未来应用的指南
在移动应用开发的广阔天地中,安卓与iOS两大平台各占半壁江山。本文将深入浅出地对比这两大操作系统的开发环境、工具和用户体验设计,揭示它们在编程语言、开发工具以及市场定位上的根本差异。我们将从开发者的视角出发,逐步剖析如何根据项目需求和目标受众选择适合的平台,同时探讨跨平台开发框架的利与弊,为那些立志于打造下一个热门应用的开发者提供一份实用的指南。
71 5
|
3月前
|
开发工具 Android开发 iOS开发
探索安卓与iOS开发的差异:构建未来应用的关键考量
在数字时代的浪潮中,安卓和iOS这两大操作系统如同双子星座般耀眼夺目,引领着移动应用的潮流。它们各自拥有独特的魅力和深厚的用户基础,为开发者提供了广阔的舞台。然而,正如每枚硬币都有两面,安卓与iOS在开发过程中也展现出了截然不同的特性。本文将深入剖析这两者在开发环境、编程语言、用户体验设计等方面的显著差异,并探讨如何根据目标受众和项目需求做出明智的选择。无论你是初涉移动应用开发的新手,还是寻求拓展技能边界的资深开发者,这篇文章都将为你提供宝贵的见解和实用的建议,帮助你在安卓与iOS的开发之路上更加从容自信地前行。