diff options
author | mark@chromium.org <mark@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-04-20 22:30:35 +0000 |
---|---|---|
committer | mark@chromium.org <mark@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-04-20 22:30:35 +0000 |
commit | 10756c518ba0fccd61abd8393bcc278829dd5ae6 (patch) | |
tree | 60964b4b67564b9d53b3e5e2317028ece40a9afe /base | |
parent | bd680cbb4008f577a28ef58cb260f36217172e3a (diff) | |
download | chromium_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.gyp | 1 | ||||
-rw-r--r-- | base/base.gypi | 2 | ||||
-rw-r--r-- | base/mac/objc_property_releaser.h | 126 | ||||
-rw-r--r-- | base/mac/objc_property_releaser.mm | 116 | ||||
-rw-r--r-- | base/mac/objc_property_releaser_unittest.mm | 229 |
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 |