OC语言学习——面向对象(下)

一、OC的包装类

OC提供了NSValue、NSNumber来封装C语言基本类型(short、int、float等)。

在 Objective-C 中,**包装类(Wrapper Classes)**是用来把基本数据类型(如 int、float、char 等)“包装”为对象的类。因为 Objective-C 是面向对象的语言,有时候我们需要把基本类型当作对象使用,比如:

  • 放入 NSArray、NSDictionary 这样的集合中(这些集合只能存放对象);

  • 使用对象方法对数值进行操作;

  • 与 Foundation 框架接口交互。

1.1 以下不是包装类:


        1、NSInteger:大致等于long型整数

        2、NSUInteger:大致等于unsigned long型整数
        3、CGFLoat:在64位平台相当于double,在32位平台相当于float


        以上的类型只是基本类型。为了更好的兼容不同的平台,当程序需要定义整形变量的时候,建议使用NSInteger,NSUInteger;当程序需要定义浮点型变量的时候,建议使用CGFLoat

1.2 以下是包装类:


        1、NSValue是NSNumber的父类,它代表一个更通用的包装类,可以包装int、short、long、float、char、指针、对象id等数据项。并将它们添加到NSArray、NSSet等集合中去。


        2、NSNumber是更具体的包装类,用于包装c语言的各种数值类型。它有如下三类方法:
            - [x] +numberWithXxx:直接将特定类型的值包装成NSNumber
            - [x] -initWithXxx:该实例方法需要先创建一个NSNumber对象,再用一个基本类型的值来初始化NSNumber
            - [x] -xxxValue:该实例方法返回该NSNumber对象包装的基本类型的值

        如上方法的Xxx是Int,Char,Double,string等各种数据类型。


        使用NSNumber的compare方法比较两个值,返回的对象可以转化为-1、0、1,分别代表小于、等于、大于。与bool值比较时,YES代表1,当另一个数大于1时返回1,小于1时返回-1。

        基本类型变量和包装类对象之间的转换关系可以理解为:基本类型变量通过调用numberWithXxx:类方法来转换并返回包装类对象;包装类对象通过调用xxxValue来获取基本类型的值。

#import <Foundation/Foundation.h>

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSNumber *num = [NSNumber numberWithInt:66];
        NSNumber *de = [NSNumber numberWithDouble:7.7];
        NSLog(@"%d",[num intValue]);
        NSLog(@"%g",[de doubleValue]);
        NSNumber *ch = [[NSNumber alloc] initWithChar:'t'];
        NSLog(@"%@",ch);
    }
    return 0;
}

二、处理对象

2.1 处理对象和description方法

在 Objective-C 中,打印对象(NSLog(@"%@", obj)) 实际上是调用对象的 -description 方法。这个方法决定了你在控制台看到的输出内容。

 一、NSLog(@"%@", obj) 做了什么?

当你写:

NSLog(@"%@", obj);

它相当于:

NSLog(@"%@", [obj description]);

也就是说:

        NSLog 并不会直接打印对象地址;

  • 它调用了 obj 的 -description 方法,获取一个 NSString* 类型的描述字符串来打印。


 二、默认行为

如果你没有重写 -description,那么会输出类似:

<ClassName: 0x10060ae50>

这是 NSObject 默认的格式,表示“类名 + 内存地址”。


 三、如何自定义打印内容?

你可以重写 -description 方法来自定义输出内容:

示例:

@interface Person : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) int age;
@end

@implementation Person
- (NSString *)description {
    return [NSString stringWithFormat:@"Person: name=%@, age=%d", self.name, self.age];
}
@end

使用:

Person *p = [[Person alloc] init];
p.name = @"Tom";
p.age = 20;
NSLog(@"%@", p);

输出:

Person: name=Tom, age=20

 四、打印集合对象(NSArray、NSDictionary)

集合类如 NSArray、NSDictionary、NSSet,当你 NSLog 打印它们时,它们也会调用内部所有对象的 -description 方法

NSArray *arr = @[p];
NSLog(@"%@", arr);

如果你没有给 p 写 -description,你就会看到一串地址;

如果写了,就会输出里面每个对象的自定义内容。

#import <Foundation/Foundation.h>

@interface Person : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) int age;
@end

@implementation Person

//重写 description 方法
- (NSString *)description {
    return [NSString stringWithFormat:@"Person: name=%@, age=%d", self.name, self.age];
}

