Making Legacy Objective-C APIs Better for Swift
When extending our Music Theory Learning app, there's no reason to migrate every existing Objective-C class to Swift. One example is `NoteScales`, a legacy class that is still used by the Daily Challenge feature.
The original interface was:
@interface NoteScales : NSObject
+ (NoteScale*) scaleNotesForKeyString:(NSString*)keyString;
+ (NSString*) scaleSpecForKeyString:(NSString*)keyString;
+ (NSString*) scaleLabelStringForKeyString:(NSString*)keyString;
+ (NSArray*) allKeyStrings;
+ (NSArray*) allMajorKeyStrings;
+ (NSArray*) allMinorKeyStrings;
@end
The problem is the three methods returning `NSArray`. Objective-C doesn't express what the arrays contain, so Swift imports them as:
static func allKeyStrings() -> [Any]
Even though we know that these methods always return strings, Swift doesn't have that information. Code using the API therefore needs unnecessary type conversions or casts.
The implementation doesn't need to change. We can improve the Objective-C interface using lightweight generics:
@interface NoteScales : NSObject
+ (NoteScale*) scaleNotesForKeyString:(NSString*)keyString;
+ (NSString*) scaleSpecForKeyString:(NSString*)keyString;
+ (NSString*) scaleLabelStringForKeyString:(NSString*)keyString;
+ (NSArray < NSString* > *) allKeyStrings;
+ (NSArray < NSString* > *) allMajorKeyStrings;
+ (NSArray < NSString* > *) allMinorKeyStrings;
@end
Swift now imports these methods as:
static func allKeyStrings() -> [String]
The change is modest, but the payoff is worthwhile: we avoid a rewrite, preserve the existing behavior, and end up with a much cleaner Swift-facing API.
That’s one of the nice things about working with a mixed Objective-C and Swift codebase. Existing Objective-C APIs don’t always have to be migrated before they can feel natural in Swift. In many cases, a small amount of additional information is enough for Swift to present them in a more useful way.
