OC 语言新特性

前言

  • 相对于 Java,OC 语言是一门古老的语言了,而它又是一门不断发展完善的语言。一些新的编译特性,为 OC 语言带来了许多新的活力。在 Xcode7 中,iOS9 的 SDK 已经全面兼容了 Objective-C 的一些新特性和新功能。这些功能都只作用于编译期,对程序的运行并没有影响,因此,它可以很好的向下进行兼容,无缝的衔接低版本的 iOS 系统,如果可以将这些新特性都应用于开发,开发效率和代码质量,相比之前会有一个很大的提升。

1、可选类型检测

  • 在 swift 语言中,通过 ! 和 ? 可以将对象声明成 Optional,用于在开发中标记这个对象是否可以为空。在 OC 中,以前是没有这样的功能的,因此我们在开发中会经常遇到因为某个函数应该返回实例而返回了空导致的崩溃。Nullability 的主要用武之地,就是在这里,它可以起到提示开发者做是否为空的判断的提示。这一特性在 Xcode6.3 中就已经支持,但在 Xcode7 中又做了一些写法上的小改动。

  • Xcode7 中,系统的框架中已经支持了 Nullability,如下所示,这是 NSArray 中的两个属性,其中 nullable 关键字说明了这里可能返回空的值。

    1
    2
    @property (nullable, nonatomic, readonly) ObjectType firstObject;
    @property (nullable, nonatomic, readonly) ObjectType lastObject;
  • 如果仅仅是在返回值中给开发者一些提示,可能觉得应用并不大,对开发者最大的帮助是这一特性可以用于函数的参数中,这样我们在调用函数时起到的提示作用,将是非常重要的,越是多人合作的项目,作用也越大。例如以下方法中,我们在调用函数时,如果传入了空值,编译器会给我们警告。

    1
    2
    3
    - (void)setName:(NSString * _Nonnull)name {

    }
  • 修饰参数:

    • 属性声明中:

      1
      2
      3
      4
      nonnull             不可为空
      nullable 可以为空
      null_unspecified 不确定是否可以为空(极少情况)
      null_resettable set 方法可以为 nil,get 方法不可返回 nil,只能用在属性的声明中。
    • 方法(函数)参数中:

      1
      2
      3
      _Nonnull            不可为空
      _Nullable 可以为空
      _Null_unspecified 不确定是否可以为空(极少情况)
  • iOS9 的 SDK 中已经完全兼容使用了这些特性,并且 nonnull 的使用会比 nullable 广泛的多,系统提供了这样一对宏,我们在这对宏之间定义的变量都会加上 nonnull 的修饰符,只有我们特殊声明 nullable 的才需要手动写。

    1
    2
    3
    #define NS_ASSUME_NONNULL_BEGIN _Pragma("clang assume_nonnull begin")

    #define NS_ASSUME_NONNULL_END _Pragma("clang assume_nonnull end")
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    #import <Foundation/Foundation.h>

    NS_ASSUME_NONNULL_BEGIN

    @interface NSData (FormData)

    - (void)q_setHttpHeaderFieldWithRequest:(NSMutableURLRequest *)request fileBoundary:(nullable NSString *)fileBoundary;

    @end

    NS_ASSUME_NONNULL_END

2、泛型

  • Xcode 7 对系统中常用的一系列容器类型都增加了泛型支持(),有了泛型后就可以指定容器类中对象的类型了。假如向泛型容器中加入错误的对象,编译器会报警告。

    • 泛型声明格式:在声明类的时候,在类型后面<泛型名称>
    • 泛型定义格式:放在限制的类型后面<类型>

    • 泛型好处:

      • 1、提高程序员开发规范,一看就知道是什么类型。
      • 2、限制类型,不允许传入其他的类型。
      • 3、从集合中取出来,直接可以使用点语法。
    • 泛型开发中使用场景:

      • 1、一般都是用来限制集合类型。
    • 泛型不定义就是 id。

    • 泛型定义是独立,只能修饰当前对象。

    • 疑问:谁才能使用泛型?只要声明了泛型的类,才能定义。

    • 自定义泛型:

      • 开发中使用场景:当声明一个类,有个属性不确定类型,当创建对象的时候才确定这个属性是什么类型的时候,就才可以采取使用泛型。

2.1 有类型约定的集合

  • 在 Xcode7 中,我们可以给集合类型添加一个泛型的约定,如下,声明了这样一个数组后,就好比告诉了编译器,这个数组中的数据类型都是 NSString 类型的。

    1
    NSMutableArray<NSString *> *array = [[NSMutableArray alloc] init];
    • 如果用这个数组中元素的方法,会出现如下提示。使用点语法可以访问到数组中泛型的方法了。

      OcNew1

    • 在我们向这个数组中追加元素的时候,编译器将元素的类型提示了出来,并且将 FromArray 方法中需要的元素类型也提示了出来。

      OcNew2

    • 同样,如果我们向这个数组中追加类型不匹配的元素,如下,编译器会给我们一个警告。

      OcNew3

