1#pragma once
2
3#include <string>
4
5#include "taichi/common/core.h"
6
7#ifdef TI_PLATFORM_OSX
8
9#include <objc/message.h>
10#include <objc/objc.h>
11#include <objc/runtime.h>
12
13namespace taichi {
14namespace mac {
15
16template <typename R, typename O, typename... Args>
17R cast_call(O *i, const char *select, Args... args) {
18 using func = R (*)(id, SEL, Args...);
19 return ((func)(objc_msgSend))(reinterpret_cast<id>(i), sel_getUid(select),
20 args...);
21}
22
23template <typename O, typename... Args>
24id call(O *i, const char *select, Args... args) {
25 return cast_call<id>(i, select, args...);
26}
27
28template <typename R = id, typename... Args>
29R clscall(const char *class_name, const char *select, Args... args) {
30 using func = R (*)(id, SEL, Args...);
31 return ((func)(objc_msgSend))((id)objc_getClass(class_name),
32 sel_getUid(select), args...);
33}
34
35template <typename O>
36class NsObjDeleter {
37 public:
38 void operator()(O *o) {
39 call(o, "release");
40 }
41};
42
43template <typename O>
44using nsobj_unique_ptr = std::unique_ptr<O, NsObjDeleter<O>>;
45
46template <typename O>
47nsobj_unique_ptr<O> wrap_as_nsobj_unique_ptr(O *nsobj) {
48 return nsobj_unique_ptr<O>(nsobj);
49}
50
51template <typename O>
52nsobj_unique_ptr<O> retain_and_wrap_as_nsobj_unique_ptr(O *nsobj) {
53 // On creating an object, it could be either the caller or the callee's
54 // responsibility to take the ownership of the object. By convention, method
55 // names with "alloc", "new", "create" imply that the caller owns the object.
56 // Otherwise, the object is tracked by an autoreleasepool before the callee
57 // returns it.
58 //
59 // For an object that is owned by the callee (released by autoreleasepool), if
60 // we want to *own* a reference to it, we must call [retain] to increment the
61 // reference counting.
62 //
63 // In practice, we find that each pthread (non main-thread) creates its own
64 // autoreleasepool. Without retaining the object, it has caused double-free
65 // on thread exit:
66 // 1. nsobj_unique_ptr calls [release] in its destructor.
67 // 2. autoreleasepool releases all the tracked objects upon thread exit.
68 //
69 // * https://stackoverflow.com/a/51080781/12003165
70 // *
71 // https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/MemoryMgmt/Articles/mmRules.html#//apple_ref/doc/uid/20000994-SW1
72 call(nsobj, "retain");
73 return wrap_as_nsobj_unique_ptr(nsobj);
74}
75
76// Prepend "TI_" to native ObjC type names, otherwise clang-format thinks this
77// is an ObjC file and is not happy formatting it.
78struct TI_NSString;
79struct TI_NSArray;
80
81struct TI_NSRange {
82 size_t location{0};
83 size_t length{0};
84};
85
86// |str| must exist during the entire lifetime of the returned object, as it
87// does not own the underlying memory. Think of it as std::string_view.
88nsobj_unique_ptr<TI_NSString> wrap_string_as_ns_string(const std::string &str);
89
90std::string to_string(TI_NSString *ns);
91
92int ns_array_count(TI_NSArray *na);
93
94template <typename R>
95R ns_array_object_at_index(TI_NSArray *na, int i) {
96 return cast_call<R>(na, "objectAtIndex:", i);
97}
98
99struct TI_NSAutoreleasePool;
100
101TI_NSAutoreleasePool *create_autorelease_pool();
102
103void drain_autorelease_pool(TI_NSAutoreleasePool *pool);
104
105class ScopedAutoreleasePool {
106 public:
107 ScopedAutoreleasePool();
108 ~ScopedAutoreleasePool();
109
110 private:
111 TI_NSAutoreleasePool *pool_;
112};
113
114} // namespace mac
115} // namespace taichi
116
117#endif // TI_PLATFORM_OSX
118