How to Make Compiler Assume `nullable` by Default - objective-c

Since Xcode 6.3, types in Objective-C can be marked with nullable or nonnull, here is Apple's blog post about this.
Problem is, when neither is specified, then the compiler imports the Objective-C code as implicitly unwrapped into Swift, e.g. NSView!. So when an object actually is nil, then it will crash when accessed from Swift. This does not produce a compiler error.
As this is extremely prone to fail, I'd like the compiler to assume everything from Objective-C by default as nullable, except when otherwise specified via nonnull, or the audited region macros NS_ASSUME_NONNULL_BEGIN / END. How can that be achieved?

Not exactly what you are looking for but from Xcode 7, you can turn on CLANG_WARN_NULLABLE_TO_NONNULL_CONVERSION in LLVM complier settings by passing -Wnullable-to-nonnull-conversion flag in complier flag.
This will warn if there is a implicit conversion from nullable to non-nullable conversion.

Seems like there is no way around adding nullability annotations in order to get rid of implicitly unwrapped optionals.
I wrote a script that finds all non-annotated headers which can also be added as a build phase, so no headers are overlooked.

Since the macro is defined as:
#define NS_ASSUME_NONNULL_BEGIN _Pragma("clang assume_nonnull begin")
#define NS_ASSUME_NONNULL_END _Pragma("clang assume_nonnull end")
It may be worth trying to see if there's a corresponding assume_nullable macro.
#define XX_ASSUME_NULLABLE_BEGIN _Pragma("clang assume_nullable begin")
#define XX_ASSUME_NULLABLE_END _Pragma("clang assume_nullable end")
I only did a quick test to check if this pragma fails compilation, which it didn't. But I haven't tested whether this produces the expected results in Swift.

Related

Swift conditional compiling didn't work properly when I use macro defined in objective-c

