1// Copyright (c) 2011 The Chromium 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#import "base/mac/objc_property_releaser.h"
6
7#import <objc/runtime.h>
8#include <stdlib.h>
9
10#include <string>
11
12#include "base/logging.h"
13
14namespace base {
15namespace mac {
16
17namespace {
18
19// Returns the name of the instance variable backing the property, if known,
20// if the property is marked "retain" or "copy". If the instance variable name
21// is not known (perhaps because it was not automatically associated with the
22// property by @synthesize) or if the property is not "retain" or "copy",
23// returns an empty string.
24std::string ReleasableInstanceName(objc_property_t property) {
25  // TODO(mark): Starting in newer system releases, the Objective-C runtime
26  // provides a function to break the property attribute string into
27  // individual attributes (property_copyAttributeList), as well as a function
28  // to look up the value of a specific attribute
29  // (property_copyAttributeValue). When the SDK defining that interface is
30  // final, this function should be adapted to walk the attribute list as
31  // returned by property_copyAttributeList when that function is available in
32  // preference to scanning through the attribute list manually.
33
34  // The format of the string returned by property_getAttributes is documented
35  // at
36  // http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtPropertyIntrospection.html#//apple_ref/doc/uid/TP40008048-CH101-SW6
37  const char* property_attributes = property_getAttributes(property);
38
39  std::string instance_name;
40  bool releasable = false;
41  while (*property_attributes) {
42    char name = *property_attributes;
43
44    const char* value = ++property_attributes;
45    while (*property_attributes && *property_attributes != ',') {
46      ++property_attributes;
47    }
48
49    switch (name) {
50      // It might seem intelligent to check the type ('T') attribute to verify
51      // that it identifies an NSObject-derived type (the attribute value
52      // begins with '@'.) This is a bad idea beacuse it fails to identify
53      // CFTypeRef-based properties declared as __attribute__((NSObject)),
54      // which just show up as pointers to their underlying CFType structs.
55      //
56      // Quoting
57      // http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/ObjectiveC/Chapters/ocProperties.html#//apple_ref/doc/uid/TP30001163-CH17-SW27
58      //
59      // > In Mac OS X v10.6 and later, you can use the __attribute__ keyword
60      // > to specify that a Core Foundation property should be treated like
61      // > an Objective-C object for memory management:
62      // >   @property(retain) __attribute__((NSObject)) CFDictionaryRef
63      // >       myDictionary;
64      case 'C':  // copy
65      case '&':  // retain
66        releasable = true;
67        break;
68      case 'V':  // instance variable name
69        // 'V' is specified as the last attribute to occur in the
70        // documentation, but empirically, it's not always the last. In
71        // GC-supported or GC-required code, the 'P' (GC-eligible) attribute
72        // occurs after 'V'.
73        instance_name.assign(value, property_attributes - value);
74        break;
75    }
76
77    if (*property_attributes) {
78      ++property_attributes;
79    }
80  }
81
82  if (releasable) {
83    return instance_name;
84  }
85
86  return std::string();
87}
88
89}  // namespace
90
91void ObjCPropertyReleaser::Init(id object, Class classy) {
92  DCHECK(!object_);
93  DCHECK(!class_);
94  CHECK([object isKindOfClass:classy]);
95
96  object_ = object;
97  class_ = classy;
98}
99
100void ObjCPropertyReleaser::ReleaseProperties() {
101  DCHECK(object_);
102  DCHECK(class_);
103
104  unsigned int property_count = 0;
105  objc_property_t* properties = class_copyPropertyList(class_, &property_count);
106
107  for (unsigned int property_index = 0;
108       property_index < property_count;
109       ++property_index) {
110    objc_property_t property = properties[property_index];
111    std::string instance_name = ReleasableInstanceName(property);
112    if (!instance_name.empty()) {
113      id instance_value = nil;
114      Ivar instance_variable =
115          object_getInstanceVariable(object_, instance_name.c_str(),
116                                     (void**)&instance_value);
117      DCHECK(instance_variable);
118      [instance_value release];
119    }
120  }
121
122  free(properties);
123
124  // Clear object_ and class_ in case this ObjCPropertyReleaser will live on.
125  // It's only expected to release the properties it supervises once per Init.
126  object_ = nil;
127  class_ = nil;
128}
129
130}  // namespace mac
131}  // namespace base
132