There are many cases in which macOS applications should take advantage of historical processes and existing code, and there are many cases that cannot be completed using Swift alone. Therefore, Objective-C code and C/C++ code coexist.
When writing code in such a situation where multiple languages coexist, I strongly feel that we should brush up the coexisting Objective-C code to make it easier to use from Swift, and put in precautions to prepare it for use from Swift and for when it is incorporated into other projects.
Declare that it is ARC compliant
This is a precautionary measure in case it is incorporated into another project.
If your code has been maintained since the days before macOS 10.6, some code is non-compliant with ARC (Automatic Reference Counting). Where we can say with confidence that this is not the case is in programs that have caught on quickly at a cost, or in small to medium-sized programs. For larger programs, it is difficult to implement ARC in the middle of a program.
In my case, I have not implemented ARC because it is a large program. However, an opportunity arose to port a small portion of the code to a program written in Swift. Most of the project to be ported is written in Swift, and some Objective-C code is also ARC-compatible. We could set it up so that only the source files to be ported are exempt from ARC, but that would be a bad idea for the future. We thought we had a good opportunity to brush up and modified the code to be ARC-compatible before incorporating it.
However, it is highly likely that this code will be ported again to some other project. It may be ported again to a project that does not support ARC. In case that happens, the following code should be written.
#if !__has_feature(objc_arc)
#error This source file must be compiled with ARC.
#endif
This code will cause a build error if you try to build in a project that does not support ARC. The source file will be built with ARC enabled in the project where the error occurred. Or, depending on the size of the project, the entire project may be ARC-enabled.
Even if you put it in a comment or something, they probably won’t see it. They won’t notice. They will only notice when an error occurs.
Conversely, incorporating non-ARC compliant code into an ARC compliant project will result in an error. Therefore, the reverse case can be noticed immediately.
Preparing for Swift’s Optional
Swift has a feature called “Optional”. It allows code to indicate whether a property, argument, or function return value can be nil
.
There is no “Optional” in Objective-C. Objective-C is a language characterized by the fact that anything you do to an instance that is nil
will be ignored. In a sense, it assumes that all instances will be nil
.
This is incompatible with Swift; it is an element that gets in the way when calling Objective-C methods from the Swift.
So Objective-C has been improved: you can now write on the Objective-C whether or not it can be nil
, so that when you use it from Swift, it will be mapped based on the code to an optional type and a non-optional type. This allows us to map between optional and non-optional types based on the code.
Specify nonnull
when there is no possibility of being nil
and nullable
when there is a possibility of being nil
.
The nullability can be specified as a return value, argument, property, etc. Of course, when you write this, be sure that the implementation of the method is as specified, rather than just writing it as is. If you specify nonnull
but nil
, Swift will have a problem with it. They are too bad.
@interface MyObject : NSObject
// nilにならないプロパティ
@property (nonnull, nonatomic, strong) NSString *firstName;
@property (nonnull, nonatomic, strong) NSString *lastName;
// nilになるかもしれないプロパティ
@property (nullable, nonatomic, strong) NSString *middleName;
// メソッドの戻り値や引数にも指定する
- (nullable MyObject *)nextObject;
- (nonnull NSString *)fullNameWithPrefix:(nullable NSString *)prefix suffix:(nullable NSString *)suffix;
@end
About pointers and instances of C++ classes
For pointers and instances of C++ classes, use _Nonnull
instead of nonnull
and _Nullable
instead of nullable
. Also, the place to write the following.
Type * _Nonnull variable
Type * _Nullable variable
For example, with a pointer in the block argument and _Nonnull
and _Nullable
, it would be written as follows.
typedef void (^ExampleBlock)(const char * _Nonnull ptr, const int * _Nullable);
For pointers to pointers such as handles, two must be written.
typedef void (^ExampleBlock)(const char * _Nonnull * _Nonnull handle, const int * _Nullable);
Write the type of the element in the collection
When you write collections such as NSArray
and NSDictionary
, you should also write the type of the element being stored. If you don’t write it, it becomes Any
in Swift, which is unwieldy.
The type of element can be declared with the following code. Of course, since it is Objective-C, you can store other things even if you have declared them. But don’t do it, because it will cause trouble.
// Array of NSString *
NSArray<NSString *> *stringArray;
// Dictionary with key is NSNumber * and value is NSString *
NSDictionary<NSNumber *, NSString *> *mappingTable;