diff options
author | droger <droger@chromium.org> | 2014-12-12 09:39:02 -0800 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2014-12-12 17:39:23 +0000 |
commit | c6762659cdcb0c0d3c2a430ae964ab04429253ca (patch) | |
tree | fb100fd13cf5d581401a8c83dce4eecf29698b28 | |
parent | 7ec6e063e36a66300570ab0adfd88c2e5acc6bd3 (diff) | |
download | chromium_src-c6762659cdcb0c0d3c2a430ae964ab04429253ca.zip chromium_src-c6762659cdcb0c0d3c2a430ae964ab04429253ca.tar.gz chromium_src-c6762659cdcb0c0d3c2a430ae964ab04429253ca.tar.bz2 |
Upstream WeakNSObject in //base/ios
Review URL: https://codereview.chromium.org/792443004
Cr-Commit-Position: refs/heads/master@{#308114}
-rw-r--r-- | base/BUILD.gn | 3 | ||||
-rw-r--r-- | base/base.gyp | 1 | ||||
-rw-r--r-- | base/base.gypi | 2 | ||||
-rw-r--r-- | base/ios/weak_nsobject.h | 166 | ||||
-rw-r--r-- | base/ios/weak_nsobject.mm | 61 | ||||
-rw-r--r-- | base/ios/weak_nsobject_unittest.mm | 97 |
6 files changed, 330 insertions, 0 deletions
diff --git a/base/BUILD.gn b/base/BUILD.gn index a928319..de464f9 100644 --- a/base/BUILD.gn +++ b/base/BUILD.gn @@ -251,6 +251,8 @@ component("base") { "ios/ios_util.mm", "ios/scoped_critical_action.h", "ios/scoped_critical_action.mm", + "ios/weak_nsobject.h", + "ios/weak_nsobject.mm", "json/json_file_value_serializer.cc", "json/json_file_value_serializer.h", "json/json_parser.cc", @@ -1195,6 +1197,7 @@ test("base_unittests") { "i18n/time_formatting_unittest.cc", "i18n/timezone_unittest.cc", "ios/device_util_unittest.mm", + "ios/weak_nsobject_unittest.mm", "json/json_parser_unittest.cc", "json/json_reader_unittest.cc", "json/json_value_converter_unittest.cc", diff --git a/base/base.gyp b/base/base.gyp index ec5444a..d3a55b8 100644 --- a/base/base.gyp +++ b/base/base.gyp @@ -519,6 +519,7 @@ 'i18n/time_formatting_unittest.cc', 'i18n/timezone_unittest.cc', 'ios/device_util_unittest.mm', + 'ios/weak_nsobject_unittest.mm', 'json/json_parser_unittest.cc', 'json/json_reader_unittest.cc', 'json/json_value_converter_unittest.cc', diff --git a/base/base.gypi b/base/base.gypi index 60ed267..5ec4142 100644 --- a/base/base.gypi +++ b/base/base.gypi @@ -253,6 +253,8 @@ 'ios/ios_util.mm', 'ios/scoped_critical_action.h', 'ios/scoped_critical_action.mm', + 'ios/weak_nsobject.h', + 'ios/weak_nsobject.mm', 'json/json_file_value_serializer.cc', 'json/json_file_value_serializer.h', 'json/json_parser.cc', diff --git a/base/ios/weak_nsobject.h b/base/ios/weak_nsobject.h new file mode 100644 index 0000000..46aecb5 --- /dev/null +++ b/base/ios/weak_nsobject.h @@ -0,0 +1,166 @@ +// Copyright 2013 The Chromium 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 BASE_IOS_WEAK_NSOBJECT_H_ +#define BASE_IOS_WEAK_NSOBJECT_H_ + +#import <Foundation/Foundation.h> +#import <objc/runtime.h> + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/logging.h" +#include "base/memory/ref_counted.h" +#include "base/threading/non_thread_safe.h" +#include "base/threading/thread_checker.h" + +// 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 (base::WeakNSObject<T>): +// scoped_nsobject<Foo> foo([[Foo alloc] init]); +// WeakNSObject<Foo> weak_foo; // No pointer +// weak_foo.reset(foo) // 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. +// +// The implementation assumes that the tracked object will be released on the +// same thread that the WeakNSObject is created on. +// +namespace base { + +// WeakContainer keeps a weak pointer to an object and clears it when it +// receives nullify() from the object's sentinel. +class WeakContainer : public base::RefCountedThreadSafe<WeakContainer> { + public: + WeakContainer(id object) : object_(object) {} + id object() { return object_; } + void nullify() { + DCHECK(checker_.CalledOnValidThread()); + object_ = nil; + } + + private: + friend base::RefCountedThreadSafe<WeakContainer>; + ~WeakContainer() {} + base::ThreadChecker checker_; + id object_; +}; + +} // namespace base + +// 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 . ++ (scoped_refptr<base::WeakContainer>)containerForObject:(id)object; +@end + +namespace base { + +// Base class for all WeakNSObject derivatives. +template <typename NST> +class WeakNSProtocol : public base::NonThreadSafe { + public: + explicit WeakNSProtocol(NST object = nil) { + container_ = [CRBWeakNSProtocolSentinel containerForObject:object]; + } + + WeakNSProtocol(const WeakNSProtocol<NST>& that) { + container_ = that.container_; + } + + ~WeakNSProtocol() { + // A WeakNSProtocol object can be allocated on one thread and released on + // another. This is not the case for the contained object. + DetachFromThread(); + } + + void reset(NST object = nil) { + DCHECK(CalledOnValidThread()); + container_ = [CRBWeakNSProtocolSentinel containerForObject:object]; + } + + NST get() const { + DCHECK(CalledOnValidThread()); + if (!container_.get()) + return nil; + return container_->object(); + } + + WeakNSProtocol& operator=(const WeakNSProtocol<NST>& that) { + DCHECK(CalledOnValidThread()); + container_ = that.container_; + return *this; + } + + bool operator==(NST that) const { + DCHECK(CalledOnValidThread()); + return get() == that; + } + + bool operator!=(NST that) const { return get() != that; } + + operator NST() const { return get(); } + + private: + // Refecounted reference to the container tracking the ObjectiveC object this + // class encapsulates. + scoped_refptr<base::WeakContainer> container_; +}; + +// 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: + explicit WeakNSObject(NST* object = nil) : WeakNSProtocol<NST*>(object) {} + + WeakNSObject(const WeakNSObject<NST>& that) : WeakNSProtocol<NST*>(that) {} + + WeakNSObject& operator=(const WeakNSObject<NST>& that) { + WeakNSProtocol<NST*>::operator=(that); + return *this; + } +}; + +// Specialization to make WeakNSObject<id> work. +template <> +class WeakNSObject<id> : public WeakNSProtocol<id> { + public: + explicit WeakNSObject(id object = nil) : WeakNSProtocol<id>(object) {} + + WeakNSObject(const WeakNSObject<id>& that) : WeakNSProtocol<id>(that) {} + + WeakNSObject& operator=(const WeakNSObject<id>& that) { + WeakNSProtocol<id>::operator=(that); + return *this; + } +}; + +} // namespace base + +#endif // BASE_IOS_WEAK_NSOBJECT_H_ diff --git a/base/ios/weak_nsobject.mm b/base/ios/weak_nsobject.mm new file mode 100644 index 0000000..36f9d3e --- /dev/null +++ b/base/ios/weak_nsobject.mm @@ -0,0 +1,61 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/ios/weak_nsobject.h" + +#include "base/mac/scoped_nsautorelease_pool.h" +#include "base/mac/scoped_nsobject.h" + +namespace { +// The key needed by objc_setAssociatedObject. +char sentinelObserverKey_; +} + +@interface CRBWeakNSProtocolSentinel () +// Container to notify on dealloc. +@property(readonly, assign) scoped_refptr<base::WeakContainer> container; +// Designed initializer. +- (id)initWithContainer:(scoped_refptr<base::WeakContainer>)container; +@end + +@implementation CRBWeakNSProtocolSentinel + +@synthesize container = container_; + ++ (scoped_refptr<base::WeakContainer>)containerForObject:(id)object { + if (object == nil) + return nullptr; + // The autoreleasePool is needed here as the call to objc_getAssociatedObject + // returns an autoreleased object which is better released sooner than later. + base::mac::ScopedNSAutoreleasePool pool; + CRBWeakNSProtocolSentinel* sentinel = + objc_getAssociatedObject(object, &sentinelObserverKey_); + if (!sentinel) { + base::scoped_nsobject<CRBWeakNSProtocolSentinel> newSentinel( + [[CRBWeakNSProtocolSentinel alloc] + initWithContainer:new base::WeakContainer(object)]); + sentinel = newSentinel; + objc_setAssociatedObject(object, &sentinelObserverKey_, sentinel, + OBJC_ASSOCIATION_RETAIN); + // The retain count is 2. One retain is due to the alloc, the other to the + // association with the weak object. + DCHECK_EQ(2u, [sentinel retainCount]); + } + return [sentinel container]; +} + +- (id)initWithContainer:(scoped_refptr<base::WeakContainer>)container { + DCHECK(container.get()); + self = [super init]; + if (self) + container_ = container; + return self; +} + +- (void)dealloc { + self.container->nullify(); + [super dealloc]; +} + +@end diff --git a/base/ios/weak_nsobject_unittest.mm b/base/ios/weak_nsobject_unittest.mm new file mode 100644 index 0000000..9758aed --- /dev/null +++ b/base/ios/weak_nsobject_unittest.mm @@ -0,0 +1,97 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/basictypes.h" +#include "base/ios/weak_nsobject.h" +#include "base/mac/scoped_nsobject.h" +#include "testing/gtest/include/gtest/gtest.h" + +using base::WeakNSObject; + +namespace { + +TEST(WeakNSObjectTest, WeakNSObject) { + base::scoped_nsobject<NSObject> p1([[NSObject alloc] init]); + WeakNSObject<NSObject> w1(p1); + EXPECT_TRUE(w1); + p1.reset(); + EXPECT_FALSE(w1); +} + +TEST(WeakNSObjectTest, MultipleWeakNSObject) { + base::scoped_nsobject<NSObject> p1([[NSObject alloc] init]); + WeakNSObject<NSObject> w1(p1); + WeakNSObject<NSObject> w2(w1); + EXPECT_TRUE(w1); + EXPECT_TRUE(w2); + EXPECT_TRUE(w1.get() == w2.get()); + p1.reset(); + EXPECT_FALSE(w1); + EXPECT_FALSE(w2); +} + +TEST(WeakNSObjectTest, WeakNSObjectDies) { + base::scoped_nsobject<NSObject> p1([[NSObject alloc] init]); + { + WeakNSObject<NSObject> w1(p1); + EXPECT_TRUE(w1); + } +} + +TEST(WeakNSObjectTest, WeakNSObjectReset) { + base::scoped_nsobject<NSObject> p1([[NSObject alloc] init]); + WeakNSObject<NSObject> w1(p1); + EXPECT_TRUE(w1); + w1.reset(); + EXPECT_FALSE(w1); + EXPECT_TRUE(p1); + EXPECT_TRUE([p1 description]); +} + +TEST(WeakNSObjectTest, WeakNSObjectResetWithObject) { + base::scoped_nsobject<NSObject> p1([[NSObject alloc] init]); + base::scoped_nsobject<NSObject> p2([[NSObject alloc] init]); + WeakNSObject<NSObject> w1(p1); + EXPECT_TRUE(w1); + w1.reset(p2); + EXPECT_TRUE(w1); + EXPECT_TRUE([p1 description]); + EXPECT_TRUE([p2 description]); +} + +TEST(WeakNSObjectTest, WeakNSObjectEmpty) { + base::scoped_nsobject<NSObject> p1([[NSObject alloc] init]); + WeakNSObject<NSObject> w1; + EXPECT_FALSE(w1); + w1.reset(p1); + EXPECT_TRUE(w1); + p1.reset(); + EXPECT_FALSE(w1); +} + +TEST(WeakNSObjectTest, WeakNSObjectCopy) { + base::scoped_nsobject<NSObject> p1([[NSObject alloc] init]); + WeakNSObject<NSObject> w1(p1); + WeakNSObject<NSObject> w2(w1); + EXPECT_TRUE(w1); + EXPECT_TRUE(w2); + p1.reset(); + EXPECT_FALSE(w1); + EXPECT_FALSE(w2); +} + +TEST(WeakNSObjectTest, WeakNSObjectAssignment) { + base::scoped_nsobject<NSObject> p1([[NSObject alloc] init]); + WeakNSObject<NSObject> w1(p1); + WeakNSObject<NSObject> w2; + EXPECT_FALSE(w2); + w2 = w1; + EXPECT_TRUE(w1); + EXPECT_TRUE(w2); + p1.reset(); + EXPECT_FALSE(w1); + EXPECT_FALSE(w2); +} + +} // namespace |