summaryrefslogtreecommitdiffstats
path: root/base
diff options
context:
space:
mode:
authormark@chromium.org <mark@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-04-20 22:30:35 +0000
committermark@chromium.org <mark@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-04-20 22:30:35 +0000
commit10756c518ba0fccd61abd8393bcc278829dd5ae6 (patch)
tree60964b4b67564b9d53b3e5e2317028ece40a9afe /base
parentbd680cbb4008f577a28ef58cb260f36217172e3a (diff)
downloadchromium_src-10756c518ba0fccd61abd8393bcc278829dd5ae6.zip
chromium_src-10756c518ba0fccd61abd8393bcc278829dd5ae6.tar.gz
chromium_src-10756c518ba0fccd61abd8393bcc278829dd5ae6.tar.bz2
Introduce ObjCPropertyReleaser for automatic releasing of Objective-C
properties marked "retain" or "copy" backed by synthesized instance variables. BUG=none TEST=base_unittests ObjCPropertyReleaserTest.SesameStreet Review URL: http://codereview.chromium.org/6881091 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@82383 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'base')
-rw-r--r--base/base.gyp1
-rw-r--r--base/base.gypi2
-rw-r--r--base/mac/objc_property_releaser.h126
-rw-r--r--base/mac/objc_property_releaser.mm116
-rw-r--r--base/mac/objc_property_releaser_unittest.mm229
5 files changed, 474 insertions, 0 deletions
diff --git a/base/base.gyp b/base/base.gyp
index 37b197d..682eacf 100644
--- a/base/base.gyp
+++ b/base/base.gyp
@@ -135,6 +135,7 @@
'linked_list_unittest.cc',
'logging_unittest.cc',
'mac/mac_util_unittest.mm',
+ 'mac/objc_property_releaser_unittest.mm',
'memory/linked_ptr_unittest.cc',
'memory/ref_counted_unittest.cc',
'memory/scoped_native_library_unittest.cc',
diff --git a/base/base.gypi b/base/base.gypi
index 8e49dd5..51a9643 100644
--- a/base/base.gypi
+++ b/base/base.gypi
@@ -119,6 +119,8 @@
'mac/foundation_util.mm',
'mac/mac_util.h',
'mac/mac_util.mm',
+ 'mac/objc_property_releaser.h',
+ 'mac/objc_property_releaser.mm',
'mac/os_crash_dumps.cc',
'mac/os_crash_dumps.h',
'mac/scoped_aedesc.h',
diff --git a/base/mac/objc_property_releaser.h b/base/mac/objc_property_releaser.h
new file mode 100644
index 0000000..1aee6b3
--- /dev/null
+++ b/base/mac/objc_property_releaser.h
@@ -0,0 +1,126 @@
+// Copyright (c) 2011 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_MAC_OBJC_PROPERTY_RELEASER_H_
+#define BASE_MAC_OBJC_PROPERTY_RELEASER_H_
+#pragma once
+
+#import <Foundation/Foundation.h>
+
+namespace base {
+namespace mac {
+
+// ObjCPropertyReleaser is a C++ class that can automatically release
+// synthesized Objective-C properties marked "retain" or "copy". The expected
+// use is to place an ObjCPropertyReleaser object within an Objective-C class
+// definition. When built with the -fobjc-call-cxx-cdtors compiler option,
+// the ObjCPropertyReleaser's destructor will be called when the Objective-C
+// object that owns it is deallocated, and it will send a -release message to
+// the instance variables backing the appropriate properties. If
+// -fobjc-call-cxx-cdtors is not in use, ObjCPropertyReleaser's
+// ReleaseProperties method can be called from -dealloc to achieve the same
+// effect.
+//
+// Example usage:
+//
+// @interface AllaysIBF : NSObject {
+// @private
+// NSString* string_;
+// NSMutableDictionary* dictionary_;
+// NSString* notAProperty_;
+// IBFDelegate* delegate_; // weak
+//
+// // It's recommended to put the class name into the property releaser's
+// // instance variable name to gracefully handle subclassing, where
+// // multiple classes in a hierarchy might want their own property
+// // releasers.
+// base::mac::ObjCPropertyReleaser propertyReleaser_AllaysIBF_;
+// }
+//
+// @property(retain, nonatomic) NSString* string;
+// @property(copy, nonatomic) NSMutableDictionary* dictionary;
+// @property(assign, nonatomic) IBFDelegate* delegate;
+// @property(retain, nonatomic) NSString* autoProp;
+//
+// @end // @interface AllaysIBF
+//
+// @implementation AllaysIBF
+//
+// @synthesize string = string_;
+// @synthesize dictionary = dictionary_;
+// @synthesize delegate = delegate_;
+// @synthesize autoProp;
+//
+// - (id)init {
+// if ((self = [super init])) {
+// // Initialize with [AllaysIBF class]. Never use [self class] because
+// // in the case of subclassing, it will return the most specific class
+// // for |self|, which may not be the same as [AllaysIBF class]. This
+// // would cause AllaysIBF's -.cxx_destruct or -dealloc to release
+// // instance variables that only exist in subclasses, likely causing
+// // mass disaster.
+// propertyReleaser_AllaysIBF_.Init(self, [AllaysIBF class]);
+// }
+// return self;
+// }
+//
+// @end // @implementation AllaysIBF
+//
+// When an instance of AllaysIBF is deallocated, the ObjCPropertyReleaser will
+// send a -release message to string_, dictionary_, and the compiler-created
+// autoProp instance variables. No -release will be sent to delegate_ as it
+// is marked "assign" and not "retain" or "copy". No -release will be sent to
+// notAProperty_ because it doesn't correspond to any declared @property.
+//
+// Another way of doing this would be to provide a base class that others can
+// inherit from, and to have the base class' -dealloc walk the property lists
+// of all subclasses in an object to send the -release messages. Since this
+// involves a base reaching into its subclasses, it's deemed scary, so don't
+// do it. ObjCPropertyReleaser's design ensures that the property releaser
+// will only operate on instance variables in the immediate object in which
+// the property releaser is placed.
+
+class ObjCPropertyReleaser {
+ public:
+ // ObjCPropertyReleaser can only be owned by an Objective-C object, so its
+ // memory is always guaranteed to be 0-initialized. Not defining the default
+ // constructor can prevent an otherwise no-op -.cxx_construct method from
+ // showing up in Objective-C classes that contain a ObjCPropertyReleaser.
+
+ // Upon destruction (expected to occur from an Objective-C object's
+ // -.cxx_destruct method), release all properties.
+ ~ObjCPropertyReleaser() {
+ ReleaseProperties();
+ }
+
+ // Initialize this object so that it's armed to release the properties of
+ // object |object|, which must be of type |classy|. The class argument must
+ // be supplied separately and cannot be gleaned from the object's own type
+ // because an object will allays identify itself as the most-specific type
+ // that describes it, but the ObjCPropertyReleaser needs to know which class
+ // type in the class hierarchy it's responsible for releasing properties
+ // for. For the same reason, Init must be called with a |classy| argument
+ // initialized using a +class (class) method such as [MyClass class], and
+ // never a -class (instance) method such as [self class].
+ //
+ // -.cxx_construct can only call the default constructor, but
+ // ObjCPropertyReleaser needs to know about the Objective-C object that owns
+ // it, so this can't be handled in a constructor, it needs to be a distinct
+ // Init method.
+ void Init(id object, Class classy);
+
+ // Release all of the properties in object_ defined in class_ as either
+ // "retain" or "copy" and with an identifiable backing instance variable.
+ // Properties must be synthesized to have identifiable instance variables.
+ void ReleaseProperties();
+
+ private:
+ id object_;
+ Class class_;
+};
+
+} // namespace mac
+} // namespace base
+
+#endif // BASE_MAC_OBJC_PROPERTY_RELEASER_H_
diff --git a/base/mac/objc_property_releaser.mm b/base/mac/objc_property_releaser.mm
new file mode 100644
index 0000000..879dee9
--- /dev/null
+++ b/base/mac/objc_property_releaser.mm
@@ -0,0 +1,116 @@
+// Copyright (c) 2011 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.
+
+#import "base/mac/objc_property_releaser.h"
+
+#import <objc/runtime.h>
+#include <stdlib.h>
+
+#include "base/logging.h"
+
+namespace base {
+namespace mac {
+
+namespace {
+
+// Returns the name of the instance variable backing the property, if known,
+// if the property is marked "retain" or "copy". If the instance variable name
+// is not known (perhaps because it was not automatically associated with the
+// property by @synthesize) or if the property is not "retain" or "copy",
+// returns NULL.
+const char* ReleasableInstanceName(objc_property_t property) {
+ // TODO(mark): Starting in newer system releases, the Objective-C runtime
+ // provides a function to break the property attribute string into
+ // individual attributes (property_copyAttributeList), as well as a function
+ // to look up the value of a specific attribute
+ // (property_copyAttributeValue). When the SDK defining that interface is
+ // final, this function should be adapted to walk the attribute list as
+ // returned by property_copyAttributeList when that function is available in
+ // preference to scanning through the attribute list manually.
+
+ // The format of the string returned by property_getAttributes is documented
+ // at
+ // http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtPropertyIntrospection.html#//apple_ref/doc/uid/TP40008048-CH101-SW6
+ const char* property_attributes = property_getAttributes(property);
+
+ bool releasable = false;
+ while (*property_attributes) {
+ switch (*property_attributes) {
+ // It might seem intelligent to check the type ('T') attribute to verify
+ // that it identifies an NSObject-derived type (the attribute value
+ // begins with '@'.) This is a bad idea beacuse it fails to identify
+ // CFTypeRef-based properties declared as __attribute__((NSObject)),
+ // which just show up as pointers to their underlying CFType structs.
+ //
+ // Quoting
+ // http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/ObjectiveC/Chapters/ocProperties.html#//apple_ref/doc/uid/TP30001163-CH17-SW27
+ //
+ // > In Mac OS X v10.6 and later, you can use the __attribute__ keyword
+ // > to specify that a Core Foundation property should be treated like
+ // > an Objective-C object for memory management:
+ // > @property(retain) __attribute__((NSObject)) CFDictionaryRef
+ // > myDictionary;
+ case 'C': // copy
+ case '&': // retain
+ releasable = true;
+ break;
+ case 'V':
+ // 'V' is specified as the last attribute to occur, so if releasable
+ // wasn't set to true already, it won't be set to true now. Since 'V'
+ // is last, its value up to the end of the attribute string is the
+ // name of the instance variable backing the property.
+ if (!releasable || !*++property_attributes) {
+ return NULL;
+ }
+ return property_attributes;
+ }
+ char previous_char = *property_attributes;
+ while (*++property_attributes && previous_char != ',') {
+ previous_char = *property_attributes;
+ }
+ }
+ return NULL;
+}
+
+} // namespace
+
+void ObjCPropertyReleaser::Init(id object, Class classy) {
+ DCHECK(!object_);
+ DCHECK(!class_);
+ DCHECK([object isKindOfClass:classy]);
+
+ object_ = object;
+ class_ = classy;
+}
+
+void ObjCPropertyReleaser::ReleaseProperties() {
+ CHECK(object_);
+ CHECK(class_);
+
+ unsigned int property_count = 0;
+ objc_property_t* properties = class_copyPropertyList(class_, &property_count);
+
+ for (unsigned int property_index = 0;
+ property_index < property_count;
+ ++property_index) {
+ objc_property_t property = properties[property_index];
+ const char* instance_name = ReleasableInstanceName(property);
+ if (instance_name) {
+ id instance_value = nil;
+ object_getInstanceVariable(object_, instance_name,
+ (void**)&instance_value);
+ [instance_value release];
+ }
+ }
+
+ free(properties);
+
+ // Clear object_ and class_ in case this ObjCPropertyReleaser will live on.
+ // It's only expected to release the properties it supervises once per Init.
+ object_ = nil;
+ class_ = nil;
+}
+
+} // namespace mac
+} // namespace base
diff --git a/base/mac/objc_property_releaser_unittest.mm b/base/mac/objc_property_releaser_unittest.mm
new file mode 100644
index 0000000..e1f715d
--- /dev/null
+++ b/base/mac/objc_property_releaser_unittest.mm
@@ -0,0 +1,229 @@
+// Copyright (c) 2011 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.
+
+#import <Foundation/Foundation.h>
+
+#import "base/mac/objc_property_releaser.h"
+#import "base/mac/scoped_nsautorelease_pool.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+// "When I'm alone, I count myself."
+// --Count von Count, http://www.youtube.com/watch?v=FKzszqa9WA4
+
+// The number of CountVonCounts outstanding.
+static int ah_ah_ah;
+
+@interface CountVonCount : NSObject<NSCopying>
+
++ (CountVonCount*)countVonCount;
+
+@end // @interface CountVonCount
+
+@implementation CountVonCount
+
++ (CountVonCount*)countVonCount {
+ return [[[CountVonCount alloc] init] autorelease];
+}
+
+- (id)init {
+ ++ah_ah_ah;
+ return [super init];
+}
+
+- (void)dealloc {
+ --ah_ah_ah;
+ [super dealloc];
+}
+
+- (id)copyWithZone:(NSZone*)zone {
+ return [[CountVonCount allocWithZone:zone] init];
+}
+
+@end // @implementation CountVonCount
+
+@interface ObjCPropertyTestBase : NSObject {
+ @private
+ CountVonCount* cvcBaseRetain_;
+ CountVonCount* cvcBaseCopy_;
+ CountVonCount* cvcBaseAssign_;
+ CountVonCount* cvcBaseNotProperty_;
+ base::mac::ObjCPropertyReleaser propertyReleaser_ObjCPropertyTestBase_;
+
+ // Note that a variable will be synthesized for the cvcBaseSynthetic
+ // property.
+}
+
+@property(retain, nonatomic) CountVonCount* cvcBaseRetain;
+@property(copy, nonatomic) CountVonCount* cvcBaseCopy;
+@property(assign, nonatomic) CountVonCount* cvcBaseAssign;
+@property(retain, nonatomic) CountVonCount* cvcBaseSynthetic;
+
+- (void)setCvcBaseNotProperty:(CountVonCount*)cvc;
+
+@end // @interface ObjCPropertyTestBase
+
+@implementation ObjCPropertyTestBase
+
+@synthesize cvcBaseRetain = cvcBaseRetain_;
+@synthesize cvcBaseCopy = cvcBaseCopy_;
+@synthesize cvcBaseAssign = cvcBaseAssign_;
+@synthesize cvcBaseSynthetic;
+
+- (id)init {
+ if ((self = [super init])) {
+ propertyReleaser_ObjCPropertyTestBase_.Init(
+ self, [ObjCPropertyTestBase class]);
+ }
+ return self;
+}
+
+- (void)dealloc {
+ [cvcBaseNotProperty_ release];
+ [super dealloc];
+}
+
+- (void)setCvcBaseNotProperty:(CountVonCount*)cvc {
+ if (cvc != cvcBaseNotProperty_) {
+ [cvcBaseNotProperty_ release];
+ cvcBaseNotProperty_ = [cvc retain];
+ }
+}
+
+@end // @implementation ObjCPropertyTestBase
+
+@protocol ObjCPropertyTestProtocol
+
+@property(retain, nonatomic) CountVonCount* cvcProtoRetain;
+@property(copy, nonatomic) CountVonCount* cvcProtoCopy;
+@property(assign, nonatomic) CountVonCount* cvcProtoAssign;
+@property(retain, nonatomic) CountVonCount* cvcProtoSynthetic;
+
+@end // @protocol ObjCPropertyTestProtocol
+
+@interface ObjCPropertyTestDerived
+ : ObjCPropertyTestBase<ObjCPropertyTestProtocol> {
+ @private
+ CountVonCount* cvcDerivedRetain_;
+ CountVonCount* cvcDerivedCopy_;
+ CountVonCount* cvcDerivedAssign_;
+ CountVonCount* cvcDerivedNotProperty_;
+ CountVonCount* cvcProtoRetain_;
+ CountVonCount* cvcProtoCopy_;
+ CountVonCount* cvcProtoAssign_;
+ base::mac::ObjCPropertyReleaser propertyReleaser_ObjCPropertyTestDerived_;
+
+ // Note that variables will be synthesized for the cvcDerivedSynthetic
+ // and cvcProtoSynthetic properties.
+}
+
+@property(retain, nonatomic) CountVonCount* cvcDerivedRetain;
+@property(copy, nonatomic) CountVonCount* cvcDerivedCopy;
+@property(assign, nonatomic) CountVonCount* cvcDerivedAssign;
+@property(retain, nonatomic) CountVonCount* cvcDerivedSynthetic;
+
+- (void)setCvcDerivedNotProperty:(CountVonCount*)cvc;
+
+@end // @interface ObjCPropertyTestDerived
+
+@implementation ObjCPropertyTestDerived
+
+@synthesize cvcDerivedRetain = cvcDerivedRetain_;
+@synthesize cvcDerivedCopy = cvcDerivedCopy_;
+@synthesize cvcDerivedAssign = cvcDerivedAssign_;
+@synthesize cvcDerivedSynthetic;
+@synthesize cvcProtoRetain = cvcProtoRetain_;
+@synthesize cvcProtoCopy = cvcProtoCopy_;
+@synthesize cvcProtoAssign = cvcProtoAssign_;
+@synthesize cvcProtoSynthetic;
+
+- (id)init {
+ if ((self = [super init])) {
+ propertyReleaser_ObjCPropertyTestDerived_.Init(
+ self, [ObjCPropertyTestDerived class]);
+ }
+ return self;
+}
+
+- (void)dealloc {
+ [cvcDerivedNotProperty_ release];
+ [super dealloc];
+}
+
+- (void)setCvcDerivedNotProperty:(CountVonCount*)cvc {
+ if (cvc != cvcDerivedNotProperty_) {
+ [cvcDerivedNotProperty_ release];
+ cvcDerivedNotProperty_ = [cvc retain];
+ }
+}
+
+@end // @implementation ObjCPropertyTestDerived
+
+namespace {
+
+TEST(ObjCPropertyReleaserTest, SesameStreet) {
+ ObjCPropertyTestDerived* test_object = [[ObjCPropertyTestDerived alloc] init];
+
+ // Assure a clean slate.
+ EXPECT_EQ(0, ah_ah_ah);
+
+ CountVonCount* baseAssign = [[CountVonCount alloc] init];
+ CountVonCount* derivedAssign = [[CountVonCount alloc] init];
+ CountVonCount* protoAssign = [[CountVonCount alloc] init];
+
+ // Make sure that worked before things get more involved.
+ EXPECT_EQ(3, ah_ah_ah);
+
+ {
+ base::mac::ScopedNSAutoreleasePool pool;
+
+ test_object.cvcBaseRetain = [CountVonCount countVonCount];
+ test_object.cvcBaseCopy = [CountVonCount countVonCount];
+ test_object.cvcBaseAssign = baseAssign;
+ [test_object setCvcBaseNotProperty:[CountVonCount countVonCount]];
+ test_object.cvcBaseSynthetic = [CountVonCount countVonCount];
+
+ // That added 4 objects, plus 1 more that was copied.
+ EXPECT_EQ(8, ah_ah_ah);
+
+ test_object.cvcDerivedRetain = [CountVonCount countVonCount];
+ test_object.cvcDerivedCopy = [CountVonCount countVonCount];
+ test_object.cvcDerivedAssign = derivedAssign;
+ [test_object setCvcDerivedNotProperty:[CountVonCount countVonCount]];
+ test_object.cvcDerivedSynthetic = [CountVonCount countVonCount];
+
+ // That added 4 objects, plus 1 more that was copied.
+ EXPECT_EQ(13, ah_ah_ah);
+
+ test_object.cvcProtoRetain = [CountVonCount countVonCount];
+ test_object.cvcProtoCopy = [CountVonCount countVonCount];
+ test_object.cvcProtoAssign = protoAssign;
+ test_object.cvcProtoSynthetic = [CountVonCount countVonCount];
+
+ // That added 3 objects, plus 1 more that was copied.
+ EXPECT_EQ(17, ah_ah_ah);
+ }
+
+ // Now that the autorelease pool has been popped, there should be 14
+ // CountVonCounts. The ones that were copied to place into the test objects
+ // will now have been deallocated.
+ EXPECT_EQ(14, ah_ah_ah);
+
+ [test_object release];
+
+ // The property releaser should have released all of the CountVonCounts
+ // associated with properties marked "retain" or "copy". The -dealloc
+ // methods in each should have released the single non-property objects in
+ // each. Only the CountVonCounts assigned to the properties marked "assign"
+ // should remain.
+ EXPECT_EQ(3, ah_ah_ah);
+
+ [baseAssign release];
+ [derivedAssign release];
+ [protoAssign release];
+
+ // Zero! Zero counts! Ah, ah, ah.
+ EXPECT_EQ(0, ah_ah_ah);
+}
+
+} // namespace