@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person *p = [[Person alloc] init];
        p.name = @"Tom";
        p.age = 20;

        // 打印对象
        NSLog(@"%@", p); // 自动调用 [p description]
    }
    return 0;
}

不重写 VS 重写后

<Person: 0x600000e812e0>

Person: name=Tom, age=20

2.2 == 和 isEqual方法

oc中测试两个变量事都相等的方式有两个,分别是:==方法和isEqual方法

2.2.1 ==方法

当用==方法时,若️①两个变量是基本类型的变量,️②两个变量都是数值型的变量(不一定要求数据类型严格相等),️③两个变量的值相等。则==判断返回真,否则返回假。
        而对于指针类型的变量,则要两个指针指向同一个对象,则==返回真,否则返回假。
        当使用==的两个类没有继承关系时,编译器会提示警告。

@“hello”和[NSString stringWithFormat:@“hello”]的区别:
         当OC直接使用@”hello“,系统会使用常量池来管理这些字符串。常量池保证相同的字符串只会有一个,不会产生多个副本,因此创建的所有指向@“hello”的指针,指针变量保存的地址都是完全相同的。
         而使用[NSStringstringWithFormat:@“hello”]创建的字符串对象是运行时创建出来的,它被保存在运行时的内存中(即堆内存),不会放入常量池中。因此它的地址和@“hello”的地址并不相同。

以下代码演示了==的用法:

#import <Foundation/Foundation.h>
 
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        int it = 65;
        int fl = 65.0f;
        char ch = 'A';
        NSString *str1 = @"hello";
        NSString *str2 = @"hello";
        NSString *str3 = @"byebye";
        NSLog(@"%d",(it==fl)); //结果为1
        NSLog(@"%d",(fl == ch)); //结果为1
        NSLog(@"%d",(str1 == str2)); //结果为1
        NSLog(@"%d",(str2 == str3)); //结果为0
        //常量池
        NSString *p1 = @"朱斌";
        NSString *p2 = @"朱斌";
        NSLog(@"p1地址:%p,p2地址:%p",p1,p2);
        NSLog(@"%d",(p1 == p2)); //结果为1
        NSString *p3 = [NSString stringWithFormat:@"朱斌"];
        NSString *p4 = [NSString stringWithFormat:@"朱斌"];
        NSLog(@"p3地址:%p",p3);
        NSLog(@"p4地址:%p",p4);
        NSLog(@"%d",(p1 == p3)); //结果为0
        NSLog(@"%d",(p4 == p3)); //结果为0
        NSString *p5 = [NSString stringWithFormat:@"zbchi"];
        NSString *p6 = [NSString stringWithFormat:@"zbchi"];
        NSLog(@"p5地址:%p",p5);
        NSLog(@"p6地址:%p",p6);
        NSLog(@"%d",(p5 == p6)); //结果为1
    }
    return 0;
}

2.2.2  isEqual方法

isEqual比较的是对象的内容。

isEqual默认实现是比较地址(跟 == 一样),但很多系统类(如NSString,NSArray,NSNumber)都 重写该方法 来比较内容。所以这个时候输出的是 内容相等

#import "FKPerson.h"
 
@implementation FKPerson
 
- (id) initWithName: (NSString*) name idStr: (NSString*) idStr {
    if(self = [super init]) {
        self.name = name;
        self.idStr = idStr;
    }
    return self;
}
- (BOOL) isEqual:(id) other {
    //如果两个对象指针相等,为同一个对象
    if(self == other) {
        return YES;
    }
    //当other不为nil且它为FKPerson的实例时
    if(other != nil && [other isMemberOfClass:FKPerson.class]) {
        FKPerson* target = (FKPerson*)other;
        //并且要判断当前对象的idStr和target对象的idStr相等才可以判断两个对象相等
        return [self.idStr isEqual: target.idStr];
    }
    return NO;
}
 
@end

三、类别与拓展

在oc中,类别和拓展都是对类进行的“补充”机制。

3.1 类别

类别是oc中用于給已有方法添加方法的一种机制,不能添加成员变量

类别的定义:

命名规则:在接口文件部分的文件命名是“类名+类别名.h” 在实现部分的文件命名是“类名+类别名.m” 的形式。

类别的接口部分的声明和类的定义十分相似,但类别不继承父类,只需要在已有类的类名后面加一个括号,写入类别名,然后再在下面定义方法。

