Last active
May 19, 2023 20:34
-
-
Save dmaclach/afafbeb1418e5d77b505fabfffe96d17 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Wraps C++ classes (ptrs and refs) so that they can be passed around in | |
* Objective-C++ programs without generating a lot of useless Objective-C | |
* runtime metadata. | |
* Use objc_metadata_hider_ptr for ptrs and objc_metadata_hider_ref for refs. | |
* Example use: | |
* ``` | |
* class Foo { | |
* public: | |
* .... | |
* }; | |
* @interface Bar | |
* @property objc_metadata_hider_ptr<Foo> foo; | |
* - (instancetype)initWithFoo:(objc_metadata_hider_ptr<Foo>)aFoo; | |
* @end | |
* ``` | |
* See https://medium.com/@dmaclach/objective-c-encoding-and-you-866624cc02de | |
* for details. | |
* | |
* NOTE: objc_metadata_hiders will by default assert at compile time if having | |
* the wrapper actually compiles to more metadata than not having the wrapper. | |
* If you would like to override this you can do it on a per instantiation basis | |
* you can use `objc_metadata_hider_ptr<Foo, false> foo;` or the equivalent | |
* `unchecked_objc_metadata_hider_ptr<Foo>`. Note that this change may still | |
* ripple throughout your codebase like any other type change. | |
* | |
*/ | |
#ifndef OBJC_METADATA_HIDER_H_ | |
#define OBJC_METADATA_HIDER_H_ | |
#ifndef __cplusplus | |
// Swift's Clang importer treats headers as Objective-C when precompiling the | |
// module. Emit an error on any other attempt to import this within a non-C++ | |
// file. | |
#ifndef __swift__ | |
#error Must be compiled using (Obj)C++ | |
#endif | |
#else // __cplusplus | |
#include <cstddef> | |
#include <memory> | |
// NOLINTBEGIN: google-explicit-constructor, whitespace/line_length | |
namespace gmac { | |
// ObjCSizeChecker will fail at compile time if the wrapper is being applied | |
// to a class that would actually have less metadata if it wasn't wrapped. | |
// See the example at the top of this file for how to control the use of this | |
// checker on a per-instantiation basis. | |
template <bool perform_check, size_t unwrapped_size, size_t wrapped_size> | |
constexpr void ObjCSizeChecker(char const (&)[unwrapped_size], char const (&)[wrapped_size]) { | |
static_assert(!perform_check || unwrapped_size >= wrapped_size, | |
"Objective-C metadata size of class wrapped in " | |
"objc_metadata_hider is greater than or equal to size of " | |
"unwrapped class. Remove unneeded use of objc_metadata_hider."); | |
} | |
// Wraps C++ ptrs so that they can be passed around in Objective-C++ code | |
// without generating a lot of useless Objective-C runtime metadata. | |
// `T` is the type being wrapped, `size_check` is whether or not the compiler | |
// should verify that the wrapped metadata is actually smaller than what would | |
// occur with unwrapped metadata. | |
// | |
// See top of file for example. | |
template <typename T, bool size_check = true> | |
class objc_metadata_hider_ptr { | |
public: | |
constexpr objc_metadata_hider_ptr() noexcept : p_(nullptr) { SizeCheck(); } | |
objc_metadata_hider_ptr(T *p) noexcept : p_(p) { SizeCheck(); } | |
objc_metadata_hider_ptr(const std::unique_ptr<T> &p) noexcept : p_(p.get()) { SizeCheck(); } | |
objc_metadata_hider_ptr(const std::shared_ptr<T> &p) noexcept : p_(p.get()) { SizeCheck(); } | |
objc_metadata_hider_ptr(const objc_metadata_hider_ptr<T, true> &p) noexcept : p_(p.get()) { | |
SizeCheck(); | |
} | |
objc_metadata_hider_ptr(const objc_metadata_hider_ptr<T, false> &p) noexcept : p_(p.get()) { | |
SizeCheck(); | |
} | |
objc_metadata_hider_ptr<T, size_check> &operator=( | |
const objc_metadata_hider_ptr<T, true> &p) noexcept { | |
p_ = p.get(); | |
return *this; | |
} | |
objc_metadata_hider_ptr<T, size_check> &operator=( | |
const objc_metadata_hider_ptr<T, false> &p) noexcept { | |
p_ = p.get(); | |
return *this; | |
} | |
objc_metadata_hider_ptr<T, size_check> &operator=(const std::unique_ptr<T> &p) noexcept { | |
p_ = p.get(); | |
return *this; | |
} | |
objc_metadata_hider_ptr<T, size_check> &operator=(const std::shared_ptr<T> &p) noexcept { | |
p_ = p.get(); | |
return *this; | |
} | |
objc_metadata_hider_ptr<T, size_check> &operator=(T *p) noexcept { | |
p_ = p; | |
return *this; | |
} | |
bool operator==(const objc_metadata_hider_ptr<T, true> &p) const noexcept { | |
return p_ == p.get(); | |
} | |
bool operator==(const objc_metadata_hider_ptr<T, false> &p) const noexcept { | |
return p_ == p.get(); | |
} | |
bool operator!=(const objc_metadata_hider_ptr<T, true> &p) const noexcept { | |
return p_ != p.get(); | |
} | |
bool operator!=(const objc_metadata_hider_ptr<T, false> &p) const noexcept { | |
return p_ != p.get(); | |
} | |
bool operator==(const std::unique_ptr<T> &p) const noexcept { return p_ == p.get(); } | |
bool operator==(const std::shared_ptr<T> &p) const noexcept { return p_ == p.get(); } | |
bool operator==(const T *p) const noexcept { return p_ == p; } | |
bool operator==(const std::nullptr_t) const noexcept { return p_ == nullptr; } | |
explicit operator bool() const noexcept { return p_ != nullptr; } | |
T &operator*() noexcept { return *p_; } | |
const T &operator*() const noexcept { return *p_; } | |
T *operator->() noexcept { return p_; } | |
const T *operator->() const noexcept { return p_; } | |
T *get() const noexcept { return p_; } | |
void reset(T *p = nullptr) noexcept { p_ = p; } | |
private: | |
T *p_; | |
constexpr void SizeCheck() { | |
#if __OBJC__ | |
ObjCSizeChecker<size_check>(@encode(T), @encode(objc_metadata_hider_ptr<T>)); | |
#endif // __OBJC__ | |
} | |
}; | |
// A type that makes it clear that you are explicitly not checking sizes at | |
// compile time. | |
template <typename T> | |
using unchecked_objc_metadata_hider_ptr = objc_metadata_hider_ptr<T, false>; | |
// Wraps C++ references so that they can be passed around in Objective-C++ | |
// code without generating a lot of useless Objective-C runtime metadata. | |
// `T` is the type being wrapped, `size_check` is whether or not the compiler | |
// should verify that the wrapped metadata is actually smaller than what would | |
// occur with unwrapped metadata. | |
// | |
// See top of file for example. | |
template <typename T, bool size_check = true> | |
class objc_metadata_hider_ref { | |
public: | |
objc_metadata_hider_ref() = delete; | |
objc_metadata_hider_ref(T &p) noexcept : p_(p) { SizeCheck(); } | |
objc_metadata_hider_ref<T, size_check> &operator=(const T &p) noexcept { | |
p_ = p; | |
return *this; | |
} | |
bool operator==(const objc_metadata_hider_ref<T, size_check> &p) const noexcept { | |
return p_ == p.p_; | |
} | |
bool operator==(const T &p) const noexcept { return p_ == p; } | |
operator T &() noexcept { return p_; } | |
private: | |
T &p_; | |
constexpr void SizeCheck() { | |
#if __OBJC__ | |
ObjCSizeChecker<size_check>(@encode(T), @encode(objc_metadata_hider_ref<T>)); | |
#endif // __OBJC__ | |
} | |
}; | |
// A type that makes it clear that you are explicitly not checking sizes at | |
// compile time. | |
template <typename T> | |
using unchecked_objc_metadata_hider_ref = objc_metadata_hider_ref<T, false>; | |
} // namespace gmac | |
// NOLINTEND: google-explicit-constructor, whitespace/line_length | |
#endif // __cplusplus | |
#endif // OBJC_METADATA_HIDER_H_ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment