macOS applications often need to leverage historical processes and existing code, as numerous tasks cannot be accomplished using only Swift. Therefore, Objective-C code and C/C++ code coexist.
When writing code where multiple languages coexist, I am confident that we must refine the concurrent Objective-C code to make it more Swift-friendly and introduce precautions to ensure its smooth integration into Swift and 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). This is different in programs that have caught on quickly at a cost or in small to medium-sized programs. However, implementing ARC midway through a program presents significant challenges for more extensive programs.
In my case, I have yet to implement 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 can exempt only the source files from being ported from ARC, but this strategy could lead to problems. We had a good opportunity to brush up and modify the code to be ARC-compatible before incorporating it.
However, this code will likely 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. This is because 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.
Most likely, they won’t recognize this issue, even if noted in a comment. The error will only become apparent when it 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 incorporates a feature known as “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 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 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 and 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. For example, if you specify nonnull
but nil
, Swift will have a problem with it. Such a situation would pose serious problems in Swift.
@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 stored element type. 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;