iOS

NSNumber

简介:一个原始纯量数值的对象包装器。

NSNumber

一个原始纯量数值的对象包装器。

声明

@interface NSNumber : NSValue

描述

NSNumber是NSValue的子类,它提供任何C标量(数字)类型的值。 它定义了一组专门用于设置和访问值的方法,如有符号或无符号字符,short intintlong intlong long intfloatdoubleBOOL。 (请注意,数字对象不一定保留它们创建的类型。)它还定义了一个compare:方法来确定两个NSNumber对象的排序。
NSNumber与其Core Foundation对应物是Toll-Free Bridging“对象桥接”:CFNumberRef用于整数和浮点值,CFBooleanRef用于布尔值。 有关Toll-Free Bridging的更多信息,请参阅Toll-Free Bridging

值转换

NSNumber提供只读属性,返回将对象的存储值转换为特定的布尔值,整数,无符号整数或浮点C标量类型。由于数字类型具有不同的存储能力,尝试使用一种类型的值进行初始化并访问另一种类型的值可能会产生错误的结果,例如,使用超过flt_max的双值进行初始化并访问其floatValue,或者使用负整数值进行初始化并访问其unsignedIn。测试值。在某些情况下,尝试使用类型的值进行初始化并访问另一类型的值可能会导致精度损失,例如,使用具有多个有效数字的双值进行初始化并访问其floatValue,或者使用大整数值进行初始化并访问其charValue。
使用特定类型的值初始化的NSNumber对象访问不同类型的转换值(如无符号int和float),将按以下方式将其存储值转换为该转换类型:

表1 来自Boolean 转换的NSNumber

Value boolValue integerValue unsignedIntegerValue floatValue
NO NO 0 0 0.0
YES YES 1 1 1.0

表2 来自Integer 转换的NSNumber

Value boolValue integerValue unsignedIntegerValue floatValue
0 NO 0 0 0.0
1 YES 1 1 1.0
-1 YES -1 invalid, erroneous result -1.0

表3 来自Unsigned Integer 转换的NSNumber

Value boolValue integerValue unsignedIntegerValue floatValue
0 NO 0 0 0.0
1 YES 1 1 1.0

表4 浮点值转换的NSNumber

Value boolValue integerValue unsignedIntegerValue floatValue
0.0 NO 0 0 0.0
1.0 YES 1 1 1.0
-1.0 YES -1 invalid, erroneous result -1.0

子类注释

与任何类集群一样,nsnumber的子类必须重写其超类nsvalue的基元方法。此外,对于您的子类所代表的数据类型,有两个要求:

  • 1.objcType的实现必须返回“c”、“c”、“s”、“s”、“i”、“i”、“l”、“l”、“q”、“q”、“f”和“d”中的一个。要使nsnumber的其他方法正常工作,这是必需的。
  • 2.子类必须重写与声明类型对应的访问器方法,例如,如果objctype的实现返回“i”,则必须重写intValue。

为什么需要NSNumber?

NSArrayNSDictionaryNSSet等集合类,只能添加继承自NSObject类型的对象,比如int a = 1000;这样的纯量数值无法添加;而且NSUserDefaultsFMDB数据库等数据保存框架,元素要求必须是继承自NSObject的对象,无法保存纯量数值;
因此,我们常用的纯量数值,需要一个“包装器”,以方便其它类作为对象存储和转换,而NSNumber 就是这样的类。
NSNumber是Foundation框架内置的类,它的类层级如下

NSObject -> NSValue -> NSNumber

可以看出NSNumber继承自NSValue,提供数值对象,作为纯C类型(数值)的封装,包括有符号和无符号的char, short int, int, long int, long long int, float, double以及 BOOL值。

封包

将值转换为对象

