【IOS 开发学习总结-OC-18】★★objective-c面向对象之——OC的包装类与对象处理

本文介绍Objective-C中如何使用NSValue和NSNumber对基本数据类型进行封装,使之具备面向对象特性。同时探讨了对象处理技巧,如打印对象、比较对象相等性的方法,并深入分析了常量池的作用。

OC 的包装类

objective-c 是由 C 语言扩展而来的面向对象的编程语言。而 C 中的基本数据类型都不是对象,所以没有属性,方法等对象的特性。
objective-c 提供了NSValue, NSNumber来封装 C 语言的基本类型——这样他们就具有了面向对象的特征。

容易误认的非包装类

下面这3个类型不是包装类。仍是基本类型。

名称说明
NSInteger大致等于 long 型整数
NSUInteger大致等于unsigned long 型整数
CGFloat在64位平台上相当于 double,32位平台上相当于 float

小提示:为了兼容不同的平台,当程序需要定义整型变量时,建议使用 NSInteger,NSUInteger,需要定义浮点型变量时,建议使用 CGFloat。

包装类 NSValue和NSNumber

NSValue和NSNumber都是包装类,其中NSValue是NSNumber的父类。
NSValue和NSNumber的使用范围是怎样的呢?请往下看:

名称使用的范围作用方法
NSValue比NSNumber更通用,可用于包装单个 short,int,long,float,char, 指针,对象 id 等数据类型。通过使用该包装类,就可以将前述的数据类型添加到 NSArray,NSSet 等集合(这些集合要求它们的元素必须为对象)中
NSNumber更具体的包装类,主要用于包装 C 语言的各种数据类型包括3类如下段落中的方法

3类如下方法:

方法名作用
+numberWithXXX:直接将特定类型的值包装成NSNumber
-initWithXXX:需要先创建一个NSNumber对象,在用一个基本类型的值初始化NSNumber
-XXXValue:返回该NSNumber对象包装的基本类型的值

其中,XXX 代表 int,char 等各种基本类型。上面3种方法中,前2种功能相似,比较而言,直接用类方法+numberWithXXX: 更加简单。

示例程序(基本类型值域包装类之间的转换):

#import <Foundation/Foundation.h>

int main(int argc , char * argv[])
{
    @autoreleasepool{
        // 调用类方法将int类型的值包装成NSNumber对象
        NSNumber* num = [[NSNumber alloc] initWithInt:20];
        NSLog(@"%@" , [num class]);
        // 调用类方法将double类型的值包装成NSNumber对象
        NSNumber* de = [NSNumber numberWithDouble: 3.4];
        NSLog(@"%d" , [num intValue]);
        NSLog(@"%g" , [de doubleValue]);
        // 先创建NSNumber对象,再调用initWithXxx方法执行初始化
        NSNumber* ch = [[NSNumber alloc] initWithChar:'J'];
        // 直接输出NSNumber对象,使用%@格式字符串
        NSLog(@"%@" , ch);
    }
}

运行结果:

2015-09-25 10:53:50.389 923[1592:66462] __NSCFNumber
2015-09-25 10:53:50.391 923[1592:66462] 20
2015-09-25 10:53:50.392 923[1592:66462] 3.4
2015-09-25 10:53:50.392 923[1592:66462] 74

说明:虽然 objective-c 也提供了类似于自动装箱的机制——比如直接进把一个整型值赋给NSNumber变量,但该机制并不完善,自动装箱生成的NSNumber不支持 ARC,也不能把浮点数赋给NSNumber类型的变量。so, 最好显式将基本类型的值包装成NSNumber对象。

对象处理

objective-c 中的对象都是 NSObject 子类的实例,都可以直接调用该类中定义的方法——通用的处理 objective-c 对象的方法

打印对象和 description 方法

示例程序:
FKPerson.h

#import <Foundation/Foundation.h>

@interface FKPerson : NSObject
@property (nonatomic , copy) NSString* name;
- (id) initWithName: (NSString*) name;
- (void) info;
@end

FKPerson.m

#import "FKPerson.h"

@implementation FKPerson
@synthesize name = _name;
- (id) initWithName: (NSString*) name
{
    if(self = [super init])
    {
        self.name = name;
    }
    return self;
}
- (void) info
{
    NSLog(@"此人名为:%@" , self.name);
}
@end

FKPersonTest.m

#import <Foundation/Foundation.h>
#import "FKPerson.h"

int main(int argc , char * argv[])
{
    @autoreleasepool{
        // 创建一个FKPerson对象,将之赋给p变量
        FKPerson* p = [[FKPerson alloc] initWithName:@"孙悟空"];
        // 打印p指向的FKPerson对象
        NSLog(@"%p" , p);
        NSLog(@"%@",[p description]);
    }
}

编译运行结果:

2015-09-25 11:06:41.975 923[1779:74557] 0x7bf4d220
2015-09-25 11:06:41.977 923[1779:74557] <FKPerson: 0x7bf4d220>

实际上,当使用 NSLog() 输出 FKPerson对象时,输出的是 FKPerson对象的 description 方法的返回值。由于,description 方法是 NSObject类的一个实例方法,而所有 OC 类都是 NSObject 类的子类,所以,所有的 OC 对象都有description 方法。
description 方法是个特殊的”自我描述”方法——当程序员直接打印噶对象时,系统将会输出该对象的”自我描述”信息,用于告诉外界该对象具有的状态信息。上面的栗子中,description 方法返回的信息并未实现我们想要的结果,因此,如果需要自定义类能真正实现”自我描述”的功能,必须重写NSObject类的description 方法。

示例程序:
FKApple.h

#import <Foundation/Foundation.h>

@interface FKApple : NSObject
@property (nonatomic , copy) NSString* color;
@property (nonatomic , assign) double weight;
- (id) initWithColor: (NSString*) color weight: (double) weight;
@end

FKApple.m

#import "FKApple.h"

@implementation FKApple
@synthesize color = _color;
@synthesize weight = _weight;
- (id) initWithColor: (NSString*) color weight:(double) weight
{
    if(self = [super init])
    {
        self.color = color;
        self.weight = weight;
    }
    return self;
}
// 重写父类的decription方法
- (NSString*) description
{
    // 返回一个字符串
    return [NSString stringWithFormat:@"<FKApple[_color=%@, _weight=%g]>"
        , self.color , self.weight];
}
@end

FKAppleTest.m

#import <Foundation/Foundation.h>
#import "FKApple.h"

int main(int argc , char * argv[])
{
    @autoreleasepool{
        // 创建一个FKPerson对象,将之赋给p变量
        FKApple* a = [[FKApple alloc] initWithColor:@"红色"
            weight:5.68];
        // 打印FKApple对象
        NSLog(@"%@" , a);
    }
}

大部分时候,重写 description 方法总是返回该对象所有令人感兴趣的信息所组成的字符串,就像上面栗子那样。返回如下格式的字符串:
<类名[实例变量1=值1,实例变量2=值2,实例变量3=值3,...]>

测试2个变量是否相等的两种方式

这两种方式分别是:1.利用==运算符;2,利用 isEqual 方法。

利用==运算符

1.如果==两边的变量是基本类型的变量,且都是数值型(不一定数据类型严格相同),则只要2个变量值相等,==判断就返回真。
2.对两个指针类型的变量,它们必须指向同一个对象(2个指针对象保存的内存地址相同),==判断才会返回真。当使用灌灌灌灌==比较类型上没有继承关系的2个指针变量时,编译器会提示警告。
示例程序:

#import <Foundation/Foundation.h>


