自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轻量级泛型等语法,各位可以在其他博客或官方文档上查阅。

    原文作者:zenny_chen
    原文地址: https://www.jianshu.com/p/9493db9d3e70
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