diff --git a/pkg/objc/class.zig b/pkg/objc/class.zig index f655c82eb..c6a310307 100644 --- a/pkg/objc/class.zig +++ b/pkg/objc/class.zig @@ -14,6 +14,21 @@ pub const Class = struct { .value = c.objc_getClass(name.ptr) orelse return null, }; } + + /// Returns a property with a given name of a given class. + pub fn getProperty(self: Class, name: [:0]const u8) ?objc.Property { + return objc.Property{ + .value = c.class_getProperty(self.value, name.ptr) orelse return null, + }; + } + + /// Describes the properties declared by a class. This must be freed. + pub fn copyPropertyList(self: Class) []objc.Property { + var count: c_uint = undefined; + const list = @ptrCast([*c]objc.Property, c.class_copyPropertyList(self.value, &count)); + if (count == 0) return list[0..0]; + return list[0..count]; + } }; test "getClass" { @@ -40,3 +55,20 @@ test "msgSend" { try testing.expect(obj.value != null); obj.msgSend(void, objc.sel("dealloc"), .{}); } + +test "getProperty" { + const testing = std.testing; + const NSObject = Class.getClass("NSObject").?; + + try testing.expect(NSObject.getProperty("className") != null); + try testing.expect(NSObject.getProperty("nope") == null); +} + +test "copyProperyList" { + const testing = std.testing; + const NSObject = Class.getClass("NSObject").?; + + const list = NSObject.copyPropertyList(); + defer objc.free(list); + try testing.expect(list.len > 20); +} diff --git a/pkg/objc/main.zig b/pkg/objc/main.zig index 6eefc51fe..9d1da6847 100644 --- a/pkg/objc/main.zig +++ b/pkg/objc/main.zig @@ -1,11 +1,17 @@ +const std = @import("std"); + pub const c = @import("c.zig"); pub usingnamespace @import("class.zig"); pub usingnamespace @import("object.zig"); +pub usingnamespace @import("property.zig"); pub usingnamespace @import("sel.zig"); -test { - @import("std").testing.refAllDecls(@This()); - - // TODO: remove once we integrate this - _ = @import("msg_send.zig"); +/// This just calls the C allocator free. Some things need to be freed +/// and this is how they can be freed for objc. +pub inline fn free(ptr: anytype) void { + std.heap.c_allocator.free(ptr); +} + +test { + std.testing.refAllDecls(@This()); } diff --git a/pkg/objc/msg_send.zig b/pkg/objc/msg_send.zig index 4f2eaf699..06edf397d 100644 --- a/pkg/objc/msg_send.zig +++ b/pkg/objc/msg_send.zig @@ -29,7 +29,10 @@ pub fn MsgSend(comptime T: type) type { // This lets msgSend magically work with Object and so on. const is_pkg_struct = comptime is_pkg_struct: { for (@typeInfo(objc).Struct.decls) |decl| { - if (decl.is_pub and Return == @field(objc, decl.name)) { + if (decl.is_pub and + @TypeOf(@field(objc, decl.name)) == type and + Return == @field(objc, decl.name)) + { break :is_pkg_struct true; } } diff --git a/pkg/objc/object.zig b/pkg/objc/object.zig index 41c779eeb..f6ef6e427 100644 --- a/pkg/objc/object.zig +++ b/pkg/objc/object.zig @@ -8,3 +8,13 @@ pub const Object = struct { pub usingnamespace MsgSend(Object); }; + +test { + const testing = std.testing; + const NSObject = objc.Class.getClass("NSObject").?; + + // Should work with our wrappers + const obj = NSObject.msgSend(objc.Object, objc.Sel.registerName("alloc"), .{}); + try testing.expect(obj.value != null); + obj.msgSend(void, objc.sel("dealloc"), .{}); +} diff --git a/pkg/objc/property.zig b/pkg/objc/property.zig new file mode 100644 index 000000000..110516b0e --- /dev/null +++ b/pkg/objc/property.zig @@ -0,0 +1,27 @@ +const std = @import("std"); +const c = @import("c.zig"); +const objc = @import("main.zig"); + +pub const Property = extern struct { + value: c.objc_property_t, + + /// Returns the name of a property. + pub fn getName(self: Property) [:0]const u8 { + return std.mem.sliceTo(c.property_getName(self.value), 0); + } +}; + +test { + // Critical properties because we ptrCast C pointers to this. + const testing = std.testing; + try testing.expect(@sizeOf(Property) == @sizeOf(c.objc_property_t)); + try testing.expect(@alignOf(Property) == @alignOf(c.objc_property_t)); +} + +test { + const testing = std.testing; + const NSObject = objc.Class.getClass("NSObject").?; + + const prop = NSObject.getProperty("className").?; + try testing.expectEqualStrings("className", prop.getName()); +}