@interface ClassName (CategoryName)
- (void)newMethod;
@end

@implementation ClassName (CategoryName)
- (void)newMethod {
    NSLog(@"Category method called");
}
@end

特点:

只能添加方法,不能添加实例变量(属性也不行)。

方法是运行时动态添加的

可以重写原类的方法,但不建议这么做(会覆盖原方法)建议是通过原有类为父类派生一个子类,然后在子类中重写父类的方法

多个类别中如果有相同方法名,最后编译进来的那个生效

//FKPerson.h


#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface FKPerson : NSObject

@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) int age;

- (void)test1;

@end

NS_ASSUME_NONNULL_END


//FKPerson.m

#import "FKPerson.h"

@implementation FKPerson

- (void)test1 {
    NSLog(@"test1");
}

@end


//FKPerson+Test2.h

#import "FKPerson.h"

NS_ASSUME_NONNULL_BEGIN

@interface FKPerson (Test2)

- (void)dda;

@end

NS_ASSUME_NONNULL_END


//FKPerson+Test2.m

#import "FKPerson+Test2.h"

@implementation FKPerson (Test2)

- (void)dda {
    NSLog(@"test2");
}

@end


//NSNumber+FK

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface NSNumber (FK)

- (NSNumber *)add:(double)num2;
- (NSNumber *)subtract:(double)num2;
- (NSNumber *)multiply:(double)num2;
- (NSNumber *)divide:(double)num2;

@end

NS_ASSUME_NONNULL_END


//NSNumber+FK.m


#import "NSNumber+FK.h"

@implementation NSNumber (FK)

- (NSNumber *)add:(double)num2 {
    return @(self.doubleValue + num2);
}

- (NSNumber *)subtract:(double)num2 {
    return @(self.doubleValue - num2);
}

- (NSNumber *)multiply:(double)num2 {
    return @(self.doubleValue * num2);
}

- (NSNumber *)divide:(double)num2 {
    return @(self.doubleValue / num2);
}

@end


//main


#import <Foundation/Foundation.h>
#import "FKPerson.h"
#import "FKPerson+Test2.h"
#import "NSNumber+FK.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // 测试 FKPerson 类和分类
        FKPerson *person = [[FKPerson alloc] init];
        person.name = @"Tom";
        person.age = 18;
        
        [person test1];  // 输出 test1
        [person dda];    // 输出 test2
        
        // 测试 NSNumber 分类
        NSNumber *num1 = @10;
        NSLog(@"Add: %@", [[num1 add:5] stringValue]);        // 15
        NSLog(@"Sub: %@", [[num1 subtract:3] stringValue]);   // 7
        NSLog(@"Mul: %@", [[num1 multiply:2] stringValue]);   // 20
        NSLog(@"Div: %@", [[num1 divide:2] stringValue]);     // 5
    }
    return 0;
}

3.1.1 利用类别进行模块化设计


        类的实现部分不能分布到多个.m文件中去,因此当某个类非常大时,会导致那个类实现所在的文件非常大,以至于维护起来非常困难。因此如果需要将一个较大的类分模块设计,使用类别是一个不错的选择。通过类别可以对类实现按模块分布到不同的*.m文件中,从而提高项目后期的可维护性。

3.1.2 使用类别来调用私有方法


        在前面的学习中我们知道,在实现部分定义的方法相当于私有方法,通常不允许调用。但是实际上私有方法仍然有办法调用。比如这一小节要学的,通过类别来定义前向引用,从而实现对私有方法的调用。

我们用代码来演示如何调用:

首先,我们定义一个FKItem类的接口部分,只声明了一个info方法

#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

我们在主函数中在调用这个私有方法的时候,会报错说没有这个方法。我们可以在main()函数下新建一个类别,然后再在类别中声明calDiscount方法,这时就可以了

#import <Foundation/Foundation.h>
#import "FKItem.h"
 
@interface FKItem (fk)
 
- (double) calDiscount: (double)diacount;
 
@end
 
int main(int argc,char *argv[]) {
    @autoreleasepool {
        FKItem *item = [[FKItem alloc] init];
        item.price = 109;
        [item info];
        NSLog(@"物品打折的价格是:%g",[item calDiscount:.75]);
    }
}

3.2拓展

拓展与类别相似,拓展相当于匿名类别,只能在类的实现文件(.m 文件)中声明,语法如下:

