面向对象(下)第6章

前言

以下是本人在学习面向对象(下)第6章时,总结的学习笔记。

1. Objective-C的包装类

Objective-C提供NSValue、NSNumber来封装C语言的基本类型(int、short、float、double)使其具有面向对象的特征。

值得注意的是,以下类型不是包装类,而是基本类型。

  • NSInteger:相当于long

  • NSUInteger:相当于unsigned long

  • CGFLoat:在64位平台上相当于double,在32位平台上相当于float

NSValueNSNumber是包装类,其中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

OC中字符串的实现方式

  • 当使用==比较类型上没有继承关系的两个指针时,编译器会提示警告。
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块结束时销毁这些对象

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值