2.2 类型通配符

  • 观察 Xcode7 中 iOS 系统的类,我们可以发现这么一个好玩的东西:ObjectType。它既不是一个类型,也不是关键字,然而却大量存在,如下是系统的 NSMutableArray 的头文件。这个 ObjectType 其实只是一个类型标识符,它具体怎么写并不重要,只是系统中都约定使用了 ObjectType,你也可以在自己的类中按自己的喜好来命名。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    @interface NSMutableArray<ObjectType> : NSArray<ObjectType>
    - (void)addObject:(ObjectType)anObject;
    - (void)insertObject:(ObjectType)anObject atIndex:(NSUInteger)index;
    - (void)removeLastObject;
    - (void)removeObjectAtIndex:(NSUInteger)index;
    - (void)replaceObjectAtIndex:(NSUInteger)index withObject:(ObjectType)anObject;
    - (instancetype)init NS_DESIGNATED_INITIALIZER;
    - (instancetype)initWithCapacity:(NSUInteger)numItems NS_DESIGNATED_INITIALIZER;
    - (nullable instancetype)initWithCoder:(NSCoder *)aDecoder NS_DESIGNATED_INITIALIZER;
    @end
  • 创建一个类,继承于 NSObject,取名叫 MyArray:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    // MyArray.h

    #import <Foundation/Foundation.h>

    // 这个类型通配符只能在 interface 里使用,作用域为 @interface 到 @end 之间,这里我使用 Type 来做这个通配符
    @interface MyArray<Type> : NSObject

    @property(nonatomic, retain, nonnull) NSMutableArray<Type> *array;

    - (void)addObject:(nonnull Type) obj;

    @end

    // MyArray.m

    @implementation MyArray

    - (void)addObject:(id) obj {

    [_array addObject:obj];
    }

    - (instancetype)init {

    self = [super init];
    if (self) {

    _array = [[NSMutableArray alloc] init];
    }
    return self;
    }

    - (NSString *)description {

    NSMutableString * str = [[NSMutableString alloc] init];

    for (int i = 0; i < _array.count; i++) {

    [str appendString:[NSString stringWithFormat:@"%@\n", _array[i]]];
    }
    return str;
    }

    @end
  • 我们在使用这个自定义的集合类型时,就会有和系统一样的效果了:

    OcNew4

2.3 多参数的泛型集合

  • 多参数的泛型集合,有一个非常好的例子,就是 NSDictionary,在 Xcode7 中我们可以这样写字典。可以看到,字典键值的类型编译器为我们提示了出来,结合上面类型通配符的使用,对于多参的集合,将参数类型用 “,” 隔开即可。

    OcNew5

2.4 协变性与逆变性

  • 因为有了泛型集合的概念,相比之前,我们的类型实际上更加复杂了,比如还拿我们自定义的集合类型来举例,array 和 muArray 在编译器看来已经是不同的类型,如果我们强行转换,会报如下的警告:

    1
    2
    MyArray<NSString *> *array;
    MyArray<NSMutableString *> *muArray;


OcNew6

  • 因此,就有了逆变和协变这个概念,不指定泛型类型的对象可以和任意泛型类型转化,但指定了泛型类型后,两个不同类型间是不可以强转的,假如你希望主动控制转化关系,就需要使用泛型的协变性和逆变性修饰符。

    1
    2
    __covariant     :协变性,子类型可以强转到父类型(里氏替换原则)。
    __contravariant :逆变性,父类型可以强转到子类型。

  • 上面的情况,我们将自定义的类做如下修改,就不会出现警告:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    // MyArray.h

    #import <Foundation/Foundation.h>

    // 在 @interface 中加入 __covariant 协变性
    @interface MyArray<__covariant Type> : NSObject

    @property(nonatomic, retain, nonnull) NSMutableArray<Type> *array;

    - (void)addObject:(nonnull Type) obj;

    @end

    MyArray<NSString *> *array;
    MyArray<NSMutableString *> *muArray;

    // NSMutableString 是 NSString 的子类
    array = muArray;

  • NSMutableString 是 NSString 的子类,在 MyArray 定义中加入了 __covariant 可以进行转换。但将 MyArray<NSString *> 转换为 MyArray<NSMutableString *> 时仍会报警告。

    OcNew7

3、类型延拓符

  • 在开发中,开发者经常会遇到这样的情况,例如通过 tag 获取某些 UI 控件时,viewWithTag 方法通常会返回给我们一个 UIView 类型的指针,这就需要开发者手动的强转一下,十分麻烦。新增加的 __kindof 修饰符可以帮助我们解除这个烦恼。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    // MyArray.h

    #import <Foundation/Foundation.h>

    @interface MyArray<__covariant Type> : NSObject

    @property(nonnull, retain, nonatomic) NSMutableArray<UIView *> * viewArray;

    @property(nonatomic, retain, nonnull) NSMutableArray<Type> *array;

    - (void)addObject:(nonnull Type) obj;

    @end
    • 创建一个自定义的数组对象,并向其中添加一个 UIButton,我们会看到有如下一个警告:

      OcNew8

    • 这也是我们开发中常遇到的问题,以前需要强转。但是以后就不需要了,我们在声明这个数组时加上一个 __kindof 修饰符。警告就消失了,这个修饰符就是告诉编译器,这里可以返回 UIView 的子类指针。

      1
      @property(nonnull, retain, nonatomic) NSMutableArray<__kindof UIView *> * viewArray;
  • id,instancetype,__kindof 作为返回值时的比较:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    id
    优点:可以调用任何对象方法。
    缺点:不能使用点语法,不能做编译检查。

    Xcode5 之前,返回 id

    instancetype
    优点:会自动识别当前类的对象.

    Xcode5 instancetype

    __kindof:
    优点:调用方法时,通过返回值提示,可以看到具体的返回类型,如:Person *,而前两者不会看到。

    xcode7 __kindof:表示当前类或者子类。
文章目录
  1. 1. 前言
  2. 2. 1、可选类型检测
  3. 3. 2、泛型
    1. 3.1. 2.1 有类型约定的集合
    2. 3.2. 2.2 类型通配符
    3. 3.3. 2.3 多参数的泛型集合
    4. 3.4. 2.4 协变性与逆变性
  4. 4. 3、类型延拓符
隐藏目录