int main(int argc , char * argv[])
{
    @autoreleasepool{
        int it = 65;
        float fl = 65.0f;
        // 将输出1代表真
        NSLog(@"65和65.0f是否相等?: %d" , (it == fl));
        char ch = 'A';
        // 将输出1代表真
        NSLog(@"65和'A'是否相等?%d" , (it == ch));
        NSString* str1 = [NSString stringWithFormat:@"hello"];
        NSString* str2 = [NSString stringWithFormat:@"hello"];
        // 将输出0代表假
        NSLog(@"str1和str2是否相等?%d"
             , (str1 == str2));
        // 将输出1代表真
        NSLog(@"str1是否isEqual str2?%d"
            , [str1 isEqual:str2]);
        // 由于NSDate与NSString类没有继承关系,
        // 所以下面语句导致编译警告
        NSLog(@"%d" , [NSDate new] == [NSString new]);
    }
}

编译运行结果:

2015-09-25 19:04:00.566 923[4568:179123] 6565.0f是否相等?: 1
2015-09-25 19:04:00.568 923[4568:179123] 65'A'是否相等?1
2015-09-25 19:04:00.569 923[4568:179123] str1和str2是否相等?0
2015-09-25 19:04:00.569 923[4568:179123] str1是否isEqual str2?1
2015-09-25 19:04:00.570 923[4568:179123] 0
常量池

问题:@"hello"[NSString stringWithFormat:@"hello"]有什么区别呢?
区别在于,使用前者@"hello"直接量时,系统将会使用常量池来管理这些字符串。常量池保证相同的字符串直接量只有一个,不会产生多个副本。[NSString stringWithFormat:@"hello"] 类方法创建的字符串对象是运行时创建出来的,它被保存在运行时的内存区内(即堆内存),不会放到常量池中,因此,示例中 s3指针变量和s1,s2 指针变量保存的地址并不相同。
示例代码:

#import <Foundation/Foundation.h>

int main(int argc , char * argv[])
{
    @autoreleasepool{
        // s1、s2直接指向常量池中的"疯狂iOS"
        NSString* s1 = @"疯狂iOS";
        NSString* s2 = @"疯狂iOS";
        // 看到s1、s2两个指针保存的地址值完全相等
        NSLog(@"s1地址:%p, s2地址:%p" , s1 , s2);
        // 所以下面程序输出1代表真
        NSLog(@"s1与s2是否相等:%d", (s1 == s2));
        // 让s3指向新生成的对象
        NSString* s3 = [NSString stringWithFormat:@"疯狂iOS"];
        // 输出说s3指针变量中保存的地址值与s1、s2并不相同
        NSLog(@"s3地址:%p" , s3); 
        // 所以下面程序输出0代表假
        NSLog(@"s1与s3是否相等:%d" , (s1 == s3));    
    }
}

运行结果:

2015-09-25 19:09:37.337 923[4669:182972] s1地址:0x26024, s2地址:0x26024
2015-09-25 19:09:37.339 923[4669:182972] s1s2是否相等:1
2015-09-25 19:09:37.340 923[4669:182972] s3地址:0x7b0549a0
2015-09-25 19:09:37.340 923[4669:182972] s1s3是否相等:0

isEqual 方法

isEqual 方法是 NSObject 类提供的一个实例方法,因此所有的指针变量都可以调用该方法来判断是否与其他指针变量相等。但是呢,这个NSObject 类的isEqual 方法判断2个对象相等的标准与==符号没有区别((只是比较对象的地址)),同样要求2个指针变量指向同一个对象才会返回真。
因此,这个 NSObject 类提供的isEqual 方法没有多大实际意义。可以通过重写isEqual 方法实现自定义的相等标准。
NSString 已经重写了NSObject 类的isEqual 方法,NSString的isEqual 方法判断2个字符串相等的标准是:只要两个字符串所包含的字符序列相同,isEqual 方法比较后将返回真,否则返回假。
示例程序:(这里重写了isEqual 方法,相等的对象比较极端)
FKItem.h

#import <Foundation/Foundation.h>

@interface FKItem : NSObject
@end

FKItem.m

#import "FKItem.h"

@implementation FKItem
@end

FKDog.h

