面向对象(下)第6章
前言
以下是本人在学习面向对象(下)第6章时,总结的学习笔记。
1. Objective-C的包装类
Objective-C提供NSValue、NSNumber来封装C语言的基本类型(int、short、float、double)使其具有面向对象的特征。
值得注意的是,以下类型不是包装类,而是基本类型。
-
NSInteger:相当于long
-
NSUInteger:相当于unsigned long
-
CGFLoat:在64位平台上相当于double,在32位平台上相当于float
NSValue和NSNumber是包装类,其中NSValue是NSNumber的父类。
- NSValue:用于包装单个short、int、long、float、char、指针、对象id等数据项,将它们添加到NSArray、NSSet等集合中。
- NSNumber:用于包装C语言的各种数值类型。
NSNumber主要包括以下3类方法:(此处xxx可以代表int、char等各种基本类型)
+numberWithXxx:(类方法)直接将特定类型值包装成NSNumber-initWithXxx:(实例方法)先创建一个NSNumber对象,再用一个基本类型的值初始化-xxxValue;(实例方法)返回该NSNumber对象包装的基本类型的值
基本类型变量转换为包装类对象
NSNumber *num = [NSNumber numberWithInt: 20];
包装类对象转换为基本类型对象
[num intValue];
2. 处理对象
2.1. 打印对象和description方法
description方法是NSObject类的一个实例方法,因此所有的Objective-C对象都具有description方法。通过重写description,可以使对象在打印时打印出对该对象的描述信息。
#import <Foundation/Foundation.h>
@interface FKPerson : NSObject
@property (nonatomic, copy) NSString *color;
@property (nonatomic, assign) double weight;
-(id) initWithColor: (NSString*) color weight : (double) weight;
@end
#import "FKPerson.h"
@implementation FKPerson
-(id) initWithColor:(NSString *)color weight:(double)weight {
if (self = [super init]) {
self.color = color;
self.weight = weight;
}
return self;
}
//重写description方法
-(NSString*) description {
return [NSString stringWithFormat:@"<FKPerson[_color = %@, _weight = %g", self.color, self.weight];
}
@end
#import <Foundation/Foundation.h>
#import "FKPerson.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
FKPerson *a = [[FKPerson alloc]initWithColor:@"红色" weight:1.23];
NSLog(@"%@", a);
}
return 0;
}
//输出结果为<FKPerson[_color = 红色, _weight = 1.23
2.2. ==和isEqual:方法
==和isEqual:方法是Objective-C测试两个变量是否相等的两种方式。
当使用==判断时
- 对于两个基本类型的变量且都是数值型(数据类型不一定严格相同):只要两个变量的恶值相等,使用==判断就返回真
int it = 65;
float fl = 65.0f;
NSLog(@"%d", (it == fl));//返回1,为真
- 对于两个指针类型的变量:只有两个指针指向同一个对象(即两个指针变量保存的内存地址相同)时,使用==判断才会返回真
这里我们会发现按照书上说的NSString的stringWithFormat类方法创建字符串对象是运行时创建的,它会被保存在运行时内存区(即堆内存)内,不会放入常量池中,因此两个指针变量中保存的地址不同,所以输出0,然而结果真正却输出了1。这里我们需要正式认识一下OC中字符串的三种实现方式。
- _NSCFConstantString
- _NSCFString
- NSTaggedPointerString
- 当使用==比较类型上没有继承关系的两个指针时,编译器会提示警告。
NSLog(@"%d", [NSDate new] == [NSString new]);//编译器会出现警告
当使用isEqual:判断时
isEqual:方法是NSoject类提供的一个实例方法,因此所有的指针变量都可以调用该方法来判断。NSString已经重写了NSObject的isEqual:方法,其判断两个字符串相等的标准是:只要两个字符串所包含的字符序列相同,通过isEqual:方法比较就会返回真。
举例说明:
#import <Foundation/Foundation.h>
@interface FKPerson : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *idStr;
-(id) initWithName: (NSString*)name idStr : (NSString*)idStr;
@end
#import "FKPerson.h"
@implementation FKPerson
-(id) initWithColor:(NSString *)name idStr:(NSString*)idStr {
if (self = [super init]) {
self.name = name;
self.idStr = idStr;
}
return self;
}
//重写isEqual:方法
-(BOOL) isEqual: (id) other {
//如果两个对象是同一个对象
if (self == other) {
return YES;
}
//如果other不为空,且它是FKPerson类的实例时
if (other != nil && [other isMemberOfClass: FKPerson.class]) {
FKPerson *target = (FKPerson*)other;
//当两个对象的idStr相等时,才判断两个对象相等
return [self.idStr isEqual:target.idStr];
}
return NO;
}
@end
#import <Foundation/Foundation.h>
#import "FKPerson.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
FKPerson *p1 = [[FKPerson alloc] initWithName:@"张三" idStr:@"123456"];
FKPerson *p2 = [[FKPerson alloc] initWithName:@"李四" idStr:@"123456"];
FKPerson *p3 = [[FKPerson alloc] initWithName:@"王麻子" idStr:@"123456789"];
//对比idStr是否相等
NSLog(@"p1 和 p2 是否相等: %d", [p1 isEqual:p2]); // 输出 1 (YES)
NSLog(@"p2 和 p3 是否相等: %d", [p2 isEqual:p3]); // 输出 0 (NO)
}
return 0;
}
以上程序指定的重写isEqual:方法的标准是:其他对象必须是FKPerson的实例且两个对象的id Str必须相等。
通常而言,重写isEqual:方法应满足以下条件:
- 自反性:[x isEqual: x]一定返回真
- 对称性:如果[y isEqual: x]返回真,[x isEqual: z]返回真,则[y isEqual: z]一定返回真
- 一致性:如果对象中用于比较的关键属性没有改变,无论调用多少次[x isEqual: y],其返回值应该保持一致
- 对任何不是nil的x,[x isEqual: nil]一定返回假。
3. 类别与扩展
在开发项目的过程中,除了使用 继承来为己有的类扩展新行为之外,也可以借助类别(category)来实现。
3.1. 类别(category)
Objective-C的动态特征允许使用类别为现有的类添加新方法,并不需要创建子类,也不需要访问原有类的源代码。
类别接口部分语法格式
@interface 已有类 (类别名)
//方法定义
...
@end
定义类与定义类别的语法差异:
- 定义类时使用的类名必须是该项目中没有的类,而定义类别必须使用已有类的类名。
- 定义类别必须使用圆括号来包含类别名。
- 类别中通常只定义方法。
类别实现部分语法格式
@implementation 已有类 (类别名)
//方法实现
...
@end
类别的接口文件命名形式
#import "类名+类别名.h"
类别的实现文件命名形式
#import "类名+类别名.m"
- 通过类别为指定类添加新方法后,这个新方法会影响NSNumber类和NSNumber类的所有子类,每个子类会获取类别扩展的方法。
- 一个类可以定义多个类别。
类别的三种用法:
- 对类进行模块化设计
- 调用私有方法
- 实现非正式协议
3.2. 利用类别对类进行模块化设计
当一个类特别大,需要利用类别将该大类进行模块化设计。
例如:
@interface NSNWindow(NSKeyboardUI)
@interface NSNWindow(NSToolbarSupport)
@interface NSNWindow(NSDrag)
通过类别就可以提供NSNWindow+NSKeyboardUI.m、NSNWindow+NSToolbarSupport,m、NSNWindow+NSDrag.m多个实现文件对类进行模块化分布实现。
3.3. 使用类别来调用私有方法
Objective-C实际上没有真正私有的方法,如果想要调用私有方法,可以使用NSObject的performSelector:方法来执行动态调用(不提倡),也可以通过类别来定义前向引用,从而实现调用私有方法。
//接口定义了一个方法
#import <Foundation/Foundation.h>
@interface FKItem : NSObject
@property (nonatomic, assign) double price;
-(void) info;
@end
#import"FKItem.h"
@implementation FKItem
@synthesize price
-(void) info {
NSLog("接口部分定义的方法");
}
//在实现部分新增一个方法
-(double) calDiscount : (double)discount {
return self.price * discount;
}
@end
//正确写法:
@interface FKItem(fk)
-(double) calDiscount:(double)discount;
@end
//在main()函数前增加类别定义
#import <Foundation/Foundation.h>
#import"FKItem.h"
int main(int argc, char *argv[]) {
@autoreleasepool {
FKItem *item = [[FKItem alloc] init];
item.price = 100;
[item info];
//[item calDiscount: 0.5];编译错误
//因为该方法只在实现部分,接口部分没有
[item calDiscount: 0.5];
//定义类别后可以实现
}
}
3.4.扩展
扩展与类别相似,相当于定义一个匿名的类别。
定义扩展的语法格式
@interface 已有类() {
实例变量
}
//方法定义
...
@end
与类别的不同点:
- 类别通常有单独的.m和.h文件,扩展则用于临时对某个类的接口进行扩展,类实现部分同时实现类接口定义的方法和扩展定义的方法。
- 在定义类的扩展时,可以额外增加实例变量,也可以使用@property来合成属性,但定义类别时不允许。
#import <Foundation/Foundation.h>
@interface FKCar : NSObject
@property(nonatomic, copy) NSString* brand;
@property(nonatomic, copy) NSString* model;
-(void) drive;
@end
//对类进行扩展
#import "FKCar.h"
@interface FKcar()
@property(nonatomic, copy) NSString* color;
-(void) drive:(NSString*)owner;
@end
//扩展新增color属性和drive方法
#import "FKCar+drive.h"
@implementation FKCar
//实现接口部分定义的所有方法
-(void) drive {
NSLog(@"%@", self);
}
//实现扩展中定义的方法
-(void) drive:(NSString*) owner {
NSLog(@"%@ %@", owner, self);
}
@end
4. 协议(protocol)与委托
4.1. 规范、协议与接口
类是一种具体实现体,**而协议定义了一种规范,通常是一组公用方法,但协议不提供任何实现,实现交给类来实现。**它体现规范和实现分离的设计哲学,是一种松耦合设计。OC中协议的作用就相当于其他语言中接口的作用。
松耦合设计(Loose Coupling)是软件工程中的一个重要原则,旨在降低系统中不同组件之间的依赖关系,使系统更具灵活性、可维护性和可扩展性
4.2. 使用类别实现非正式协议
类别可以实现非正式协议,这种类别以NSObject为基础,为NSObject创建类别,创建时可指定该类别应该新增的方法。
当某个类实现NSObject的该类别时,就需要实现该类别下的方法,这种基于NSObject定义的类别即可认为是非正式协议。
#import<Foundation/Foundation.h>
@interface NSObject (Eatable)
-(void) taste;
@end
以上代码在NSObject的Eatable类别中定义了一个taste方法,接下来继承NSObject类的子类都会自动带有该方法,且子类可根据需要决定是否要实现该方法。Eatable类别即非正式协议,相当于定义了一个规范,遵守该协议的子类都会实现这个方法。
//为NSObject派生一个子类FKApple
#import<Foundation/Foundation.h>
#import "NSObject+Eatable.h"
@interface FKApple : NSObject
@end
//FKApple类实现了taste方法,即遵守了Eatable协议
//接下来可以把FKApple类当作Eatable对象来调用
#import "FKApple.h"
@implementation FKApple
-(void) taste {
NSLog("实现方法");
}
@end
4.3. 正式协议的定义
定义正式协议关键字:@protocol
定义正式协议的基本语法格式
@protocol 协议名 <父协议1, 父协议2,...> {
零个到多个方法定义...
}
语法格式说明:
- 协议名应与类名采用相同的命名规则
- 一个协议可以有多个父协议,但协议只能继承协议,不能继承类。
- **协议中定义的方法只有方法签名,没有方法实现,**协议中包含的方法既可以是类方法,也可以是实例方法。
协议里所有的方法都具有公开的访问权限。
举例:该协议定义两个方法,即定义了这个协议的规范:只要某个类能添加数据并将数据输出,那它就是一个输出设备,实现细节协议不予关心。
#import <Foundation/Foundation.h>
@protocol myProtocol
-(void) output;//表示输出
-(void) addData:(NSString*)msg;//表示添加数据
@end
4.4. 遵守(实现)协议
类定义的接口部分指定该类的父类及协议的语法格式
@interface 类名 : 父类 <协议1, 协议2>
为协议提供一个实现类的接口部分
#import <Foundation/Foundation.h>
#import "FKPrintable.h"
//定义一个类FKPrinter,继承NSObject,遵守FKPrintable协议
@interface FKPrinter : NSObject <FKPrintable>
@end
类实现部分
#import "FKPrinter.h"
@implementation FKPrinter {
//实现FKPrintable协议和父协议所有方法
}
主函数中调用方法
//创建一个FKPrinter对象,当成FKProductable使用
NSObject<FKProductable> *p = [[FKPrinter alloc] init];
//这样p就只能调用FKProductable协议中方法
NSLog(@"%@", p.getProduceTime);
//FKPrinter直接定义的对象可以调用所有协议中的方法
FKPrinter *printer = [[FKPrinter alloc] init];
[printer addData:@"iOS"];
[printer output];
使用协议定义变量的语法
使用协议定义的变量只能实现该协议里的方法。
NSObject<协议1, 协议2...> *变量;
id<协议1, 协议2...> 变量;
正式协议与非正式协议的差异:
- 非正式协议通过NSObject创建类别来实现,正式协议直接使用@protocol创建。
- 遵守非正式协议通过继承带特定类别的NSObject来实现,遵守正式协议通过专门的Objective-C语法。
- 遵守非正式协议不要求实现所有方法,遵守正式协议必须实现所有方法。
为弥补遵守正式协议要实现所有方法造成的灵活性不足问题,新增两个关键字@optional和@required。
- @optional:位于其之后声明的方法是可选的(实现类中方法时可以不实现)
- @required:位于其之后声明的方法是必需的(实现类时必须实现,否则编译器会警告)(如果没有@optional,则默认为@required)
通过使用以上两个关键字,正式协议完全可以代替非正式协议。
4.5. 委托(delegate)
在 iOS 开发中,委托(Delegate) 是一种设计模式,允许对象将特定任务的处理 “委托” 给另一个对象。它是实现对象间单向通信的常用方式,核心思想是通过协议(Protocol)定义行为规范,使一个对象(委托者)可以调用另一个对象(代理者)的方法。
5. 自动引用计数
自动引用计数是系统负责管理对象的引用计数,系统可以判断何时需要保持对象,何时需要自动释放对象。
任何在@autoreleasepool块中创建的对象都由ARC自动释放,并在@autoreleasepool块结束时销毁这些对象
1689

被折叠的 条评论
为什么被折叠?



