自Objective-C 2.0以来的新增语法特性

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直接支持的对象字面量的类型有:NSNumberNSStringNSArrayNSDictionary。此外还可以有自定义的对象字面量,这在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;
}

还有其他一些诸如ARC、Module轻量级泛型等语法,各位可以在其他博客或官方文档上查阅。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,591评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,448评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,823评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,204评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,228评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,190评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,078评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,923评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,334评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,550评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,727评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,428评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,022评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,672评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,826评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,734评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,619评论 2 354

推荐阅读更多精彩内容