Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

[ios] introduce weak_nsobject #47947

Merged
merged 8 commits into from
Nov 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions ci/licenses_golden/excluded_files
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,8 @@
../../../flutter/fml/platform/darwin/scoped_nsobject_arc_unittests.mm
../../../flutter/fml/platform/darwin/scoped_nsobject_unittests.mm
../../../flutter/fml/platform/darwin/string_range_sanitization_unittests.mm
../../../flutter/fml/platform/darwin/weak_nsobject_arc_unittests.mm
../../../flutter/fml/platform/darwin/weak_nsobject_unittests.mm
../../../flutter/fml/platform/win/file_win_unittests.cc
../../../flutter/fml/platform/win/wstring_conversion_unittests.cc
../../../flutter/fml/raster_thread_merger_unittests.cc
Expand Down
4 changes: 4 additions & 0 deletions ci/licenses_golden/licenses_flutter
Original file line number Diff line number Diff line change
Expand Up @@ -2799,6 +2799,8 @@ ORIGIN: ../../../flutter/fml/platform/darwin/scoped_policy.h + ../../../flutter/
ORIGIN: ../../../flutter/fml/platform/darwin/scoped_typeref.h + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/fml/platform/darwin/string_range_sanitization.h + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/fml/platform/darwin/string_range_sanitization.mm + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/fml/platform/darwin/weak_nsobject.h + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/fml/platform/darwin/weak_nsobject.mm + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/fml/platform/fuchsia/message_loop_fuchsia.cc + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/fml/platform/fuchsia/message_loop_fuchsia.h + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/fml/platform/fuchsia/paths_fuchsia.cc + ../../../flutter/LICENSE
Expand Down Expand Up @@ -5563,6 +5565,8 @@ FILE: ../../../flutter/fml/platform/darwin/scoped_policy.h
FILE: ../../../flutter/fml/platform/darwin/scoped_typeref.h
FILE: ../../../flutter/fml/platform/darwin/string_range_sanitization.h
FILE: ../../../flutter/fml/platform/darwin/string_range_sanitization.mm
FILE: ../../../flutter/fml/platform/darwin/weak_nsobject.h
FILE: ../../../flutter/fml/platform/darwin/weak_nsobject.mm
FILE: ../../../flutter/fml/platform/fuchsia/message_loop_fuchsia.cc
FILE: ../../../flutter/fml/platform/fuchsia/message_loop_fuchsia.h
FILE: ../../../flutter/fml/platform/fuchsia/paths_fuchsia.cc
Expand Down
12 changes: 10 additions & 2 deletions fml/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,8 @@ source_set("fml") {
"platform/darwin/scoped_typeref.h",
"platform/darwin/string_range_sanitization.h",
"platform/darwin/string_range_sanitization.mm",
"platform/darwin/weak_nsobject.h",
"platform/darwin/weak_nsobject.mm",
]

frameworks = [ "Foundation.framework" ]
Expand Down Expand Up @@ -369,7 +371,10 @@ if (enable_unittests) {
}

if (is_mac || is_ios) {
sources += [ "platform/darwin/scoped_nsobject_unittests.mm" ]
sources += [
"platform/darwin/scoped_nsobject_unittests.mm",
"platform/darwin/weak_nsobject_unittests.mm",
]
}

