Refine Objective-C code for Swift

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.

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;


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;
Let's share this post !