OC类与对象(下)
文章目录
- OC类与对象(下)
- 不是包装类的
- NSValue 和NSNumber
- 处理对象
- 打印对象和description方法
- == 和 isEqual方法
- 类别
- 类别语法部分
- 利用类别进行模块化设计
- 使用类别来调用私有方法
- 类的扩展
- 协议与委托
- 规范,协议与接口
- 使用类别实现非正式协议
- 正式协议的定义
- 遵守协议
- 正式与非正式的差异
- 协议与委托
不是包装类的
- NSInterger: 大致相当于long型整数
- NSUInterger: 大致相当于 unsigned long型整数
- CGFLoat:在64位平台上相当于double
它们仍然是一个基本类型。
NSValue 和NSNumber
这两者都是一个包装类。NSValue是NSNumber的一个父类,它可以用于包装单个short,int,long,指针,对象id等数据项,通过这些包装类,可以把它添加到NSArray等集合中
NSNumber是更具体的包装类,主要用于包装C语言的各种数值类型,下面主要介绍三类方法
- +numberWithXxx:这个类方法直接将特定类型的值包装成NSNumber。
- -initWithXxx:这个实例方法先要创建一个NSNumber对象,再用一个基本类型值来初始化NSnumber
- -xxxValue:这个实例方法返回该NSNumber对象包装的基本类型的值。
下面给出一段代码,下面是用不同的方法来对基本类型进行一个包装
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSNumber* num1 = [NSNumber numberWithInt:32];
NSNumber* num2 = [NSNumber numberWithDouble:3.14];
NSLog(@"%d", [num1 intValue]);
NSLog(@"%g", [num2 doubleValue]);
NSNumber* ch = [[NSNumber alloc] initWithChar:'A'];
NSLog(@"%@", ch); // 用来输出
NSLog(@"%c", [ch charValue]); //用来输出字符
}
return 0;
}
处理对象
打印对象和description方法
OC中的对象都是NSObject子类的实例,都可以直接调用该类中定义的方法,这些方法提供了处理OC对象的通用方法。
我们先定义个类
接口部分
@interface FKPerson : NSObject {
@private
int _age;
NSString* _name;
}
@property (nonatomic) int age;
@property (nonatomic) NSString* name;
- (id) initWithName: (NSString*) name andAge: (int) age;
@end
实现部分
@implementation FKPerson
@synthesize age = _age;
@synthesize name = _name;
- (id) initWithName:(NSString *)name andAge:(int)age {
if (self = [super init]) {
self.name = name;
self.age = age;
}
return self;
}
@end
主函数部分
int main(int argc, const char * argv[]) {
@autoreleasepool {
FKPerson* p1 = [[FKPerson alloc] initWithName:@"messi" andAge: 30];
NSLog(@"%@", [p1 description]); //打印一个对象
}
return 0;
}
打印结果为:
这个结果会随着每一次运行而改变,这个description方法是NSObject类的一个实例方法,所有对象具有description方法。这个类提供的description方法总是返回**<FKPerson: 16进制的首地址>**,这个返回值并不能实现一个自我描述的功能,所以我们大部分时候按下面的格式重写如下格式的字符串:
<类名 [实例变量 1 = 值 1, 实例变量 2 = 值 2]>
下面我们将对这个description方法进行一个重写,让他可以描述自己的一些特性。
- (NSString*) description {
return [NSString stringWithFormat:@"<FKPerson[_name = %@, _age = %d]>", self.name, self.age];
}
然后在运行我们的主函数
int main(int argc, const char * argv[]) {
@autoreleasepool {
FKPerson* p1 = [[FKPerson alloc] initWithName:@"messi" andAge: 30];
NSLog(@"%@", p1); //直接对对象进行一个打印
}
return 0;
}
这样我们就可以实现一个对于对象的一个打印
== 和 isEqual方法
我们用来判断两个变量是否相等时,如果两个变亮是数值类型的数值相等(并不要求数据类型严格),如果是指针类型的话,那么就是判断两者的内存值是否相同,当使用比较类型snag没有继承关洗的两个指针变量的时候,编译器会提示警告。下面程序示例使用==比较类型变量是否相等的结果。
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSInteger it = 65;
CGFloat it1 = 65.0;
char ch = 'A';
NSLog(@"%d", it == ch); //返回1
NSLog(@"%d", it == it1); // 返回1
NSString* str3 = @"hello"; //常量池
NSString* str4 = @"hello";
NSString* str1 = [NSString stringWithFormat:@"hello"];
NSString* str2 = [NSString stringWithFormat:@"hello"];
NSString* str5 = [NSString stringWithFormat:@"梅西"];
NSString* str6 = [NSString stringWithFormat:@"梅西"];
NSLog(@"%p %p %p %p", str1, str2, str3, str4);
NSLog(@"%d", (str1 == str2)); // 返回1
NSLog(@"%d", [str1 isEqual: str2]); // 返回1
NSLog(@"%d", (str5 == str6)); // 返回0
NSLog(@"%p %p", str5, str6);
}
return 0;
}
下面是他的一个打印结果
tips:
这里我们发现了个事情就是我们在用stringWithFormat这个函数去写英文字符串的时候,发现它们的地址居然相同,这是因为在英文字符串较短的时候,OC会创建一种 NSTaggedPointerString的类型,这种类型虽然也是在堆区创建一块内存,但是会建立两个值相同的字符串,所以我们创建的str1, str2会指向同一块内存。
但是我们用stringWithFormat方法创建中文字符串或者是创建一个长英文字符串的时候,他还是正常在堆区开辟两块内存来保存相同的字符串,用不同的指针指向不同的内存块。(这时候创建的字符串为一个**_NSCFString类型)。而当我们用一个直接给字符串赋值的方法的话,OC会把他存储在一个常量池**中,然后所有值相同的字符串只会创建一次。
需要注意的是一个isEqual方法在NSObject类中与**==没有任何区别,所以我们在NSString**中间看到的是这个子类重写他的父类的方法,只要两个字符串所包含的字符序列相同,通过isEqual比较就要返回一个真。
我们在大部分时候需要的isEuqal方法一般希望的是两个相同类型的对象中的关键标识符是相等的,才可以认为这两个对象相同。所以我们需要重写isEqual方法才可以实现我们的结果。
下面我们给出一个FKUser类,用下面的程序来测试它。
接口部分
@interface FKUser : NSObject
@property NSString* idstr;
@property NSString* name;
- (BOOL) isEqual: (id) object;
- (id) initWithId: (NSString*) idstr andName: (NSString*) name;
@end
实现部分
@implementation FKUser
@synthesize idstr;
@synthesize name;
- (id) initWithId:(NSString *)idstr andName:(NSString *)name { // 重写初始化方法
if (self = [super init]) {
self.idstr = idstr;
self.name = name;
}
return self;
}
- (BOOL) isEqual: (id) object { // 重写isEqual方法
if (self == object) {
return YES;
} else if (object != nil && [object isMemberOfClass: FKUser.class]) {
FKUser* target = (FKUser*) object;
return [self.idstr isEqual: target.idstr];
}
return NO;
}
@end
主函数部分
int main(int argc, const char * argv[]) {
@autoreleasepool {
id preson = [[FKUser alloc] initWithId:@"10" andName: @"梅西" ];
id p2 = [[FKUser alloc] initWithId:@"10" andName: @"球王"];
NSLog(@"%d", [preson isEqual: p2]);
NSLog(@"%@", [preson description]);
}
return 0;
}
输出结果如下:
这样我们就实现了一个自己重写的isEqual方法。
注意,我们重写的isEqual方法要满足下面的要求
类别
类别语法部分
OC的动态特征允许使用类别为现有的类添加方法,并且不需要创建子类,不需要访问原有类的源代码,通过使用类别即可动态地为现有的类添加新方法,而且可以将类定义模块化的分布到多个相关文件中。
类别同样是接口和实现部分组成,接口部分的语法格式如下:
@interface 已有类 (类别名)
...
@end
这个语法格式和定义类的语法存在如下差异:
- 定义类时使用类名必须时该类中不存在的类,而定义类别时使用的类名必须是已有的类
- 定义类别是必须用圆括号来包括类别名
- 类别中通常只定义方法
实现部分
@implmentation 已有类 (类别名)
...
@end
下面我们给出类别的一个实现,接口部分
@interface NSNumber (fk)
- (NSNumber*) add: (double) num1;
- (NSNumber*) substract: (double) num2;
- (NSNumber*) multiply: (double) num2;
- (NSNumber*) divide: (double) num2;
@end
实现部分
@implementation NSNumber (fk)
- (NSNumber*) add: (double) num1 {
return [NSNumber numberWithDouble: ([self doubleValue] + num1)];
}
- (NSNumber*) substract:(double)num2 {
return [NSNumber numberWithDouble: ([self doubleValue] - num2)];
}
- (NSNumber*) multiply:(double)num2 {
return [NSNumber numberWithDouble: ([self doubleValue] * num2)];
}
- (NSNumber*) divide:(double)num2 {
return [NSNumber numberWithDouble: ([self doubleValue] / num2)];
}
@end
主函数部分
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSNumber* p1 = [NSNumber numberWithInt: 3];
NSNumber* num1 = [p1 add: 2.4];
NSLog(@"%@", num1);
NSNumber* num2 = [p1 substract: 2.4];
NSLog(@"%@", num2);
NSNumber* num3 = [p1 divide: 2.4];
NSLog(@"%@", num3);
}
return 0;
}
利用类别进行模块化设计
类的实现部分不能分布到多个.m文件中去,因此当某个类非常大时,会导致那个类实现所在的文件非常大,以至于维护起来非常困难。因此如果需要将一个较大的类分模块设计,使用类别是一个不错的选择。通过类别可以对类实现按模块分布到不同的*.m文件中,从而提高项目后期的可维护性。
使用类别来调用私有方法
在我们定义类的部分有提到,没有在接口部分定义而是在类的实现部分定义的方法相当于私有方法,通常不允许调用。但后面也说过OC实际上不存在真正的一个私有方法
我们定义一个类的接口部分
类的实现部分,这一块定义两个内容一个是在实现部分定义的,一个是在实现部分定义的方法
我们在主函数里面调用这两个方法
这时候我们调用这个方法,我们发现调用不到这个方法,为了让我们的方法可以在主函数中调用,所以我们需要进行增加一个类别。
@interface FKItem (fk)
- (double) calDiscount: (double) discount;
@end
类的扩展
扩展与类别类似,扩展相当于匿名类别,定义里扩展的语法格式如下
@interafce 已有类 (){ //没有类别名
实例变量
}
//方法定义
@end
扩展和类别的语法类似,但是类的扩展用于临时对于类的莫哥接口进行一个临时扩展,类实现部分同时实现接口部分定义的方法和扩展中的。
tips:定义类的扩展的时候,可以额外增加实例变量,也可以用@property,@synathesize来合成我们的setter,getter方法,但是定义类的类别,但定义类的列表,则不允许额外定义实例变量。
然后我们就编写一下扩展部分的代码
接口部分
@interface FKCar : NSObject
@property (nonatomic, copy) NSString* brand;
@property (nonatomic, copy) NSString* model;
- (void) drive;
@end
扩展部分
@interface FKCar ()
@property (nonatomic, copy) NSString* color;
- (void) drive: (NSString*) owner;
@end
实现部分
@implementation FKCar
@synthesize brand;
@synthesize model;
@synthesize color;
- (void) drive {
NSLog(@"%@汽车飞驰", self);
}
- (void) drive: (NSString *) owner {
NSLog(@"%@开着%@汽车上飞驰", owner, self);
}
- (NSString*) description {
return [NSString stringWithFormat:@"<FKCar[brand = %@, model = %@, color = %@]>", self.brand, self.model, self.color];
}
@end
在上面的程序中间,我们可以调用点语法来访问color属性,也可以调用drive方法。
int main(int argc, const char * argv[]) {
@autoreleasepool {
FKCar* c = [[FKCar alloc] init];
c.color = @"black";
c.model = @"e300L";
c.brand = @"ben-z";
[c drive];
[c drive: @"krystal"];
}
return 0;
}
他的结果如下所示:
协议与委托
规范,协议与接口
类是一种具体实现体,而协议则是定义了一种规范,定义了某一批类所需要遵守的规范。协议不提供任何实现,它体现的是规范和实现分离的设计哲学。让规范和实现分离正是协议的好处,是一种松耦合的设计。
协议定义的是多个类共同的公共行为规范,协议里通常定义的是一组公用方法,但不会为这些方法提供实现,方法的实现则交给类去实现
使用类别实现非正式协议
这正是类别的作用之一:当某个类实现NSObject的该类别时,就需要实现该类别下所有方法,这种基于NSObject定义的类别即可认为是非正式协议。
@interface NSObject (Eatable)
- (void) taste;
@end
这里我们给NSObject提供了一个类别,这个类别可以理解为一个声非正式的一个协议,因为剩余的所有子类都会自动继承这个方法,我们可以根据我们的子类是否需要来决定是否要实现这个方法。
然后我们给他派生一个子类FKApple,(接口部分过于简单就不给出了)。
#import "FKApple.h"
#import "NSObject+Eatable.h"
@implementation FKApple
- (void) taste {
NSLog(@"apple is tasty");
}
@end
这时候我们在主函数中调用taste方法,这样我们就可以理解为他遵守了这个(Eatable)协议。
int main(int argc, const char * argv[]) {
@autoreleasepool {
FKApple* a = [[FKApple alloc] init];
[a taste];
}
return 0;
}
下面是代码的结果。
tips:我们对于非正式协议的类而言,OC中并不强制要求我们要实现该协议中的所有方法,也就是我们的FKApple类可以不实现该方法,但如果FKApple类不实现该方法的话,我们的程序就会报错。
正式协议的定义
和定义类不同,我们不用@interface
这类关键字而是用@protocol
关键字,基本语法如下
@protocol 协议名 <父协议1, 父协议2> {
//方法的定义。
}
上面语法的详细说明如下
-
协议名和类名采用相同的命名规则(遵守我们的一个可读性的规范)
-
一个协议可以有多个直接父协议,但协议只能继承协议,不能继承类
-
协议中定义的方法只有方法签名,但没有方法实现,协议中可以是类方法也可以是实例方法。
-
协议中的所有方法都具有公开的访问权限
下面定义两个协议。
FKOutput
@protocol FKOutput
- (void) output;
- (void) addDate (String mag);
@end
FKPrintable
@protocol FKPrintable
- (NSDate*) getProdouceTime;
@end
上面我们用一个新的协议,该协议同时继承两个上面两个协议。
FKProdutable
@protocol FKProdutable <FKOutput, FKPrintable>
- (NSString*) printColor;
@end
遵守协议
下面我们为了这个FKProdutable协议提供一个实现类:FKPrinter
这个类实现了我们的两个协议的要求。
如果我们没有实现协议中规定的函数那么我们就会报错。
主函数部分我们定义了两个通过协议定义的两个变量。
NSObject<协议名> * 变量
id<协议名> * 变量
这两个编译类型仅仅只是所遵循的协议类型,因此只能调用该协议中所定义的方法。
正式与非正式的差异
1、非正式协议通过为NSObject创建类别来实现,而正式协议直接使用@protocol创建;
2、遵守非正式协议通过继承带特定类别的NSObject来实现,而遵守正式协议则有专门的OC语法来实现;
3、遵守非正式协议不要求实现协议中定义的所有方法;而遵守正式协议则必须实现协议中定义的所方法
在OC中为了增加灵活性,新增了两个关键字。
@optional:实现类可以选择是否实现这些方法
@require:位于该关键字之后、@optional或@end之前声明的方法是必需的,实现类必需实现这些方法。
协议与委托
协议体现的是一种规范,定义协议的类可以把协议定义的方法委托给实现协议的类,这样可以让类定义具有更好的通用性,因为具体的动作将由该协议的实现类去完成。无论是基于Mac的Cococa应用开发还是iOS开发,各种应用程序大量依赖委托这个概念。