当使用-Wnullable-to-nonnull-conversion
编译时,我们会得到一个正确的警告,代码如下:
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.
如何安全地将foo
从NSString * _Nullable
转换为NSString * _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.
而将maybeFoo
转换为NSString * _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
3条答案
按热度按时间wb1gzix01#
到目前为止,我发现的最好的是泛型的技巧。
实际上,您定义了一个使用泛型的接口,并有一个返回泛型类型
nonnull
的方法。然后在宏中使用typeof
,但在泛型类型上,这会给您正确的类型。请注意,泛型类从不示例化,它只是用来获取正确的类型。
不过,这不是我的主意。来源:https://gist.github.com/robb/d55b72d62d32deaee5fa
yhived7q2#
我使用这个宏:
当然,只有在代码中进行适当的测试之后:
忽略宏,编译器会告诉我参数可能是
NULL
,但processParameters
需要一个非NULL参数。在我的例子中,这甚至被配置为一个错误,而不仅仅是一个警告。省略
if
检查,代码将编译,但如果我输入NULL
,应用程序将当场崩溃。所以你应该只在测试后使用宏,或者如果你绝对确定这个值不可能是NULL
,并且你非常确定,你愿意把你的应用程序稳定性押在上面。如果有疑问,请始终进行测试并记住,如果测试显然是不必要的(例如如果这个条件之前已经被测试过了,如果这个值是
NULL
,那么这个代码将永远不会被达到),编译器将在优化阶段检测到这个情况,并为你删除这个测试。不必要的测试几乎从来都不是性能问题,尤其是对于如此廉价的测试。更新
从Xcode 14.3(LLVM 15)开始,clang不再理解if语句确保
_value
不是NULL
(毕竟abort()
是一个不返回函数),而是仍然抛出错误。参见issue 63018。作为解决方法,您可以使用此宏:
在大多数情况下可以正常工作,但不适用于块,因为您无法取消对块的引用。我仍然希望找到一个更好的解决办法。
这种变化的原因可能是一个旧的雷达:http://www.openradar.me/36877120
同样在两个月前,有人已经在苹果开发者论坛上抱怨了这个变化:https://developer.apple.com/forums/thread/726000
7qhs6swi3#
Michael Ochs' answer基本上是正确的,但我后来遇到了一些静态分析器警告,因为其中缺乏硬
_Nonnull
保证。简而言之,如果我们收到一个nil
,我们必须abort
,否则当我们这样做时:在
Release
配置中(在我的例子中,当存档时),静态分析器将抱怨您试图将_Nullable
值分配给_Nonnull
左值。我收到了这样的警告:这是我的更新版本: