├── .gitignore ├── src ├── c.zig ├── objc.zig ├── message.zig ├── main.zig ├── type-encoding.zig └── runtime.zig ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | *zig-cache 2 | zig-out 3 | -------------------------------------------------------------------------------- /src/c.zig: -------------------------------------------------------------------------------- 1 | pub usingnamespace @cImport({ 2 | // message.h includes the other headers for us! 3 | @cInclude("objc/message.h"); 4 | }); 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD Zero Clause License 2 | 3 | Copyright (c) 2022 Chris Heyes 4 | 5 | Permission to use, copy, modify, and/or distribute this software for any 6 | purpose with or without fee is hereby granted. 7 | 8 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 9 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 10 | AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 11 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 12 | LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 13 | OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 14 | PERFORMANCE OF THIS SOFTWARE. 15 | -------------------------------------------------------------------------------- /src/objc.zig: -------------------------------------------------------------------------------- 1 | //! This module provides type-safe bindings to the API defined in objc/obj.h 2 | // TODO(hazeycode): add missing definitions 3 | 4 | pub const Error = error{ 5 | FailedToRegisterMethodName, 6 | }; 7 | 8 | const c = @import("c.zig"); 9 | 10 | /// An opaque type that represents an Objective-C class. 11 | pub const Class = *c.objc_class; 12 | 13 | /// Represents an instance of a class. 14 | pub const object = c.objc_object; 15 | 16 | /// A pointer to an instance of a class. 17 | pub const id = *object; 18 | 19 | /// An opaque type that represents a method selector. 20 | pub const SEL = *c.objc_selector; 21 | 22 | /// A pointer to the function of a method implementation. 23 | pub const IMP = *const anyopaque; 24 | 25 | /// Registers a method with the Objective-C runtime system, maps the method 26 | /// name to a selector, and returns the selector value. 27 | /// 28 | /// @param str The name of the method you wish to register. 29 | /// 30 | /// Returns A pointer of type SEL specifying the selector for the named method. 31 | /// 32 | /// NOTE: You must register a method name with the Objective-C runtime system to obtain the 33 | /// method’s selector before you can add the method to a class definition. If the method name 34 | /// has already been registered, this function simply returns the selector. 35 | pub fn sel_registerName(str: [:0]const u8) Error!SEL { 36 | return c.sel_registerName(str) orelse Error.FailedToRegisterMethodName; 37 | } 38 | 39 | /// Registers a method name with the Objective-C runtime system. 40 | /// The implementation of this method is identical to the implementation of sel_registerName. 41 | /// 42 | /// @param str The name of the method you wish to register. 43 | /// 44 | /// Returns A pointer of type SEL specifying the selector for the named method. 45 | /// 46 | /// NOTE: Prior to OS X version 10.0, this method tried to find the selector mapped to the given name 47 | /// and returned NULL if the selector was not found. This was changed for safety, because it was 48 | /// observed that many of the callers of this function did not check the return value for NULL. 49 | pub fn sel_getUid(str: [:0]const u8) Error!SEL { 50 | return c.sel_getUid(str) orelse Error.FailedToRegisterMethodName; 51 | } 52 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # zig-objcrt 2 | 3 | Objective-C Runtime bindings for Zig.. 4 | 5 | **NOTE:** I don't have any plans to work on this within the near future. Archiving for reference. 6 | 7 | Provides thin API bindings with a little added type-safety and error handling 8 | 9 | Also provides higher-level wrappers to make working with the objc runtime a little bit nicer 10 | 11 | 12 | **NOTE:** This is for working with the runtime API, user-friendly bindings to Apple Frameworks are not provided by this library but may be built on top. For bindings to Apple's C libraries, see https://github.com/kubkon/ZigKit 13 | 14 | 15 | **WARNING:** In early development. Does not yet have full API coverage and needs more testing. The higher-level APIs, i.e. `defineAndRegisterClass` are very WIP as there are some things to be worked out regarding memory management. Open to suggestions and issues and PRs are most welcome. 16 | 17 | 18 | **NOTE:** Currently uses translate-c on the objective-c headers that are vendored with Zig. It's perhaps better not to rely on these and replace with extern definitions? 19 | 20 | 21 | ### Example usage 22 | 23 | ```zig 24 | const objc = @import("zig-objcrt"); 25 | 26 | const NSInteger = c_long; 27 | const NSApplicationActivationPolicyRegular: NSInteger = 0; 28 | 29 | var application: objc.id = undefined; 30 | 31 | pub fn main() anyerror!void { 32 | const NSObject = try objc.getClass("NSObject"); 33 | const NSApplication = try objc.getClass("NSApplication"); 34 | 35 | application = try objc.msgSendByName(objc.id, NSApplication, "sharedApplication", .{}); 36 | 37 | const AppDelegate = try objc.defineAndRegisterClass( 38 | "AppDelegate", 39 | NSObject, 40 | .{}, 41 | .{ 42 | .{ "applicationDidFinishLaunching:", applicationDidFinishLaunching }, 43 | }, 44 | ); 45 | 46 | const app_delegate = try objc.new(AppDelegate); 47 | 48 | try objc.msgSendByName(void, application, "setDelegate:", .{app_delegate}); 49 | 50 | try objc.msgSendByName(void, application, "run", .{}); 51 | } 52 | 53 | pub fn applicationDidFinishLaunching(_: objc.id, _: objc.SEL, _: objc.id) callconv(.C) void { 54 | objc.msgSendByName(void, application, "setActivationPolicy:", .{NSApplicationActivationPolicyRegular}) catch unreachable; 55 | 56 | objc.msgSendByName(void, application, "activateIgnoringOtherApps:", .{true}) catch unreachable; 57 | 58 | std.debug.print("Hello from Objective-C!\n", .{}); 59 | } 60 | ``` 61 | 62 | -------------------------------------------------------------------------------- /src/message.zig: -------------------------------------------------------------------------------- 1 | //! This module provides type-safe-ish bindings to the API defined in objc/message.h 2 | // TODO(hazeycode): add missing definitions 3 | 4 | const std = @import("std"); 5 | 6 | const c = @import("c.zig"); 7 | 8 | const objc = @import("objc.zig"); 9 | const object = objc.object; 10 | const Class = objc.Class; 11 | const id = objc.id; 12 | const SEL = objc.SEL; 13 | const sel_getUid = objc.sel_getUid; 14 | 15 | /// Sends a message to an id or Class and returns the return value of the called method 16 | pub fn msgSend(comptime ReturnType: type, target: anytype, selector: SEL, args: anytype) ReturnType { 17 | const target_type = @TypeOf(target); 18 | if ((target_type == id or target_type == Class) == false) @compileError("msgSend target should be of type id or Class"); 19 | 20 | const args_meta = @typeInfo(@TypeOf(args)).Struct.fields; 21 | const FnType = blk: { 22 | { 23 | // NOTE(hazeycode): The following commented out code crashes the compiler :( last tested with Zig 0.9.0 24 | // https://github.com/ziglang/zig/issues/9526 25 | // comptime var fn_args: [2 + args_meta.len]std.builtin.TypeInfo.FnArg = undefined; 26 | // fn_args[0] = .{ 27 | // .is_generic = false, 28 | // .is_noalias = false, 29 | // .arg_type = @TypeOf(target), 30 | // }; 31 | // fn_args[1] = .{ 32 | // .is_generic = false, 33 | // .is_noalias = false, 34 | // .arg_type = SEL, 35 | // }; 36 | // inline for (args_meta) |a, i| { 37 | // fn_args[2 + i] = .{ 38 | // .is_generic = false, 39 | // .is_noalias = false, 40 | // .arg_type = a.field_type, 41 | // }; 42 | // } 43 | // break :blk @Type(.{ .Fn = .{ 44 | // .calling_convention = .C, 45 | // .alignment = 0, 46 | // .is_generic = false, 47 | // .is_var_args = false, 48 | // .return_type = ReturnType, 49 | // .args = &fn_args, 50 | // } }); 51 | } 52 | { 53 | // TODO(hazeycode): replace this hack with the more generalised code above once it doens't crash the compiler 54 | break :blk switch (args_meta.len) { 55 | 0 => fn (@TypeOf(target), SEL) callconv(.C) ReturnType, 56 | 1 => fn (@TypeOf(target), SEL, args_meta[0].field_type) callconv(.C) ReturnType, 57 | 2 => fn (@TypeOf(target), SEL, args_meta[0].field_type, args_meta[1].field_type) callconv(.C) ReturnType, 58 | 3 => fn (@TypeOf(target), SEL, args_meta[0].field_type, args_meta[1].field_type, args_meta[2].field_type) callconv(.C) ReturnType, 59 | 4 => fn (@TypeOf(target), SEL, args_meta[0].field_type, args_meta[1].field_type, args_meta[2].field_type, args_meta[3].field_type) callconv(.C) ReturnType, 60 | else => @compileError("Unsupported number of args: add more variants in zig-objcrt/src/message.zig"), 61 | }; 62 | } 63 | }; 64 | 65 | // NOTE: func is a var because making it const causes a compile error which I believe is a compiler bug 66 | var func = @ptrCast(FnType, c.objc_msgSend); 67 | 68 | return @call(.{}, func, .{ target, selector } ++ args); 69 | } 70 | -------------------------------------------------------------------------------- /src/main.zig: -------------------------------------------------------------------------------- 1 | //! This is the top level module that exposes type-safe Objective-C runtime bindings and convenient wrappers 2 | 3 | const std = @import("std"); 4 | const testing = std.testing; 5 | 6 | pub const nil: ?id = null; 7 | 8 | const objc = @import("objc.zig"); 9 | pub const object = objc.object; 10 | pub const Class = objc.Class; 11 | pub const id = objc.id; 12 | pub const SEL = objc.SEL; 13 | pub const IMP = objc.IMP; 14 | pub const sel_registerName = objc.sel_registerName; 15 | pub const sel_getUid = objc.sel_getUid; 16 | 17 | const runtime = @import("runtime.zig"); 18 | pub const Method = runtime.Method; 19 | pub const Ivar = runtime.Ivar; 20 | pub const Category = runtime.Category; 21 | pub const Property = runtime.Property; 22 | pub const Protocol = runtime.Protocol; 23 | pub const object_getClass = runtime.object_getClass; 24 | pub const object_getInstanceVariable = runtime.object_getInstanceVariable; 25 | pub const getClass = runtime.getClass; 26 | pub const getMetaClass = runtime.getMetaClass; 27 | pub const lookUpClass = runtime.lookUpClass; 28 | pub const class_getClassVariable = runtime.class_getClassVariable; 29 | pub const class_getInstanceMethod = runtime.class_getInstanceMethod; 30 | pub const class_getClassMethod = runtime.class_getClassMethod; 31 | pub const class_respondsToSelector = runtime.class_respondsToSelector; 32 | pub const class_conformsToProtocol = runtime.class_conformsToProtocol; 33 | pub const allocateClassPair = runtime.allocateClassPair; 34 | pub const registerClassPair = runtime.registerClassPair; 35 | pub const disposeClassPair = runtime.disposeClassPair; 36 | pub const class_addMethod = runtime.class_addMethod; 37 | pub const class_replaceMethod = runtime.class_replaceMethod; 38 | pub const class_addIvar = runtime.class_addIvar; 39 | pub const method_setImplementation = runtime.method_setImplementation; 40 | pub const getProtocol = runtime.getProtocol; 41 | 42 | const message = @import("message.zig"); 43 | pub const msgSend = message.msgSend; 44 | 45 | const type_encoding = @import("type-encoding.zig"); 46 | const writeEncodingForType = type_encoding.writeEncodingForType; 47 | 48 | pub const Error = error{ 49 | ClassDoesNotRespondToSelector, 50 | InstanceDoesNotRespondToSelector, 51 | FailedToAddIvarToClass, 52 | FailedToAddMethodToClass, 53 | }; 54 | 55 | /// Checks whether the target implements the method described by selector and then sends a message 56 | /// Returns the return value of the called method or an error if the target does not implement the corresponding method 57 | pub fn msgSendChecked(comptime ReturnType: type, target: anytype, selector: SEL, args: anytype) !ReturnType { 58 | switch (@TypeOf(target)) { 59 | Class => { 60 | if (class_getClassMethod(target, selector) == null) return Error.ClassDoesNotRespondToSelector; 61 | }, 62 | id => { 63 | const class = try object_getClass(target); 64 | if (class_getInstanceMethod(class, selector) == null) return Error.InstanceDoesNotRespondToSelector; 65 | }, 66 | else => @compileError("Invalid msgSend target type. Must be a Class or id"), 67 | } 68 | return msgSend(ReturnType, target, selector, args); 69 | } 70 | 71 | /// The same as calling msgSendChecked except takes a selector name instead of a selector 72 | pub fn msgSendByName(comptime ReturnType: type, target: anytype, sel_name: [:0]const u8, args: anytype) !ReturnType { 73 | const selector = try sel_getUid(sel_name); 74 | return msgSendChecked(ReturnType, target, selector, args); 75 | } 76 | 77 | /// The same as calling msgSend except takes a selector name instead of a selector 78 | pub fn msgSendByNameUnchecked(comptime ReturnType: type, target: anytype, sel_name: [:0]const u8, args: anytype) !ReturnType { 79 | const selector = try sel_getUid(sel_name); 80 | return msgSend(ReturnType, target, selector, args); 81 | } 82 | 83 | /// Convenience fn for sending an new message to a Class object 84 | /// Which is equivilent to sending an alloc message to a Class object followed by an init message to the returned Class instance 85 | pub fn new(class: Class) !id { 86 | const new_sel = try sel_getUid("new"); 87 | return msgSend(id, class, new_sel, .{}); 88 | } 89 | 90 | /// Convenience fn for sending a dealloc message to object 91 | pub fn dealloc(instance: id) !void { 92 | const dealloc_sel = try sel_getUid("dealloc"); 93 | msgSend(void, instance, dealloc_sel, .{}); 94 | } 95 | 96 | /// Convenience fn for defining and registering a new Class 97 | /// dispose of the resultng class using `disposeClassPair` 98 | pub fn defineAndRegisterClass(name: [:0]const u8, superclass: Class, ivars: anytype, methods: anytype) !Class { 99 | const class = try allocateClassPair(superclass, name, 0); 100 | errdefer disposeClassPair(class); 101 | 102 | // reuseable buffer for type encoding strings 103 | var type_encoding_buf = [_]u8{0} ** 256; 104 | 105 | // add ivars to class 106 | inline for (ivars) |ivar| { 107 | const ivar_name = ivar.@"0"; 108 | const ivar_type = ivar.@"1"; 109 | const type_enc_str = encode: { 110 | var fbs = std.io.fixedBufferStream(&type_encoding_buf); 111 | try writeEncodingForType(ivar_type, fbs.writer()); 112 | const len = fbs.getWritten().len + 1; 113 | break :encode type_encoding_buf[0..len :0]; 114 | }; 115 | var ivar_name_terminated = [_]u8{0} ** (ivar_name.len + 1); 116 | std.mem.copy(u8, &ivar_name_terminated, ivar_name); 117 | if (class_addIvar(class, ivar_name_terminated[0..ivar_name.len :0], @sizeOf(ivar_type), @alignOf(ivar_type), type_enc_str) == false) { 118 | return Error.FailedToAddIvarToClass; 119 | } 120 | std.mem.set(u8, &type_encoding_buf, 0); 121 | } 122 | 123 | // add methods to class 124 | inline for (methods) |m| { 125 | const fn_name = m.@"0"; 126 | const func = m.@"1"; 127 | const FnType = @TypeOf(func); 128 | const type_enc_str = encode: { 129 | var fbs = std.io.fixedBufferStream(&type_encoding_buf); 130 | try writeEncodingForType(FnType, fbs.writer()); 131 | const len = fbs.getWritten().len + 1; 132 | break :encode type_encoding_buf[0..len :0]; 133 | }; 134 | const selector = try sel_registerName(fn_name); 135 | const result = class_addMethod( 136 | class, 137 | selector, 138 | func, 139 | type_enc_str, 140 | ); 141 | if (result == false) { 142 | return Error.FailedToAddMethodToClass; 143 | } 144 | std.mem.set(u8, &type_encoding_buf, 0); 145 | } 146 | 147 | registerClassPair(class); 148 | 149 | return class; 150 | } 151 | 152 | test { 153 | testing.refAllDecls(@This()); 154 | } 155 | 156 | test "new/dealloc NSObject" { 157 | const NSObject = try getClass("NSObject"); 158 | const new_obj = try new(NSObject); 159 | try dealloc(new_obj); 160 | } 161 | 162 | test "register/call/deregister Objective-C Class" { 163 | try struct { 164 | pub fn runTest() !void { 165 | const NSObject = try getClass("NSObject"); 166 | 167 | const TestClass = try defineAndRegisterClass( 168 | "TestClass", 169 | NSObject, 170 | .{ 171 | .{ "foo", c_int }, 172 | }, 173 | .{ 174 | .{ "add", add }, 175 | }, 176 | ); 177 | 178 | const instance = try new(TestClass); 179 | 180 | try testing.expectEqual( 181 | @as(c_int, 3), 182 | try msgSendByName(c_int, instance, "add", .{ @as(c_int, 1), @as(c_int, 2) }), 183 | ); 184 | 185 | try dealloc(instance); 186 | } 187 | 188 | pub fn add(_: id, _: SEL, a: c_int, b: c_int) callconv(.C) c_int { 189 | return a + b; 190 | } 191 | }.runTest(); 192 | } 193 | -------------------------------------------------------------------------------- /src/type-encoding.zig: -------------------------------------------------------------------------------- 1 | //! This module provides functions to convert supported Zig types to Objective-C type encodings 2 | // TODO(hazeycode): more tests 3 | 4 | const std = @import("std"); 5 | const testing = std.testing; 6 | 7 | const objc = @import("objc.zig"); 8 | const object = objc.object; 9 | const Class = objc.Class; 10 | const id = objc.id; 11 | const SEL = objc.SEL; 12 | 13 | /// Enum representing typecodes as defined by Apple's docs https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html 14 | const TypeEncodingToken = enum(u8) { 15 | char = 'c', 16 | int = 'i', 17 | short = 's', 18 | long = 'l', // treated as a 32-bit quantity on 64-bit programs 19 | long_long = 'q', 20 | unsigned_char = 'C', 21 | unsigned_int = 'I', 22 | unsigned_short = 'S', 23 | unsigned_long = 'L', 24 | unsigned_long_long = 'Q', 25 | float = 'f', 26 | double = 'd', 27 | bool = 'B', // A C++ bool or a C99 _Bool 28 | void = 'v', 29 | char_string = '*', // A character string (char *) 30 | object = '@', // An object (whether statically typed or typed id) 31 | class = '#', // A class object (Class) 32 | selector = ':', // A method selector (SEL) 33 | array_begin = '[', 34 | array_end = ']', 35 | struct_begin = '{', 36 | struct_end = '}', 37 | union_begin = '(', 38 | union_end = ')', 39 | pair_separator = '=', // Used to separate name-types pairs in structures and unions 40 | bitfield = 'b', // Precedes a number representing the size of the bitfield 41 | pointer = '^', // Precedes a typecode to represent a pointer to type 42 | unknown = '?', // An unknown type (among other things, this code is used for function pointers) 43 | }; 44 | 45 | pub fn writeEncodingForType(comptime MaybeT: ?type, writer: anytype) !void { 46 | var levels_of_indirection: u32 = 0; 47 | return writeEncodingForTypeInternal(MaybeT, &levels_of_indirection, writer); 48 | } 49 | 50 | fn writeEncodingForTypeInternal(comptime MaybeT: ?type, levels_of_indirection: *u32, writer: anytype) !void { 51 | const T = MaybeT orelse { 52 | try writeTypeEncodingToken(.void, writer); 53 | return; 54 | }; 55 | switch (T) { 56 | i8 => try writeTypeEncodingToken(.char, writer), 57 | c_int => try writeTypeEncodingToken(.int, writer), 58 | c_short => try writeTypeEncodingToken(.short, writer), 59 | c_long => try writeTypeEncodingToken(.long, writer), 60 | c_longlong => try writeTypeEncodingToken(.long_long, writer), 61 | u8 => try writeTypeEncodingToken(.unsigned_char, writer), 62 | c_uint => try writeTypeEncodingToken(.unsigned_int, writer), 63 | c_ushort => try writeTypeEncodingToken(.unsigned_short, writer), 64 | c_ulong => try writeTypeEncodingToken(.unsigned_long, writer), 65 | c_ulonglong => try writeTypeEncodingToken(.unsigned_long_long, writer), 66 | f32 => try writeTypeEncodingToken(.float, writer), 67 | f64 => try writeTypeEncodingToken(.double, writer), 68 | bool => try writeTypeEncodingToken(.bool, writer), 69 | void => try writeTypeEncodingToken(.void, writer), 70 | [*c]u8, [*c]const u8 => try writeTypeEncodingToken(.char_string, writer), 71 | id => try writeTypeEncodingToken(.object, writer), 72 | Class => try writeTypeEncodingToken(.class, writer), 73 | SEL => try writeTypeEncodingToken(.selector, writer), 74 | object => { 75 | try writeTypeEncodingToken(.struct_begin, writer); 76 | try writer.writeAll(@typeName(T)); 77 | try writeTypeEncodingToken(.pair_separator, writer); 78 | try writeTypeEncodingToken(.class, writer); 79 | try writeTypeEncodingToken(.struct_end, writer); 80 | }, 81 | else => switch (@typeInfo(T)) { 82 | .Fn => |fn_info| { 83 | try writeEncodingForTypeInternal(fn_info.return_type, levels_of_indirection, writer); 84 | inline for (fn_info.args) |arg| { 85 | try writeEncodingForTypeInternal(arg.arg_type, levels_of_indirection, writer); 86 | } 87 | }, 88 | .Array => |arr_info| { 89 | try writeTypeEncodingToken(.array_begin, writer); 90 | try writer.print("{d}", .{arr_info.len}); 91 | try writeEncodingForTypeInternal(arr_info.child, levels_of_indirection, writer); 92 | try writeTypeEncodingToken(.array_end, writer); 93 | }, 94 | .Struct => |struct_info| { 95 | try writeTypeEncodingToken(.struct_begin, writer); 96 | try writer.writeAll(@typeName(T)); 97 | if (levels_of_indirection.* < 2) { 98 | try writeTypeEncodingToken(.pair_separator, writer); 99 | inline for (struct_info.fields) |field| try writeEncodingForTypeInternal(field.field_type, levels_of_indirection, writer); 100 | } 101 | try writeTypeEncodingToken(.struct_end, writer); 102 | }, 103 | .Union => |union_info| { 104 | try writeTypeEncodingToken(.union_begin, writer); 105 | try writer.writeAll(@typeName(T)); 106 | if (levels_of_indirection.* < 2) { 107 | try writeTypeEncodingToken(.pair_separator, writer); 108 | inline for (union_info.fields) |field| try writeEncodingForTypeInternal(field.field_type, levels_of_indirection, writer); 109 | } 110 | try writeTypeEncodingToken(.union_end, writer); 111 | }, 112 | .Pointer => |ptr_info| switch (ptr_info.size) { 113 | .One => { 114 | levels_of_indirection.* += 1; 115 | try writeTypeEncodingToken(.pointer, writer); 116 | try writeEncodingForTypeInternal(ptr_info.child, levels_of_indirection, writer); 117 | }, 118 | else => @compileError("Unsupported type"), 119 | }, 120 | else => @compileError("Unsupported type"), 121 | }, 122 | } 123 | } 124 | 125 | fn writeTypeEncodingToken(token: TypeEncodingToken, writer: anytype) !void { 126 | try writer.writeByte(@enumToInt(token)); 127 | } 128 | 129 | test "write encoding for array" { 130 | var buffer: [0x100]u8 = undefined; 131 | var fbs = std.io.fixedBufferStream(&buffer); 132 | 133 | try writeEncodingForType([12]*f32, fbs.writer()); 134 | try testing.expectEqualSlices(u8, "[12^f]", fbs.getWritten()); 135 | } 136 | 137 | test "write encoding for struct, pointer to struct and pointer to pointer to struct" { 138 | const Example = struct { 139 | anObject: id, 140 | aString: [*c]u8, 141 | anInt: c_int, 142 | }; 143 | 144 | var buffer: [0x100]u8 = undefined; 145 | 146 | { 147 | var fbs = std.io.fixedBufferStream(&buffer); 148 | try writeEncodingForType(Example, fbs.writer()); 149 | try testing.expectEqualSlices(u8, "{Example=@*i}", fbs.getWritten()); 150 | } 151 | 152 | { 153 | var fbs = std.io.fixedBufferStream(&buffer); 154 | try writeEncodingForType(*Example, fbs.writer()); 155 | try testing.expectEqualSlices(u8, "^{Example=@*i}", fbs.getWritten()); 156 | } 157 | 158 | { 159 | var fbs = std.io.fixedBufferStream(&buffer); 160 | try writeEncodingForType(**Example, fbs.writer()); 161 | try testing.expectEqualSlices(u8, "^^{Example}", fbs.getWritten()); 162 | } 163 | } 164 | 165 | test "write encoding for fn" { 166 | try struct { 167 | pub fn runTest() !void { 168 | var buffer: [0x100]u8 = undefined; 169 | var fbs = std.io.fixedBufferStream(&buffer); 170 | try writeEncodingForType(@TypeOf(add), fbs.writer()); 171 | try testing.expectEqualSlices(u8, "i@:ii", fbs.getWritten()); 172 | } 173 | 174 | fn add(_: id, _: SEL, a: c_int, b: c_int) callconv(.C) c_int { 175 | return a + b; 176 | } 177 | }.runTest(); 178 | } 179 | -------------------------------------------------------------------------------- /src/runtime.zig: -------------------------------------------------------------------------------- 1 | //! This module provides type-safe bindings to the API defined in objc/runtime.h 2 | // TODO(hazeycode): add missing definitions 3 | 4 | const std = @import("std"); 5 | 6 | const objc = @import("objc.zig"); 7 | const Class = objc.Class; 8 | const object = objc.object; 9 | const id = objc.id; 10 | const SEL = objc.SEL; 11 | const IMP = objc.IMP; 12 | 13 | const c = @import("c.zig"); 14 | 15 | pub const Error = error{ 16 | FailedToGetClassForObject, 17 | FailedToGetInstanceVariable, 18 | ClassNotRegisteredWithRuntime, 19 | FailedToGetClassVariable, 20 | FailedToAllocateClassPair, 21 | NoSuchProtocol, 22 | }; 23 | 24 | // ----- Types ----- 25 | 26 | /// An opaque type that represents a method in a class definition. 27 | pub const Method = *c.objc_method; 28 | 29 | /// An opaque type that represents an instance variable. 30 | pub const Ivar = *c.objc_ivar; 31 | 32 | /// An opaque type that represents a category. 33 | pub const Category = *c.objc_category; 34 | 35 | /// An opaque type that represents an Objective-C declared property. 36 | pub const Property = *c.objc_property; 37 | 38 | pub const Protocol = c.objc_object; 39 | 40 | // ----- Working with Instances ----- 41 | 42 | /// Returns the class of an object. 43 | /// 44 | /// @param obj An id of the object you want to inspect. 45 | /// 46 | /// @return The class object of which object is an instance 47 | pub fn object_getClass(obj: id) Error!Class { 48 | return c.object_getClass(obj) orelse Error.FailedToGetClassForObject; 49 | } 50 | 51 | /// Obtains the value of an instance variable and the assosciated `Ivar` of a class instance. 52 | /// 53 | /// @param obj An instance of the class containing the instance variable whose value you wish to obtain. 54 | /// @param name The name of the instance variable whose value you wish to obtain. 55 | /// @param outValue On return, contains a pointer to the value of the instance variable. 56 | /// 57 | /// Returns a struct containing an `Ivar` that defines the type and name of the instance 58 | /// variable specified by `name` and the 59 | pub fn object_getInstanceVariable(comptime ValueType: type, obj: id, name: [:0]const u8) Error!struct { ivar: Ivar, value: ValueType } { 60 | var value_ptr: *ValueType = undefined; 61 | const maybe_ivar = c.object_getInstanceVariable(obj, name, @ptrCast([*c]?*anyopaque, &value_ptr)); 62 | return if (maybe_ivar) |ivar| .{ .ivar = ivar, .value = value_ptr.* } else Error.FailedToGetInstanceVariable; 63 | } 64 | 65 | // ----- Obtaining Class Definitions ---- 66 | 67 | /// Returns the class definition of a specified class, or an error if the class is not registered 68 | /// with the Objective-C runtime. 69 | /// 70 | /// @param name The name of the class to look up. 71 | /// 72 | /// @note getClass is different from lookUpClass in that if the class 73 | /// is not registered, getClass calls the class handler callback and then checks 74 | /// a second time to see whether the class is registered. lookUpClass does 75 | /// not call the class handler callback. 76 | /// 77 | /// @warning Earlier implementations of this function (prior to OS X v10.0) 78 | /// terminate the program if the class does not exist. 79 | pub fn getClass(class_name: [:0]const u8) Error!Class { 80 | return c.objc_getClass(class_name) orelse Error.ClassNotRegisteredWithRuntime; 81 | } 82 | 83 | /// Returns the metaclass definition of a specified class, or an error if the class is not registered 84 | /// with the Objective-C runtime. 85 | /// 86 | /// @param name The name of the class to look up. 87 | /// 88 | /// @note If the definition for the named class is not registered, this function calls the class handler 89 | /// callback and then checks a second time to see if the class is registered. However, every class 90 | /// definition must have a valid metaclass definition, and so the metaclass definition is always returned, 91 | /// whether it’s valid or not. 92 | pub fn getMetaClass(class_name: [:0]const u8) Error!Class { 93 | return c.objc_getMetaClass(class_name) orelse Error.ClassNotRegisteredWithRuntime; 94 | } 95 | 96 | /// Returns the class definition of a specified class, or null if the class is not registered 97 | /// with the Objective-C runtime 98 | /// 99 | /// @param name The name of the class to look up. 100 | /// 101 | /// @note getClass is different from this function in that if the class is not 102 | /// registered, getClass calls the class handler callback and then checks a second 103 | /// time to see whether the class is registered. This function does not call the class handler callback. 104 | pub fn lookUpClass(class_name: [:0]const u8) ?Class { 105 | return c.objc_lookUpClass(class_name); 106 | } 107 | 108 | /// Returns the Ivar for a specified class variable of a given class. 109 | /// 110 | /// @param cls The class definition whose class variable you wish to obtain. 111 | /// @param name The name of the class variable definition to obtain. 112 | pub fn class_getClassVariable(class: Class, name: [:0]const u8) Error!Ivar { 113 | return c.class_getClassVariable(class, name) orelse Error.FailedToGetClassVariable; 114 | } 115 | 116 | /// Returns an instance method corresponding to the implementation of a given selector for given class 117 | /// null if the specified class or its superclasses do not contain an instance method with the specified selector. 118 | /// NOTE: This function searches superclasses for implementations, whereas `class_copyMethodList` does not. 119 | pub fn class_getInstanceMethod(class: Class, selector: SEL) ?Method { 120 | return c.class_getInstanceMethod(class, selector); 121 | } 122 | 123 | /// Returns an class method corresponding to the implementation of a given selector for given class 124 | /// null if the specified class or its superclasses do not contain an class method with the specified selector. 125 | /// NOTE: This function searches superclasses for implementations, whereas `class_copyMethodList` does not. 126 | pub fn class_getClassMethod(class: Class, selector: SEL) ?Method { 127 | return c.class_getClassMethod(class, selector); 128 | } 129 | 130 | /// Returns a Boolean value that indicates whether instances of a class respond to a particular selector. 131 | pub fn class_respondsToSelector(class: Class, selector: SEL) bool { 132 | return (c.class_respondsToSelector(class, selector) != 0); 133 | } 134 | 135 | /// Returns a bool that indicates whether a class conforms to a given protocol. 136 | pub fn class_conformsToProtocol(class: Class, protocol: *Protocol) bool { 137 | return (c.class_conformsToProtocol(class, protocol) != 0); 138 | } 139 | 140 | // ----- Working with Classes ----- 141 | 142 | /// Adds a new method to a class with a given name and implementation. 143 | /// 144 | /// @param class The class to which to add a method. 145 | /// @param selector A selector that specifies the name of the method being added. 146 | /// @param imp A function which is the implementation of the new method. The function must take at least two arguments—self and _cmd. 147 | /// @param types An array of characters that describe the types of the arguments to the method. 148 | /// 149 | /// @return `true` if the method was added successfully, otherwise `false` 150 | /// (for example, the class already contains a method implementation with that name). 151 | /// @note `class_addMethod` will add an override of a superclass's implementation, 152 | /// but will not replace an existing implementation in this class. 153 | /// To change an existing implementation, use `method_setImplementation`. 154 | pub fn class_addMethod(class: Class, selector: SEL, imp: IMP, types: [:0]const u8) bool { 155 | return (c.class_addMethod(class, selector, @ptrCast(fn () callconv(.C) void, imp), types) != 0); 156 | } 157 | 158 | /// Replaces the implementation of a method for a given class. 159 | /// 160 | /// @param class The class you want to modify. 161 | /// @param selector A selector that identifies the method whose implementation you want to replace. 162 | /// @param imp The new implementation for the method identified by name for the class identified by cls. 163 | /// @param types An array of characters that describe the types of the arguments to the method. 164 | /// Since the function must take at least two arguments—self and _cmd, the second and third characters 165 | /// must be “@:” (the first character is the return type). 166 | /// 167 | /// @return The previous implementation of the method identified by `name` for the class identified by `class`. 168 | /// 169 | /// @note This function behaves in two different ways: 170 | /// - If the method identified by `name` does not yet exist, it is added as if `class_addMethod` were called. 171 | /// The type encoding specified by `types` is used as given. 172 | /// - If the method identified by `name` does exist, its `IMP` is replaced as if `method_setImplementation` were called. 173 | /// The type encoding specified by `types` is ignored. 174 | pub fn class_replaceMethod(class: Class, selector: SEL, imp: IMP, types: [:0]const u8) ?IMP { 175 | return c.class_replaceMethod(class, selector, @ptrCast(fn () callconv(.C) void, imp), types); 176 | } 177 | 178 | /// Adds a new instance variable to a class. 179 | /// 180 | /// @return `true` if the instance variable was added successfully, otherwise `false` 181 | /// (for example, the class already contains an instance variable with that name). 182 | /// 183 | /// @note This function may only be called after `objc_allocateClassPair` and before `objc_registerClassPair`. 184 | /// Adding an instance variable to an existing class is not supported. 185 | /// @note The class must not be a metaclass. Adding an instance variable to a metaclass is not supported. 186 | /// @note The instance variable's minimum alignment in bytes is 1<