// MyClass.m
@interface MyClass ()  // 没有名字
@property (nonatomic, strong) NSString *secret;  // 私有属性
- (void)privateMethod;                        // 私有方法
@end

@implementation MyClass
// 实现方法
@end

 从语法上来看,扩展相当于定义一个匿名的类别,但从用法来看,类别一般是有特定的.h和.m文件,扩展则用于临时对某个类的接口进行扩展,类实现部分同时实现类接口部分定义的方法和扩展中定义的方法。

        扩展和类别的不同点还有就是:定义类的扩展的时候,可以额外增加实例变量,也可以用@property来合成属性(包括setter和getter方法和对应的成员变量),但定义类的类别的时候,是不允许额外定义实例变量和合成属性的。

#import <Foundation/Foundation.h>
 
NS_ASSUME_NONNULL_BEGIN
 
@interface FKCar : NSObject
 
@property (nonatomic,copy) NSString *brand;
@property (nonatomic,copy) NSString *model;
 
- (void) drive;
 
@end
 
NS_ASSUME_NONNULL_END

上面只是定义了一个FKCar接口,在该接口中定义了两个属性和一个方法,接下来对该类进行拓展

#import <Foundation/Foundation.h>
#import "FKCar.h"
 
NS_ASSUME_NONNULL_BEGIN
 
@interface FKCar ()
 
@property (nonatomic,copy) NSString *color;
- (void) drive: (NSString*) owner;
 
@end
 
NS_ASSUME_NONNULL_END

接下来是FKCar的实现部分:

#import "FKCar.h"
#import "FKCar+drive.h"
 
@implementation FKCar
 
@synthesize brand;
@synthesize model;
@synthesize color;
 
- (void) drive {
    NSLog(@"汽车正在路上跑");
}
- (void) drive: (NSString*) owner {
    NSLog(@"%@正驾驶着%@汽车在路上跑",owner,self);
}
- (NSString*) description {
    return [NSString stringWithFormat:@"<FK[brand = %@,model = %@,color = %@",self.brand,self.model,self.color];
}
 
@end
#import <Foundation/Foundation.h>
#import "FKCar+drive.h"
 
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        FKCar *car = [[FKCar alloc] init];
        car.brand = @"宝马";
        car.model = @"X5";
        car.color = @"黑色";
        [car drive];
        [car drive: @"孙悟空"];
    }
    return 0;
}

相同点

说明

都是通过 @interface 声明的

都以 @interface 开始来声明方法或属性扩展

都能为已有类添加方法

都可以为类添加实例方法和类方法

都不需要修改原始类的实现

用于对已有类功能的增强或补充

编译后的表现一样

编译器会把类拓展和类别“合并进原类”

比较项

类别(Category)

拓展(Extension)

声明位置

通常在 .h 文件中公开使用

一般在 .m 文件中,仅在类实现内部使用

是否有名字

有名字,如 MyClass (Custom)

没有名字,是匿名的

方法可见性

添加的是公有方法

添加的是私有方法

属性添加

不能添加属性(除非用 runtime 关联对象)

可以添加属性,编译器自动生成 getter/setter

实例变量

不能添加实例变量

可以间接添加实例变量(通过属性)

编译器检查

不会强制你实现类别中声明的方法

会强制你实现拓展中声明的方法

典型用途

为系统类或第三方类添加功能

实现类的私有功能(属性和方法)

四、协议(protocol)与委托 


4.1 协议


        类是一种具体实现体,而协议则是定义了一种规范,定义了某一批类所需要遵守的规范。协议不提供任何实现,它体现的是规范和实现分离的设计哲学。

        让规范和实现分离正是协议的好处,是一种松耦合的设计。

4.1.1 使用类别实现非正式协议


        这也是类别的作用之一。

        当某个类实现NSObject的该类别时,就需要实现该类别下所有方法,这种基于NSObject定义的类别即可认为是非正式协议。

以下用代码来演示非正式协议:

首先我们创建一个NSObject的类别,名为EaTable,并且为其定义一个taste方法:


 4.1.2 正式协议的定义


        定义正式协议的时候,不再使用@interface和@implementation关键字了,而是使用@protocol关键字,定义正式协议的语法如下:

@protocol 协议名 <父协议1,子协议2> {
    零到多个方法定义......
}


注意:

        1、协议名应与类名采用相同的命名规则。即协议名应该由多个有意义的单词连接而成,每个单词首字母大写。

        2、一个协议可以有多个直接父协议,但协议只能继承协议,不能继承类。

        3、协议中定义的方法只有方法签名,没有方法实现,协议中包含的方法即可以是类方法也可以是实例方法。

        4、协议中所有方法都是公开的访问权限。

以下是三个定义正式协议的代码:

#import <Foundation/Foundation.h>
 
NS_ASSUME_NONNULL_BEGIN
 
@protocol FKOutput
 
- (void) output;
- (void) addData(String msg);
 
@end
 
NS_ASSUME_NONNULL_END


上面定义了一个FKOutput协议,这个协议定义了两个方法,分别表示添加数据和输出数据。

#import <Foundation/Foundation.h>
 
NS_ASSUME_NONNULL_BEGIN
 
@protocol FKProoductable
 
- (NSData*) getProductTime;
 
@end
 
NS_ASSUME_NONNULL_END


以上定义了一个FKProductable协议,其中定义了一个getProductTime方法,用来返回产品的生产时间。

#import <Foundation/Foundation.h>
#import "FKOutput.h"
#import "FKProductable.h"
 
NS_ASSUME_NONNULL_BEGIN
 
@protocol FKPrintable <FKOutput,FKProductable>
 
- (NSString*) printColor;
 
@end
 
NS_ASSUME_NONNULL_END


上面这个是一个打印机协议,该协议同时继承以上两个协议。

        协议的继承和类的继承不一样,协议完全支持多继承,即一个协议可以有多个直接的父协议。和类继承相似,子协议继承某个父协议,将会获得父协议中的所有方法。

        一个协议继承多个父协议时,多个父协议排在<>中间,多个协议口见以(,)隔开。

4.1.3 遵守(实现)协议


        在类定义的接口部分可以指定该类继承的父类,以及遵守的协议,语法如下:

@interface 类名: 父类<协议1,协议2...>


        由上面的语法格式可以看出,一个类可以同时遵守多个协议。

        如果程序需要使用协议来定义变量,有如下两种语法:

        1、NSObject<协议1,协议2...>* 变量;

        2、id<协议1,协议2...>* 变量;

        通过上面的语法格式定义的变量,它们的编译时的类型仅仅只是所遵守的协议类型,因此只能调用该协议中定义的方法。

声明一个协议

@protocol MyDelegate <NSObject>
@required
- (void)didFinishTask;
@optional
- (void)didStartTask;
@end

如何让类遵守协议? 在类的声明中使用尖括号<协议名> 来表示遵守

@interface Worker : NSObject <MyDelegate>
@end

然后在实现中写出协议方法 

 

@implementation Worker

- (void)didFinishTask {
    NSLog(@"Task finished!");
}

@end

如果你没有实现@required 方法,编译器会报警告 

使用协议的类如何调用这些方法?

@interface Manager : NSObject
@property (nonatomic, weak) id<MyDelegate> delegate;
- (void)doWork;
@end

@implementation Manager

- (void)doWork {
    NSLog(@"Manager is working...");

    if ([self.delegate respondsToSelector:@selector(didStartTask)]) {
        [self.delegate didStartTask];
    }

    // 工作完成
    if ([self.delegate respondsToSelector:@selector(didFinishTask)]) {
        [self.delegate didFinishTask];
    }
}
@end

4.1.4 正式协议与非正式协议的差异


        1、非正式协议通过为NSObject创建类别来实现,而正式协议直接使用@protocol创建;

        2、遵守非正式协议通过继承带特定类别的NSObject来实现,而遵守正式协议则有专门的OC语法来实现;

        3、遵守非正式协议不要求实现协议中定义的所有方法;而遵守正式协议则必须实现协议中定义的所有方法。

        在OC中还有两个关键字:

        1、@optional:位于该关键字只后、@required或@end之前声明的方法是可选的,实现类可选择是否实现这些方法。

        2、@required:位于该关键字之后、@optional或@end之前声明的方法是必需的,实现类必需实现这些方法。

        通过在正式协议中使用以上两个关键字,正式协议完全可以代替非正式协议的功能。

4.1.5 协议与委托(delegate)
        定义协议的类可以把协议定义的方法委托给实现协议的类,这样可以让类定义具有更好的通用性质。

        更通用的,当应用程序启动时,应用程序启动的开始加载、加载完成等系列事件,都是委托给相应的代理对象完成的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值