// 封包
- (void)scalarValueToNumber {
    // 数值前加@标记
    NSNumber *num1 = @1;
    // 通过类工厂方法初始化
    NSNumber *num2 = [NSNumber numberWithInteger:20];
    // 声明一个16进制
    NSNumber *num3 = @0xff;
    // 声明浮点数
    NSNumber *floatNum = @0.18f;
    // 声明BOOL 类型
    NSNumber *boolNumber = @NO;
    // 将一个纯量数值的变量封装为一个number对象
    int a = 100;
    NSNumber *numObj = @(a);

    NSLog(@"%@", num1);
    NSLog(@"%@", num2);
    NSLog(@"%@", num3);
    NSLog(@"%@", floatNum);
    NSLog(@"%@", boolNumber);
    NSLog(@"%@", numObj);

    // 这里注意,16进制默认是无符号整形,如果需要符号数,只能使用:
    NSNumber *aNumber = @0xff;
    NSLog(@"%@", aNumber.stringValue);
    // 输出为`255`
    NSNumber *aCharObj = [[NSNumber alloc] initWithChar:0xFF];
    NSLog(@"%@", aCharObj.stringValue);
    // 输出为`-1`
}

解包

将对象转换为纯量数值

每种数值类型都有对应的取值方法,intValueintegerValue并不完全等价,不同类型可交叉调用取值方法,隐含着精度转换,需要当心转换时的精度损失。如:

// 不同类型的数值转换,关注精度问题
- (void)example2 {
    NSNumber *floatNum = @1.88;
    NSLog(@"%d", [floatNum  intValue]); // 结果为1
}

不过不用担心比较时的精度丢失,因为int类型会转换为double类型再做比较,只要记住低精度默认转换为高精度即可。

比较

  • 运算符比较
    - (void)compareExample {
        NSNumber *num1 = @10;
        NSNumber *num2 = @2;
    
        // 第一种:运算符比较 需要先解包,然后使用运算符,
        // 注意:使用num1 > num2这种比较指针的大小是错误的
        if (num1.integerValue > num2.integerValue) {
            NSLog(@"num1 > num2");
        }
    }
    

compare比较

- (void)compareExample {
    NSNumber *num1 = @10;
    NSNumber *num2 = @2;
    // 第二种:compare 比较
    if ([num1 compare:num2] == NSOrderedAscending) { //
        // NSOrderedAscending 升序 相当于 num1.integerValue < num2.integerValue
        NSLog(@"num1 < num2");
    }
}

类型判断

在解包时,如果不知道一个NSNumber存储的原始类型,取值时就有可能损失精度,NSValue中提供了- objCType方法,可以获取到存储的原始类型。

  • -objCType
    此对象方法返回包含NSNumer对象中包含的数据的objective-c类型的C字符串。

方法定义

- (const char *)objCType

由@encode()编译器指令编码的包含number对象中包含的数据的object-c类型的C字符串。

返回值是一个纯C字符串, 只有一个字符,含义如下:

字符 值类型
‘c’ char
‘i’ int
‘s’ short
‘l’ long
‘q’ long long
‘I’ unsigned int
‘S’ unsigned short
‘L’ unsigned long
‘Q’ unsigned long long
‘f’ float
‘d’ double

通过以下方法来判断NSNumber的原始类型,如下:

NS_ENUM(NSInteger, NSNumberType) {
    NSNumberTypeUnknow,
    NSNumberTypeFloat,
    NSNumberTypeDouble,
    NSNumberTypeInt,
    NSNumberTypeBOOL,
    NSNumberTypeLong,
};
- (enum NSNumberType)originTypeFromNumber:(NSNumber *)obj {
    enum NSNumberType type = NSNumberTypeUnknow;
    // 判断是不是数字类型
    if ([obj isKindOfClass:[NSNull class]]) {
        return type;
    }
    if ([obj isKindOfClass:[NSNumber class]]) {
        if (strcmp([obj objCType], @encode(float)) == 0) {
            type = NSNumberTypeFloat;
        }
        else if (strcmp([obj objCType], @encode(double)) == 0) {
            type = NSNumberTypeDouble;
        }
        else if (strcmp([obj objCType], @encode(int)) == 0) {
            type = NSNumberTypeInt;
        }
        else if (strcmp([obj objCType], @encode(BOOL)) == 0) {
            type = NSNumberTypeBOOL;
        }
        else if (strcmp([obj objCType], @encode(long)) == 0) {
            type = NSNumberTypeLong;
        }
    }
    return type;
}

- (void)example3 {
    NSNumber *num1 = @(10.9);
    enum NSNumberType type = [self originTypeFromNumber:num1];
    NSLog(@"%ld", (long)type);
}

推荐阅读

目录