SoatDev IT Consulting
SoatDev IT Consulting
  • About us
  • Expertise
  • Services
  • How it works
  • Contact Us
  • News
  • July 17, 2023
  • Rss Fetcher

Writing Swift-Friendly Kotlin Multiplatform APIs — Part II

Learn how to code libraries that your iOS teammates will not frown upon using it. In this chapter: name clashing

Image generated by DALL-E

This is the second article in the series. I advise you to read the first one before proceeding.

You wrote your first Kotlin Multiplatform library. You are now happy and proud of yourself. You even merged that PR using your Android app’s new library. And then, all of a sudden, you received this message from your iOS teammate:

Why is there an underscore in that class name?

You are confused: “I didn’t put any underscores in my library.”

Underscores in the exported Objective-C headers indicate some naming conflict. In the previous article, we saw that:

  • Swift does not implement proper namespacing
  • Parameter names are part of the method signature

Thus, name clashing may be more common in Swift than in Kotlin. Let’s see some examples of how they can occur.

Different Packages, but the Same Class Name

Suppose you created a class Item to represent the payload of a network response and another class Item to implement a domain model. Despite the same name, they are in different packages:

// KOTLIN API CODE
package io.aoriani.network

class Item

...

package io.aoriani.models

class Item
// EXPORTED OBJ-C HEADER
__attribute__((objc_subclassing_restricted))
__attribute__((swift_name("Item")))
@interface SharedItem : SharedBase
- (instancetype)init __attribute__((swift_name("init()"))) __attribute__((objc_designated_initializer));
+ (instancetype)new __attribute__((availability(swift, unavailable, message="use object initializers instead")));
@end

__attribute__((objc_subclassing_restricted))
__attribute__((swift_name("Item_")))
@interface SharedItem_ : SharedBase
- (instancetype)init __attribute__((swift_name("init()"))) __attribute__((objc_designated_initializer));
+ (instancetype)new __attribute__((availability(swift, unavailable, message="use object initializers instead")));
@end
// SWIFT CLIENT CODE
let item1 = Item()
let item2 = Item_()

The problem

Although the classes are in different packages, there is a single namespace within an Objective-C framework, and consequently, the names will clash. The Kotlin native compiler preventively adds an underscore to one of the classes. But another problem is that the package information was lost, so which class implements the domain model? The one with an underscore or the one without?

The solution

The solution consists in renaming one of the classes, either in Kotlin or in Objective-C/Swift, by using @ObjCName() annotation, which we introduced in the first article of this series.

// KOTLIN API CODE
package io.aoriani.network

import kotlin.experimental.ExperimentalObjCName
import kotlin.native.ObjCName

@OptIn(ExperimentalObjCName::class)
@ObjCName("ItemResponse")
class Item

...

package io.aoriani.Models

class Item
// EXPORTED OBJ-C HEADER
__attribute__((objc_subclassing_restricted))
__attribute__((swift_name("ItemResponse")))
@interface SharedItemResponse : SharedBase
- (instancetype)init __attribute__((swift_name("init()"))) __attribute__((objc_designated_initializer));
+ (instancetype)new __attribute__((availability(swift, unavailable, message="use object initializers instead")));
@end

__attribute__((objc_subclassing_restricted))
__attribute__((swift_name("Item")))
@interface SharedItem : SharedBase
- (instancetype)init __attribute__((swift_name("init()"))) __attribute__((objc_designated_initializer));
+ (instancetype)new __attribute__((availability(swift, unavailable, message="use object initializers instead")));
@end
// SWIFT CLIENT CODE
let itemDomainModel = Item()
let itemNetworkResponse = ItemResponse()

Function Overloading

Now, suppose you are overloading a sort function to work with different data types:

// KOTLIN API CODE
fun sort(data: List<Int>){}
fun sort(data: Map<String, Int>){}
// EXPORTED OBJ-C HEADER
+ (void)sortData:(NSArray<SharedInt *> *)data __attribute__((swift_name("sort(data:)")));
+ (void)sortData_:(NSDictionary<NSString *, SharedInt *> *)data __attribute__((swift_name("sort(data_:)")));
// SWIFT CLIENT CODE
ExampleKt.sort(data: [1, 2, 3])
ExampleKt.sort(data_: ["A":1, "B": 2])

The problem

As said before, parameter names are part of the function signature, and thus they cannot match in Swift.

The solution

Rename the parameters to avoid conflicts.

// KOTLIN API CODE
fun sort(listOfInts: List<Int>){}
fun sort(mapOfStringToInt: Map<String, Int>){}
// EXPORTED OBJ-C HEADER
+ (void)sortListOfInts:(NSArray<SharedInt *> *)listOfInts __attribute__((swift_name("sort(listOfInts:)")));
+ (void)sortMapOfStringToInt:(NSDictionary<NSString *, SharedInt *> *)mapOfStringToInt __attribute__((swift_name("sort(mapOfStringToInt:)")));
// SWIFT CLIENT CODE
ExampleKt.sort(listOfInts: [1, 2, 3])
ExampleKt.sort(mapOfStringToInt: ["A" : 1, "B": 2])

Interfaces With Similarly Named Properties

Kotlin allows a single class to implement two interfaces with properties with the same name as long as they agree on the type.

// KOTLIN API CODE
interface Animal {
val id: String
}

interface Person {
val id: String
}

interface Product {
val id: Long
}

//Compiler Error: Conflicting declarations: public open val id: String, public open val id: Long
//class Android: Person, Product {
// override val id: String
// get() = TODO("Not yet implemented")
// override val id: Long
// get() = TODO("Not yet implemented")
//}

class ScoobyDoo: Animal, Person {
override val id: String
get() = TODO("Not yet implemented")
}
// EXPORTED OBJ-C HEADER
__attribute__((swift_name("Animal")))
@protocol SharedAnimal
@required
@property (readonly) NSString *id __attribute__((swift_name("id")));
@end

__attribute__((swift_name("Person")))
@protocol SharedPerson
@required
@property (readonly) NSString *id __attribute__((swift_name("id")));
@end

__attribute__((swift_name("Product")))
@protocol SharedProduct
@required
@property (readonly) int64_t id_ __attribute__((swift_name("id_")));
@end

__attribute__((objc_subclassing_restricted))
__attribute__((swift_name("ScoobyDoo")))
@interface SharedScoobyDoo : SharedBase <SharedAnimal, SharedPerson>
- (instancetype)init __attribute__((swift_name("init()"))) __attribute__((objc_designated_initializer));
+ (instancetype)new __attribute__((availability(swift, unavailable, message="use object initializers instead")));
@property (readonly) NSString *id __attribute__((swift_name("id")));
@end
// SWIFT CLIENT CODE
class Android: Person, Product {
let id: String = ""
let id_: Int64 = 0
}

The problem

Even though Swift has similar restrictions to Kotlin, the Kotlin native compiler will again act preemptively and add an underscore, even as it will be unlikely that a Swift class will try to implement both interfaces/protocols.

The solution

Unfortunately, like the other cases, the solution is to rename one of the properties.

// KOTLIN API CODE
interface Animal {
val id: String
}

interface Person {
val id: String
}

interface Product {
val identifier: Long
}
// EXPORTED OBJ-C HEADER
__attribute__((swift_name("Animal")))
@protocol SharedAnimal
@required
@property (readonly) NSString *id __attribute__((swift_name("id")));
@end

__attribute__((swift_name("Person")))
@protocol SharedPerson
@required
@property (readonly) NSString *id __attribute__((swift_name("id")));
@end

__attribute__((swift_name("Product")))
@protocol SharedProduct
@required
@property (readonly) int64_t identifier __attribute__((swift_name("identifier")));
@end
// SWIFT CLIENT CODE
class Android: Person, Product {
let id: String = ""
let identifier: Int64 = 0
}

And this is the end of Part II. We dealt with situations that caused name clashes in Swift. Check the next chapter, in which I will tell you why the types are disappearing in Swift:

Writing Swift-friendly Kotlin Multiplatform APIs — Part III


Writing Swift-Friendly Kotlin Multiplatform APIs — Part II was originally published in Better Programming on Medium, where people are continuing the conversation by highlighting and responding to this story.

Previous Post
Next Post

Recent Posts

  • Heybike’s Alpha step-through e-bike is an affordable, all-terrain dreamboat
  • U.S. lawmakers have concerns about Apple-Alibaba deal
  • Microsoft’s Satya Nadella is choosing chatbots over podcasts
  • MIT disavows doctoral student paper on AI’s productivity benefits
  • Laser-powered fusion experiment more than doubles its power output

Categories

  • Industry News
  • Programming
  • RSS Fetched Articles
  • Uncategorized

Archives

  • May 2025
  • April 2025
  • February 2025
  • January 2025
  • December 2024
  • November 2024
  • October 2024
  • September 2024
  • August 2024
  • July 2024
  • June 2024
  • May 2024
  • April 2024
  • March 2024
  • February 2024
  • January 2024
  • December 2023
  • November 2023
  • October 2023
  • September 2023
  • August 2023
  • July 2023
  • June 2023
  • May 2023
  • April 2023

Tap into the power of Microservices, MVC Architecture, Cloud, Containers, UML, and Scrum methodologies to bolster your project planning, execution, and application development processes.

Solutions

  • IT Consultation
  • Agile Transformation
  • Software Development
  • DevOps & CI/CD

Regions Covered

  • Montreal
  • New York
  • Paris
  • Mauritius
  • Abidjan
  • Dakar

Subscribe to Newsletter

Join our monthly newsletter subscribers to get the latest news and insights.

© Copyright 2023. All Rights Reserved by Soatdev IT Consulting Inc.