2017-02-24 838 views
3

-Wnullable-to-nonnull-conversion编译,我们可以得到下面的代码一个适当的警告:如何在Objective-C中安全地将`_Nullable`转换为`_Nonnull`?

NSString * _Nullable maybeFoo = @"foo"; 
^(NSString * _Nonnull bar) { 
}(maybeFoo); 

Tests.m:32:7: error: implicit conversion from nullable pointer 'NSString * _Nullable' to non-nullable pointer type 'NSString * _Nonnull' [-Werror,-Wnullable-to-nonnull-conversion] 
    }(maybeFoo); 
    ^
1 error generated. 

如何安全地转换fooNSString * _NullableNSString * _Nonnull

我有最好的解决方案至今

我想出的最好的是这个宏:

#define ForceUnwrap(type, nullableExpression) ^type _Nonnull() { \ 
    type _Nullable maybeValue___ = nullableExpression; \ 
    if (maybeValue___) { \ 
    return (type _Nonnull) maybeValue___; \ 
    } else { \ 
    NSLog(@"Attempted to force unwrap a null: " #nullableExpression); \ 
    abort(); \ 
    } \ 
}() 

这是用这样的:

NSString * _Nullable maybeFoo = @"foo"; 
if (maybeFoo) { 
    NSString * _Nonnull foo = ForceUnwrap(NSString *, maybeFoo); 
    ^(NSString * _Nonnull bar) { 
    }(foo); 
} 

和如果分配给错误类型的变量会产生错误:

NSString * _Nullable maybeFoo = @"foo"; 
if (maybeFoo) { 
    NSNumber * _Nonnull foo = ForceUnwrap(NSString *, maybeFoo); 
    ^(NSNumber * _Nonnull bar) { 
    }(foo); 
} 

Tests.m:40:29: error: incompatible pointer types initializing 'NSNumber * _Nonnull' with an expression of type 'NSString * _Nonnull' [-Werror,-Wincompatible-pointer-types] 
     NSNumber * _Nonnull foo = ForceUnwrap(NSString *, maybeFoo); 
          ^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 
1 error generated. 

并产生一个错误,如果投给了错误的类型:

NSString * _Nullable maybeFoo = @"foo"; 
if (maybeFoo) { 
    NSNumber * _Nonnull foo = ForceUnwrap(NSNumber *, maybeFoo); 
    ^(NSNumber * _Nonnull bar) { 
    }(foo); 
} 

Tests.m:40:35: error: incompatible pointer types initializing 'NSNumber * _Nullable' with an expression of type 'NSString * _Nullable' [-Werror,-Wincompatible-pointer-types] 
     NSNumber * _Nonnull foo = ForceUnwrap(NSNumber *, maybeFoo); 
           ^      ~~~~~~~~ 
Tests.m:27:16: note: expanded from macro 'ForceUnwrap' 
type _Nullable maybeValue___ = nullableExpression; \ 
      ^    ~~~~~~~~~~~~~~~~~~ 
1 error generated. 

不幸的是,如果你需要转换为通用型多参数,你不得不求助于preprocessorhacks

NSDictionary<NSString *, NSString *> * _Nullable maybeFoo = 
[NSDictionary<NSString *, NSString *> new]; 
if (maybeFoo) { 
    NSDictionary<NSString *, NSString *> * _Nonnull foo = 
#define COMMA , 
    ForceUnwrap(NSDictionary<NSString * COMMMA NSString *>, maybeFoo); 
#undef COMMA 
    ^(NSDictionary<NSString *, NSString *> * _Nonnull bar) { 
    }(foo); 
} 

事情我已经试过,不工作

分配maybeFoo直接到NSString * _Nonnull不起作用。它产生相同的错误面前:

NSString * _Nullable maybeFoo = @"foo"; 
if (maybeFoo) { 
    NSString * _Nonnull foo = maybeFoo; 
    ^(NSString * _Nonnull bar) { 
    }(foo); 
} 

Tests.m:30:35: error: implicit conversion from nullable pointer 'NSString * _Nullable' to non-nullable pointer type 'NSString * _Nonnull' [-Werror,-Wnullable-to-nonnull-conversion] 
     NSString * _Nonnull foo = maybeFoo; 
           ^
1 error generated. 

而且铸造maybeFooNSString * _Nonnull是不是安全的,因为如果maybeFoo的类型变化,编译器将不会打破:

NSNumber * _Nullable maybeFoo = @"foo"; 
if (maybeFoo) { 
    NSString * _Nonnull foo = (NSString * _Nonnull) maybeFoo; 
    ^(NSString * _Nonnull bar) { 
    }(foo); 
} 
// no errors! 

我也尝试过使用__typeof__,铸造时,但__typeof__携带空性说明符,所以当你尝试转换为__typeof__(maybeFoo) _Nonnull你得到一个非空冲突:

NSString * _Nullable maybeFoo = @"foo"; 
if (maybeFoo) { 
    NSString * _Nonnull foo = (__typeof__(maybeFoo) _Nonnull) maybeFoo; 
    ^(NSString * _Nonnull bar) { 
    }(foo); 
} 

Tests.m:30:57: error: nullability specifier '_Nonnull' conflicts with existing specifier '_Nullable' 
     NSString * _Nonnull foo = (__typeof__(maybeFoo) _Nonnull) maybeFoo; 
                 ^
