author: Huawei Technologies Co., Ltd. title: Cangjie Multiplatform Programmer's Guide subtitle: for iOS™ titlepage: true numbersections: true toc-own-page: true listings-no-page-break: true textables: false
Introduction
The Cangjie Multiplatform technology enables Cangjie developers to incorporate their code into new Android/iOS applications and/or gradually replace the Java/Objective-C parts of existing applications with Cangjie code. The central mechanism that enables the cross-language, cross-runtime interoperability is mirror types, which expose the types of one language to the other.
On the Cangjie side, mirror types enable the inheritance of Java/Objective-C classes with method overriding, as well as the implementation of Java interfaces and Objective-C protocols, all while using conventional Cangjie syntax. On the Java/Objective-C side, the mirror types that represent Cangjie types are expressed in terms of the respective language's own types. All in all, this enables seamless transitions between the Java/Objective-C and Cangjie parts of the application, which includes usage of the target O/S APIs in Cangjie code.
The Challenge and the Solution
Java, Objective-C and Cangjie are object-oriented languages that support inheritance and polymorphism. However, the differences in their semantics, object models, and execution models preclude direct usage of Cangjie objects in Java or Objective-C code and vice versa.
Then, all three languages have managed runtimes that support automatic memory management, threading, exception handling and other low-level features, but again they do that differently. Making two separately designed complex language runtimes aware of each other might push the complexity of the entire system beyond human comprehension.
Instead, the interoperability between Cangjie and Java is achieved
by disguising them both as low-level languages. Cangjie and Java parts
of the application see each other through the lens of the Java Native Interface
(JNI), originally designed to facilitate the development of Java native
methods in languages such as C and C++. JNI is a rich, but low-level API,
and writing native Java methods is known to be cumbersome. Fortunately,
CJMP automates away the complexity of JNI.
The Objective-C Runtime module API serves a similar role on iOS. It is also designed specifically to enable the creation of bridge layers between Objective-C and other languages. And just like with JNI, CJMP takes care about the cumbersome parts.
Additional Setup
The current version of the Cangjie SDK for iOS requires you to download
the Cangjie.h header file from the Cangjie open source repository:
https://gitcode.com/Cangjie/cangjie_runtime/blob/dev/runtime/src/Cangjie.h
and integrate it into your XCode project in order to use the interoperability features described in this document.
Key Concepts
Mirror Types
Considering Cangjie and Objective-C as a pair of interoperating languages,
a mirror type T' defined in one language of the pair is a type that
represents an existing type T defined in the other language, enabling
the code written in the first language to use that type, possibly with some
limitations.
The Boolean types and numeric types that are essentially the same in both
languages naturally mirror each other: the Objective-C mirror type for the
Cangjie type Int32 is int and vice versa. Mirroring of numeric types
that the other language does not naturally support is not currently possible,
so e.g. the Cangjie type Float16 cannot be mirrored to an Objective-C
primitive type.
For a user-defined type such as class, struct, protocol, or interface,
the mirror type would be its closest equivalent in the other language.
For instance, the only Objective-C type that can approximate a Cangjie struct
type with reasonable accuracy is an Objective-C class attributed
with objc_subclassing_restricted.
A mirror type exposes the members and constructors of the original user-defined
type that are accessible and can be used in the other language, so again
a Cangjie function that returns a value of type Float16 cannot be mirrored
straight to Objective-C.
Normally you would obtain mirror type definitions for a type that you want
to use in the other language and its required dependencies automatically.
For Objective-C types, a standalone
mirror generator is provided,
whereas Objective-C mirrors of Cangjie types are generated by the
cjc compiler as a byproduct
of the normal compilation process.
Mirroring Objective-C Types to Cangjie
The cjc compiler replaces any uses of Objective-C mirror types with the
appropriate glue code, so only the names of types themselves and names and
types of their accessible members matter. Therefore, mirrors of Objective-C
types only contain member declarations, not definitions: constructors and
member functions/properties have no bodies and member variables have no
initializers. For the same reason, @private members are omitted.
For such an extension of the regular Cangjie syntax to work, each mirror type
must be marked with an @ObjCMirror annotation. It helps the compiler
distinguish between mirror type declarations and normal Cangjie type
definitions.
For example, a mirror class for the following Objective-C class:
@interface Node : NSObject {
}
- (id)initWith:(int)x;
- (int)getX;
@end
might look like this:
@ObjCMirror
public open class Node <: NSObject {
@ForeignName["initWith:"]
public init(x: Int32)
public open func getX(): Int32
}
Mirroring Cangjie Types to Objective-C
Unlike the mirrors of Objective-C types, for which cjc has dedicated support,
mirrors of Cangjie types must look exactly like normal Objective-C types
from the outside, for the iOS toolchain to pick them up. The cjc compiler
therefore generates those mirrors in the form of Objective-C source code
files containing glue code responsible for linking the Objective-C and Cangjie
worlds together.
For instance, if the class Node from the example in the
previous section was originally defined
in Cangjie as follows:
public class Node {
private let _x: Int
public func x(): Int {
_x
}
public init(x: Int) {
this._x = x
}
}
the cjc compiler could generate the following Objective-C mirror for it
(glue code mostly omitted for brevity):
// Node.h
@interface Node : NSObject
- (id)init:(int64_t)x;
- (int)x;
@end
// Node.m
@implementation Node
- (id)init:(int64_t)x {
/* Glue code constructing a Cangjie Node instance and associating
* it with the Objective-C Node instance being constructed, i.e. 'self'.
*/
}
- (int64_t)x {
/* Glue code invoking the 'x' member function of the associated
* Cangjie Node instance and returning the result.
*/
}
@end
Mirror Functions
Both Objective-C and Cangjie support top-level, global functions that are not members of any other type. They are exposed to the other language as mirror functions, which are essentially automatically generated pieces of glue code that pass control and data through the inter-language barrier.
Interop Classes
An interop class is essentially a Cangjie class that is derived from one
or more mirror types and is usable from Objective-C. All its
constructors and non-inherited public member functions are exposed
to Objective-C code via a conjugate wrapper class, automatically generated
by the cjc compiler. The wrapper class itself defines no other user-callable
methods or constructors, but any methods it may have inherited from its
supertypes may be called from both Objective-C and Cangjie code.
For example, when compiling the following Objective-C interop class:
@ObjCImpl
public class BooleanNode <: Node {
private let _flag: Bool
public init(x: Int32, flag: Bool) {
super.init(x)
this._flag = flag
}
public func flag(): Bool {
_flag
}
}
the cjc compiler will also yield a pair of Objective-C source code files
similar to the following:
// BooleandNode.h
@interface BooleanNode : Node
/* glue code */
- (id)init:(int32_t)x:(BOOL)flag;
- (BOOL)flag;
/* more glue code */
@end
// BooleandNode.m
@implementation BooleanNode : Node
/* glue code */
- (id)init:(int32_t)x:(BOOL)flag {
/* Glue code constructing a Cangjie BooleanNode(x, flag) instance and
* associating it with the Objective-C instance being constructed,
* i.e. 'self'.
*/
}
- (BOOL)flag {
/* Glue code invoking the 'flag' member function of the Cangjie
* BooleanNode instance associated with 'self' and returning the result.
*/
}
/* more glue code */
@end
Foreign Types
The mirror types and interop classes are not perfectly native to the language in which they are defined. Extending the analogy, their status is more like temporary visitors and work visa holders respectively, so they are collectively called foreign types throughout this document.
Objective-C-Compatible Types
The following Cangjie types are called Objective-C-compatible:
- Value types that have direct equivalents among Objective-C primitive types
(
Int16is included, butFloat16is not) - Foreign types
- Types of the form
Option<T>whereTis a foreign type (seenullHandling for reasoning)
For obvious reasons, the parameters and return values of public member functions of a foreign type may only have types compatible with the respective language.
The member variables of an interop class may have any types. Their public member variables that have Objective-C-compatible types may be accessed from both Objective-C and Cangjie.
Interop Scenarios
There are essentially two Objective-C-Cangjie interoperation scenarios:
-
In one scenario, Cangjie types and functions are exposed to Objective-C, enabling the use of Cangjie APIs, libraries and application components in Objective-C code.
-
In the other scenario, Objective-C classes and interfaces are exposed to Cangjie, enabling Cangjie code to access iOS APIs, third party Objective-C libraries and components of the application that remain coded in Objective-C.
The above scenarios are not mutually exclusive, one application may as well be utilizing both. Also, class inheritance and interface implementation are supported in both scenarios (an Objective-C class may subclass a Cangjie class and vice versa). That enables seamless callbacks, so neither scenario is unidirectional. However, each scenario has its own feature set, limitations, tooling, etc., so they are described separately.
Using Cangjie in Objective-C
To enable access to a Cangjie library, API, or some other piece of code
from Objective-C, you need to generate Objective-C
mirror types for the Cangjie types that you want to expose.
The cjc compiler can do that for you as described
in Cangjie Mirror Generation Reference.
NOTE: The type system of the Cangjie language is more versatile than its Objectiove-C counterpart. There are also some principal differences in the features shared by the two languages, most notably generics. Expressing certain features of Cangjie in Objective-C is therefore impossible, and other features can be difficult to express in a natural and compact manner. See Cangjie to Objective-C Mapping for the exact list of supported features and associated restrictions.
Example:
Suppose you want to expose to Objective-C a Cangjie struct Vector defined
as follows:
package cj
import interoplib.objc.*
public struct Vector {
private let _x: Int32
private let _y: Int32
public prop x: Int32 {
get() {
_x
}
}
public prop y: Int32 {
get() {
_y
}
}
public init(x: Int32, y: Int32) {
_x = x
_y = y
}
public func add(v: Vector): Vector {
Vector(x + v.x, y + v.y)
}
}
First, you need to import the interoplib.objc package into the source code
file where Vector is defined:
import interoplib.objc.*
When compiling Vector.cj, you would invoke the cjc compiler with two
additional options: --experimental --enable-interop-cjmapping=ObjC.
The compiler would then generate two extra files, Vector.h and Vector.m,
similar to the following:
// Vector.h
#import <Foundation/Foundation.h>
#import <stddef.h>
__attribute__((objc_subclassing_restricted))
@interface Vector : NSObject
/* glue code */
- (id)init:(int32_t)x :(int32_t)y;
@property (readonly, getter=x) int32_t x;
- (int32_t)x;
@property (readonly, getter=y) int32_t y;
- (int32_t)y;
- (Vector*)add:(Vector*)v;
/* glue code */
@end
// Vector.m
/* glue code */
@implementation Vector
/* glue code */
- (id)init:(int32_t)x:(int32_t)y {
/* Glue code constructing an instance of Cangjie Vector(x,y) and associating
* it with 'self'.
*/
}
- (int32_t)x {
/* Glue code retrieving the value of the 'x' property of the
* assocated instance of Cangjie Vector.
*/
}
- (int32_t)y {
/* Glue code retrieving the value of the 'y' property of the
* assocated instance of Cangjie Vector.
*/
}
- (Vector*)add:(Vector*)v {
/* Glue code invoking the 'add' mmber function of the Cangjie Vector
* instance associated with 'self', passing over the Cangjie Vector
* instance associated with 'v', and wrapping the result in a new
* instance of the Objective-C Vector class.
*/
}
/* more glue code */
@end
Now you can add Vector.h and Vector.m to your XCode project and use the
class Vector in your Objective-C code as if it was a normal Objective-C class:
define variables of the type Vector*, create instances, pass them around,
including to Cangjie code via [Vector add], etc.
IMPORTANT: The current version of the Cangjie SDK for iOS also requires
downloading and integrating into the XCode project the Cangjie.h header
file from the Cangjie open source repository:
https://gitcode.com/Cangjie/cangjie_runtime/blob/dev/runtime/src/Cangjie.h
Limiting the Exposure
The option --enable-interop-cjmapping instructs the cjc compiler
to generate mirror types for all public Cangjie types that it compiles,
and expose all public member functions and constructors of those types.
Oftentimes, however, such total exposure is undesirable, as what you want
to use in Objective-C may be just a small percentage of the functionality
provided by a Cangjie library or framework.
You may precisely control the exposure of Cangjie types and their members
by passing the pathname of a dedicated configuration file to cjc via the
option --import-interop-cj-package-config-path. That file is a plain
text file the contents of which must conform to the TOML
syntax.
In the [default] section of that file, you can specify the default API
exposure strategy for all Cangjie packages, and then modify it
for particular packages in the respective [[package]] entries. For example:
[default]
APIStrategy="None" # Expose nothing by default
[[packages]]
name="com.example.pkg1" # From this specific package,
APIStrategy="Full" # expose everything
# . . .
Furthermore, for each package you can use either the included_apis
or excluded_apis property to respectively expose or hide particular types
and/or members:
# . . .
[[packages]]
name="com.example.pkg2" # From this specific package,
included_apis = [ "Vector", # Only the type 'Vector'
"Vector.add" # and its member function 'add'
] # are exposed
[[packages]]
name="com.example.pkg3" # From this specific package,
APIStrategy="Full" # everything
excluded_apis = [ "TopSecret", # but the type 'TopSecret' and
"Auth.getPwd" # member function 'Auth.getPwd'
] # are exposed
Refer to Cangjie Mirror Generation Reference for more details.
Generic Cangjie Types Mirroring
Many important Cangjie types are parameterized. Unfortunately, certain fundamental differences between Cangjie and Objective-C generics preclude mirroring of the former into the latter. As a workaround, the Cangjie SDK supports monomorphization of mirror types: it generates a separate non-generic Objective-C mirror type for each supplied set of valid type arguments for the given parameterized Cangjie type.
NOTE: The current version only supports mirroring of generic types instantiated with primitive types used as type arguments.
For instance, if you have a generic Cangjie class p.Pair<T,U>:
package p
public class Pair<T,U> { . . . }
and want to manipulate instances of Pair<Int, Bool> in your Objective-C code,
you would add a generic_object_configuration property to the [[package]]
entry for p in the cjc mirror generation configuration file,
with the following content:
# . . .
[[package]]
name = "p"
generic_object_configuration = [
{ name = "Pair", type_arguments = [ "Int, Bool" ] },
# . . .
cjc would then generate a non-generic Objective-C class:
@interface PairIntBool
. . .
@end
representing that particular instantiation of the type Pair<T,U>.
Refer to Generics Instantiations for more details.
Cangjie to Objective-C Mapping
The current version of the cjc compiler uses Cangjie → Objective-C mapping
described in this chapter. See
Cangjie Mirror Generation Reference
for applicable cjc command-line options.
General Considerations {#cangjie-general-considerations}
The Cangjie type system is different from that of the Objective-C language. Certain Cangjie types and their features do not have direct or even reasonably close equivalents in Objective-C, so they have limited support in the current version, and some are not supported at all.
Consequently, if the type of a public member, function/constructor parameter, or function return type is not supported, the respective member, function or constructor cannot be mirrored. The current compiler does not report such usages as errors, the respective entity is simply not mirrored.
Mirror types should be compiled into a separate dynamic library with ARC
support disabled (-fno-objc-arc Objective-C compiler option). For details,
see the notice in the [Classes and Interfaces(#cangjie-classes-and-interfaces)
section.
Names {#cangjie-names}
The original names of Cangjie packages, functions, types and type members
are currently preserved, which means that some of them may clash
with Objective-C keywords, such as "int", or contain characters that
are not permitted in Objective-C names.
Boolean and Numeric Types
Cangjie Boolean and numeric types that have equivalents among Objective-C
types are mirrored to those equivalent; those that don't (Float16)
are not supported:
| Cangjie type | Objective-C type |
|---|---|
Bool |
BOOL |
Int8 |
int8_t |
Int16 |
int16_t |
Int32 |
int32_t |
Int64 |
int64_t |
Int |
int64_t |
IntNative |
ssize_t |
UInt8 |
uint8_t |
UInt16 |
uint16_t |
UInt32 |
uint32_t |
UInt64 |
uint64_t |
UInt |
uint64_t |
UIntNative |
size_t |
Float16 |
Not supported |
Float32 |
float |
Float64 |
double |
See General Considerations for information about the handling of unsupported types.
Rune
The type Rune is not supported.
See General Considerations for information about the handling of unsupported types.
Special Types
The type Unit is only supported as a function return type, mapped
to Objective-C void.
The type Nothing cannot be supported.
The type Any is not supported yet.
See General Considerations for information about the handling of unsupported types.
Tuple
Tuple types are not supported yet.
See General Considerations for information about the handling of unsupported types.
Struct Types
Public Cangjie struct type definitions are mirrored into Objective-C class
definitions attributed with objc_subclassing_restricted.
Those definitions are generated automatically by the cjc
compiler and contain glue code that transfers control and data between
Objective-C and Cangjie. They should not be modified manually.
package cj
import interoplib.objc.*
public struct Vector {
let x: Int32
let y: Int32
public init(x: Int32, y: Int32) {
this.x = x
this.y = y
}
public func add(v: Vector): Vector {
Vector(x + v.x, y + v.y)
}
}
// Vector.h
#import <Foundation/Foundation.h>
#import <stddef.h>
__attribute__((objc_subclassing_restricted))
@interface Vector : NSObject
// Auxiliary glue code methods
- (id)init:(int32_t)x :(int32_t)y;
- (Vector*)add:(Vector*)v;
// More auxiliary glue code methods
@end
// Vector.m
// Glue code
@implementation Vector
// Glue code
- (id)init:(int32_t)x :(int32_t)y {
// Glue code creating an instance of Cangjie Vector(x, y) and
// associating it with 'self'.
}
- (Vector*)add:(Vector*)v {
// Glue code retireving instances of Cangjie Vector associated
// with 'self' and 'v', invoking the add() member function
// of Cangjie-self with Cangjie-v passed a parameter, and then
// creating a new instance of Vector and associating it with the
// result of the add() call.
}
// Glue code
@end
Only public struct members and common constructors are mirrored.
Member functions are mirrored into methods with the respective
mirror types substituted for parameter types and return value type. Mirrors
of member functions returning Unit are mirrored into void methods.
Instance member functions are mirrored into instance methods (prefixed
with a minus sign -), static member functions are mirrored
into class methods (prefixed with a plus sign +).
Member properties of supported types are mirrored into @property
declarations with the same name and with type that mirrors the Cangjie
property type, attributed with readonly. The getter is mirrored
into a parameterless method with the same name as the property
and return type that is the mirror of the Cangjie property type.
NOTICE: Setters are currently not mirrored, so mirrors of mut
member properties are also readonly.
Member variables of supported types are mirrored into @property
declarations with the same name and with type that mirrors the Cangjie
variable type, attributed with readonly. A getter is synthesized
to enable access.
NOTICE: Setters are currently not synthesized for var member
variables, so mirrors of the latter are also readonly.
Constructors are mirrored into constructors with the respective mirror types substituted for parameter types. This includes the default constructor that might have been implicitly declared.
The current version imposes a number of severe limitations on the struct types that can be mirrored, to the extent that it may be fair to say that a struct type needs to be designed specifically for exposing it to Cangjie:
-
Function overloading is not supported: mirrors of overloaded member functions are generated with the same name, which results in a name clash.
-
Both
letandvarmember variables are mirrored intoreadonlyproperties in the current version, so the latter may not be altered from Objective-C code. This limitation will be removed in a future version. In the meantime, you may add setter functions as a workaround. -
Mirroring of
mutinstance member functions is a work in progress. They are mirrored, but incorrect glue code is generated in some circumstances. -
Mirrored structs may implement interfaces other than
Any, but the "implements" relationship is not propagated to Objecttive-C: th generated@interfacedirective does not bear the names of mirrors of implemented interfaces in angle brackets<>after the class name. -
Mirroring of member operator functions is not supported. They are skipped during mirror generation.
-
Mirroring of generic structs is supported through monomorphization. See Generics for details.
-
Struct member functions and constructors with unsupported parameter types, as well as member functions with unsupported return types, are not mirrored.
-
A mirrored struct may have direct and/or interface extensions, but they are ignored during mirror generation.
-
Memeber functions may not have any type parameters.
Classes and Interfaces {#cangjie-classes-and-interfaces}
Cangjie class and interface definitions are mirrored respectively into Objective-C class and protocol definitions. Those definitions contain automatically generated glue code and should not be altered in any way.
package cj
import interoplib.objc.*
public interface Valuable {
public func value(): Int
}
public open class Singleton <: Valuable {
private let _v: Int
public init(v: Int) {
_v = v
}
public func value(): Int {
_v
}
}
public class Zero <: Singleton {
public init() {
super(0)
}
}
// Valuable.h
@protocol Valuable
- (int64_t)value;
@end
// Singleton.h
// Glue code
@interface Singleton : NSObject
// Glue code
-(id)init:(int64_t)v;
- (int64_t)value;
// Glue code
@end
// Singleton.m
// Glue code
@implementation Singleton
// Glue code
-(id)init:(int64_t)v {
if (self = [super init]) {
// Glue code creating an instance of Cangjie Singleton(v)
// and associating it with `self`.
}
return self;
}
- (int64_t)value {
// Glue code calling the value() member function of the Cangjie
// Singleton instance associated with 'self' and returning the result
}
// Glue code
@end
// Zero.h
#import "Singleton.h"
__attribute__((objc_subclassing_restricted))
@interface Zero : Singleton
// Glue code
- (id)init;
// Glue code
@end
// Zero.m
// Glue code
@implementation Zero
- (id)init {
if (self = [super init]) {
// Glue code creating an instance of Cangjie Zero()
// and associating it with `self`.
}
return self;
}
@end
Identifiers that serve as names of mirrored types and their members are
currently preserved even if they clash with Objective-C keywords such as int.
Mirrors of non-open classes are attributed
with objc_subclassing_restricted.
public members and constructors are mirrored. In addition, protected open
instance member functions of open classes are also mirrored, so that they
could be overridden in subclasses. No other class or interface members are
mirrored.
CAUTION: As there are neither packages nor namespaces of any other kind in Objective-C, the mirrors of such
protected openmember functions are accessible from anywhere in Objective-C code.
Member variables are not mirrored in the current version and no means for accessing them from Objective-C is provided.
Member properties are not mirrored in the current version.
Member functions are mirrored into methods with the respective mirror
types substituted for parameter types and return value type. Mirrors of
member functions returning Unit are mirrored into void methods.
static member functions are mirrored into class methods (prefixed
with "+"), instance member functions -- into instance methods (prefixed
with "-").
Common constructors are mirrored into constructors with the respective mirror types substituted for parameter types. This includes the default constructor that might have been implicitly declared.
IMPORTANT: From the outside, types that mirror Canglie classes and interfaces are normal Objective-C classes and protocols in all aspects: they can be inherited/implemented, their methods can be overloaded and overridden with conventional Objective-C methods, types of their instances can be queried, and so on. The differences between the languages, however, pose a few potential problems that require extra care when using such types in Objective-C code:
-
There is no attribute that would make Objective-C methods non-overrideable, so methods that mirror non-
openmember functions ofopenclasses can, but obviously should not, be overridden. -
The current implementation does not support overriding of mirrors of
staticCangjie methods ofopenclasses. They are mirrored into Objective-C class methods (those prefixed with a plus sign+), and should never be overridden in subclasses. There is no way to enforce this restriction at compile-time. -
Constructors are not inherited in Cangjie, whereas init methods in Objective-C are inherited just like other methods, which may lead to undesirable effects. For instance, consider the following Cangjie classes:
public class A { public init() {...} } public class B <: A { private init() {...} public init(x: Int) {...} }In Cangjie, the class
Bdoes not expose its parameterless common constructor. However, the mirror ofBwill inherit the parameterlessinitmethod from the mirror ofA, so the expression[[B alloc] init]will compile and run, but theinitmethod inherited fromAwill create and associate withselfan instance of the Cangjie classA, notB.
NOTICE: Mirrors of open Cangjie classes rely on low-level Objective-C
features that are not compatible with Automatic Reference Counting (ARC).
That is why all mirror types should generally be compiled into a separate
dynamic library with ARC support disabled (-fno-objc-arc Objective-C
compiler option).
The current version imposes a number of severe limitations on the class and interface types that can be mirrored, to the extent that it may be fair to say that such types need to be designed specifically for exposing them to Cangjie:
-
Abstract classes are not mirrored and mirrors of concrete classes that implement abstract classes won't compile.
-
Function overloading is not supported: mirrors of overloaded member functions are generated with the same name, which results in a name clash.
-
Member variables are not mirrored and no means for accessing them is provided. This limitation will be removed in a future version. In the meantime, you may add getter/setter functions to Cangjie classes manually as a workaround.
-
Mirroring of classes that implement interfaces other than
Anyis not currently supported in the sense that explicit interface implementation information gets lost during mirror generation. -
Mirroring of generic classes is supported via monomorphization. See Generics for more information.
-
Mirroring of classes/interfaces that contain public member properties, member operator functions, or primary constructors is not supported. Such entities are simply not mirrored without a warning.
-
For member functions and constructors, the only universally supported parameter types are
Booland numeric types. For member functions, the only universally supported return value types areBool, numeric types andUnit. Specifically for member functions and constructors of non-openclasses, structs, enums, and non-openclasses are also supportted as parameter and return value types, provided that they are defined in the same package. Member functions and constructors with unsupported parameter types, as well as member functions with unsupported return types, are not mirrored. -
Inheritance relationships between interfaces are not preserved during mirror generation.
-
Interface implementation information is not propagated to Objective-C during mirror generation (notice how in the above example the
@interfacedirective inSingleton.hdoes not end with<Valuable>). -
A mirrored class may have direct and/or interface extensions, but they are ignored during mirror generation.
Enums
A Cangjie enum is mirrored into an Objective-C class attributed
with objc_subclassing_restricted, with static methods matching
the enum constructors (factory methods) and without a public
constructor.
public enum TimeUnit {
| Year(Int)
| Month(Int)
| Year
| Month
}
__attribute__((objc_subclassing_restricted))
@interface TimeUnit : NSObject
// Glue code
+ (TimeUnit*)Year:(int64_t)p1;
+ (TimeUnit*)Month:(int64_t)p1;
+ (TimeUnit*)Year;
+ (TimeUnit*)Month;
// Glue code
@end
Enum constructors are mirrored into static factory methods with the same
names and matching types and numbers of parameters; names of parameters
are synthesized (p1, p2, ...).
Only the public enum members are mirrored.
Member functions are mirrored into methods with the respective mirror
types substituted for parameter types and return value type. Mirrors of
member functions returning Unit are mirrored into void methods.
static member functions are mirrored into class methods (prefixed
with "+"), instance member functions -- into instance methods (prefixed
with "-").
Member properties of supported types are mirrored into @property
declarations with the same name and with type that mirrors the Cangjie
property type, attributed with readonly. The getter is mirrored
into a parameterless method with the same name as the property
and return type that is the mirror of the Cangjie property type.
NOTICE: Setters are currently not mirrored, so mirrors of mut
member properties are also readonly.
The current version imposes a number of limitations on the enum that can be mirrored:
-
Function overloading is not supported: mirrors of overloaded member functions are generated with the same name, which results in a name clash.
-
Mirrored enums may implement interfaces other than
Any, but the "implements" relationship is not propagated to Objecttive-C: th generated@interfacedirective does not bear the names of mirrors of implemented interfaces in angle brackets<>after the class name. -
Mirroring of generic enums is not supported yet; will be supported through monomorphization. See Generics for details.
-
For member functions and constructors, the only supported parameter types are
Booland numeric types. For member functions, the only universally supported return value types areBool, numeric types andUnit. Member functions and constructors with unsupported parameter types, as well as member functions with unsupported return types, are not mirrored. -
Mirroring of enums that contain member operator functions is not supported. An attempt to mirror an enum containing such an entity currently results in the generation of invalid Objectove-C code.
-
Enum member functions and constructors with unsupported parameter types, as well as member functions with unsupported return types, are not mirrored and no warning is issued.
-
A mirrored enum may have direct and/or interface extensions, but they are ignored during mirror generation.
Recursively defined enums are supported:
public enum Peano {
| Z
| S(Peano)
public func toInt(): Int {
match (this) {
case Z => 0
case S(x) => 1 + x.toInt()
}
}
}
Generics {#cangjie-generics}
Objective-C and Cangjie generics are fundamentally different, so the latter
cannot be mirrored into the former. However, specific instantiations
of parameterized Cangjie types, such as G<Int64>, are concrete types,
and therefore can be mirrored into non-generic Objective-C types.
Such types are also called monomorphized generics.
NOTE: The current version only supports mirroring of generic types instantiated with primitive types used as type arguments. Mirroring of member functions that have their own type arguments is not supported either.
Use the respective
configuration file
of the cjc compiler to specify which instantiations of which types
to mirror. Refer to Generics Instantiations
for details.
Example:
When compiling the following Cangjie class definition:
public class G<T> {
private let _t: T
public init(t: T) {
_t = t
}
public func get(): T {
_t
}
}
with the following lines in the respective section of the configuration file:
. . .
generic_object_configuration = [
{ name = "G", type_arguments = ["Bool", "Int"] },
]
. . .
the compiler will generate Objective-C mirror classes for G<Bool> and
G<Int>, named respectively GBool and GInt.
Refer to the Cangjie Mirror Generation Reference section for complete instructions.
null Handling {#cangjie-null-handling}
As Cangjie has no null type, passing nil as a parameter to a mirrored
Cangjie method results in a run-time exception in glue code:
public class Node {
private let next: ?Node
public init() {
next = None
}
public init(next: Node) { /* Pure Cangjie */
this.next = Some(next)
}
}
__attribute__((objc_subclassing_restricted))
@interface Node
/* glue code */
- (id)init;
- (id)init:(Node *)next;
/* glue code */
@end
. . .
Node *list = [[Node alloc] init:nil]; /* NullPointerException */
Conversely, there is currently no way to return nil from a public member
function of an Objective-C-compatible type.
NOTICE: The current version supports automatic Option<T> (un)wrapping
with nil mapped to None, but only for Objective-C pointers to classes
and protocols mirrored to Cangjie, not the other way round.
See nil Handling for details.
Cangjie Mirror Generation Reference
There is no standalone mirror generator facilitating the exposure of Cangjie
types to Java and Objective-C code in the Cangjie SDK. The cjc compiler
itself is capable of generating Java/Objective-C mirror type definitions
for Cangjie types right when it compiles the Cangjie source code of those
types.
Command-line Syntax {#cjc-command-line-syntax}
The following additional cjc options enable and control the generation
of mirrors for Cangjie types during compilation:
--experimental (mandatory)
This option must be specified as of the current version, since the whole interop feature is still in development, but will not be necessary in the future.
--enable-interop-cjmapping=Java or
--enable-interop-cjmapping=ObjC (mandatory)
Enables the generation of mirror type definitions for Cangjie types respectively in the form of Java or Objective-C source code files.
--output-interop-cjmapping-dir pathname (optional)
The pathname must point to a directory into which the cjc
compiler shall place its output Java or Objective-C source code files
containing mirror type definitions. If pathname does not point to
an existing file system location, cjc attempts to create a directory there.
If pathname points to something other than a directory, cjc
terminates with an error message.
The default value of pathname is either ./java-gen or ./objc-gen,
depending on whether --enable-interop-cjmapping is set respectively
to "Java" or "ObjC".
--import-interop-cj-package-config-path pathname (optional)
The pathname must point to a configuration file that defines which
Cangjie types are exposed to Java or Objective-C as mirror types. See
Cangjie Mirror Generation Configuration
for details.
Cangjie Mirror Generation Configuration
A Cangjie mirror generation configuration file is a plain text file in the TOML syntax. It specifies:
- Non-generic Cangjie types to mirror
- Specific instances of generic Cangjie types to mirror
Defaults
The [default] table defines the default settings for packages for which
either no package-specific settings are provided at all, or those
settings don't override the defaults.
Properties:
APIStrategy (optional)
A string defining whether all public entities shall be mirrored by default or not. Valid values:
"Full"- all public entities shall be mirrored by default"None"- no public entities shall be mirrored by default
GenericTypeStrategy (optional)
A string defining whether generic public entities shall be mirrored by default or not. Valid values:
"Partial"- instantiations explicitly listed ingeneric_object_configurationshall be mirrored"None"- no generic public entities shall be mirrored by default
Packages
Each entry in the [[packages]] array specifies the name of a separate
source Cangjie package, and, optionally:
- A filter that defines which types from that package to include in, or exclude from, the set of mirrors
- API mirroring strategy (if other than the default)
- Generic type mirroring strategy (if other than the default)
- Specific generic type instantiations to mirror
Properties:
name (mandatory)
A string containing the name of the source Cangjie package to which
the settings in this [[packages]] array entry apply.
Example:
name = "com.example.VectorMath"
APIStrategy (optional)
A string defining whether all public entities of the given package shall be mirrored by default or not. Valid values:
"Full"- all public entities shall be mirrored by default"None"- no public entities shall be mirrored by default
If this property is absent, the default value is used.
included_apis (optional)
An array of strings each containing the name of a public entity that
needs to be mirrored. If the array contains the qualified name of a type
member, the type itself is also mirrored. Either included_apis or
excluded_apis may be present in a given [[packages]] array entry,
but not both.
Example:
included_apis = [
"Vector.product", // Vector also exposed
"HiddenV"
]
excluded_apis (optional)
An array of strings each containing the name of a public Cangjie entity
defined in the given package that has to be not mirrored.
Either included_apis or excluded_apis may be present in a given
[[packages]] array entry, but not both.
Example:
excluded_apis = [
"Vector.product", // Even if Vector is exposed, product is not
"HiddenV"
]
GenericTypeStrategy (optional)
A string defining whether generic public entities defined in this package shall be mirrored by default or not. Valid values:
"Partial"- instantiations explicitly listed ingeneric_object_configurationshall be mirrored"None"- no generic public entities shall be mirrored by default
If this property is absent, the default value is used.
generic_object_configuration (optional)
A list of tables defining the specific instantiations of generic entities to be mirrored. See Generics Instantiations for details.
Generics Instantiations
The generic_object_configuration property of a [[packages]] array
entry defines which specific instantiations of generic Cangjie entities
will be mirrored.
The value of the property is a (possibly empty) array of tables. Each entry of the array has the following two mandatory properties:
name
A string containing the name of a public generic Cangjie type or global function defined in the current package.
type_arguments
An array of strings, each containing valid type argument(s) for the generic
type or global function the name of which is specified in the name property.
Simply put, "valid" means that the type/function can be instantiated with the given string between the angle brackets
< >.Consider the following Cangjie class:
public class G<T> { public func f(t: T): Unit {} public func f(b: Bool): Unit {} }
G<Bool>cannot be instantiated, sogeneric_object_configuration = [ { name = "G", type_arguments = ["Bool"] } ]would trigger a compiler error.
NOTE: The current version only supports mirroring of generic types instantiated with primitive types used as type arguments.
Example:
Given the following Cangjie definitions
public class G<T> { . . . }
public func f<T>(): Unit { . . . }
public struct S<T,U> { . . . }
the property
generic_object_configuration = [
{ name = "G", type_arguments = ["Int32"] },
{ name = "f", type_arguments = ["Float32", "Float64"] }
{ name = "S", type_arguments = ["Int, Bool"] }
]
instructs the cjc compiler to generate the following generics
instantiations and mirrors for them:
G<Int32>
f<Float32>
f<Float64>
S<Int, Bool>
naming the mirrors GInt32, fFloat32, fFloat64, and SIntBool
respectively.
Using Objective-C in Cangjie
In the first interoperation scenario, described in Using Cangjie in Objective-C, user-defined Cangjie types are mapped to Objective-C types that behave just like any conventional Objective-C classes and protocols: classes can be extended, protocols implemented, instances created and passed around freely, and so on.
The Cangjie SDK for iOS also supports similar features for the reverse
scenario, in which Objective-C classes and protocols are mapped to Cangjie
classes and interfaces and can be extended, implemented, instantiated.
However, the exact set of features and limitations is different, as well
as tooling: mirror type and function declarations for Objective-C entities are
generated by a standalone tool,
not the cjc compiler.
The process of enabling this second scenario is therefore more involved. Here are the steps you need to take:
-
Design the interoperability layer in terms of Objective-C classes and methods.
developer →
interop layer design(Objective-C pseudo-code) -
Generate mirror declarations for any Objective-C classes and protocols used in the design of the interoperability layer.
.h→ mirror generator →.cj(mirrors) -
Write the classes constituting the interoperability layer in Cangjie. Use the mirrored Objective-C types as needed: create instances, call member functions, etc.
interop layer design+.cj(mirrors) → developer →.cj(interop layer) -
Compile the interoperability layer classes (interop classes for short) and mirror types together with
cjc.cjcwill generate:- the necessary glue code for all uses of the mirror types.
- the actual Objective-C source for the Objective-C part of the interoperability layer (so called Objective-C wrappers for interop classes)
.cj(mirrors + interop layer) →cjc→.dylib+.h/.m(interop layer) -
Add to your iOS project:
.hand.mfiles generated bycjc.dylibfile generated bycjc- runtime libraries (
.dylib) from the Cangjie SDK
iOS project +
.h,.m,.dylib→ iOS toolchain → iOS application -
Now you may start using the interop class wrappers in the Objective-C code of your application, effectively calling Cangjie code from Objective-C.
IMPORTANT: The current version of the Cangjie SDK for iOS also requires
downloading and integrating into the XCode project the Cangjie.h header
file from the Cangjie open source repository:
https://gitcode.com/Cangjie/cangjie_runtime/blob/dev/runtime/src/Cangjie.h
Initial Interop Class Creation Workflow
Setup
-
Install the
llvm@16Homebrew formula:brew install llvm@16 -
Add the
llvm@16/lib/subdirectory to theDYLD_LIBRARY_PATHenvironment variable:export DYLD_LIBRARY_PATH=/opt/homebrew/opt/llvm@16/lib:$DYLD_LIBRARY_PATH -
Run the
envsetup.shscript from the Cangjie SDK. -
Verify the installation by running the following command in the terminal:
ObjCInteropGenIf mirror generator usage instructions start to appear, you now have everything you need to begin using the iOS interoperability features of the Cangjie SDK.
Step 1: Design the Interoperability Layer {#step-1}
On this step, you design the API of one or more interop classes from the Objective-C code perspective, i.e. you need to decide, for each interop class:
- What Objective-C class it will extend, e.g.
NSObject; - Which Objective-C protocols it will implement, if any; and
- What
public/protectedmethods and/or constructors it will have. You only need to know the types of parameters and, for methods, their names and return value types; the implementations you will write in Cangjie.
See also Features and Limitations of Interop Classes.
For the Objective-C part of the application, the interoperability layer is
indistinguishable from a set of regular Objective-C classes. So you can design
it in the form of Objective-C pseudo-code. Write down the specifications
and @interface definitions of one or more Objective-C classes. Note:
You do not need to write the respective @implementation parts as you would
re-write them in Cangjie at a later step anyway.
Supported parameter types: any mapped Objective-C types.
Supported return types: any mapped Objective-C type or void.
Supported inheritance: interop classes may only extend mirrored Objective-C classes (not other interop classes!), and may only implement interfaces that mirror Objective-C protocols.
Limitations:
-
Variable arity (varargs) methods and constructors are not supported.
-
Neither an interop class nor its individual non-private member functions may be generic.
-
Generic Objective-C types are erased before mirror generation; any type variables are replaced with the respective upper bounds.
End-to-end Example:
For the sake of simplicity, suppose that in your iOS application there is
already a class M with a single parameterless void instance method
foo():
// M.h
#import <Foundation/Foundation.h>
@interface M : NSObject
- (void)foo;
@end
// M.m
#import "M.h"
@implementation M
- (void) foo {
printf("Hello from ObjC M.foo()\n");
}
@end
and you want to override that method foo() with a Cangjie implementation
in a subclass of M called A.
Your interop class design would then look like this:
#import "M.h"
@interface A : M
- (void)foo;
@end
Step 2: Generate Mirror Type Declarations {#step-2}
Now, you need mirror type declarations for all Objective-C types on which your interop classes depend: their supertypes, types of member properties and local variables, types of method parameters/return values, and, if any of the foregoing are array types, their element types.
Before you proceed further, build your iOS application normally without changing anything in it, to ensure that the mirror generator takes a complete and consistent set of Objective-C headers as input.
Then write an appropriate mirror generator configuration file and run the mirror generator:
ObjCInteropGen --mode=normal <config-file>
where <config-file> is the pathname of the configuration file.
End-to-end Example (continued):
The only immediate dependency of your class A is its superclass M,
so the configuration file is pretty simple:
# A.toml
# Place the mirror of M and any dependencies it may have in the 'cjworld' package:
[[packages]]
filters = { include = ["M", "NS.+"] }
package-name = "cjworld"
# Write the output files with mirror type definitions to the current directory:
[output-roots.default]
path = "."
# Specify the pathname of the input header:
[sources.all]
paths = ["M.h"]
[sources-mixins.default]
sources = [".*"]
arguments-append = [
# Uncomment the following line if you get "unknown type name" errors
# "-DTARGET_OS_IPHONE=1",
# Edit the pathnames below to match the locations of Objective-C headers on your system:
"-F", "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/System/Library/Frameworks",
"-isystem", "/Library/Developer/CommandLineTools/usr/lib/clang/17/include",
"-isystem", "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include"
]
Mirror generator command line:
ObjCInteropGen --mode=normal A.toml
The above command will generate files cjworld/M.cj with a mirror type
declaration for the class M and a bunch of cjworld/NS*.cj files
with mirror type declarations for the Foundation framework classes
and protocols on which M depends.
Common Misconfiguration Issues
The mirror generator is unable to find the standard header files, such as
stdarg.h or stdbool.h.
Example error messsage:
..../CoreFoundation.h:19:10: error: 'stdarg.h' file not found
This usially means that the pathnames in arguments-append array of the
[sources-mixins] table are not correct. Double check that they match the
locations of the header files on your system.
The mirror generator reports "Unknown type name 'NSUInteger'" errors or similar.
Example error messsage:
.../NSObjCRuntime.h:626:74: error: unknown type name 'NSUInteger'
On some systems, you need to pass an additional argument to Clang,
either "-DTARGET_OS_IPHONE=1" or "-DTARGET_OS_OSX=1".
Add it to the arguments-append array of the [sources-mixins] table.
In the above example, it is present but commented out:
. . .
arguments-append = [
# Uncomment the following line if you get "unknown type name" errors
# "-DTARGET_OS_IPHONE=1",
. . .
Step 3: Write the Interop Classes {#step-3}
For each Objective-C class skeleton that you included in your interop layer design on Step 1, write a matching Cangjie class as follows:
-
Use the appropriate package and class names (the Objective-C wrapper class that
cjcwill generate for you will have the same fully qualified name). -
Import
interoplib.objc.*. -
Import the mirror types, if you've generated any on Step 2. Do not import all generated dependencies, only the types you actually need.
-
Annotate the interop class with
@ObjCImpl. -
Make the interop class inherit the respective
openmirror class, orNSObjectif it has no explicit supertype in your design. -
Annotate
publicclass members and constructors with@ForeignName["foreign-name"], whereforeign-nameis the desired Objective-C name for that member or constructor, according to the following rules:-
Do not annotate member functions that override member functions of mirror supertypes. The presence of a
@ForeignNameannotation on such a member function of an interop class triggers a compile-time error.The Objective-C names of such overriding functions must match the names of the overridden Objective-C methods. The latter are propagated from Objective-C to Cangjie automatically via
@ForeignNameannotations in mirror type declarations (see Classes and Protocols for details). Thecjccompiler picks those annotations up. -
Do annotate constructors and non-overriding member functions that have two or more parameters. The absence of a
@ForeignNameannotation on such a member function or constructor triggers a compile-time error.The compiler is unable to morph the original Cangjie name of a member function or constructor with two or more parameters into a valid Objective-C name automatically. Such names essentially consist of the respective number of fragments separated with the colon character
:. -
Overloaded member functions and constructors must be given distinctive Objective-C names using
@ForeignName, as there is no method overloading in Objective-C. Exacly one of such overloaded entities can retain its original Cangjie name, provided it has at most one parameter. -
Annotating the remaining members and constructors with
@ForeignNameis optional. In the absence of a@ForeignNameannotation, the original Cangjie name of such a member is used as the Objective-C name of the corresponding entity, with colon:appended to names of single-parameter methods, whereas theinit-method matching the solepublicconstructor with one or zero parameters is called respectivelyinit:orinit.
NOTE: The current version of the
cjccompiler does not fully validateforeign-name. In particular, it does not check that the number of colon:separators mathes the number of method or constructor parameters. -
-
Use the following Objective-C-to-Cangjie type mapping (
T'is either the matching value type or the respective mirror type):Objective-C Cangjie Remark voidUnitBOOLBoolsigned charInt8shortInt16intInt32longInt64long longInt64unsigned charUInt8unsigned shortUInt16unsigned intUInt32unsigned longUInt64unsigned long longUInt64floatFloat32doubleFloat64struct@C struct'(*) enumw/base typeTT'idObjCId(†) Objective-C Cangjie If Tis...Remark T*CPointer<T'>... a primitive or structure type T*CPointer<U'>... an enum type and Uis its base typeT*CFunc<T>... a pure C function type T*T'... a class type (†) (*) The Objective-C structure must not contain fields of types that are not
CType-compatible. See Structs for details.(†) Use
?<T'>(Option<T'>) types for parameters, return values, and local variables of mirror types and interop classe types that may receive/hold the Objective-Cnilvalue.See Objective-C to Cangjie Mapping for details.
Supported features:
Member functions of interop classes:
- Can override/redefine
openinstance/static member functions of their mirrored superclasses, implement member functions of their mirrored superinterfaces, and be newly introduced member functions.
Constructors and member functions of interop classes:
-
Can use any Cangjie language features in the body when working solely with (values of) regular Cangjie types.
-
Can use conventional Cangjie syntax to:
- instantiate objects of interop classes and mirrored Objective-C classes
- call static and instance methods of interop classes and mirrored
Objective-C types (that includes using
superto call Objective-C superconstructors and superclass methods) - access their own member properties and non-
privatemember properties of other interop classes and mirrored Objective-C types
Limitations:
-
Interop classes may implement interfaces that are mirrored Objective-C protocols, but not regular Cangjie interfaces.
-
Interop classes may not be declared as
openorabstractand may not be extended usingextend. -
Non-
privatemember functions and constructors of interop classes:-
May only use the Objective-C-mapped types listed above as parameter and return value types. (As a consequence, variable-length arguments are not supported as that requires using the Cangjie
Array<T>type, which has no Objective-C mapping.) -
May not have named parameters.
-
May not have type parameters, i.e. be generic.
-
-
Non-
privatemember properties may only have the Objective-C-mapped types listed above. -
Generic Objective-C types are erased; mirror types have upper bounds in place of the respective type parameters.
-
IMPORTANT: Instances of mirror types and interop classes (in other words, values of Objective-C reference types) must not escape to global or static Cangjie variables or any data structures that persist between calls.
End-to-end Example (continued):
To continue the above example, the interop class A would look
like this:
package cjworld // Same package name
import interoplib.objc.* // Always required
@ObjCImpl
public class A <: M {
public init() {
super()
}
@ForeignName["foo"]
public open override func foo(): Unit {
println("Hello from overridden A.foo()")
}
}
Step 4: Compile the Interop Classes {#step-4}
Command line:
cjc --target=arm64-apple-ios-simulator \
--sysroot=$(xcrun --show-sdk-path --sdk iphonesimulator) \
--output-type=dylib \
--int-overflow=wrapping \
<source-files> \
-o <target-file> \
--link-options "-undefined dynamic_lookup"
where
<source-files> are the source code files of the interop classes and
mirror type declarations.
<target-file> is the desired name of the output .dylib file with
compiled Cangjie code for the interop classes, such as libcjworld.dylib.
The compiler will also generate Objective-C source files (.h and .m)
for the Objective-C wrappers or the interop classes, placing them in the
./objc-gen/ subdirectory.
The .dylib file has to be signed:
xcrun codesign --sign - <dylib-file>
End-to-end example (continued):
Compile the interop class:
cd cjworld
cjc --target=arm64-apple-ios-simulator \
--sysroot=$(xcrun --show-sdk-path --sdk iphonesimulator) \
--output-type=dylib \
--int-overflow=wrapping \
*.cj \
-o libcjworld.dylib \
--link-options "-undefined dynamic_lookup"
The compiler will produce three files: ./libcjworld.dylib,
./objc-gen/A.h and ./objc-gen/A.m.
Sign the generated dynamic library:
xcrun codesign --sign - libcjworld.dylib
Step 5: Put It All Together {#step-5}
-
Create another subdirectory in your project and copy all dynamic libraries from the
$CANGJIE_HOME/runtime/lib/ios_simulator_aarch64_cjnative/directory into it. -
Add those all dynamic libraries and the
.dylibfile generated on the previous step to your Xcode project as dependencies (BuildPhases - in both “Copy Files” and “Link Binary With Libraries” lists). -
Move the
.hand.mfiles generated on the previous step to project root.
Then re-build the project.
End-to-end Example (continued):
Create a new subdirectory in the root directory of your project and copy the dynamic libraries constituting the Cangjie Runtime for iOS into it:
cd ..
mkdir -p CJRuntimeDylibs
cp $CANGJIE_HOME/runtime/lib/ios_simulator_aarch64_cjnative/*.dylib CJRuntimeDylibs/
Add those dynamic libraries and ./cjworld/libcjworld.dylib to your Xcode
project as dependencies (BuildPhases - in both “Copy Files” and “Link Binary
With Libraries” lists).
Move the .h and .m files generated by cjc to project root:
mv cjworld/objc-gen/*.h ./
mv cjworld/objc-gen/*.m ./
Rebuild the Xcode project.
Calling Objective-C from Cangjie
Once you have designed, built and integrated the interoperability layer as described in the previous section, you can add code that uses Objective-C types to the member functions of your interop classes. The type mapping is the same:
Add the code invoking a Cangjie function from Objective-C via the respective method of an interop class wrapper and re-build your iOS project again.
End-to-end Example (continued):
Insert an instantiation of A and a call of the method foo() of that
instance into your Objective-C code:
. . .
#import "M.h"
#import "A.h"
. . .
M* a = [[A alloc] init];
[a foo];
. . .
Rebuild the Xcode project and run your app on the iOS simulator.
Subsequent Enhancements
Following the above process should have helped you develop a good understanding of how to add more classes to the interoperability layer and/or enable the use of more Objective-C classes in its Cangjie code. Below please find a brief summary with links to the respective subsections:
Step 1: Add more interop classes to your design and/or enhance the existing ones with new methods and/or properties as you wish.
Step 2: If any additional, not yet mirrored Objective-C reference types will now get involved, build the current version of the application to ensure consistency, edit the mirror generator configuration file appropriately, and then (re-)generate all mirror type declarations.
Step 3: Implement the newly added interop classes, if any, and/or change the code of existing ones, using the mirrored Objective-C types as necessary.
Step 4: Compile all interop classes together.
Step 5: Copy the .h, .m and .dylib files that cjc has
generated on the previous step to their respective locations in the project
and re-build it.
Now you can start using the newly added interop classes and/or methods in the Objective-C part of your application.
Features and Limitations of Interop Classes
-
An interop class must be a direct subclass of a mirror class.
-
An interop class may implement one or more mirror interfaces, but never a conventional Cangjie interface. Conversely, a conventional Cangjie type may not implement or inherit an interface mirroring an Objective-C protocol.
-
An interop class may not be declared as
openorabstractand may not be extended usingextend. -
An interop class may introduce new instance fields of any Cangjie type and override the member functions of its mirrored superclass.
-
The constructors of an interop class may call superclass constructors using
super(), but have the same limitation on instance member function calls as the conventional Cangjie constructors, as well as the requirement to initialize all newly introduced member variables. -
The instance member functions of an interop class may call instance member functions which that interop class has inherited from its mirrored superclass, and/or use
super.to call the member functions of that superclass that the interop class overrides. -
The signatures of constructors and member functions of all mirror types and interop classes can only use types that are (a) mirror types or interop classes themselves, or (b) are 100% analogous to Objective-C primitive types. See the Type Mapping table in Step 3 above and the Chapter Objective-C to Cangjie Mapping.
Objective-C to Cangjie Mapping
The current version of the mirror generator does the following Objective-C → Cangjie conversions.
General Considerations
The Objective-C mirror generator relies on Clang for parsing Objective-C
sources, invoking it with the -fobjc-arc option.
Declarations marked with the unavailable attribute are ignored.
Global, file-level function and variable declarations are also ignored.
The order of declarations in the output Cangjie source code is the same as the order of the respective definitions in the input Objective-C source code, with the exception of nested type definitions.
Names
The original Objective-C identifiers are preserved, with the following exceptions:
-
Objective-C identifiers that clash with Cangjie keywords, such as
catch,false, orUInt32, are enclosed in backticks``. -
Name conflicts between Objective-C classes and protocols are resolved by adding the suffix
Protocolto the name of the protocol. -
Name conflicts between instance and static methods are resolved, if possible, by adding either
InstanceorStaticsuffix to the highest (closest to the root) class/protocol where the conflict is encountered. If the conflicting instance/static methods are first introduced in the same class/protocol, the static method is renamed. For example:@interface A +(void)foo; @end @interface B : A -(void)foo; +(void)bar; -(void)bar; @endwill be converted to
@ObjCMirror public open class A <: ObjCId { public static func foo() } @ObjCMirror public open class B <: A { public open func fooInstance() public static func barStatic() public open func bar() } -
Conflicts between
initmethods having different names, but the same number and types of parameters are not resolved. See Classes for details.
Type Aliases
C typedef declarations are converted to public Cangjie type aliases.
Primitive Types
Objective-C primitive types map to the respective Cangjie value types. C types with platform-specific sizes map to Cangjie in accordance with their actual sizes on the host platform, i.e. the platform on which the mirror generator runs. For example, on macOS it can be the following:
| Objective-C | Cangjie |
|---|---|
void |
Unit |
BOOL |
Bool |
signed char |
Int8 |
short |
Int16 |
int |
Int32 |
long |
Int64 |
long long |
Int64 |
unsigned char |
UInt8 |
unsigned short |
UInt16 |
unsigned int |
UInt32 |
unsigned long |
UInt64 |
unsigned long long |
UInt64 |
float |
Float32 |
double |
Float64 |
Structs
A C struct is converted to a public Cangjie struct marked as @C if it is
CType-compatible, that is, contains fields of CType-compatible types only.
Other scenarios, e.g. the original Objective-C structure containing pointers
to objects, are not currently supported.
Nested structures are converted to top-level Cangjie structs.
Incomplete structure declarations are converted to empty structs (without any members).
Fields of Objective-C structures are converted to public member variables
of the respective Cangjie structs. The static modifier is maintained.
Bit fields are not supported by the Cangjie language, so their widths are ignored and a warning is issued.
The Cangjie language, unlike Objective-C, requires all member variables
of structs (even @C ones) to be initialized. Therefore, mirrored structures
contain explicit zero initializers for all fields. For example:
struct A {
int x;
double y;
BOOL z;
struct A *w;
};
is mirrored to
@C
public struct A {
public var x: Int32 = 0
public var y: Float64 = 0.0
public var z: Bool = false
public var w: CPointer<A> = CPointer<A>()
}
Enumerations
Named C enumeration declarations are converted to abstract sealed Cangjie
classes (effectively, namespaces). Enumerators are converted to public static
constants initialized with their values. Their type corresponds to the
explicitly specified underlying type for the enumeration, if any, otherwise
it is Int32.
Unions
The Cangjie language does not support C union types. They are converted to structs with sequential fields and a warning is issued.
Classes and Protocols
Objective-C classes and protocols are mirrored respectively to Cangjie
classes and interfaces. All such mirror classes and interfaces explicitly
implement the built-in ObjCId mirror interface
(see Built-in Types).
In methods with a variable number of parameters, , ... is ignored.
Full Objective-C method names (selectors) decorated with : are not
valid Cangjie identifiers. Such names are mangled as follows:
- Each letter that immediately follows a
:character, if any, is capitalized. - All
:characters are removed.
The original selector is specified in the @ForeignName attribute
on the Cangjie side.
Example:
@interface A
- (void)foo;
- (void)foo:(int)i;
- (void)foo:(int)i bar:(int) j;
- (void)foo:(int)i bar:(int) j baz:(int) k;
@end
is mirrored to
@ObjCMirror
public open class A {
public open func foo(): Unit
@ForeignName["foo:"] public open func foo(i: Int32): Unit
@ForeignName["foo:bar:"] public open func fooBar(i: Int32, j: Int32): Unit
@ForeignName["foo:bar:baz:"] public open func fooBarBaz(i: Int32, j: Int32, k: Int32): Unit
}
In Cangjie, a property that overrides another property must have the same
type. Also, the getter/setter functions of Cangjie properties may not be named
arbitrarily. Hence any properties declared in Objective-C classes are only
mirrored into Cangjie properties when possible. In all other cases,
their getter/setter methods are mirrored as ordinary instance member
functions. This includes properties declared within @protocol directives.
Instance variables are mirrored into instance member variables.
instancetype is converted to the name of the current declaration.
Classes
Objective-C @interface class declarations are converted to Cangjie classes
marked as @ObjCMirror public open.
Objective-C @interface category and extension declarations are merged
with the respective Cangjie class declarations.
Objective-C @implementation declarations are ignored.
Forward class declarations (@class directives) are mirrored into empty
classes (without any members).
Methods identified as init methods as described in the
Method families section of the Clang documentation on Objective-C ARC
are mirrored into Cangjie constructors, with two limitations:
-
If two or more
initmethods of a class differ only by name, i.e. have the same number and types of parameters, the mirror generator comments out the respective Cangjie constructor declarations and issues a conflict warning. -
initmethods are inherited like any other methods, whereas constructors in Cangjie are not inherited. The constructors that mirror theinitmethods of superclasses are therefore unavailable at the point of a mirror class or interop class instantiation.
Other methods of Objective-C classes are mirrored into public open member
functions of the respective Cangjie mirror types. Mirrors of class methods
(those with the "+" prefix) are modified with static.
Protocols
Objective-C @protocol directives are mirrored into Cangjie interfaces
marked as @ObjCMirror public. However, the cjc compiler does not support
such interfaces yet.
Forward protocol declarations are mirrored into empty interfaces (without any members).
Methods of Objective-C protocols are mirrored into public open member functions
of the respective Cangjie interfaces. The static modifier is maintained.
The @optional directive is ignored.
Pointers
The modifiers const, volatile, and restrict are ignored.
Pointers to C primitives and their type aliases are converted to CPointer
with the respective type parameters.
A pointer to a C enumeration is converted to a CPointer to its underlying
type, with the actual enumeration name specified in a comment.
Pointers to Structures
A pointer to a C structure T is converted to CPointer<T'>, where T' is
the mirror type for T, if the structure is CType-compatible. Otherwise,
they are converted to ObjCPointer<T'> (provisional name), which is a type
that should be implemented in the interop library.
Pointers to Functions
Pointers to C functions are converted to either CFunc (if the function
parameter types and return value are all CType-compatible) or to ObjCFunc<F>
(provisional name) otherwise. The latter is a type implemented in the interop
library.
Pointers to Blocks
Pointers to Objective-C blocks are converted to ObjCBlock<F> (provisional
name), which is a type implemented in the interop library. The type argument F
of ObjCBlock<F> must be the respective Cangjie function type.
Pointers to Class Instances
Pointers to Objective-C class instances are mirrored as follows:
-
Pointers to class instances (
SomeClass*) are mirrored to the respective mirror classes. -
The mirror of the Objective-C type
idnarrowed with a single protocol (for example,id<NSCopying>), is the mirror interface for that protocol. -
idnarrowed with several protocols, e.g.id<NSCopying, NSSecureCoding>, is mirrored to a pureObjCId(the interface that all@ObjCMirrorclasses and interfaces respectively implement and inherit), with the actual protocol list specified in a comment. -
If a generic type parameter is used inside a generic template with a narrowing protocol specified, it is converted to a reference to that protocol, with the name of the type parameter specified in a comment.
Generics
Parameterized Objective-C classes are mirrored as regular, non-generic
Cangjie class declarations. The original lightweight generics syntax
such as "<T>" or "<Foo>" is retained in comments; type parameter
usages are replaced with ObjCId.
Example:
@interface G<T> : NSObject
- (void)f:(T)t;
@end
is mirrored to
@ObjCMirror
public open class G/*<T>*/ <: NSObject {
@ForeignName["f:"] public open func f(t: ?ObjCId /*T*/): Unit
}
However. type constraints are lost as of the current version, i.e.
@interface G<T: SomeType*> : NSObject
- (void)f:(T)t;
@end
is now mirrored exactly as the first sample above.
Built-in Types
The mirror generator assumes that the following user-available Cangjie types are implemented in the interop library. As of the current version, however, they are not yet implemented, and their names may change.
| Objective-C | Cangjie (*) | Description |
|---|---|---|
id |
ObjCId |
Interface that should be implemented by all @ObjCMirror classes and interfaces. Bound to Objective-C id. |
SEL |
SEL? |
Class bound to Objective-C SEL. |
Class |
Class? |
Class bound to Objective-C Class. |
Protocol |
Protocol? |
Class bound to Objective-C Protocol. |
| pointer type | ObjCPointer<T>? |
Structure bound to Objective-C pointers of two kinds: with arity more than one and when T is a not CType-compatible structure. |
| non-C function type | ObjCFunc<F>? |
Class implementing CFunc for functions that are not CType-compatible. F is a Cangjie function type. |
| block type | ObjCBlock<F>? |
Structure implementing an Objective-C block. F is a Cangjie function type. |
__builtin_va_list |
Helper type alias for CPointer<Unit>, technically needed in the current generator implementation. May and should be dropped in the future. |
(*) Names of Cangjie types are provisional.
Not (Yet) Implemented Features
Objective-C interoperability support in the Cangjie SDK is still in development. Certain features are not implemented yet, others may change in the first production release, and some cannot be (fully) implemented due to principal differences between the two languages.
-
The C language features that alter the default size, packing, padding, and/or alignment of data are very ABI-specific. The Cangjie language does not support such low-level features, and bit fields in particular. The mirror generator therefore ignores bit field width specifiers and issues warnings.
-
The Cangjie language does not provide support for C union types, so they are mirrored as structs and warnings are issued.
-
Structures with fields of types that are not
CType-compatible are not supported. -
Anonymous C enumeration declarations are ignored. Named ones are mirrored to abstract sealed Cangjie classes. See Enumerations for details.
-
In methods with a variable number of parameters,
...is ignored. -
The
@optionaldirective is ignored. -
Properties are mirrored as properties if possible, otherwise their getter and setter methods are mirrored as if they were regular instance methods. See Classes and Protocols for details.
-
The modifiers
const,volatile, andrestrictare ignored. -
initmethods are mirrored into Cangjie constructors, which imposes two limitations:-
Two constructors of a Cangjie class may not have the same number and types of parameters, whereas
initmethod may differ by name, and -
Constructors in Cangjie are not inherited.
See Classes for details.
-
-
Parameterized Objective-C classes are mirrored as regular, non-generic Cangjie class declarations. See Generics for details.
-
Mirroring of
@protocoldeclarations into Cangjie interfaces is not fully supported yet. -
Pointers to functions with parameter/return types that are not
CType-compatible are not supported. -
Objective-C blocks are not supported.
-
The intrinsic helper functions for converting values of the Objective-C type
NSStringto/from values of the Cangjie typeStringare not implemented yet.
nil Handling
Cangjie has no concept of null references and hence no equivalent
for the Objective-C nil value. If pointers to Objective-C
classes and protocols were mirrored to Cangjie classes and interfaces,
any such value passed over from Objective-C to Cangjie could lead
to a segmentation fault. Conversely, there would be no way
to pass a nil value from Cangjie to Objective-C either.
The Option<T> enum is therefore generally used to represent the values
of those Objective-C types, with None standing for the nil
value, and Some(r) representing a (non-null) reference value r.
The cjc compiler recognizes Option<T> as an Objective-C compatible
type if T is a mirror type or interop class and wraps/unwraps
the values of T accordingly.
For instance, the following Objective-C @interface directive:
@interface MyContainer: NSObject
. . .
- (void)addItem:(MyItem *)item withUuid:(NSString *)uuid;
- (MyItem *)itemWithUuid:(NSString *)uuid;
- (NSString *)uuidForItem:(MyItem *)item;
@property (copy) NSArray<MyItem *> *allItems;
@end
will be mirrored to (@ForeignName annotations omitted for brevity):
@ObjCMirror
open class MyContainer <: NSObject {
. . .
public open func addItemWithUuid(item: ?MyItem, uuid: ?NSString): Unit
public open func itemWithUuid(uuid: ?NSString): ?MyItem
public open func uuidForItem:(item: ?MyItem): ?NSString
public mut prop allItems ?NSArray/*<MyItem>*/
}
The Option<T> wrapping ensures that the code won't break if a nil
value sneaks into the Cangjie world from the Objective-C one,
but it does that at the cost of performance and memory footprint.
The other disadvantage of this approach is loss of variance.
That being said, support for nullability annotations
considerably reduces the impact of reference wrapping, at least when
it comes to the use of iOS APIs in Cangjie code.
NOTE: This problem does not exist for the low-level C types that get
mirrored to the conventional CPointer<T> type, as the latter provides
explicit null check functions.
Loss of Variance
One limitation imposed by the Option<T> wrapping
of Objective-C mirror types and interop classes is that such wrapped types follow
the semantics of Cangjie in all other respects. In particular, Option<T> is
invariant by its type parameter T: Option<U> is not a subtype of Option<T>
if U is a subtype of T unless T and U are the same type. For mirror types
that means that any overriding Objective-C method that relies on return type
covariance may not be mirrored that way with Option<T> wrapping. The return
value type of the mirror of such a method has to be propagated
from the method that it overrides.
Example:
Suppose an Objective-C class Foo is a direct superclass of the class Bar:
@interface Foo : NSObject
@end
@interface Bar : Foo
@end
and the class C declares a method get that returns an instance of Foo:
@interface C : NSObject
- (Foo*) get;
@end
A subclasss of C may then override get with a more precise return type,
Bar:
@interface D : C
- (Bar*) get;
@end
Without Option<T> wrapping, all those classes could be mirrored to:
@ObjCMirror
open class Foo <: NSObject {}
@ObjCMirror
open class Bar <: Foo {}
@ObjCMirror
open class C <: NSObject {
open func get(): Foo
}
@ObjCMirror
open class D <: C {
open func get(): Bar // Return type covariance in action
}
but if get() can possibly return nil, an application crash is inevitable.
Option<T> wrapping makes it safe, but the return types of all overriding
methods have to be lowered to the return type of the original method:
@ObjCMirror
open class Foo <: NSObject {}
@ObjCMirror
open class Bar <: Foo {}
@ObjCMirror
open class C <: NSObject {
open func get(): Option<Foo>
}
@ObjCMirror
open class D <: C {
// open func get(): Option<Bar> // Error, `Option<T>` is not covariant by T
open func get(): Option<Foo> // OK, but the return type is lowered
}
Nullability annotations alleviate the problem partially.
Nullability Annotations
Nullability keywords were introduced to Objective-C with the release of XCode 6.3 back in the day, for better integration with the new iOS/OS X development language, Swift. The definitions of all iOS APIs were enhanced with nullability annotations to reduce the use of optionals in Swift code utilizing those APIs.
Objective-C Nullability Annotations
The keywords
nullableandnonnullcan be used for Objective-C properties, method result types and method parameter types. They mean that the given entity respectively may or may not hold or accept thenilvalue. The (very rare)null_unspecifiedannotation denotes the entities about which it is unknown whether their value may benil.In addition, any pointer type may be annotated with
__nullable,__nonnullor__null_unspecifiedwith the same semantics.Finally, a property may also ne annotated with
null_resettable, meaning that its getter will never returnnil, but passingnilto its setter would reset the property to some default value.For details, see Designating Nullability in Objective-C APIs
All uses of Objective-C reference types annotated as non-nullable are therefore
exempt from Option<T> wrapping. In other words, the Objective-C mirror
generator emits wrapped types only for properties, method result types and
method parameter types that are not annotated with either nonull
or _Nonnull in the original Objective-C code.
Suppose the example from the introduction to this section was enhanced with nullability annotations as follows:
@interface MyContainer: NSObject
. . .
- (void)addItem:(nonnull MyItem *)item withUuid:(nonnull NSString *)uuid;
- (nullable MyItem *)itemWithUuid:(nonnull NSString *)uuid;
- (nullable NSString *)uuidForItem:(nonnull MyItem *)item;
@property (copy, nonnull) NSArray<MyItem *> *allItems;
@end
The mirror generator would then bypass Option<T> wrapping for the
nonnull entities (@ForeignName annotations omitted for brevity):
@ObjCMirror
open class MyContainer <: NSObject {
. . .
public open fund addItemWithUuid(item: MyItem, uuid: NSString): Unit
public open func itemWithUuid(uuid: NSString): ?MyItem
public open func uuidForItem:(item: MyItem): ?NSString
public open prop allItems: NSArray
}
NOTE: It is currently not possible to mirror null_resettable properties
into Cangjie properties while propagating the semantics of that annotation,
hence the latter is treated exactly as nullable.
If your Objective-C code that you want to access from Cangjie is not taking
advantage of the nullability annotations, you may want to add at least the
nonnull annotations as appropriate before you begin using the Cangjie SDK
interoperability features described here. That may considerably reduce
the use of Option<T> wrapping in the respective mirror types, making the
code of interop classes cleaner and easier to read.
Objective-C Mirror Generator Reference
Prerequisites
Make sure to run the envsetup.sh script from the Cangjie SDK before using
the mirror generator.
You need to know the pathnames of the header files of the frameworks and libraries in which all dependencies of the classes you are going to mirror are defined. This includes the locations of the iOS standard library headers and generally all directories in which the Objective-C compiler looks up header files when it builds your project.
Command-line Syntax
ObjCInteropGen [-v] [--mode=normal config-file]
-v
Produce verbose output.
--mode=normal
Enforces the normal operation mode. Other modes are only used
for the development and testing of the mirror generator itself.
The use of --mode=normal is mandatory in the current version
if config-file is specified, but will be optional in the future.
config-file
The pathname of the configuration file.
Configuration File Syntax
An Objective-C mirror generator configuration file is a plain text file in the TOML syntax. It specifies:
- Output directories
- Names of the source Objective-C headers (
.h-files) - Names of the output Cangjie packages and the distribution of mirror types across them
- Mappings for types that you want to be handled in a special way
String values that are interpreted as regular expressions must follow the ECMAScript regular expression syntax.
Output Directories Roots
Each entry in the [output-roots] table of tables defines a symbolic name
for the pathname of a directory in the local file system. The mirror generator
will use that pathname as the common root of one or more package-specific
output directories, set using the output-root property of the respective
[[packages]] array elements.
Example:
[output-roots.lib]
path = "./lib/src"
[output-roots.app]
path = "./main/src"
[[packages]]
package-name = "com.vendor1.lib1"
output-root = "lib" # Output to "./lib/src/com/vendor1/lib1"
filters = ...
[[packages]]
package-name = "com.vendor2.lib2"
output-root = "lib" # Output to "./lib/src/com/vendor2/lib2"
filters = ...
[[packages]]
package-name = "com.mycompany.app"
output-root = "app" # Output to "./main/src/com/mycompany/app"
filters = ...
Source Files
Each entry in the [sources] table of tables specifies a set of individual
header files that the mirror generator must take as input.
Properties:
paths (mandatory)
An array of strings that contain pathnames of individual header files that the mirror generator must read.
arguments (optional)
Array of strings containing Clang options that the mirror generator will
pass over when processing the source files listed in paths.
Example:
[sources.all]
paths = ["original-objc/M.h"]
Additional Clang Arguments
Each entry in the [sources-mixins] table of tables specifies a regular
expression matching one or more keys of the [sources] table
and additional arguments that must be passed to Clang when processing
the header files from its respective entries.
Properties:
sources (mandatory)
A string containing a regular expression.
arguments-prepend
arguments-append (optional)
Arrays of strings that the mirror generator will pass over to Clang
as options when processing the source files listed in any [sources]
table entry which key matches the regular expression specified in
the sources property.
The options listed in arguments-prepend and arguments-append
will be passed respectively before and after the options specified
in the arguments property of each matching [sources] entry,
if any.
Example:
[sources.UIWidgets]
paths = ["objc/UIWidgets.h"]
arguments = [ "-I", "/usr/local/include/share/Widgets" ]
[sources.UIPanels]
paths = ["objc/UIPanels.h"]
arguments = [ "-I", "/usr/local/include/share/Panels" ]
[sources-mixins.UI]
# Add these Clang arguments for both UIWidgets and UIPanels
sources = ["UI.+"]
arguments-append = [
"-I", "/usr/local/include/Frameworks/AcmeUI"
]
Packages
Each entry in the [[packages]] array specifies a target Cangjie package
name, a set of name filters that define which Objective-C entities will
be mirrored to that package, and, optionally, the output directory
specific for that package.
Properties:
package-name (mandatory)
A string containing the target Cangjie package name.
output-path (optional)
A string containing the exact pathname of the directory into which the mirror generator shall place the output files for the given package. If any directories in that pathname do not exist, the mirror generator will attempt to create them.
output-root (optional)
A string containing a key from the output-roots table
The name of the target Cangjie package will be appended to the value
of the respective output-roots table entry, dots . replaced with
the file separator /, and the resulting pathname will be used
as if it was the value of output-path.
If output-root is not set, and if there is only one element
in [output-roots], the pathname from that entry is used.
Otherwise, an error is shown.
Example:
[output-roots.main]
path="./cj-mirrors"
[[packages]]
package-name = "objc.foundation"
output-root = "main"
The output files will be placed in ./cj-mirrors/objc/foundation.
filters (mandatory)
A table defining a set of name filters that select only those Objective-C entity declarations from the source files that must be mirrored to the given Cangjie package. See Name Filters for details.
Example:
# The Foundation framework
[[packages]]
package-name = "objc.foundation"
filters = { include = "NS.+" }
Name Filters
Each name filter is, in turn, a TOML table that contains:
- Exactly one of the properties
include,exclude,union,intersectandnot, and - Optionally, the
filterproperty and/or thefilter-notproperty.
Descriptions of the properties follow.
include
The value can be a regular expression or an array thereof. If the value is a single regex, only names that match it pass the filter. If the value is an array, a name only needs to match any single regex from that array to pass.
Examples:
# Only include entities that names of which start with "NS":
filters = { include = "NS.+" }
# Only include entities with names that either start with "Foo"
# or end with "Bar":
filters = { include = ["Foo.*", ".*Bar"] }
exclude
The opposite of include (see above). A name that would pass
an include filter with the given value does not pass an exclude
filter with the same value and vice versa.
Example:
# Include everything but entities the names of which start with "INTERNAL_":
filters = { exclude = "INTERNAL_.+" }
union
An operator combining two or more filters. Its value must be an
array of filters. A name that passes any single one of those
filters passes the entire union filter.
Example:
# An equivalent of the second `include` example above:
filters = { union = [ { include = "Foo.*" }, { include = ".*Bar" } ] }
intersect
An operator combining two or more filters. The value must be an
array of filters. A name must pass all of them to pass the
the entire intersect filter.
Example:
// Adding a negative filter:
filters = { intersect = [ { include = "NS.+" },
{ exclude = "NSAccidentalClash" } ] }
not
An operator that reverses the meaning of a filter. The value must be a single filter:
Example:
// Another way to add a negative filter:
filters = { intersect = [ { include = "NS.+" },
{ not = { include = "NSAccidentalClash" } } ] }
filter
filter-not (optional)
These properties must be mixed with other properties, i.e.
they cannot be the only properties of a filters table.
Their values can be regular expressions or arrays thereof,
just as those of the include and exclude properties.
filter reduces the set that passed the main filter to include
only names that match the given single regular expression or any
regex from the array.
filter-not reduces the set that passed the main filter to
include only names that do not match the given single regular
expression or any regex from the array.
filter and filter-not are actually shorthands for intersect
operations with an include and exclude filter respectively.
They can be specified together.
Examples:
# Without filter-not:
filters = { intersect = [ { include = "NS.+" },
{ exclude = "NSAccidentalClash" } ] }
# With filter-not:
filters = { include = "NS.+", filter-not = "NSAccidentalClash" }
# 'filter' and 'filter-not' can be used together:
filters = { include = ".*Fizz.+",
filter = ".+Buzz.*",
filter-not = ".*FizzBuzz.*" }
Type Substitutions
[[mappings]] is an array of tables, each adding to the list of substitutions
of one Objective-C type for another. All mentions of almost any type
(except for C primitive types such as int) can be replaced with mentions
of another type.
Example:
# Replace the type id, the root of the Objective-C type hierarchy,
# with NSObjectProtocol everywhere.
[[mappings]]
id = "NSObjectProtocol"
Configuration File Import
imports is an array of strings each containing the pathname of another
configuration file, the settings from which will be added to the current
configuration. The entries of packages and mappings arrays found
in the imported file are appended to those present in the importing file.
Nested imports are supported, circular import is detected and results in an error.
Example:
import = "../common.toml"