I define a simple macro in objective-c header file, and import this header file into Swift through project bridging header. I was able to use this macro as a constant in Swift, but when I use it to do conditional compiling, it doesn't work properly.
I create a simple project in Xcode 10.2.1 and add some code to reproduce it.
In ViewController.h
#define TEST_FLAG 1
#interface ViewController : UIViewController
#end
In ViewController.m
#import "testMacro-Swift.h"
- (void)viewDidLoad {
[super viewDidLoad];
SwiftClass *s = [[SwiftClass alloc] init];
[s printMSG];
#if TEST_FLAG
NSLog(#"Objc works.");
#endif
}
In testMacro-Bridging-Header.h
#import "ViewController.h"
SwiftFile
#objc class SwiftClass: NSObject {
#objc func printMSG() {
print("Macro \(TEST_FLAG)")
#if TEST_FLAG
print("compiled XXXxXXXXX")
#endif
}
}
Console Output
Macro 1
2019-07-03 14:38:07.370231-0700 testMacro[71724:11911063] Objc works.
I expected compiled XXXxXXXXX to be printed after Macro 1, but it not.
I am curious why this will happen.
My project is mixed with objc and swift. I don't want to declare a same flag in swift.
Based on this Apple article, https://developer.apple.com/documentation/swift/imported_c_and_objective-c_apis/using_imported_c_macros_in_swift, simple C (and Objective-C) macros are imported into Swift as global constants. This is demonstrated by the output from your line
print("Macro \(TEST_FLAG)")
The snippet
#if TEST_FLAG
print("compiled XXXxXXXXX")
#endif
uses a different TEST_FLAG, which is a Swift preprocessor flag. You could define it under Build Settings -> Active Compilation Conditions as TEST_FLAG or under Build Settings -> Other Swift Flags as -DTEST_FLAG.
The above explains why this happens. I can't think of a simple way to avoid defining the same flag separately for Objective-C and Swift preprocessor in Xcode. If you just want to control whether some Swift code is executed based on the TEST_FLAG, you can do something like this:
if TEST_FLAG != 0 {
print("compiled XXXxXXXXX")
}
However, if you want to control compilation of the code, then you may have to use separate TEST_FLAGs for Objective-C and Swift and make sure they are consistent. To aid in making them consistent, you could set the TEST_FLAG used by Objective-C code in Other C Flags, which allow you to define different flags for different SDKs, Architectures, and build types (Release/Debug). Active Compilation Conditions allow the same flexibility.
Another trick to facilitate consistency between (Objective-)C and Swift compiler flags is to create a new user-defined build setting: click on + to the left of the search box under Build Settings.
Say, call it COMMON_TEST_FLAG and set its value to TEST_FLAG. Then add -D$(COMMON_TEST_FLAG) to Other C Flags and Other Swift Flags. Now when you build your code TEST_FLAG will be defined in both Objective-C and Swift code within your target. If you don't want it to be defined, just change the value of COMMON_TEST_FLAG to something else. A couple of things to watch for, though:
You cannot make COMMON_TEST_FLAG empty: this will cause the other
flags to be just -D, leading to a build error.
Make sure the value of COMMON_TEST_FLAGdoes not conflict with
macros defined elsewhere.
Swift's preprocessor is (intentionally) waaaaaaay more limited than C's. Macros come with really serious draw backs.
They define regions of code that aren't active in every build target. Because of this, your tests won't be hitting every branch (or even compiling every branch). This quickly becomes a maintenance disaster. For n unique flags, there are 2^npossible sets of values and thus,2^n` possible builds. Do you have to test each of them? Maybe, maybe not, but even just reasoning about what to test isn't easy.
They can get tangled up and incredibly complex, especially with nested blocks.
In general, try to express as much of your code in the main programming language (C, ObjC, Swift), and resort to using macros only when there's a good reason, such as:
Reducing repetition in a way that can't be done with a function.
Improving performance in a critical region of code (by forcing inlining of macro code, e.g. #define max(a, b) ((a) < (b) ? (b) : (a))). Though this is exceptionally rare.
Expressing a piece of logic that can't be expressed in the language. For example, there's no way to express if #available(...) in Swift, without using the preprocessor.
Your example doesn't meet any of these criteria. It's increasing repetition, it's not performance-critical, and it's not doing something that can't be done in regular Swift code.
A much better approach to this (in both Swift and Objective C), is to create a logger that is initialized with different configurations between debug and release builds. Your viewDidLoad method should not concern itself whether or not TEST_FLAG is set. View controllers should control views, not make decisions as to what things should be logged. It should just call the logger to send off whatever log messages it wants to send, and leave it up to the logger to decide how to log those messages (to an output stream, file, in-memory circular buffer, database, stream to an analytics API, ignore them, etc.)

Does clang-format recognize new NS_CLOSED_ENUM Objective-C macro?

As of Xcode 10.2, there is a new macro for specifying enums in Objective-C, NS_CLOSED_ENUM. Using this in place of NS_ENUM is required, to allow those enums to be used in Swift, as if they were declared in Swift (meaning without either requiring an #unknown handler in switch statements, or getting a warning). See here for more.
It appears that clang-format has not caught up to this new reality, and won't recognize
typedef NS_CLOSED_ENUM(...
the same as it does
typedef NS_ENUM(...
Is this true?
As of these commits (around July 2019), clang-format apparently does support NS_CLOSED_ENUM.

"Initializer is not a compile time constant" error works in .mm?

I'm puzzled by a situation I'm observing and would love some insight. First, I'm using Xcode 5, with LLVM 5 compiler options set to defaults.
I have a line in a .m file such as:
static NSArray * const kSchemaVersions = #[#"1"];
And, as expected, I see a compiler error saying Initializer element is not a compile-time constant.
However, if I place this same line in a .mm (Objective C++) file, the compiler does not complain.
I completely understand why it shouldn't work, but I'm baffled as to why it appears to.
Thoughts?
As you mentioned, in C and Objective-C static variables can only be initialised with compile-time constants. In C++ (and therefore Objective-C++) on the other hand, static variables are assigned at run time, before main runs.
For more details have a look at Eli Bendersky's Non-constant global initialization in C and C++

Pragma to explicitly enable ARC?

Is there a #pragma (or otherwise some construct) to explicitly enable automatic reference counting (ARC) in an Objective-C (or Objective-C++) source file? Even better if the source file can cause compilation to fail if ARC is not enabled.
I'm starting to have a number of ARC-only source files that can be potentially shared with other projects. Most of these contain category methods to extend built-in classes. I just don't want to accidentally include these in a non-ARC project and starts leaking out memory.
Thanks in advance!
As far as I can tell there is no way to explicitly enable or disable ARC.
However it is possible to detect if it is enabled. Simply add the following snippet to any file that requires ARC.
#ifndef __has_feature
#define __has_feature(x) 0 /* for non-clang compilers */
#endif
#if !__has_feature(objc_arc)
#error ARC must be enabled!
#endif
More info:
http://clang.llvm.org/docs/AutomaticReferenceCounting.html
http://clang.llvm.org/docs/LanguageExtensions.html#__has_feature_extension

Why is it NOT a compiler error to specify a protocol but not implement required methods?

In objective-c, I can do something like
#interface MyViewController : UIViewController <UITextInputDelegate>
to create the specification for a class MyViewController which implements the UITextInputDelegate protocol. Now, this protocol has several required methods, so I would think that the compiler would not let the code compile at all unless those methods did in fact have implementations in the .m file. Nonethless, this compiles. The compiler does spout warnings, so obviously it's detecting that I didn't implement the required methods, but I'm wondering why it makes sense to allow this to compile at all, on the part of the language designer.
You get a compiler error when the code can't be compiled. Not implementing a method doesn't prevent the code from being compiled because objective-c is a dynamic language. That means that the methods aren't directly linked, so their location doesn't need to be known at compile time. Warnings mean that there is something which could cause errors at runtime, but that the code successfully compiled.
If you want to turn this warning into an error then just add -Werror=protocol into Other Warning Flags in the Build Settings.
As ughoavgfhw pointed out, it's not an error because the dynamic nature of the language allows for those methods to be added at runtime. Just because the method isn't found at compile time doesn't mean it won't be there at run time.