Tests.m:30:35: error: implicit conversion from nullable pointer 'NSString * _Nullable' to non-nullable pointer type 'NSString * _Nonnull' [-Werror,-Wnullable-to-nonnull-conversion] 
     NSString * _Nonnull foo = (__typeof__(maybeFoo) _Nonnull) maybeFoo; 
           ^
2 errors generated. 

一切都与深静态分析仪运行并与Xcode 8.2.1有以下标志编译:

-Wnon-modular-include-in-framework-module 
-Werror=non-modular-include-in-framework-module 
-Wno-trigraphs 
-Werror 
-Wno-missing-field-initializers 
-Wno-missing-prototypes 
-Wunreachable-code 
-Wno-implicit-atomic-properties 
-Wno-arc-repeated-use-of-weak 
-Wduplicate-method-match 
-Wno-missing-braces 
-Wparentheses 
-Wswitch 
-Wunused-function 
-Wno-unused-label 
-Wno-unused-parameter 
-Wunused-variable 
-Wunused-value 
-Wempty-body 
-Wuninitialized 
-Wno-unknown-pragmas 
-Wno-shadow 
-Wno-four-char-constants 
-Wno-conversion 
-Wconstant-conversion 
-Wint-conversion 
-Wbool-conversion 
-Wenum-conversion 
-Wshorten-64-to-32 
-Wpointer-sign 
-Wno-newline-eof 
-Wno-selector 
-Wno-strict-selector-match 
-Wundeclared-selector 
-Wno-deprecated-implementations 
-Wno-sign-conversion 
-Wno-infinite-recursion 
-Weverything 
-Wno-auto-import 
-Wno-objc-missing-property-synthesis 
-Wno-cstring-format-directive 
-Wno-direct-ivar-access 
-Wno-double-promotion 
+1

你不知道。不要为Objective-C使用该属性。他们是Swift的。 Objective-C有一个明确定义的消息来消除行为。 –

+0

在将'_Nullable'右值传递给'_Nonnull'左值时,clang中会有警告。因此,它们不仅仅适用于Swift。这个问题是关于管理该警告。 –

+1

他们只是为了Swift。编译器不会更改生成的代码的位,这取决于引用的可空性。 –

回答

2

我迄今发现的最好的是与仿制药一招。

本质上你定义了一个使用泛型的接口,并且有一个返回泛型类型为nonnull的方法。然后在你的宏中使用typeof,但是在泛型上,这给你正确的类型。

请注意,泛型类从不实例化,它只是用来获取正确的类型。

@interface RBBBox<__covariant Type> 

- (nonnull Type)asNonNull; 

@end 

#define RBBNotNil(V) \ 
    ({ \ 
     NSCAssert(V, @"Expected '%@' not to be nil.", @#V); \ 
     RBBBox<__typeof(V)> *type; \ 
     (__typeof(type.asNonNull))V; \ 
    }) 

虽然这不是我的想法。来源:https://gist.github.com/robb/d55b72d62d32deaee5fa

+0

我更新了答案中的代码,并附加了一些关于它在新答案中引起的静态分析器警告的附加说明。 https://stackoverflow.com/a/46123981/9636 –

0

Michael Ochs' answer基本上是正确的,但我自从碰到一些静态分析器警告,因为缺乏硬性_Nonnull保证内。总之,我们必须如果我们接到nil否则当我们做这样一个分配abort

@interface Foo : NSObject 
+ (NSString * _Nullable)bar; 
@end 

int main(int argc, char * argv[]) { 
    NSString * _Nonnull bar = RBBNotNil([Foo bar]); 
} 

Release配置(在我的情况下,当归档),静态分析会抱怨你试图将一个_Nullable值分配给一个_Nonnull左值。我收到这样的警告:

nil assigned to a pointer which is expected to have non-null value 

这是我的更新版本:

// We purposefully don't have a matching @implementation. 
// We don't want +asNonnull to ever actually be called 
// because that will add a lot of overhead to every RBBNotNil 
// and we want RBBNotNil to be very cheap. 
// If there is no @implementation, then if the +asNonnull is 
// actually called, we'll get a linker error complaining about 
// the lack of @implementation. 
@interface RBBBox <__covariant Type> 

// This as a class method so you don't need to 
// declare an unused lvalue just for a __typeof 
+ (Type _Nonnull)asNonnull; 

@end 

/*! 
* @define RBBNotNil(V) 
* Converts an Objective-C object expression from _Nullable to _Nonnull. 
* Crashes if it receives a nil! We must crash or else we'll receive 
* static analyzer warnings when archiving. I think in Release mode, 
* the compiler ignores the _Nonnull cast. 
* @param V a _Nullable Objective-C object expression 
*/ 
#define RBBNotNil(V) \ 
_Pragma("clang diagnostic push") \ 
_Pragma("clang diagnostic ignored \"-Wgnu-statement-expression\"") \ 
({ \ 
__typeof__(V) __nullableV = V; \ 
NSCAssert(__nullableV, @"Expected '%@' not to be nil.", @#V); \ 
if (!__nullableV) { \ 
    abort(); \ 
} \ 
(__typeof([RBBNotNil<__typeof(V)> asNonnull]))__nullableV; \ 
}) \ 
_Pragma("clang diagnostic pop") 
+1

对,我通常在释放模式下启用断言,否则在版本中运行的代码不同于调试中的代码。但我想如果你没有这个,这个测试从来没有完成,因此铿锵再次发出警告。 –

相关问题