OC基础–数据类型与表达式

 前言

做iOS开发有3年了,从当初的小白到现在,断断续续看过很多资料,之前也写过一些博文来记录,但是感觉知识点都比较凌乱。所以最近准备抽时间把iOS开发的相关知识进行一个梳理,主要分为OC基础、UI控件、多线程、动画、网络、数据持久化、自动布局、第三方框架等几个模块进行梳理。本系列的所有博文集合参见:iOS开发知识梳理博文集。本文主要介绍 OC基础–数据类型与表达式。

一 数据类型 

 Objective-C是在C语言基础上拓展出的新语言,所以它是完全兼容C语言代码的,C语言中的基本数据类型如int、float、double和char在Objective-C中是完全可以正常使用的。除此之外,Objective-C还拓展了一些新的数据类型如BOOL、id、instancetype等。

1.1 基本数据类型

因为Objective-C是在C语言基础上拓展出的新语言,所以它是完全兼容C语言代码的,C语言中的基本数据类型都可以正常使用,直接来自C语言中的数据类型如下所示。当然,这些数据类型我们在实际开发过程中很少用到(枚举类型有时候会用到)。

#import <Foundation/Foundation.h>

int main(int argc, const char * argv[]) {
    @autoreleasepool {       
        char    ch = 'a';   //字符型
        short   sh = 4;     //短整型
        int     i8 = 015;   //整型 八进制
        int     i10 = 15;   //整型 十进制
        int     i16 = 0x15; //整型 十六进制
        //输出结果: i8 = 13, i10 = 15 i16 = 21
        NSLog(@"i8 = %d, i10 = %d i16 = %d", i8, i10, i16);
        long    l = 6l;     //长整型
        float   f = 3.4f;   //单精度浮点型
        double  d = 5.6;    //双精度浮点型//枚举类型
enum EnumDemo { Spring, //默认为0,后面依次自增 Summer, Autumn = 3, //可以指定整数,后面的在这个基础上自增 Winter }; //结构体是一种集合,它里面包含了多个变量或数组,它们的类型可以相同,也可以不同,每个这样的变量或数组都称为结构体的成员 //结构体占用的内存大于等于所有成员占用的内存的总和(成员之间可能会存在缝隙) struct StructDemo { NSString *name; int length; }; //共用体占用的内存等于最长的成员占用的内存。共用体使用了内存覆盖技术,共用体的所有成员占用同一段内存,同一时刻只能保存一个成员的值,修改一个成员会影响其余所有成员。 union UnionDemo { int n; char ch; double f; }; } return 0; }

1.1.1 不同数据类型的占用存储空间 

不同的数据类型占用的存储空间不同,同一数据类型在不同编译器环境下占用的存储空间也不一样。各数据类型占用的存储空间如下表所示。

数据类型 16位编译器 32位编译器 64位编译器
char 1byte 1byte 1byte
int 2byte 4byte 4byte
float 4byte 4byte 4byte
double 8byte 8byte 8byte
short int 2byte 2byte 2byte
unsigned int 2byte 4byte 4byte
long 4byte 4byte 8byte
unsigned long 4byte 4byte 8byte
long long 8byte 8byte 8byte

1.1.2 不同数据类型的输出格式

不同数据类型在打印时,我们需要按照指定的格式进行输出,在OC中NSLog输出格式如下表所示。

格式字符 说明 格式字符 说明

带符号十进制 f 小数形式输出,默认输出6位小数
o 无符号八进制 e 指数形式输出,数值不分默认输出6位小数
x 无符号十六进制 g 自动选用%f或%e输出,保证以最简形式输出,并不会输出无意义的0
u 无符号十进制 p 以十六尽职形式输出指针变量所代表的地址值
s 输出C风格字符串 l 用在d、o、x、u之前用于输出长整型;在f、e、g之前用于输出长浮点型
m 用于制定输出数据所占的最小宽度为m位 .n 对于浮点数,表示输出n位小数,对于字符串,表示截取的字符个数
_ 表述输出的数值向左边对齐    

1.2 OC封装的数据类型

除了上面的基本数据类型之外,Objective-C还拓展了一些新的数据类型如BOOL、NSInteger、NSString、CGFloatid、instancetype等。此外,还有NSNumber、NSValue、NSData等封装类型,有NSDictionary、NSArray、NSSet等集合数据类型,有CGRect/NSRect、CGPoint/NSPoint、CGSize/NSSize等尺寸相关的 ,还有NSRange、NSIndex等范围相关。

1.2.1 BOOL/Boolean

Objective-C中的BOOL类型在不同的架构系统上是不一样的,所以在64-bit架构系统下BOOL是对应C语言中的bool,值只能是1(YES)和0(NO),32-bit架构下是无符号字符型。下面是OC中对BOOL的定义 :

#if defined(__OBJC_BOOL_IS_BOOL)
    // Honor __OBJC_BOOL_IS_BOOL when available.
#   if __OBJC_BOOL_IS_BOOL
#       define OBJC_BOOL_IS_BOOL 1
#   else
#       define OBJC_BOOL_IS_BOOL 0
#   endif
#else
    // __OBJC_BOOL_IS_BOOL not set.
#   if TARGET_OS_OSX || TARGET_OS_MACCATALYST || ((TARGET_OS_IOS || 0) && !__LP64__ && !__ARM_ARCH_7K)
    //非64-bit架构或非__ARM_ARCH_7K
#      define OBJC_BOOL_IS_BOOL 0
#   else
    //64-bit架构并且__ARM_ARCH_7K
#      define OBJC_BOOL_IS_BOOL 1
#   endif
#endif

//所以在64-bit架构系统下BOOL是对应C语言中的bool,否则是无符号字符型
#if OBJC_BOOL_IS_BOOL
    typedef bool BOOL;
#else
#   define OBJC_BOOL_IS_CHAR 1
    typedef signed char BOOL; 
    // BOOL is explicitly signed so @encode(BOOL) == "c" rather than "C" 
    // even if -funsigned-char is used.
#endif

#define OBJC_BOOL_DEFINED

#if __has_feature(objc_bool)
#define YES __objc_yes
#define NO  __objc_no
#else
#define YES ((BOOL)1)
#define NO  ((BOOL)0)
#endif
//用iPhone5和iPhone8模拟器做个实验

BOOL isOK = 23;
NSLog(@"%d", isOK);

// iPhone5的打印结果 23
// iPhone8的打印结果 1

Objective-C中的Boolean类型其实就是一个无符号字符型

/********************************************************************************

    Boolean types and values
    
        Boolean         Mac OS historic type, sizeof(Boolean)==1
        bool            Defined in stdbool.h, ISO C/C++ standard type
        false           Now defined in stdbool.h
        true            Now defined in stdbool.h
        
*********************************************************************************/
typedef unsigned char                   Boolean;

1.2.2 NSInteger

OC中的NSInteger就是对整型的一个封装,64-bit系统上NSInteger对应的是长整形,32-bit系统上对应的是整型。具体我们可以看OC中的源码的定义

//64-bit系统上NSInteger对应的是长整形,32-bit系统上对应的是整型
#if __LP64__ || 0 || NS_BUILD_32_LIKE_64
typedef long NSInteger;
typedef unsigned long NSUInteger;
#else
typedef int NSInteger;
typedef unsigned int NSUInteger;
#endif

1.2.3 数值封装类型 NSNumber、NSValue、NSData

我们在编码中,很多时候需要将C里面原生的数据 (通常是一些结构体) 封装成对象,这样可以用NSDictionary或者NSArray来存取访问。尤其是一些做适配的情况下,这种封装是不可避免的。Objective-C提供了不少类可以帮助我们,比较常见的是NSNumber,NSValue和NSData。

NSValue主要就是将这些原生的数据封装成对象,方便我们进行存储访问。NSValue主要用来封装自定义的数据结构,可以是系统框架提供的CGRect/CGPoint/CGSize等数据结构,也可以是自己定义的struct。

//封装
+ (NSValue *)valueWithBytes:(const void *)value objCType:(const char *)type;

//解封
- (void)getValue:(void *)value;

//此外还提供了对基本的尺寸范围相关的封装和解封 + (NSValue *)valueWithPoint:(NSPoint)point; + (NSValue *)valueWithSize:(NSSize)size; + (NSValue *)valueWithRect:(NSRect)rect; + (NSValue *)valueWithEdgeInsets:(NSEdgeInsets)insets API_AVAILABLE(macos(10.10), ios(8.0), watchos(2.0), tvos(9.0)); @property (readonly) NSPoint pointValue; @property (readonly) NSSize sizeValue; @property (readonly) NSRect rectValue; @property (readonly) NSEdgeInsets edgeInsetsValue API_AVAILABLE(macos(10.10), ios(8.0), watchos(2.0), tvos(9.0));

使用NSValue对NSPoint进行封装、解封的示例代码如下。

NSPoint point = NSPointFromCGPoint(CGPointMake(0, 0));
// NSDictionary *dic = @{@"point" : point};  //报错,字典中必须是对象
        
//通过转化为NSValue进行保存
NSValue *value = [NSValue valueWithPoint:point];
NSDictionary *dic = @{@"point" : value};
//从字典中获取NSValue,并从该对象中获取对应的NSPoint值
NSValue *vv = dic[@"point"];
NSPoint pp = [vv pointValue];
NSLog(@"%@", NSStringFromPoint(pp));

使用NSValue对自定义结构体进行封装和解封的示例代码如下。

//结构体定义
typedef struct StructDemo {
    NSString *name;
    int age;
} StructDemoTag;

//数据创建
StructDemoTag stDemo = {@"zhangsan", 12};

//封装
NSValue *vv2 = [NSValue value:&stDemo withObjCType:@encode(StructDemoTag)];

NSDictionary *dic2 = @{@"struct_demo" : vv2};

//解封
NSValue *vv3 = dic2[@"struct_demo"];
StructDemoTag stDemo2 = {};
[vv3 getValue:&stDemo2];

NSLog(@"name: %@, age: %d", stDemo2.name, stDemo2.age);
//name: zhangsan, age: 12

NSNumber继承自NSValue,主要是用来封装ANSI C内置的数据,比如char,float,int等等。这个类提供了一些封装/解封的方法,这个使用方法很简单,就不展示了。

//封装方法
+ (NSNumber *)numberWithChar:(char)value;
+ (NSNumber *)numberWithUnsignedChar:(unsigned char)value;
+ (NSNumber *)numberWithShort:(short)value;
+ (NSNumber *)numberWithUnsignedShort:(unsigned short)value;
+ (NSNumber *)numberWithInt:(int)value;
+ (NSNumber *)numberWithUnsignedInt:(unsigned int)value;
+ (NSNumber *)numberWithLong:(long)value;
+ (NSNumber *)numberWithUnsignedLong:(unsigned long)value;

//解封方法
- (char)charValue;
- (unsigned char)unsignedCharValue;
- (short)shortValue;
- (unsigned short)unsignedShortValue;
- (int)intValue;
- (unsigned int)unsignedIntValue;
- (long)longValue;
- (unsigned long)unsignedLongValue;

NSData主要是提供一块原始数据的封装,将一些图片、文件、字符串等数据转化为字节流数据,方便数据的封装和流动,比较常见的是NSString/NSImage以及文件数据的封装与传递。在应用中,最常用于访问存储在文件中或者网络资源中的数据。一般解封方法在图片UIImage、字符串NSString中有对应的从NSData数据创建。

//以下类方法全部都有成员方法的实现和接口,这里不一一展示

//直接从data封装
+ (instancetype)dataWithData:(NSData *)data;

//指定长度的封装
+ (instancetype)dataWithBytes:(nullable const void *)bytes length:(NSUInteger)length;

//封装文件对应的
+ (nullable instancetype)dataWithContentsOfFile:(NSString *)path;
+ (nullable instancetype)dataWithContentsOfFile:(NSString *)path options:(NSDataReadingOptions)readOptionsMask error:(NSError **)errorPtr;

//封装url对应的
+ (nullable instancetype)dataWithContentsOfURL:(NSURL *)url;
+ (nullable instancetype)dataWithContentsOfURL:(NSURL *)url options:(NSDataReadingOptions)readOptionsMask error:(NSError **)errorPtr;

NSData在字符串中的使用示例代码如下:

NSString *str = @"hello object-c";
//封装
NSData *data = [NSData dataWithBytes:[str UTF8String] length:str.length];
//解封
NSString *str2 = [NSString stringWithUTF8String:[data bytes]];
NSLog(@"str:%@", str2);   

1.2.4 字符串NSString/NSMutableString

Objective-C里核心的处理字符串的类就是NSString和NSMutableString这两个类,这两个类完成了Objective-C中字符串大部分功能的处理。字符串内容比较多,我们后面单独写一篇,到时候链接补上来。

1.2.5 集合数据类型

 OC中的集合框架主要就是数组(NSArray / NSMutableArray、字典(NSDictionry / NSMutableDictionry)、集合(NSSet / NSMutableSet)。这一部分内容也比较多,我们后面也会单独补充一篇,到时候链接补上来。

1.2.6 尺寸、范围相关的类型

Object-C中有CGRect/NSRect、CGPoint/NSPoint、CGSize/NSSize等尺寸相关的,其实CG开头的和NS开头的都是一个东西,都是struct定义的尺寸相关的结构体,只是定义在不同的框架中。

/* Points. */
struct CGPoint {
    CGFloat x;
    CGFloat y;
};
typedef struct CG_BOXABLE CGPoint CGPoint;

//NSPoint就是CGPoint
typedef CGPoint NSPoint;


/* Sizes. */

struct CGSize {
    CGFloat width;
    CGFloat height;
};
typedef struct CG_BOXABLE CGSize CGSize;

//NSSize就是CGSize
typedef CGSize NSSize;

/* Rectangles. */
struct CGRect {
    CGPoint origin;
    CGSize size;
};
typedef struct CG_BOXABLE CGRect CGRect;

//NSRect就是CGRect
typedef CGRect NSRect;
CGRect rect = CGRectMake(0, 0, 100, 200);
NSRect rect2 = NSRectFromCGRect(rect);

CGPoint point = CGPointMake(2, 3);
NSPoint point2 = NSPointFromCGPoint(point);

CGSize size = CGSizeMake(100, 50);
NSSize size2 = NSSizeFromCGSize(size);

NSRange range = NSMakeRange(0, 2);

 各种尺寸相关的结构体类型数据在OC中打印该如何打印呢?我们需要将结构体类型转为字符串进行打印,系统提供了相应的方法。示例代码如下。

CGRect rect = CGRectMake(0, 0, 100, 200);
NSRect rect2 = NSRectFromCGRect(rect);
NSLog(@"%@", NSStringFromRect(rect));

CGPoint point = CGPointMake(2, 3);
NSPoint point2 = NSPointFromCGPoint(point);
NSLog(@"%@", NSStringFromPoint(point));

CGSize size = CGSizeMake(100, 50);
NSSize size2 = NSSizeFromCGSize(size);
NSLog(@"%@", NSStringFromSize(size));

NSRange range = NSMakeRange(0, 2);
NSLog(@"%@", NSStringFromRange(range));