diff --git a/kotlin-native/runtime/src/main/cpp/objc_support/ObjectPtr.hpp b/kotlin-native/runtime/src/main/cpp/objc_support/ObjectPtr.hpp index 464eb074f0a..16a5d4ad7f6 100644 --- a/kotlin-native/runtime/src/main/cpp/objc_support/ObjectPtr.hpp +++ b/kotlin-native/runtime/src/main/cpp/objc_support/ObjectPtr.hpp @@ -20,12 +20,16 @@ OBJC_FORWARD_DECLARE(NSObject); namespace kotlin::objc_support { +struct object_ptr_retain_t{}; +inline constexpr object_ptr_retain_t object_ptr_retain{}; + namespace internal { class ObjectPtrImpl { public: ObjectPtrImpl() noexcept; explicit ObjectPtrImpl(NSObject* object) noexcept; + ObjectPtrImpl(object_ptr_retain_t, NSObject* object) noexcept; ObjectPtrImpl(const ObjectPtrImpl& rhs) noexcept; ObjectPtrImpl(ObjectPtrImpl&& rhs) noexcept; @@ -51,6 +55,7 @@ public: void reset() noexcept; void reset(NSObject* object) noexcept; + void reset(object_ptr_retain_t, NSObject* object) noexcept; bool operator==(const ObjectPtrImpl& rhs) const noexcept; bool operator<(const ObjectPtrImpl& rhs) const noexcept; @@ -65,8 +70,10 @@ private: // `std::shared_ptr`-like smart pointer for ObjC objects. // -// IMPORTANT: Must be constructed from a retained ObjC object. +// IMPORTANT: By default constructed from a retained ObjC object. // This optimizes for the common case of constructing directly from `[[T alloc] init...]`. +// To construct and additionally retain the object, use `object_ptr_retain` as the first argument +// to the constructor. // // Unlike a regular C++ smart pointer `operator*` does not return a reference but return a pointer // for convenient syntax of calling ObjC methods: `[*smartPtr methodName]`. @@ -88,6 +95,9 @@ public: // IMPORTANT: this does not `retain` `ptr`. explicit object_ptr(T* ptr) noexcept : impl_(ptr) {} + // Construct from ObjC object, additionally retaining `ptr`. + object_ptr(object_ptr_retain_t, T* ptr) noexcept : impl_(object_ptr_retain, ptr) {} + void swap(object_ptr& rhs) noexcept { impl_.swap(rhs.impl_); } T* get() const noexcept { return (T*)impl_.get(); } @@ -104,6 +114,9 @@ public: // IMPORTANT: this does not `retain` `ptr`. void reset(T* ptr) noexcept { impl_.reset(ptr); } + // Release stored pointer and store `ptr` instead, additionally retaining `ptr`. + void reset(object_ptr_retain_t, T* ptr) noexcept { impl_.reset(object_ptr_retain, ptr); } + template bool operator==(const object_ptr& rhs) const noexcept { return impl_ == rhs.impl_; diff --git a/kotlin-native/runtime/src/main/cpp/objc_support/ObjectPtr.mm b/kotlin-native/runtime/src/main/cpp/objc_support/ObjectPtr.mm index aeaf39e3cca..50efe7480ac 100644 --- a/kotlin-native/runtime/src/main/cpp/objc_support/ObjectPtr.mm +++ b/kotlin-native/runtime/src/main/cpp/objc_support/ObjectPtr.mm @@ -15,6 +15,8 @@ objc_support::internal::ObjectPtrImpl::ObjectPtrImpl() noexcept : object_(nil) { objc_support::internal::ObjectPtrImpl::ObjectPtrImpl(NSObject* object) noexcept : object_(object) {} +objc_support::internal::ObjectPtrImpl::ObjectPtrImpl(object_ptr_retain_t, NSObject* object) noexcept : object_([object retain]) {} + objc_support::internal::ObjectPtrImpl::ObjectPtrImpl(const ObjectPtrImpl& rhs) noexcept : object_([rhs.object_ retain]) {} objc_support::internal::ObjectPtrImpl::ObjectPtrImpl(ObjectPtrImpl&& rhs) noexcept : object_(rhs.object_) { @@ -51,6 +53,10 @@ void objc_support::internal::ObjectPtrImpl::reset(NSObject* object) noexcept { } } +void objc_support::internal::ObjectPtrImpl::reset(object_ptr_retain_t, NSObject* object) noexcept { + reset([object retain]); +} + std::size_t objc_support::internal::ObjectPtrImpl::computeHash() const noexcept { return object_.hash; } diff --git a/kotlin-native/runtime/src/main/cpp/objc_support/ObjectPtrTest.mm b/kotlin-native/runtime/src/main/cpp/objc_support/ObjectPtrTest.mm index 6300351f51b..23d724b6ea8 100644 --- a/kotlin-native/runtime/src/main/cpp/objc_support/ObjectPtrTest.mm +++ b/kotlin-native/runtime/src/main/cpp/objc_support/ObjectPtrTest.mm @@ -85,6 +85,21 @@ TEST(ObjectPtrTest, ObjectCtor) { testing::Mock::VerifyAndClearExpectations(&destructorHook); } +TEST(ObjectPtrTest, ObjectCtorWithRetain) { + testing::StrictMock> destructorHook; + + auto* ptr = [[WithDestructorHookObjC alloc] initWithDestructorHook:destructorHook.AsStdFunction()]; + { + objc_support::object_ptr obj(objc_support::object_ptr_retain, ptr); + EXPECT_CONTAINS(obj, ptr); + EXPECT_CALL(destructorHook, Call(ptr.impl)).Times(0); + } + testing::Mock::VerifyAndClearExpectations(&destructorHook); + + EXPECT_CALL(destructorHook, Call(ptr.impl)); + [ptr release]; +} + TEST(ObjectPtrTest, CopyCtor) { testing::StrictMock> destructorHook; @@ -304,6 +319,24 @@ TEST(ObjectPtrTest, ResetObjectFromEmpty) { EXPECT_CALL(destructorHook, Call(ptr.impl)); } +TEST(ObjectPtrTest, ResetObjectFromEmptyWithRetain) { + testing::StrictMock> destructorHook; + + auto* ptr = [[WithDestructorHookObjC alloc] initWithDestructorHook:destructorHook.AsStdFunction()]; + { + objc_support::object_ptr obj; + + obj.reset(objc_support::object_ptr_retain, ptr); + EXPECT_CONTAINS(obj, ptr); + + EXPECT_CALL(destructorHook, Call(ptr.impl)).Times(0); + } + testing::Mock::VerifyAndClearExpectations(&destructorHook); + + EXPECT_CALL(destructorHook, Call(ptr.impl)); + [ptr release]; +} + TEST(ObjectPtrTest, ResetObjectFromSet) { testing::StrictMock> destructorHook; @@ -319,6 +352,27 @@ TEST(ObjectPtrTest, ResetObjectFromSet) { EXPECT_CALL(destructorHook, Call(ptr2.impl)); } +TEST(ObjectPtrTest, ResetObjectFromSetWithRetain) { + testing::StrictMock> destructorHook; + + auto* ptr1 = [[WithDestructorHookObjC alloc] initWithDestructorHook:destructorHook.AsStdFunction()]; + auto* ptr2 = [[WithDestructorHookObjC alloc] initWithDestructorHook:destructorHook.AsStdFunction()]; + { + objc_support::object_ptr obj(ptr1); + + EXPECT_CALL(destructorHook, Call(ptr1.impl)); + obj.reset(objc_support::object_ptr_retain, ptr2); + testing::Mock::VerifyAndClearExpectations(&destructorHook); + EXPECT_CONTAINS(obj, ptr2); + + EXPECT_CALL(destructorHook, Call(ptr2.impl)).Times(0); + } + testing::Mock::VerifyAndClearExpectations(&destructorHook); + + EXPECT_CALL(destructorHook, Call(ptr2.impl)); + [ptr2 release]; +} + #define EXPECT_EQUAL(expr1, expr2) \ do { \ EXPECT_TRUE((expr1) == (expr2)); \