#import <Foundation/Foundation.h>

@interface FKDog : NSObject
@end

FKDog.m

#import "FKDog.h"

@implementation FKDog
- (BOOL) isEqual:(id)other
{
    // 不加判断,总是返回YES,即FKUser对象与任何对象都相等
    return YES;
}
@end

FKDogTest.m

#import <Foundation/Foundation.h>
#import "FKDog.h"
#import "FKItem.h"

int main(int argc , char * argv[])
{
    @autoreleasepool{
        FKDog* p = [[FKDog alloc] init];
        NSLog(@"FKUser对象是否isEqual FKItem对象?%d"
            , [p isEqual: [FKItem new]]);
        NSLog(@"FKUser对象是否isEqual NSString对象?%d" 
            , [p isEqual: [NSString stringWithFormat:@"Hello"]]);
    }
}

运行结果:

2015-09-25 20:04:29.082 923[5158:204772] FKUser对象是否isEqual FKItem对象?1
2015-09-25 20:04:29.085 923[5158:204772] FKUser对象是否isEqual NSString对象?1

上面的程序中对象相等的不太符合实际情况。我们希望2个类型相同的对象才可能相等,而且必须是关键的实例变量相等才算相等。看下面重写isEqual的示例程序:
FKUser.h

#import <Foundation/Foundation.h>

@interface FKUser : NSObject
@property (nonatomic , copy) NSString* name;
@property (nonatomic , copy) NSString* idStr;
- (id) initWithName: (NSString*) name 
    idStr: (NSString*) idStr;
@end

FKUser.m

#import "FKUser.h"

@implementation FKUser
@synthesize name = _name;
@synthesize idStr = _idStr;
- (id) initWithName: (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不为null,且它是FKUser类的实例时
    if (other != nil && [other isMemberOfClass:FKUser.class])
    {
        FKUser* target = (FKUser*)other;
        //并且当前对象的idStr与target对象的idStr相等才可判断两个对象相等
        return [self.idStr isEqual: target.idStr];
    }
    return NO;
}
@end

FKUserTest.m

#import <Foundation/Foundation.h>
#import "FKUser.h"

int main(int argc , char * argv[])
{
    @autoreleasepool{
        FKUser* p1 = [[FKUser alloc] initWithName:@"孙悟空" 
            idStr: @"12343433433"];
        FKUser* p2 = [[FKUser alloc] initWithName:@"孙行者" 
            idStr: @"12343433433"];
        FKUser* p3 = [[FKUser alloc] initWithName:@"孙悟饭" 
            idStr: @"99933433"];
        //p1和p2的idStr相等,所以输出代表真的1
        NSLog(@"p1和p2是否相等?%d" 
            , [p1 isEqual: p2]);    
        //p2和p3的idStr不相等,所以输出代表假的0
        NSLog(@"p2和p3是否相等?%d"
            , [p2 isEqual: p3]);
    }
}

编译运行结果:

2015-09-25 20:12:02.925 923[5279:209968] p1p2是否相等?1
2015-09-25 20:12:02.927 923[5279:209968] p2p3是否相等?0

上面程序重写了isEqual方法,指定了相等的标准:另一个对象必须是 FKUser类的实例,且2个FKUser对象的 idStr相等,即可判断2个FKUser对象相等。

正确重写isEqual方法应满足的条件
  1. 自反性:对任意 x,[x isEqual:x]一定返回真。
  2. 对称性:对任意 x和 y, 如果[x isEqual:y]返回真,则[y isEqual:x]也返回真。
  3. 传递性:对任意 x, y,z, 如果[x isEqual:y]返回真,[y isEqual:z]也返回真,则[x isEqual:z] 也返回真。
  4. 一致性:对任意x,y,如果对象中用于等价比较的关键信息没有改变,那么无论调用[x isEqual:y] 多少次,返回的结果应该保持一致。
  5. 对任何不适 nil 的 x,[x isEqual:nil] 一定返回假。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值