if (is_win) {
Expand Down Expand Up @@ -399,7 +404,10 @@ if (enable_unittests) {
testonly = true
if (is_mac || is_ios) {
cflags_objcc = flutter_cflags_objc_arc
sources = [ "platform/darwin/scoped_nsobject_arc_unittests.mm" ]
sources = [
"platform/darwin/scoped_nsobject_arc_unittests.mm",
"platform/darwin/weak_nsobject_arc_unittests.mm",
]
}

deps = [
Expand Down
276 changes: 276 additions & 0 deletions fml/platform/darwin/weak_nsobject.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,276 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef FLUTTER_FML_PLATFORM_DARWIN_WEAK_NSOBJECT_H_
#define FLUTTER_FML_PLATFORM_DARWIN_WEAK_NSOBJECT_H_

#import <Foundation/Foundation.h>
#import <objc/runtime.h>

#include <stdlib.h>
#include "flutter/fml/compiler_specific.h"
#include "flutter/fml/logging.h"
#include "flutter/fml/memory/ref_counted.h"
#include "flutter/fml/memory/ref_ptr.h"
#include "flutter/fml/memory/thread_checker.h"

namespace debug {
struct DebugThreadChecker {
FML_DECLARE_THREAD_CHECKER(checker);
};
} // namespace debug

// WeakNSObject<> is patterned after scoped_nsobject<>, but instead of
// maintaining ownership of an NSObject subclass object, it will nil itself out
// when the object is deallocated.
//
// WeakNSProtocol<> has the same behavior as WeakNSObject, but can be used
// with protocols.
//
// Example usage (fml::WeakNSObject<T>):
// WeakNSObjectFactory factory([[Foo alloc] init]);
// WeakNSObject<Foo> weak_foo; // No pointer
// weak_foo = factory.GetWeakNSObject() // Now a weak reference is kept.
// [weak_foo description]; // Returns [foo description].
// foo.reset(); // The reference is released.
// [weak_foo description]; // Returns nil, as weak_foo is pointing to nil.
//
//
// Implementation wise a WeakNSObject keeps a reference to a refcounted
// WeakContainer. There is one unique instance of a WeakContainer per watched
// NSObject, this relationship is maintained via the ObjectiveC associated
// object API, indirectly via an ObjectiveC CRBWeakNSProtocolSentinel class.
//
// Threading restrictions:
// - Several WeakNSObject pointing to the same underlying object must all be
// created and dereferenced on the same thread;
// - thread safety is enforced by the implementation, except:
// - it is allowed to destroy a WeakNSObject on any thread;
// - the implementation assumes that the tracked object will be released on the
// same thread that the WeakNSObject is created on.
//
// fml specifics:
// WeakNSObjects can only originate from a |WeakNSObjectFactory| (see below), though WeakNSObjects
// are copyable and movable.
//
// WeakNSObjects are not in general thread-safe. They may only be *used* on
// a single thread, namely the same thread as the "originating"
// |WeakNSObjectFactory| (which can invalidate the WeakNSObjects that it
// generates).
//
// However, WeakNSObject may be passed to other threads, reset on other
// threads, or destroyed on other threads. They may also be reassigned on
// other threads (in which case they should then only be used on the thread
// corresponding to the new "originating" |WeakNSObjectFactory|).
namespace fml {

// Forward declaration, so |WeakNSObject<NST>| can friend it.
template <typename NST>
class WeakNSObjectFactory;

// WeakContainer keeps a weak pointer to an object and clears it when it
// receives nullify() from the object's sentinel.
class WeakContainer : public fml::RefCountedThreadSafe<WeakContainer> {
public:
explicit WeakContainer(id object, debug::DebugThreadChecker checker);

id object() {
CheckThreadSafety();
return object_;
}

void nullify() { object_ = nil; }

private:
friend fml::RefCountedThreadSafe<WeakContainer>;
~WeakContainer();

__unsafe_unretained id object_;

void CheckThreadSafety() const { FML_DCHECK_CREATION_THREAD_IS_CURRENT(checker_.checker); }

// checker_ is unused in non-unopt mode.
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunused-private-field"
debug::DebugThreadChecker checker_;
#pragma clang diagnostic pop
};

} // namespace fml

// Sentinel for observing the object contained in the weak pointer. The object
// will be deleted when the weak object is deleted and will notify its
// container.
@interface CRBWeakNSProtocolSentinel : NSObject
// Return the only associated container for this object. There can be only one.
// Will return null if object is nil .
+ (fml::RefPtr<fml::WeakContainer>)containerForObject:(id)object
threadChecker:(debug::DebugThreadChecker)checker;
@end

namespace fml {

// Base class for all WeakNSObject derivatives.
template <typename NST>
class WeakNSProtocol {
public:
WeakNSProtocol() = default;

// A WeakNSProtocol object can be copied on one thread and used on
// another.
WeakNSProtocol(const WeakNSProtocol<NST>& that)
: container_(that.container_), checker_(that.checker_) {}

~WeakNSProtocol() = default;

void reset() {
container_ = [CRBWeakNSProtocolSentinel containerForObject:nil threadChecker:checker_];
}

NST get() const {
CheckThreadSafety();
if (!container_.get()) {
return nil;
}
return container_->object();
}

WeakNSProtocol& operator=(const WeakNSProtocol<NST>& that) {
// A WeakNSProtocol object can be copied on one thread and used on
// another.
container_ = that.container_;
checker_ = that.checker_;
return *this;
}

bool operator==(NST that) const {
CheckThreadSafety();
return get() == that;
}

bool operator!=(NST that) const {
CheckThreadSafety();
return get() != that;
}

operator NST() const {
CheckThreadSafety();
return get();
}

protected:
friend class WeakNSObjectFactory<NST>;

explicit WeakNSProtocol(RefPtr<fml::WeakContainer> container, debug::DebugThreadChecker checker)
: container_(container), checker_(checker) {}

// Refecounted reference to the container tracking the ObjectiveC object this
// class encapsulates.
RefPtr<fml::WeakContainer> container_;
void CheckThreadSafety() const { FML_DCHECK_CREATION_THREAD_IS_CURRENT(checker_.checker); }

debug::DebugThreadChecker checker_;
};

// Free functions
template <class NST>
bool operator==(NST p1, const WeakNSProtocol<NST>& p2) {
return p1 == p2.get();
}

template <class NST>
bool operator!=(NST p1, const WeakNSProtocol<NST>& p2) {
return p1 != p2.get();
}

template <typename NST>
class WeakNSObject : public WeakNSProtocol<NST*> {
public:
WeakNSObject() = default;
WeakNSObject(const WeakNSObject<NST>& that) : WeakNSProtocol<NST*>(that) {}

WeakNSObject& operator=(const WeakNSObject<NST>& that) {
WeakNSProtocol<NST*>::operator=(that);
return *this;
}

private:
friend class WeakNSObjectFactory<NST>;

explicit WeakNSObject(RefPtr<fml::WeakContainer> container, debug::DebugThreadChecker checker)
: WeakNSProtocol<NST*>(container, checker) {}
};

// Specialization to make WeakNSObject<id> work.
template <>
class WeakNSObject<id> : public WeakNSProtocol<id> {
public:
WeakNSObject() = default;
WeakNSObject(const WeakNSObject<id>& that) : WeakNSProtocol<id>(that) {}

WeakNSObject& operator=(const WeakNSObject<id>& that) {
WeakNSProtocol<id>::operator=(that);
return *this;
}

private:
friend class WeakNSObjectFactory<id>;

explicit WeakNSObject(RefPtr<fml::WeakContainer> container, debug::DebugThreadChecker checker)
: WeakNSProtocol<id>(container, checker) {}
};

// Class that produces (valid) |WeakNSObject<NST>|s. Typically, this is used as a
// member variable of |NST| (preferably the last one -- see below), and |NST|'s
// methods control how WeakNSObjects to it are vended. This class is not
// thread-safe, and should only be created, destroyed and used on a single
// thread.
//
// Example:
//
// ```objc
// @implementation Controller {
// std::unique_ptr<fml::WeakNSObjectFactory<Controller>> _weakFactory;
// }
//
// - (instancetype)init {
// self = [super init];
// _weakFactory = std::make_unique<fml::WeakNSObjectFactory<Controller>>(self)
// }

// - (fml::WeakNSObject<Controller>) {
// return _weakFactory->GetWeakNSObject()
// }
//
// @end
// ```
template <typename NST>
class WeakNSObjectFactory {
public:
explicit WeakNSObjectFactory(NST* object) {
FML_DCHECK(object);
container_ = [CRBWeakNSProtocolSentinel containerForObject:object threadChecker:checker_];
}

~WeakNSObjectFactory() { CheckThreadSafety(); }

// Gets a new weak pointer, which will be valid until this object is
// destroyed.e
WeakNSObject<NST> GetWeakNSObject() const { return WeakNSObject<NST>(container_, checker_); }

private:
// Refecounted reference to the container tracking the ObjectiveC object this
// class encapsulates.
RefPtr<fml::WeakContainer> container_;

void CheckThreadSafety() const { FML_DCHECK_CREATION_THREAD_IS_CURRENT(checker_.checker); }

debug::DebugThreadChecker checker_;

FML_DISALLOW_COPY_AND_ASSIGN(WeakNSObjectFactory);
};

} // namespace fml

#endif // FLUTTER_FML_PLATFORM_DARWIN_WEAK_NSOBJECT_H_
Loading