{"id":19075322,"url":"https://github.com/gershnik/objc-helpers","last_synced_at":"2026-01-02T23:20:06.325Z","repository":{"id":143477845,"uuid":"313238968","full_name":"gershnik/objc-helpers","owner":"gershnik","description":"Utilities to make coding on Apple platforms in C++ or ObjectiveC++ more pleasant","archived":false,"fork":false,"pushed_at":"2024-08-11T02:03:41.000Z","size":194,"stargazers_count":5,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-04-18T19:42:04.683Z","etag":null,"topics":["apple","async-generator","asyncgenerator","c-plus-plus","comparators","coroutines","cpp","dispatch-queues","dispatchqueue","gcd","grand-central-dispatch","header-only","ios","macos","no-dependencies","nsobject","objective-c","objective-c-plus-plus","objectivec","xctest"],"latest_commit_sha":null,"homepage":"","language":"C++","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-3-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/gershnik.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2020-11-16T08:41:03.000Z","updated_at":"2025-03-29T05:34:22.000Z","dependencies_parsed_at":null,"dependency_job_id":"e103d614-588c-40df-8843-9bfc4f4b4a78","html_url":"https://github.com/gershnik/objc-helpers","commit_stats":null,"previous_names":[],"tags_count":8,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gershnik%2Fobjc-helpers","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gershnik%2Fobjc-helpers/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gershnik%2Fobjc-helpers/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gershnik%2Fobjc-helpers/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/gershnik","download_url":"https://codeload.github.com/gershnik/objc-helpers/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":251599220,"owners_count":21615486,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["apple","async-generator","asyncgenerator","c-plus-plus","comparators","coroutines","cpp","dispatch-queues","dispatchqueue","gcd","grand-central-dispatch","header-only","ios","macos","no-dependencies","nsobject","objective-c","objective-c-plus-plus","objectivec","xctest"],"created_at":"2024-11-09T01:54:22.065Z","updated_at":"2026-01-02T23:20:06.316Z","avatar_url":"https://github.com/gershnik.png","language":"C++","readme":"# ObjC-Helpers #\n\nAn ever-growing collection of utilities to make coding on Apple platforms in C++ or ObjectiveC++ more pleasant. Some functionality is also available on Linux.\n\n\u003c!-- TOC depthfrom:2 --\u003e\n\n- [What's included?](#whats-included)\n    - [Convert ANY C++ callable to a block](#convert-any-c-callable-to-a-block)\n    - [Coroutines that execute on GCD dispatch queues](#coroutines-that-execute-on-gcd-dispatch-queues)\n    - [Boxing of any C++ objects in ObjectiveC ones](#boxing-of-any-c-objects-in-objectivec-ones)\n    - [Comparators for ObjectiveC objects](#comparators-for-objectivec-objects)\n    - [Printing ObjectiveC objects to C++ streams and std::format](#printing-objectivec-objects-to-c-streams-and-stdformat)\n    - [Accessing NSString/CFString as a char16_t container](#accessing-nsstringcfstring-as-a-char16_t-container)\n    - [Conversions between NSString/CFString and char/char16_t/char32_t/char8_t/wchar_t ranges](#conversions-between-nsstringcfstring-and-charchar16_tchar32_tchar8_twchar_t-ranges)\n    - [XCTest assertions for C++ objects](#xctest-assertions-for-c-objects)\n- [Linux notes](#linux-notes)\n\n\u003c!-- /TOC --\u003e\n\n## What's included? ##\n\nThe library is a collection of mostly independent header files. There is nothing to link with. Simply add these headers to your include path and include them as needed.\n\n`sample` directory contains a sample that demonstrates the usage of main features.\n\n\n### Convert ANY C++ callable to a block ###\n\nWith modern Clang compiler you can seamlessly convert C++ lambdas to blocks like this:\n```c++\ndispatch_async(someQueue, []() { \n    //do something\n})\n```\nThis works and works great but there are a few things that don't:\n* You can only pass a *lambda* as a block, not any other kind of callable. For example this does not compile:\n  ```cpp\n  struct foo { void operator()() const {} };\n  dispatch_async(someQueue, foo{});\n  ```\n* You cannot pass a *mutable* lambda this way. This doesn't compile either\n  ```cpp\n  dispatch_async(someQueue, []() mutable { \n     //do something\n  });\n  ```\n  Neither cannot you pass a block that captures anything mutable (like your lambda) - captured variables are all const\n* Your lambda captured variables are always *copied* into the block, not *moved*. If you have captures that are\n  expensive to copy - oh well...\n* Because of the above you cannot have move-only thinks in your block. Forget about using `std::unique_ptr` for example.\n\nThe `BlockUtils.h` header gives you an ability to solve all of these problems.\n\nIt provides two functions: `makeBlock` and `makeMutableBlock` that take any C++ callable as an input and return an object\nthat is implicitly convertible to a block and can be passed to any block-taking API. They (or rather the object they return)\nhave the following features:\n\n* You can wrap any C++ callable, not just a lambda. \n* `makeBlock` returns a block that invokes `operator()` on a `const` callable and \n  `makeMutableBlock` returns a block that invokes it on a non-const one. Thus `makeMutableBlock` can be used with \n  mutable lambdas or any other callable that provides non-const `operator()`.\n* If callable is movable it will be moved into the block, not copied. It will also be moved if the block is \"copied to heap\" \n  by ObjectiveC runtime or `Block_copy` in plain C++.\n* It is possible to use move-only callables.\n* All of this is accomplished with NO dynamic memory allocation\n* This functionality is also available on Linux under CLang (see [Linux notes](#linux-notes) below).\n  \nSome examples of their usage are as follows:\n\n```c++ \n//Convert any callable\nstruct foo { void operator()() const {} };\ndispatch_async(someQueue, makeBlock(foo{})); //this moves foo in since it's a temporary\n\n//Copy or move a callable in\nfoo callable;\ndispatch_async(someQueue, makeBlock(callable));\ndispatch_async(someQueue, makeBlock(std::move(callable)));\n\n//Convert mutable lambdas\nint captureMeByValue;\ndispatch_async(someQueue, makeMutableBlock([=]() mutable { \n    captureMeByValue = 5; //the local copy of captureMeByValue is mutable\n}));\n\n//Use move-only callables\nauto ptr = std::make_unique\u003cSomeType\u003e();\ndispatch_async(someQueue, makeBlock([ptr=str::move(ptr)]() {\n    ptr-\u003esomeMethod();\n}));\n\n```\n\nOne important thing to keep in mind is that the object returned from `makeBlock`/`makeMutableBlock` **is the block**. It is NOT a block pointer (e.g. Ret (^) (args)) and it doesn't \"store\" the block pointer inside. The block's lifetime is this object's lifetime and it ends when this object is destroyed. You can copy/move this object around and invoke it as any other C++ callable.\nYou can also convert it to the block _pointer_ as needed either using implicit conversion or a `.get()` member function.\n\nIn ObjectiveC++ the block pointer lifetime **in a single scope** is not-related to the block object's one. The objective C++ ARC machinery will do the \nnecessary magic behind the scenes. For example:\n\n```c++\n//In ObjectiveC++\nvoid (^block)(int) = makeBlock([](int){});\nblock(7); // this works even though the original block object is already destroyed\n```\n\nHowever, if you want to preserve the block pointer outside of the current scope you need to manually copy it:\n\n```objc\n//In ObjectiveC++\n@implementation foo {\n    void (^_instanceVar)(int);\n}\n\n- (void) someMethod {\n    //moving the pointer outside of the current scope\n    _instanceVar = copy(makeBlock([](int){}));\n}\n@end\n```\n\nIn plain C++ there is no ARC magic so you need to manually manage block pointers lifecycle using\n`copy` and `Block_release`. For example:\n```c++\n//In plain C++ \nvoid (^block)() = copy(makeBlock([](int){}));\nblock(7); //this works because we made a copy\nBlock_release(block);\n```\n\n`BlockUtil.h` also provides two helpers: `makeWeak` and `makeStrong` that simplify the \"strongSelf\" \ncasting dance around avoiding circular references when using blocks/lambdas.\n\nHere is the intended usage:\n\n```objc++ \ndispatch_async(someQueue, [weakSelf = makeWeak(self)] () {\n    auto self = makeStrong(weakSelf);\n    if (!self)\n        return;\n    [self doSomething];\n});\n```\n\n### Coroutines that execute on GCD dispatch queues ###\n\nHeader `CoDispatch.h` allows you to use **asynchronous** C++ coroutines that execute on GCD dispatch queues. Yes there is [this library](https://github.com/alibaba/coobjc) but it is big, targeting Swift and ObjectiveC rather than C++/\\[Objective\\]C++ and has a library to integrate with. It also has more features, of course. Here you get basic powerful C++ coroutine support in a single not very large (~800 loc) header.\n\nWorking with coroutines is discussed in greater detail in [a separate doc](doc/CoDispatch.md).\n\nHere is a small sample of what you can do:\n\n```objc++\nDispatchTask\u003cint\u003e coro() {\n\n    //this will execute asyncronously on the main queue\n    int i = co_await co_dispatch([]() {\n        return 7;\n    });\n\n    //you can specify a different queue of course\n    auto queue = dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0);\n    int j = co_await co_dispatch(queue, []() {\n        return 42;\n    }).resumeOnMainQueue(); //add this to resume back on main queue\n\n    //you can convert ObjC APIs with asynchronous callbacks to couroutines\n    auto status = co_await makeAwaitable\u003cint\u003e([](auto promise) {\n        NSError * err;\n        [NSTask launchedTaskWithExecutableURL:[NSURL fileURLWithPath:@\"/bin/bash\"]\n                                    arguments:@[@\"-c\", @\"ls\"]\n                                        error:\u0026err\n                           terminationHandler:^(NSTask * res){\n            promise.success(res.terminationStatus);\n        }];\n        if (err)\n            throw std::runtime_error(err.description.UTF8String);\n    }).resumeOnMainQueue();\n\n    //this will switch execution to a different queue\n    co_await resumeOn(queue);\n}\n\n//coroutines can await other corotines\nDispatchTask\u003cint\u003e anotherCoro() {\n    int res = co_await coro();\n\n    co_return res;\n}\n\n//you can also have asynchronous generators\nDispatchGenerator\u003cstd::string\u003e generator() {\n    co_yield \"Hello\";\n    co_yield \"World\";\n    //in real life you probably will use something like\n    //co_yield co_await somethingAsync(); \n}\n\nDispatchTask\u003cint\u003e useGenerator() {\n    std::vector\u003cstd::string\u003e dest;\n    //this will run generator asynchrnously on the main queue\n    for (auto it = co_await generator().begin(); it; co_await it.next()) {\n        res.push_back(*it);\n    }\n\n    //you can also say things like\n    //auto it = generator().resumingOnMainQueue().beginOn(queue)\n    //to control the running and resuming queues\n}\n\nint main() {\n\n    //fire and forget\n    anotherCoro();\n    useGenerator();\n\n    dispatch_main();\n}\n```\n\nThis facility can also be used both from plain C++ (.cpp) and ObjectiveC++ (.mm) files. It is also available on Linux using [libdispatch][libdispatch] library (see [Linux notes](#linux-notes) below).\n\n\n### Boxing of any C++ objects in ObjectiveC ones ###\n\nSometimes you want to store a C++ object where an ObjectiveC object is expected. Perhaps there is\nsome `NSObject * tag` which you really want to put an `std::vector` in or something similar. You can,\nof course, do that by creating a wrapper ObjectiveC class that stores `std::vector` but it is a huge annoyance. Yet another ObjectiveC class to write (so a new header and a .mm file) lots of boilerplate code for `init` and value access and, after all that, it is going to to be `std::vector` specific. If you later need to wrap another C++ class you need yet another wrapper. \n\nFor plain C structs ObjectiveC has a solution: `NSValue` that can store any C struct and let you retrieve it back later. Unfortunately in C++ this only works for \"trivially copyable\" types (which more or less correspond to \"plain C structs\"). Trying to stick anything else in `NSValue` will appear to work but likely do very bad things - it simply copies object bytes into it and out! Whether bytes copied out will work as the original object is undefined.\n\nTo solve this issue `BoxUtil.h` provides generic facilities for wrapping and unwrapping of any C++ object in an `NSObject`-derived classes without writing any code. Such wrapping and unwrapping of native objects in higher-level language ones are usually called \"boxing\" and \"unboxing\", hence the\nname of the header and it's APIs. \n\nThe only requirement for the C++ class to be wrappable is having a public destructor and at least one public constructor. The constructor doesn't need to be default - boxing works with objects that need to be \"emplaced\".\n\nYou use it like this:\n\n```objc++\nstd::vector\u003cint\u003e someVector{1,2,3};\n//this copies the vector into the wrapper\nNSObject * obj1 = box(someVector);\n//and this moves it\nNSObject * obj2 = box(std::move(someVector));\n//you can also do this\nNSObject * obj3 = box(std::vector\u003cint\u003e{1,2,3});\n//and you can emplace the object directly rather than copy or move it\nNSObject * obj4 = box\u003cstd::vector\u003cint\u003e\u003e(5, 3); //emplaces {3,3,3,3,3}\n\n//You can get a reference to wrapped object\n//This will raise an ObjectiveC exception if the type doesn't macth\n\nauto \u0026 vec = boxedValue\u003cstd::vector\u003cint\u003e\u003e(obj1);\nassert(vec.size() == 3);\nassert(vec[1] == 2);\n\nThe reference you get back is mutable by default. If you want immutability do this\nNSObject * immuatbleObj = box\u003cconst std::vector\u003cint\u003e\u003e(...any of the stuff above...);\n\n//if your C++ object has a copy constructor the wrapper \n//will implement NSCopying\nauto * obj5 = (NSObject *)[obj1 copy];\n\n//this uses operator== if available, which it is\nassert([obj1 isEqual:obj3]);\n\n//and this uses std::hash if available\n//it will raise an exception if you have operator== but not std::hash!\n//as incositent equality and hashing is one of the most common ObjectiveC errors\nauto hash = obj1.hash\n\n//you can obtain a sensible description\n//it will try to use:\n//std::to_string \n//iostream \u003c\u003c \n//fall back on \"boxed object of type \u003cname of the class\u003e\"\n\nauto desc = obj1.description;\n\n//if your object supports \u003c=\u003e operator that returns std::strong_ordering\n//you can use compare: method\nassert([box(5) compare:box(6)] == NSOrderingAscending);\n\n```\n\n### Comparators for ObjectiveC objects ###\n\nHeader `NSObjectUtil.h` provides `NSObjectEqual` and `NSObjectHash` - functors that evaluate equality and hash code for any NSObject and allow them to be used as keys in `std::unordered_map` and `std::unordered_set` for example. These are implemented in terms of `isEqual` and `hash` methods of `NSObject`. \n\nHeader `NSStringUtil.h` provides `NSStringLess` and `NSStringLocaleLess` comparators. These allow `NSString` objects to be used as keys in `std::map` or `std::set `as well as used in STL sorting and searching algorithms. \n\nAdditionally it provides `NSStringEqual` comparator. This is more efficient than `NSObjectEqual` and is implemented in terms of `isEqualToString`.\n\nHeader `NSNumberUtil.h` provides `NSNumberLess` comparator. This allows `NSNumber` objects to be used as keys in `std::map` or `std::set` as well as used in STL sorting and searching algorithms.  \n\nAdditionally it provides `NSNumberEqual` comparator. This is more efficient than `NSObjectEqual` and is implemented in terms of `isEqualToNumber`. \n\n\nFor all comparators `nil`s are handled properly. A `nil` is equal to `nil` and is less than any non-`nil` object.\n\n### Printing ObjectiveC objects to C++ streams and std::format ###\n\nHeader `NSObjectUtil.h` provides `operator\u003c\u003c` for any `NSObject` to print it to an `std::ostream`. This behaves similarly to `%@` formatting flag by delegating either to `descriptionWithLocale:` or to `description`.\n\nHeader `NSStringUtil.h` provides additional `operator\u003c\u003c` to print an `NSString` to an `std::ostream`. This outputs `UTF8String`.\n\nBoth headers also provide `std::formatter`s with the same functionality if `std::format` is available in the standard library and\n`fmt::formatter` if a macro `NS_OBJECT_UTIL_USE_FMT` is defined. In the later case presence of `\u003cfmt/format.h\u003e` or `\"fmt/format.h\"` include file is required.\n\nNote that since version 0.8 `fmt` library disallows formatting of any kind of naked pointers, whether they have custom formatter or not.\n(See https://github.com/fmtlib/fmt/issues/4037). Thus to format ObjC pointers you need to use `fmt::nsptr` wrapper provided by this library\nand patterned after `fmt::ptr`. Here is a short example of equivalent printing an `NSObject *` using all 3 methods:\n\n```cpp\n\nNSObject * obj = ...;\n\nstd::cout \u003c\u003c obj \u003c\u003c '\\n';\nstd::println(\"{}\", obj);\nfmt::println(\"{}\", fmt::nsptr(obj));\n\n```\n\n\n### Accessing NSString/CFString as a char16_t container ###\n\nHeader `NSStringUtil.h` provides `NSStringCharAccess` - a fast accessor for `NSString` characters (as `char16_t`) via an STL container interface. This uses approach similar to [`CFStringInlineBuffer`](https://developer.apple.com/documentation/corefoundation/cfstringinlinebuffer?language=objc) one. This facility can be used both from ObjectiveC++ and plain C++.\n\nHere are some examples of usage\n\n```cpp\n\nfor (char16_t c: NSStringCharAccess(@\"abc\")) {\n    ...\n}\n\nstd::ranges::for_each(NSStringCharAccess(@\"abc\") | std::views::take(2), [](char16_t c) {\n    ...\n});\n```\n\nNote that `NSStringCharAccess` is a _reference class_ (akin in spirit to `std::string_view`). It does not hold a strong reference to the `NSString`/`CFString` it uses and is only valid as long as that string exists.\n\n### Conversions between `NSString`/`CFString` and `char`/`char16_t`/`char32_t`/`char8_t`/`wchar_t` ranges\n\nHeader `NSStringUtil.h` provides `makeNSString` and `makeCFString` functions that accept:\n* Any contiguous range of Chars (including `std::basic_string_view`, `std::basic_string`, `std::span` etc. etc.)\n* A pointer to a null-terminated C string of Chars\n* An `std::initializer_list\u003cChar\u003e`\n\nwhere Char can be any of `char`, `char16_t`, `char32_t`, `char8_t`, `wchar_t`\n\nand converts it to `NSString`/`CFString`. They return `nil` on failure.\n\nConversions from `char16_t` are exact and can only fail when out of memory. Conversions from other formats will fail also when encoding is invalid. Conversions from `char` assume UTF-8 and from `wchar_t`, UTF-32. \n\nTo convert in the opposite direction the header provides `makeStdString\u003cChar\u003e` overloads. These accept:\n\n* `NSString *`/`CFStringRef`, optional start position (0 by default) and optional length (whole string by default)\n* A pair of `NSStringCharAccess` iterators\n* Any range of `NSStringCharAccess` iterators\n\nThey return an `std::basic_string\u003cChar\u003e`. A `nil` input produces an empty string.  Similar to above conversions from `char16_t` are exact and conversions to other char types transcode from an appropriate UTF encoding. If the source `NSString *`/`CFStringRef` contains invalid UTF-16 the output is an empty string.\n\nThis functionality is available in both ObjectiveC++ and plain C++\n\n### XCTest assertions for C++ objects ###\n\nWhen using XCTest framework you might be tempted to use `XCTAssertEqual` and similar on C++ objects. While this works and works safely you will quickly discover that when the tests fail you get a less than useful failure message that shows _raw bytes_ of the C++ object instead of any kind of logical description. This happens because in order to obtain the textual description of the value `XCTAssertEqual` and friends stuff it into an `NSValue` and then query its description. And, as mentioned in [BoxUtil.h](#boxutilh) section, `NSValue` simply copies raw bytes of a C++ object. \n\nWhile this is still safe, because nothing except the description is ever done with those bytes the end result is hardly usable. To fix this `XCTestUtil.h` header provides the following replacement macros:\n\n- `XCTAssertCppEqual`\n- `XCTAssertCppNotEqual`\n- `XCTAssertCppGreaterThan`\n- `XCTAssertCppGreaterThanOrEqual`\n- `XCTAssertCppLessThan`\n- `XCTAssertCppLessThanOrEqual`\n\nThat, in the case of failure, try to obtain description using the following methods:\n\n- If there is an ADL call `testDescription(obj)` that produces `NSString *`, use that.\n- Otherwise, if there is an ADL call `to_string(obj)` in `using std::to_string` scope, use that\n- Otherwise, if it is possible to do `ostream \u003c\u003c obj`, use that\n- Finally produce `\"\u003cfull name of the type\u003e object\"` string.\n\nThus if an object is printable using the typical means those will be automatically used. You can also make your own objects printable using either of the means above. The `testDescription` approach specifically exists to allow you to print something different for tests than in normal code.\n\n## Linux notes ##\n\n`BlockUtil.h` and `CoDispatch.h` headers can also be used on Linux. Currently this requires \n* CLang 16 or above (for blocks support). See [this issue][gcc-blocks] for status of blocks support in GCC\n* [swift-corelibs-libdispatch][libdispatch] library. Note that **most likely you need to build it from sources**. The versions available via various package managers (as of summer 2024) are very old and cannot be used.\n\nYou must use:\n```\n--std=c++20 -fblocks\n```\nflags to use these headers.\n\nFor `CoDispatch.h` link with:\n```\n-ldispatch -lBlocksRuntime\n```\n\nFor `BlockUtil.h` link with:\n```\n-lBlocksRuntime\n```\n\n\n\u003c!-- References --\u003e\n\n[libdispatch]: https://github.com/apple/swift-corelibs-libdispatch\n[gcc-blocks]: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=78352","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgershnik%2Fobjc-helpers","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgershnik%2Fobjc-helpers","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgershnik%2Fobjc-helpers/lists"}