This is a minimal Objective C runtime written in C, designed to be portable across different platforms, including ARM and x86 architectures, mostly targeting embedded systems. It uses the "gcc" ABI for the moment, as that is the most portable across different platforms.
You will minimally need the following tools to build the runtime:
- Build system
make
andcmake
- for the build system - Compiler
clang
orgcc
- for compiling the runtime (clang
is not supported on Apple Silicon). You can use the environment variableCC
to specify the compiler, e.g.CC=clang
orCC=gcc
. - Library Dependencies
openssl
for Linux and Darwin - for the hash functions, which are used in the runtime and Foundation framework. - Documentation
docker
is needed for generating the documentation from the source code. - Cross-Compilation For cross-compilation for embedded systems based on some ARM variant, get the ARM LLVM toolchain: https://github.com/ARM-software/LLVM-embedded-toolchain-for-Arm/releases. Install this to the
/opt
directory. You can use the environment variableTOOLCHAIN_PATH
to specify the path to the toolchain, e.g.TOOLCHAIN_PATH=/opt/LLVM-ET-Arm-19.1.5-Darwin-universal
.
Several static libraries are currently built:
objc-gcc
- the Objective C runtime library using the ancient GCC ABIruntime-sys
- a minimal set of system functions, needed to bind the runtime to the underlying system, on a per-platform basis. Includes cryptographic hash functions (MD5, SHA-256).runtime-hw
- hardware interfaces like SPI, I2C, GPIO. These are stubs for Linux and Darwin, but I guess some of these interfaces couple be implemented later.runtime-fs
- organizing files, directories and volumes which can either be volatile (in-memory) or non-volatile (file-based or in flash)drivers
andruntime-pix
have a long way to go but should provide hardware driver and display support eventually.Foundation
- a minimal set of classes, to support memory management and basic types such as string, array, dictionary, date, time and number.Application
- the application framework, which provides a runloop and handles input, display, power and sensors.
A lot of this code is in development still. See the "examples" or documentation on what's been implemented so far.
Download the source code from GitHub:
git clone git@github.com:djthorpe/objc.git
cd objc
The TOOLCHAIN_PATH
environment variable should point to the directory where the toolchain is installed.
For Macintosh, you can use Homebrew to install the GCC toolchain, which is required for compiling the runtime on MacOS:
# Compile with GCC 15 for MacOS
brew install gcc@15
TOOLCHAIN_PATH=/opt/homebrew RELEASE=1 CC=gcc-15 make
Once you've made the libraries, use the make tests
target to run the unit tests. There is information about test coverage in the tests directory.
On macOS, build and run all tests:
TOOLCHAIN_PATH=/opt/homebrew CC=gcc-15 RELEASE=1 make tests
Run an individual test target (replace <test_target>
with e.g. fs_09
):
cmake --build build --target <test_target>
ctest --output-on-failure --test-dir build --tests-regex <test_target>
See the tests README for a catalog of tests (fs_01..fs_09 and others) and details.
You can target different architectures by setting the TARGET
environment variable. For a RP2040-based board, you can use the clang
compiler with the ARM toolchain. The TARGET
environment variable should be set to the target architecture, such as armv6m-none-eabi
for the RP2040 Pico board:
# Compile for the RP2040 Pico board
CC=clang TARGET=armv6m-none-eabi TOOLCHAIN_PATH=/opt/LLVM-ET-Arm-19.1.5-Darwin-universal RELEASE=1 make
See the list of supported targets in the cmake directory.
You can exclude the environment variable RELEASE=1
to build debugging versions of the libraries.
For Pico cross-compilation, after building, you can load a specific test onto a Pico W board (example for <test_target>
):
cmake --build build --target <test_target> && picotool load -x build/src/tests/<test_target>/<test_target>.uf2
For Linux cross-compilation using Docker (Bookworm):
docker run --rm -i -v $(pwd):/root bookworm-builder bash -c "cd /root && make clean && CC=gcc make"
docker run --rm -i -v $(pwd):/root bookworm-builder bash -c "ctest --output-on-failure --test-dir build --tests-regex <test_target>"
The libraries should be installed under a prefix path:
PREFIX=/opt/picofuse make install
- /opt/objc/lib/pkgconfig/picofuse-linux-arm64.pc
- /opt/objc/lib/linux-arm64/libobjc-gcc.a
- /opt/objc/lib/linux-arm64/libruntime-sys.a
- /opt/objc/lib/linux-arm64/libFoundation.a
- /opt/objc/include/objc
- /opt/objc/include/sys
- /opt/objc/include/Foundation
- /opt/objc/bin/...
These can subsequently be used in projects to make executables for any combination of platform.
To build the API documentation, you will need to have docker
installed. The documentation is built using doxygen
through docker
so you don't need to have it installed directly.
make docs
open docs/index.html
The documentation is also published on the project documentation site.
- Registering classes
- Simple message calling
-
NXConstantString
- Resolving super classes and meta classes for message lookup
- Calling methods in super classes - implement
[super init]
for example - Calling methods in categories
- Memory management - alloc, dealloc, memory arenas - require malloc in an
NXZone
- Printf support -
vsprintf
- printf-like function which can be used by NXLog, NXString and sys_panicf - Memory management - retain, release - reference counting for objects through NXZone
- Date and Time -
NXDate
- mutable date and time -
NXNumber
with booleans -
NXNull
- singleton for null objects that can be inserted into collections - Protocols and
conformsTo:
- Number -
NXNumber
withNXNumberInt16
andNXNumberUnsignedInt16
- Number -
NXNumber
withNXNumberInt32
andNXNumberUnsignedInt32
- Number -
NXNumber
withNXNumberInt64
andNXNumberUnsignedInt64
- Fix linking categories in static libraries (see test NXFoundation_05)
-
NXString
- mutable strings - append, appendFormat -
NXArray
- ordered collections of objects, with methods for adding, removing, and accessing objects -
NXData
- mutable binary data with Base64/hex encoding and append operations - Hash functions - MD5 and SHA-256 hash computation through
sys_hash_*
API - Pico toolchain - integrate with Pico SDK
- printf -
%@
format specifier for logging objects and[Object description]
- printf -
%T
format specifier for time intervals -
NXArray
- string methods - join - Robust sys_hash functions which hash some void* data against an arbitrary key, and a hashing function that can be used
-
NXMap
- unordered collections with string-keys (what aboutNXDictionary
?) -
NXMap
- arbitary key sizes - clang compatibility
- runtime-hw SPI support
- runtime-hw watchdog support
- runtime-hw PWM support - make tying to a GPIO pin a function, focus on slices
- runtime-hw Power support - callbacks for low battery, battery level, power on/off, etc.
- runtime-hw Power support - measure voltage on VSYS to get %age and also whether connected to USB or battery
-
GPIO
uses static instances, and<GPIODelegate>
protocol to handle GPIO events which are pushed from the runloop - mqtt make it asynchronous for publish, subscribe, unsubscribe and new message
- MQTT objective-c client
- runtime-hw LED support - blink and fade re-work
-
NXInputManager
- input manager for handling keyboard, mouse, GPIO and other input devices with single click, double click and so forth. - Wire GPIO into NXInputManager
-
NXApplication
andNXRunLoop
running on both cores - Shared instances need to be finalized on exit
-
Power
class and<PowerDelegate>
for managing power, resets, uptime, etc. -
@synchronized
support - use fixed-size table to store locks for objects, no allocations - NXNumber -
NXNumberUnsignedInt8
andNXNumberInt8
- Make all NX classes thread-safe, so that they can be used in multi-threaded applications
-
NXScanner
,ReaderProtocol
- scanning, parsing and tokenizing - needs unicode work -
NXData
- fix hexString encoding and decoding (see test NXFoundation_22)? -
NXArray
sortWithFunction: sortedArrayWithFunction: reverse: and reversedArray: -
NXArray
filterWithFunction: filteredArrayWithFunction: - Pico - timer alarm pool should be on both cores, not just core 0, and then use the right pool for the core that the timer is running on
- Pico - when building RELEASE=1 builds it includes stdout and printf, which is not needed. Same for cyw43 and lwip.
-
NXRange
andNXString
- substringWithRange and substringWithRange:options: -
NXString
- rangeOfSubstring and rangeOfSubstring:options: -
NXArray
-subarrayWithRange:
andsubarrayWithRange:options:
-
NXString
- array methods - componentsSeparatedByString and componentsSeparatedByByte - Number -
NXNumber
withNXNumberFloat
andNXNumberDouble
- printf -
%f and %lf
format specifier for floats and doubles - More efficient method implementation lookup
-
make install
will compile the libraries and install them to a prefix path - third-party repositories can compile against it -
respondsToSelector:
(see testruntime_14
) and lots of tests - might be working? - USB Support for keyboards & mice and input manager - some way of knowing when USB device inserted and removed
- Touchscreen support for Pimironi device
- Remote decoding of IR protocols and wire into NXInputManager
- runtime-pix with SDL devices to start with
- runtime-pix with eInk devices
- runtime-pix with LED devices
- runtime-pix with Pimironi device
- runtime-pix vector drawing
- runtime-pix 7x5 fonts
- runtime-pix vector fonts
- runtime-fs Low-level flash block device
- runtime-fs Low-level SD card block device
- runtime-fs littlefs filesystem - register block devices, format, etc.
-
NXSensorManager
- sensor manager for handling sensors such as accelerometer, gyroscope, magnetometer, temperature, humidity, pressure, etc. -
NXWirelessManager
- keeps track of wireless networks, connections, disconnections - runtime-pix load/save fonts and images
- low-level http client
-
NXURL
class - URL/filepath parsing and manipulation -
NXFile
- represents an open file -
NXFileManager
- manages files and directories on a volume -
NXFileDirectory
- represents a directory on a volume - Classes have a unique number so we can do NXCoder to serialize and deserialize objects from binary data?
-
NXCoder
- JSON / Binary marshalling and unmarshalling - Calling
+[initialise]
for categories - Exception handling?
- Raspberry Pi support - GPIO, SPI, I2C, PWM, ADC, etc.
Here are some references that may be useful for understanding the Objective C runtime and its implementation: