summaryrefslogtreecommitdiffstats
path: root/base/mac/objc_property_releaser.mm
blob: f7ee88fbcc4dd0e4c8d7b7ce95d9030021042324 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
// 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 <string>

#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 an empty string.
std::string 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);

  std::string instance_name;
  bool releasable = false;
  while (*property_attributes) {
    char name = *property_attributes;

    const char* value = ++property_attributes;
    while (*property_attributes && *property_attributes != ',') {
      ++property_attributes;
    }

    switch (name) {
      // 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':  // instance variable name
        // 'V' is specified as the last attribute to occur in the
        // documentation, but empirically, it's not always the last. In
        // GC-supported or GC-required code, the 'P' (GC-eligible) attribute
        // occurs after 'V'.
        instance_name.assign(value, property_attributes - value);
        break;
    }

    if (*property_attributes) {
      ++property_attributes;
    }
  }

  if (releasable) {
    return instance_name;
  }

  return std::string();
}

}  // namespace

void ObjCPropertyReleaser::Init(id object, Class classy) {
  DCHECK(!object_);
  DCHECK(!class_);
  CHECK([object isKindOfClass:classy]);

  object_ = object;
  class_ = classy;
}

void ObjCPropertyReleaser::ReleaseProperties() {
  DCHECK(object_);
  DCHECK(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];
    std::string instance_name = ReleasableInstanceName(property);
    if (!instance_name.empty()) {
      id instance_value = nil;
      Ivar instance_variable =
          object_getInstanceVariable(object_, instance_name.c_str(),
                                     (void**)&instance_value);
      DCHECK(instance_variable);
      [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