2017-02-24 67 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

對,我通常在釋放模式下啓用斷言,否則在版本中運行的代碼不同於調試中的代碼。但我想如果你沒有這個,這個測試從來沒有完成,因此鏗鏘再次發出警告。 –

相關問題