Objective-C 2.0一开始用在GCC编译器上,后来因为GCC严格的GPL许可证使得Apple不得不寻找新的良好的编译器开源项目,而LLVM很快就被她盯上了。
Objective-C在Clang上发展速度非常快!先后加入了许多出色的语法特性,包括Blocks(在纯C语言上也能使用),ARC,Module等等。这里笔者将列出我们常用的一些新增语法特性,这些语法特性不仅可以在Apple LLVM 9.0编译器上使用,而且也能在Clang 5.0中使用。
1. 关联结果类型
在很早之前,我们写一个类的初始化器往往用 id
关键字,但这个关键字仅仅表明我们所返回的对象类型为一个Objective-C类类型,而不能表征当前类类型本身。这所引发的问题就是我们在用链式方法调用时,由于当前方法所返回的对象类型并非为当前类类型,因而不能直接访问其方法或属性。我们举一个简单的🌰。
/// 定义MyObject,并且声明其init方法的返回类型为id。
/// 这也是传统的使用方法。
@interface MyObject: NSObject
@property (nonatomic, assign) int myValue;
@end
@implementation MyObject
@synthesize myValue;
- (id)init
{
self = super.init;
self.myValue = 10;
return self;
}
@end
// 使用MyObject
int a = MyObject.new.autorelease.myValue;
NSLog(@"a = %d", a);
我们如果要编译上述代码的话,在较新的Apple LLVM上可能仍然没有问题,但如果在一般的Clang编译器上编译的话估计就会有报错或者至少会有warning。原因就是 id
类型不能表明当前对象属于自己所定义的MyObject类型。
因此Apple后来新引入了一个关键字—— instancetype
。这个关键字只能作为一个Objective-C类的类型方法或实例方法的返回类型进行使用,不能用在其他地方,它指明了当前方法所返回的类型即为其自身类型,以此帮助编译器做类型自动推导,因此 instancetype
也被称作“关联结果类型”。
在上述代码中,我们直接把 - (id)init
修改为 -(instancetype)init
即可正常通过编译。
2. 枚举可带有一个基本类型
这个语法扩展是为了能兼容C++11标准新增的对应语法特性。不过各位需要注意的是,此语法扩展只针对Objective-C,而不针对C。不过后续C2X标准也有可能会支持此语法特性。
这个语法特性可以比较灵活地配置当前枚举类型的基本类型,从而可以指定该枚举类型的大小。下面我们举一个简单的🌰。
/// 这里定义了一个uint8_t基本类型的枚举,
/// 其每个枚举值的类型均为uint8_t类型
enum MyEnum: uint8_t
{
MyEnum_HELLO,
MyEnum_HI
};
// 使用MyEnum
enum MyEnum em = MyEnum_HI;
// 这里可以看到,这里MyEnum枚举类型的大小为1字节
NSLog(@"The size is: %zu", sizeof(em));
这个语法特性还是挺给力的。我们之前可能要纠结是让枚举类型作为int类型还是无符号8位整数类型。但现在无需纠结了,我们一般可以让枚举仍然保持为默认的int类型,然后在需要的地方将某些枚举类型定制为8位整数类型,以节省存储空间。
3. 对象字面量
对象字面量是一个比较灵活方便的语法糖,它直接将一些数值类型以及字符串类型的Objective-C对象以字面量的形式给出,而不需要用这些类进行构建。除了字符串字面量比较特殊之外,其他类型的对象字面量均为autorelease的对象。而字符串对象则为全局唯一的对象,我们不用担心它被自动释放。
当前Objective-C直接支持的对象字面量的类型有:NSNumber
,NSString
,NSArray
,NSDictionary
。此外还可以有自定义的对象字面量,这在Objective-C中也称为可装箱表达式(boxable expression)。下面我们来一一介绍。
(1) NSNumber
类型:此类型的对象字面量非常简单,直接在数值之前添加 @ 符号即可。下面以代码的方式举例:
// 整数对象字面量
NSNumber *si = @-100;
// 相当于:
si = [NSNumber numberWithInt:-100];
// 无符号整数对象字面量
NSNumber *ui = @100;
// 相当于:
ui = [NSNumber numberWithUnsignedInt:100];
// 长整型对象字面量
NSNumber *sl = @-10000L;
// 相当于:
sl = [NSNumber numberWithLong:-10000L];
// 无符号长整型对象字面量
NSNumber *ul = @10000UL;
// 相当于:
ul = [NSNumber numberWithUnsignedLong:10000UL];
// long long类型对象字面量
NSNumber *sll = @-1000000LL;
// 相当于:
sll = [NSNumber numberWithLongLong:-1000000LL];
// 无符号long long类型对象字面量
NSNumber *ull = @1000000ULL;
// 相当于:
ull = [NSNumber numberWithUnsignedLongLong:1000000ULL];
// 字符类型对象字面量
NSNumber *ch = @'a';
// 相当于:
ch = [NSNumber numberWithChar:'a'];
// 布尔类型对象字面量
NSNumber *b = @YES;
// 相当于:
b = [NSNumber numberWithBool:YES];
// 单精度浮点对象字面量
NSNumber *f = @3.14f;
// 相当于
f = [NSNumber numberWithFloat:3.14f];
// 双精度浮点对象字面量
NSNumber *d = @3.14159;
// 相当于
d = [NSNumber numberWithDouble:4.14159];
(2) NSString
类型:这个类型的对象字面量是我们非常常用的,我们刚接触Objective-C的时候就会接触此对象字面量。
NSString *str = @"Hello, world. 你好世界!";
(3) NSArray
类型:此类型对象字面量也是非常实用,其形式如下:
/// 定义了一个NSArray数组对象,并用一个数组对象字面量对它初始化
NSArray *array = @[@100, @"Hello", @2.5, @NO];
NSLog(@"array[0] = %@", array[0]);
NSLog(@"array[3] = %@", array[3]);
(4) NSDictionary
类型:此类型的对象字面量非常直观。我们用 @{ } 来封装一组键值对的列表,每个元素的形式为<key> : <value>,相邻两个元素之间用逗号分隔。下面我们看个例子。
NSDictionary *dict = @{
@"key1" : @"value1",
@"key2" : @100,
@3 : @"value3"
};
NSLog(@"key1 value = %@", dict[@"key1"]);
NSLog(@"key2 value = %@", dict[@"key2"]);
NSLog(@"3 value = %@", dict[@3]);
(5) Objective-C可装箱的表达式:从Clang3.7开始,Objective-C引入了可装箱的表达式,可以将任一指定的结构体和联合体对象封装为一个 NSValue
对象。我们可以通过对一个结构体或联合体指定 __attribute__((objc_boxable))
这一属性来指明该类型是可装箱的。对于一个可装箱的对象,我们直接使用 @() 将它包围即可将它转换为对应的Objective-C类型对象。像之前的int、long、long long、const char* 等基本类型均属于可装箱的对象类型。下面我们举些🌰来说明~
/// 这里定义了MyPoint结构体,并且将它声明为可装箱的
struct __attribute__((objc_boxable)) MyPoint
{
float x, y;
};
// 这里定义了MyPoint结构体对象
struct MyPoint mp = { 10.0, -20.0 };
// 这里用一个NSValue对象对装箱的mp进行引用
NSValue *value = @(mp);
// 我们这里再定义一个np的MyPoint结构体对象
struct MyPoint np = { };
// 我们把value存放的值输出给np结构体对象
[value getValue:&np];
NSLog(@"x = %.1f, y = %.1f", np.x, np.y);
int i = 100;
// 对int对象直接装箱为NSNumber对象
NSNumber *ni = @(i);
NSLog(@"ni = %@", ni);
const char *s = u8"Hello, world!";
// 对s对象直接装箱为NSString类型
NSString *ns = @(s);
NSLog(@"ns = %@", ns);
4. 基于数组下标的属性访问模式
在iOS 6.0以及macOS 10.8之后,Apple引入了一套非正式协议(informal protocol)与Objective-C语法直接绑定。当你实现了这其中的方法之后即可使用数组下标来访问属性元素。
在Foundation库中,NSArray类实现了 - (id)objectAtIndexedSubscript:(NSUInteger)idx
方法。因此,我们可以这么来访问数组元素:
NSArray *arr = @[@100, @200, @300];
NSNumber *num = arr[0];
上述代码中的arr[0]就相当于[arr objectAtIndex:0]。
而NSMutableArray在基于NSArray的基础上又实现了 - (void)setObject:(id)anObject atIndexedSubscript:(NSUInteger)index
方法。这样我们可以通过数组下标来修改相应元素,比如:
NSMutableArray *arr = [NSMutableArray arrayWithArray:@[@100, @200, @300]];
arr[2] = arr[0];
而NSDictionary类实现了 - (id)objectForKeyedSubscript:(id)key
方法。这样我们能以数组下标的形式来访问相应键的值。比如:
NSDictionary *dict = @{@"key" : @"value"};
NSString *value = dict[@"key"];
而NSMutableDictionary在NSDictionary类的基础上又实现了 - (void)setObject:(id)object forKeyedSubscript:(id < NSCopying >)aKey
方法。这样,我们能以数组下标的方式来修改相应键的值。比如:
NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithDictionary:@{@"key":"@Hello"}];
dict[dict[@"key"]] = @"world";
下面我们通过实现这四个方法,自己实现一个能同时使用这四种下标方式访问模式的类。
//
// main.m
// objCTest
//
// Created by Zenny Chen on 12-2-7.
// Copyright (c) 2014年 GreenGames Studio. All rights reserved.
//
@import Foundation;
@interface MyContainer : NSObject
{
@private
NSMutableDictionary *mDict;
NSMutableArray *mArray;
}
- (void)setObject:(id)object forKeyedSubscript:(id <NSCopying>)aKey;
- (id)objectForKeyedSubscript:(id)key;
- (void)setObject:(id)anObject atIndexedSubscript:(NSUInteger)index;
- (id)objectAtIndexedSubscript:(NSUInteger)idx;
@end
@implementation MyContainer
- (instancetype)init
{
self = super.init;
mDict = [NSMutableDictionary.alloc initWithDictionary:@{@"key1":@"value1", @"key2":@"value2"}];
mArray = [NSMutableArray.alloc initWithArray:@[@100, @200, @300, @400]];
return self;
}
- (void)dealloc
{
if(mDict != nil)
{
[mDict removeAllObjects];
[mDict release];
mDict = nil;
}
if(mArray != nil)
{
[mArray removeAllObjects];
[mArray release];
mArray = nil;
}
[super dealloc];
}
- (void)setObject:(id)object forKeyedSubscript:(id <NSCopying>)aKey
{
[mDict setObject:object forKey:aKey];
}
- (id)objectForKeyedSubscript:(id)key
{
return [mDict objectForKey:key];
}
- (void)setObject:(id)anObject atIndexedSubscript:(NSUInteger)index
{
const NSUInteger length = mArray.count;
if(index > length)
return;
if(index == length)
[mArray addObject:anObject];
else
[mArray replaceObjectAtIndex:index withObject:anObject];
}
- (id)objectAtIndexedSubscript:(NSUInteger)idx
{
if(idx >= mArray.count)
return nil;
return [mArray objectAtIndex:idx];
}
@end
int main (int argc, const char * argv[])
{
@autoreleasepool
{
// insert code here...
MyContainer *cont = MyContainer.new;
cont[@"mykey"] = @"myvalye";
NSLog(@"key1 is: %@", cont[@"key1"]);
NSLog(@"key2 is: %@", cont[@"key2"]);
NSLog(@"mykey is: %@", cont[@"mykey"]);
cont[4] = @500;
cont[2] = @-300;
NSLog(@"The value[4] = %@", cont[4]);
NSLog(@"The value[3] = %@", cont[3]);
NSLog(@"The value[2] = %@", cont[2]);
}
return 0;
}