|
| 1 | +// Copyright 2013 The Flutter Authors. All rights reserved. |
| 2 | +// Use of this source code is governed by a BSD-style license that can be |
| 3 | +// found in the LICENSE file. |
| 4 | + |
| 5 | +#ifndef FLUTTER_FML_PLATFORM_DARWIN_WEAK_NSOBJECT_H_ |
| 6 | +#define FLUTTER_FML_PLATFORM_DARWIN_WEAK_NSOBJECT_H_ |
| 7 | + |
| 8 | +#import <Foundation/Foundation.h> |
| 9 | +#import <objc/runtime.h> |
| 10 | + |
| 11 | +#include <stdlib.h> |
| 12 | +#include "flutter/fml/compiler_specific.h" |
| 13 | +#include "flutter/fml/logging.h" |
| 14 | +#include "flutter/fml/memory/ref_counted.h" |
| 15 | +#include "flutter/fml/memory/ref_ptr.h" |
| 16 | +#include "flutter/fml/memory/thread_checker.h" |
| 17 | + |
| 18 | +namespace debug { |
| 19 | +struct DebugThreadChecker { |
| 20 | + FML_DECLARE_THREAD_CHECKER(checker); |
| 21 | +}; |
| 22 | +} // namespace debug |
| 23 | + |
| 24 | +// WeakNSObject<> is patterned after scoped_nsobject<>, but instead of |
| 25 | +// maintaining ownership of an NSObject subclass object, it will nil itself out |
| 26 | +// when the object is deallocated. |
| 27 | +// |
| 28 | +// WeakNSProtocol<> has the same behavior as WeakNSObject, but can be used |
| 29 | +// with protocols. |
| 30 | +// |
| 31 | +// Example usage (fml::WeakNSObject<T>): |
| 32 | +// WeakNSObjectFactory factory([[Foo alloc] init]); |
| 33 | +// WeakNSObject<Foo> weak_foo; // No pointer |
| 34 | +// weak_foo = factory.GetWeakNSObject() // Now a weak reference is kept. |
| 35 | +// [weak_foo description]; // Returns [foo description]. |
| 36 | +// foo.reset(); // The reference is released. |
| 37 | +// [weak_foo description]; // Returns nil, as weak_foo is pointing to nil. |
| 38 | +// |
| 39 | +// |
| 40 | +// Implementation wise a WeakNSObject keeps a reference to a refcounted |
| 41 | +// WeakContainer. There is one unique instance of a WeakContainer per watched |
| 42 | +// NSObject, this relationship is maintained via the ObjectiveC associated |
| 43 | +// object API, indirectly via an ObjectiveC CRBWeakNSProtocolSentinel class. |
| 44 | +// |
| 45 | +// Threading restrictions: |
| 46 | +// - Several WeakNSObject pointing to the same underlying object must all be |
| 47 | +// created and dereferenced on the same thread; |
| 48 | +// - thread safety is enforced by the implementation, except: |
| 49 | +// - it is allowed to destroy a WeakNSObject on any thread; |
| 50 | +// - the implementation assumes that the tracked object will be released on the |
| 51 | +// same thread that the WeakNSObject is created on. |
| 52 | +// |
| 53 | +// fml specifics: |
| 54 | +// WeakNSObjects can only originate from a |WeakNSObjectFactory| (see below), though WeakNSObjects |
| 55 | +// are copyable and movable. |
| 56 | +// |
| 57 | +// WeakNSObjects are not in general thread-safe. They may only be *used* on |
| 58 | +// a single thread, namely the same thread as the "originating" |
| 59 | +// |WeakNSObjectFactory| (which can invalidate the WeakNSObjects that it |
| 60 | +// generates). |
| 61 | +// |
| 62 | +// However, WeakNSObject may be passed to other threads, reset on other |
| 63 | +// threads, or destroyed on other threads. They may also be reassigned on |
| 64 | +// other threads (in which case they should then only be used on the thread |
| 65 | +// corresponding to the new "originating" |WeakNSObjectFactory|). |
| 66 | +namespace fml { |
| 67 | + |
| 68 | +// Forward declaration, so |WeakNSObject<NST>| can friend it. |
| 69 | +template <typename NST> |
| 70 | +class WeakNSObjectFactory; |
| 71 | + |
| 72 | +// WeakContainer keeps a weak pointer to an object and clears it when it |
| 73 | +// receives nullify() from the object's sentinel. |
| 74 | +class WeakContainer : public fml::RefCountedThreadSafe<WeakContainer> { |
| 75 | + public: |
| 76 | + explicit WeakContainer(id object, debug::DebugThreadChecker checker); |
| 77 | + |
| 78 | + id object() { |
| 79 | + CheckThreadSafety(); |
| 80 | + return object_; |
| 81 | + } |
| 82 | + |
| 83 | + void nullify() { object_ = nil; } |
| 84 | + |
| 85 | + private: |
| 86 | + friend fml::RefCountedThreadSafe<WeakContainer>; |
| 87 | + ~WeakContainer(); |
| 88 | + |
| 89 | + __unsafe_unretained id object_; |
| 90 | + |
| 91 | + void CheckThreadSafety() const { FML_DCHECK_CREATION_THREAD_IS_CURRENT(checker_.checker); } |
| 92 | + |
| 93 | +// checker_ is unused in non-unopt mode. |
| 94 | +#pragma clang diagnostic push |
| 95 | +#pragma clang diagnostic ignored "-Wunused-private-field" |
| 96 | + debug::DebugThreadChecker checker_; |
| 97 | +#pragma clang diagnostic pop |
| 98 | +}; |
| 99 | + |
| 100 | +} // namespace fml |
| 101 | + |
| 102 | +// Sentinel for observing the object contained in the weak pointer. The object |
| 103 | +// will be deleted when the weak object is deleted and will notify its |
| 104 | +// container. |
| 105 | +@interface CRBWeakNSProtocolSentinel : NSObject |
| 106 | +// Return the only associated container for this object. There can be only one. |
| 107 | +// Will return null if object is nil . |
| 108 | ++ (fml::RefPtr<fml::WeakContainer>)containerForObject:(id)object |
| 109 | + threadChecker:(debug::DebugThreadChecker)checker; |
| 110 | +@end |
| 111 | + |
| 112 | +namespace fml { |
| 113 | + |
| 114 | +// Base class for all WeakNSObject derivatives. |
| 115 | +template <typename NST> |
| 116 | +class WeakNSProtocol { |
| 117 | + public: |
| 118 | + WeakNSProtocol() = default; |
| 119 | + |
| 120 | + // A WeakNSProtocol object can be copied on one thread and used on |
| 121 | + // another. |
| 122 | + WeakNSProtocol(const WeakNSProtocol<NST>& that) |
| 123 | + : container_(that.container_), checker_(that.checker_) {} |
| 124 | + |
| 125 | + ~WeakNSProtocol() = default; |
| 126 | + |
| 127 | + void reset() { |
| 128 | + container_ = [CRBWeakNSProtocolSentinel containerForObject:nil threadChecker:checker_]; |
| 129 | + } |
| 130 | + |
| 131 | + NST get() const { |
| 132 | + CheckThreadSafety(); |
| 133 | + if (!container_.get()) { |
| 134 | + return nil; |
| 135 | + } |
| 136 | + return container_->object(); |
| 137 | + } |
| 138 | + |
| 139 | + WeakNSProtocol& operator=(const WeakNSProtocol<NST>& that) { |
| 140 | + // A WeakNSProtocol object can be copied on one thread and used on |
| 141 | + // another. |
| 142 | + container_ = that.container_; |
| 143 | + checker_ = that.checker_; |
| 144 | + return *this; |
| 145 | + } |
| 146 | + |
| 147 | + bool operator==(NST that) const { |
| 148 | + CheckThreadSafety(); |
| 149 | + return get() == that; |
| 150 | + } |
| 151 | + |
| 152 | + bool operator!=(NST that) const { |
| 153 | + CheckThreadSafety(); |
| 154 | + return get() != that; |
| 155 | + } |
| 156 | + |
| 157 | + operator NST() const { |
| 158 | + CheckThreadSafety(); |
| 159 | + return get(); |
| 160 | + } |
| 161 | + |
| 162 | + protected: |
| 163 | + friend class WeakNSObjectFactory<NST>; |
| 164 | + |
| 165 | + explicit WeakNSProtocol(RefPtr<fml::WeakContainer> container, debug::DebugThreadChecker checker) |
| 166 | + : container_(container), checker_(checker) {} |
| 167 | + |
| 168 | + // Refecounted reference to the container tracking the ObjectiveC object this |
| 169 | + // class encapsulates. |
| 170 | + RefPtr<fml::WeakContainer> container_; |
| 171 | + void CheckThreadSafety() const { FML_DCHECK_CREATION_THREAD_IS_CURRENT(checker_.checker); } |
| 172 | + |
| 173 | + debug::DebugThreadChecker checker_; |
| 174 | +}; |
| 175 | + |
| 176 | +// Free functions |
| 177 | +template <class NST> |
| 178 | +bool operator==(NST p1, const WeakNSProtocol<NST>& p2) { |
| 179 | + return p1 == p2.get(); |
| 180 | +} |
| 181 | + |
| 182 | +template <class NST> |
| 183 | +bool operator!=(NST p1, const WeakNSProtocol<NST>& p2) { |
| 184 | + return p1 != p2.get(); |
| 185 | +} |
| 186 | + |
| 187 | +template <typename NST> |
| 188 | +class WeakNSObject : public WeakNSProtocol<NST*> { |
| 189 | + public: |
| 190 | + WeakNSObject() = default; |
| 191 | + WeakNSObject(const WeakNSObject<NST>& that) : WeakNSProtocol<NST*>(that) {} |
| 192 | + |
| 193 | + WeakNSObject& operator=(const WeakNSObject<NST>& that) { |
| 194 | + WeakNSProtocol<NST*>::operator=(that); |
| 195 | + return *this; |
| 196 | + } |
| 197 | + |
| 198 | + private: |
| 199 | + friend class WeakNSObjectFactory<NST>; |
| 200 | + |
| 201 | + explicit WeakNSObject(RefPtr<fml::WeakContainer> container, debug::DebugThreadChecker checker) |
| 202 | + : WeakNSProtocol<NST*>(container, checker) {} |
| 203 | +}; |
| 204 | + |
| 205 | +// Specialization to make WeakNSObject<id> work. |
| 206 | +template <> |
| 207 | +class WeakNSObject<id> : public WeakNSProtocol<id> { |
| 208 | + public: |
| 209 | + WeakNSObject() = default; |
| 210 | + WeakNSObject(const WeakNSObject<id>& that) : WeakNSProtocol<id>(that) {} |
| 211 | + |
| 212 | + WeakNSObject& operator=(const WeakNSObject<id>& that) { |
| 213 | + WeakNSProtocol<id>::operator=(that); |
| 214 | + return *this; |
| 215 | + } |
| 216 | + |
| 217 | + private: |
| 218 | + friend class WeakNSObjectFactory<id>; |
| 219 | + |
| 220 | + explicit WeakNSObject(RefPtr<fml::WeakContainer> container, debug::DebugThreadChecker checker) |
| 221 | + : WeakNSProtocol<id>(container, checker) {} |
| 222 | +}; |
| 223 | + |
| 224 | +// Class that produces (valid) |WeakNSObject<NST>|s. Typically, this is used as a |
| 225 | +// member variable of |NST| (preferably the last one -- see below), and |NST|'s |
| 226 | +// methods control how WeakNSObjects to it are vended. This class is not |
| 227 | +// thread-safe, and should only be created, destroyed and used on a single |
| 228 | +// thread. |
| 229 | +// |
| 230 | +// Example: |
| 231 | +// |
| 232 | +// ```objc |
| 233 | +// @implementation Controller { |
| 234 | +// std::unique_ptr<fml::WeakNSObjectFactory<Controller>> _weakFactory; |
| 235 | +// } |
| 236 | +// |
| 237 | +// - (instancetype)init { |
| 238 | +// self = [super init]; |
| 239 | +// _weakFactory = std::make_unique<fml::WeakNSObjectFactory<Controller>>(self) |
| 240 | +// } |
| 241 | + |
| 242 | +// - (fml::WeakNSObject<Controller>) { |
| 243 | +// return _weakFactory->GetWeakNSObject() |
| 244 | +// } |
| 245 | +// |
| 246 | +// @end |
| 247 | +// ``` |
| 248 | +template <typename NST> |
| 249 | +class WeakNSObjectFactory { |
| 250 | + public: |
| 251 | + explicit WeakNSObjectFactory(NST* object) { |
| 252 | + FML_DCHECK(object); |
| 253 | + container_ = [CRBWeakNSProtocolSentinel containerForObject:object threadChecker:checker_]; |
| 254 | + } |
| 255 | + |
| 256 | + ~WeakNSObjectFactory() { CheckThreadSafety(); } |
| 257 | + |
| 258 | + // Gets a new weak pointer, which will be valid until this object is |
| 259 | + // destroyed.e |
| 260 | + WeakNSObject<NST> GetWeakNSObject() const { return WeakNSObject<NST>(container_, checker_); } |
| 261 | + |
| 262 | + private: |
| 263 | + // Refecounted reference to the container tracking the ObjectiveC object this |
| 264 | + // class encapsulates. |
| 265 | + RefPtr<fml::WeakContainer> container_; |
| 266 | + |
| 267 | + void CheckThreadSafety() const { FML_DCHECK_CREATION_THREAD_IS_CURRENT(checker_.checker); } |
| 268 | + |
| 269 | + debug::DebugThreadChecker checker_; |
| 270 | + |
| 271 | + FML_DISALLOW_COPY_AND_ASSIGN(WeakNSObjectFactory); |
| 272 | +}; |
| 273 | + |
| 274 | +} // namespace fml |
| 275 | + |
| 276 | +#endif // FLUTTER_FML_PLATFORM_DARWIN_WEAK_NSOBJECT_H_ |
0 commit comments