motion-memorymanagement 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,16 @@
1
+ //
2
+ // MAWeakDictionary.h
3
+ // ZeroingWeakRef
4
+ //
5
+ // Created by Mike Ash on 7/13/10.
6
+ //
7
+
8
+ #import <Foundation/Foundation.h>
9
+
10
+
11
+ @interface MAWeakDictionary : NSMutableDictionary
12
+ {
13
+ NSMutableDictionary *_dict;
14
+ }
15
+
16
+ @end
@@ -0,0 +1,66 @@
1
+ //
2
+ // MAWeakDictionary.m
3
+ // ZeroingWeakRef
4
+ //
5
+ // Created by Mike Ash on 7/13/10.
6
+ //
7
+
8
+ #import "MAWeakDictionary.h"
9
+
10
+ #import "MAZeroingWeakRef.h"
11
+
12
+
13
+ @implementation MAWeakDictionary
14
+
15
+ - (id)init
16
+ {
17
+ if((self = [super init]))
18
+ {
19
+ _dict = [[NSMutableDictionary alloc] init];
20
+ }
21
+ return self;
22
+ }
23
+
24
+ - (void)dealloc
25
+ {
26
+ [_dict release];
27
+ [super dealloc];
28
+ }
29
+
30
+ - (NSUInteger)count
31
+ {
32
+ return [_dict count];
33
+ }
34
+
35
+ - (id)objectForKey: (id)aKey
36
+ {
37
+ MAZeroingWeakRef *ref = [_dict objectForKey: aKey];
38
+ id obj = [ref target];
39
+
40
+ // clean out keys whose objects have gone away
41
+ if(ref && !obj)
42
+ [_dict removeObjectForKey: aKey];
43
+
44
+ return obj;
45
+ }
46
+
47
+ - (NSEnumerator *)keyEnumerator
48
+ {
49
+ // enumerate over a copy because -objectForKey: mutates
50
+ // which could cause an exception in code that should
51
+ // appear to be correct
52
+ return [[_dict allKeys] objectEnumerator];
53
+ }
54
+
55
+ - (void)removeObjectForKey: (id)aKey
56
+ {
57
+ [_dict removeObjectForKey: aKey];
58
+ }
59
+
60
+ - (void)setObject: (id)anObject forKey: (id)aKey
61
+ {
62
+ [_dict setObject: [MAZeroingWeakRef refWithTarget: anObject]
63
+ forKey: aKey];
64
+ }
65
+
66
+ @end
@@ -0,0 +1,31 @@
1
+ //
2
+ // MAZeroingWeakProxy.h
3
+ // ZeroingWeakRef
4
+ //
5
+ // Created by Michael Ash on 7/17/10.
6
+ // Copyright 2010 Michael Ash. All rights reserved.
7
+ //
8
+
9
+ #import <Foundation/Foundation.h>
10
+
11
+
12
+ @class MAZeroingWeakRef;
13
+
14
+ @interface MAZeroingWeakProxy : NSProxy
15
+ {
16
+ MAZeroingWeakRef *_weakRef;
17
+ Class _targetClass;
18
+ }
19
+
20
+ + (id)proxyWithTarget: (id)target;
21
+
22
+ - (id)initWithTarget: (id)target;
23
+
24
+ - (id)zeroingProxyTarget;
25
+
26
+ #if NS_BLOCKS_AVAILABLE
27
+ // same caveats/restrictions as MAZeroingWeakRef cleanup block
28
+ - (void)setCleanupBlock: (void (^)(id target))block;
29
+ #endif
30
+
31
+ @end
@@ -0,0 +1,99 @@
1
+ //
2
+ // MAZeroingWeakProxy.m
3
+ // ZeroingWeakRef
4
+ //
5
+ // Created by Michael Ash on 7/17/10.
6
+ // Copyright 2010 Michael Ash. All rights reserved.
7
+ //
8
+
9
+ #import "MAZeroingWeakProxy.h"
10
+
11
+ #import "MAZeroingWeakRef.h"
12
+
13
+ @implementation MAZeroingWeakProxy
14
+
15
+ + (id)proxyWithTarget: (id)target
16
+ {
17
+ return [[[self alloc] initWithTarget: target] autorelease];
18
+ }
19
+
20
+ - (id)initWithTarget: (id)target
21
+ {
22
+ // stash the class of the target so we can get method signatures after it goes away
23
+ _targetClass = [target class];
24
+ _weakRef = [[MAZeroingWeakRef alloc] initWithTarget: target];
25
+ return self;
26
+ }
27
+
28
+ - (void)dealloc
29
+ {
30
+ [_weakRef release];
31
+ [super dealloc];
32
+ }
33
+
34
+ - (id)zeroingProxyTarget
35
+ {
36
+ return [_weakRef target];
37
+ }
38
+
39
+ #if NS_BLOCKS_AVAILABLE
40
+ - (void)setCleanupBlock: (void (^)(id target))block
41
+ {
42
+ [_weakRef setCleanupBlock: block];
43
+ }
44
+ #endif
45
+
46
+ - (id)forwardingTargetForSelector: (SEL)sel
47
+ {
48
+ return [_weakRef target];
49
+ }
50
+
51
+ - (NSMethodSignature *)methodSignatureForSelector: (SEL)sel
52
+ {
53
+ return [_targetClass instanceMethodSignatureForSelector: sel];
54
+ }
55
+
56
+ - (void)forwardInvocation: (NSInvocation *)inv
57
+ {
58
+ NSMethodSignature *sig = [inv methodSignature];
59
+ NSUInteger returnLength = [sig methodReturnLength];
60
+
61
+ if(returnLength)
62
+ {
63
+ char buf[returnLength];
64
+ bzero(buf, sizeof(buf));
65
+ [inv setReturnValue: buf];
66
+ }
67
+ }
68
+
69
+ - (BOOL)respondsToSelector: (SEL)sel
70
+ {
71
+ id target = [_weakRef target];
72
+ if(target)
73
+ return [target respondsToSelector: sel];
74
+ else
75
+ return [_targetClass instancesRespondToSelector: sel];
76
+ }
77
+
78
+ - (BOOL)conformsToProtocol: (Protocol *)protocol
79
+ {
80
+ id target = [_weakRef target];
81
+ if(target)
82
+ return [target conformsToProtocol: protocol];
83
+ else
84
+ return [_targetClass conformsToProtocol: protocol];
85
+ }
86
+
87
+ // NSProxy implements these for some incomprehensibly stupid reason
88
+
89
+ - (NSUInteger)hash
90
+ {
91
+ return [[_weakRef target] hash];
92
+ }
93
+
94
+ - (BOOL)isEqual: (id)obj
95
+ {
96
+ return [[_weakRef target] isEqual: obj];
97
+ }
98
+
99
+ @end
@@ -0,0 +1,73 @@
1
+ //
2
+ // MAZeroingWeakRef.h
3
+ // ZeroingWeakRef
4
+ //
5
+ // Created by Michael Ash on 7/5/10.
6
+ //
7
+
8
+ #import <Foundation/Foundation.h>
9
+
10
+
11
+ @interface MAZeroingWeakRef : NSObject
12
+ {
13
+ id _target;
14
+ BOOL _nativeZWR;
15
+ #if NS_BLOCKS_AVAILABLE
16
+ void (^_cleanupBlock)(id target);
17
+ #endif
18
+ }
19
+
20
+ + (BOOL)canRefCoreFoundationObjects;
21
+
22
+ + (id)refWithTarget: (id)target;
23
+
24
+ - (id)initWithTarget: (id)target;
25
+
26
+ #if NS_BLOCKS_AVAILABLE
27
+ // ON 10.7:
28
+ // cleanup block runs while the target's memory is still
29
+ // allocated but after all dealloc methods have run
30
+ // (it runs at associated object cleanup time)
31
+ // you can use the target's pointer value but don't
32
+ // manipulate its contents!
33
+
34
+ // ON 10.6 AND BELOW:
35
+ // cleanup block runs while the global ZWR lock is held
36
+ // so make it short and sweet!
37
+ // use GCD or something to schedule execution later
38
+ // if you need to do something that may take a while
39
+ //
40
+ // it is unsafe to call -target on the weak ref from
41
+ // inside the cleanup block, which is why the target
42
+ // is passed in as a parameter
43
+ // note that you must not resurrect the target at this point!
44
+ - (void)setCleanupBlock: (void (^)(id target))block;
45
+ #endif
46
+
47
+ - (id)target;
48
+
49
+ @end
50
+
51
+ #ifndef __has_feature
52
+ #define __has_feature(feature) 0
53
+ #endif
54
+
55
+ #define MAWeakVar(var) __weak_ ## var
56
+
57
+ #if __has_feature(objc_arc_weak)
58
+
59
+ #define MAWeakDeclare(var) __weak __typeof__((var)) MAWeakVar(var) = var
60
+ #define MAWeakImport(var) __typeof__((MAWeakVar(var))) var = MAWeakVar(var)
61
+ #define MAWeakImportReturn(var) MAWeakImport(var); do { if(var == nil) return; } while(NO)
62
+
63
+ #else
64
+
65
+ #define MAWeakDeclare(var) __typeof__((var)) MAWeakVar(var) = (id)[MAZeroingWeakRef refWithTarget:var]
66
+ #define MAWeakImport(var) __typeof__((MAWeakVar(var))) var = [(MAZeroingWeakRef *)MAWeakVar(var) target]
67
+ #define MAWeakImportReturn(var) MAWeakImport(var); do { if(var == nil) return; } while(NO)
68
+
69
+ #endif
70
+
71
+ #define MAWeakSelfDeclare() MAWeakDeclare(self)
72
+ #define MAWeakSelfImport() MAWeakImport(self)
73
+ #define MAWeakSelfImportReturn() MAWeakImportReturn(self)
@@ -0,0 +1,852 @@
1
+ //
2
+ // MAZeroingWeakRef.m
3
+ // ZeroingWeakRef
4
+ //
5
+ // Created by Michael Ash on 7/5/10.
6
+ //
7
+
8
+ #import "MAZeroingWeakRef.h"
9
+
10
+ #import "MAZeroingWeakRefNativeZWRNotAllowedTable.h"
11
+
12
+ #if __APPLE__
13
+ #import <CommonCrypto/CommonDigest.h>
14
+
15
+ #import <dlfcn.h>
16
+ #import <libkern/OSAtomic.h>
17
+ #import <objc/runtime.h>
18
+ #import <mach/mach.h>
19
+ #import <mach/port.h>
20
+ #import <pthread.h>
21
+ #else
22
+ #import <pthread.h>
23
+ #endif
24
+
25
+
26
+ /*
27
+ The COREFOUNDATION_HACK_LEVEL macro allows you to control how much horrible CF
28
+ hackery is enabled. The following levels are defined:
29
+
30
+ 3 - Completely insane hackery allows weak references to CF objects, deallocates
31
+ them asynchronously in another thread to eliminate resurrection-related race
32
+ condition and crash.
33
+
34
+ 2 - Full hackery allows weak references to CF objects by doing horrible
35
+ things with the private CF class table. Extremely small risk of resurrection-
36
+ related race condition leading to a crash.
37
+
38
+ 1 - Mild hackery allows foolproof identification of CF objects and will assert
39
+ if trying to make a ZWR to one.
40
+
41
+ 0 - No hackery, checks for an "NSCF" prefix in the class name to identify CF
42
+ objects and will assert if trying to make a ZWR to one
43
+ */
44
+ #ifndef COREFOUNDATION_HACK_LEVEL
45
+ #define COREFOUNDATION_HACK_LEVEL 0
46
+ #endif
47
+
48
+ /*
49
+ The KVO_HACK_LEVEL macro allows similar control over the amount of KVO hackery.
50
+
51
+ 1 - Use the private _isKVOA method to check for a KVO dynamic subclass.
52
+
53
+ 0 - No hackery, uses the KVO overridden -class to check.
54
+ */
55
+ #ifndef KVO_HACK_LEVEL
56
+ #define KVO_HACK_LEVEL 0
57
+ #endif
58
+
59
+ /*
60
+ The USE_BLOCKS_BASED_LOCKING macro allows control on the code structure used
61
+ during lock checking. You want to disable blocks if you want your app to work
62
+ on iOS 3.x devices. iOS 4.x and above can use blocks.
63
+
64
+ 1 - Use blocks for lock checks.
65
+
66
+ 0 - Don't use blocks for lock checks.
67
+ */
68
+ #ifndef USE_BLOCKS_BASED_LOCKING
69
+ #define USE_BLOCKS_BASED_LOCKING 1
70
+ #endif
71
+
72
+ #if KVO_HACK_LEVEL >= 1
73
+ @interface NSObject (KVOPrivateMethod)
74
+
75
+ - (BOOL)_isKVOA;
76
+
77
+ @end
78
+ #endif
79
+
80
+
81
+ @interface NSObject (MAZeroingWeakRefSwizzled)
82
+ - (void)MAZeroingWeakRef_KVO_original_release;
83
+ - (void)MAZeroingWeakRef_KVO_original_dealloc;
84
+ - (void)MAZeroingWeakRef_KVO_original_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath context:(void *)context;
85
+ - (void)MAZeroingWeakRef_KVO_original_removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;
86
+ - (void)MAZeroingWeakRef_KVO_original_removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath context:(void *)context;
87
+ @end
88
+
89
+
90
+ static void EnsureCustomSubclass(id obj);
91
+
92
+ @interface MAZeroingWeakRef ()
93
+
94
+ - (void)_zeroTarget;
95
+ - (void)_executeCleanupBlockWithTarget: (id)target;
96
+
97
+ @end
98
+
99
+
100
+ static id (*objc_loadWeak_fptr)(id *location);
101
+ static id (*objc_storeWeak_fptr)(id *location, id obj);
102
+
103
+ @interface _MAZeroingWeakRefCleanupHelper : NSObject
104
+ {
105
+ MAZeroingWeakRef *_ref;
106
+ id _target;
107
+ }
108
+
109
+ - (id)initWithRef: (MAZeroingWeakRef *)ref target: (id)target;
110
+
111
+ @end
112
+
113
+ @implementation _MAZeroingWeakRefCleanupHelper
114
+
115
+ - (id)initWithRef: (MAZeroingWeakRef *)ref target: (id)target
116
+ {
117
+ if((self = [self init]))
118
+ {
119
+ objc_storeWeak_fptr(&_ref, ref);
120
+ _target = target;
121
+ }
122
+ return self;
123
+ }
124
+
125
+ - (void)dealloc
126
+ {
127
+ MAZeroingWeakRef *ref = objc_loadWeak_fptr(&_ref);
128
+ [ref _executeCleanupBlockWithTarget: _target];
129
+ objc_storeWeak_fptr(&_ref, nil);
130
+
131
+ [super dealloc];
132
+ }
133
+
134
+ @end
135
+
136
+
137
+ @implementation MAZeroingWeakRef
138
+
139
+ #if COREFOUNDATION_HACK_LEVEL >= 2
140
+
141
+ typedef struct __CFRuntimeClass { // Version 0 struct
142
+ CFIndex version;
143
+ const char *className;
144
+ void (*init)(CFTypeRef cf);
145
+ CFTypeRef (*copy)(CFAllocatorRef allocator, CFTypeRef cf);
146
+ void (*finalize)(CFTypeRef cf);
147
+ Boolean (*equal)(CFTypeRef cf1, CFTypeRef cf2);
148
+ CFHashCode (*hash)(CFTypeRef cf);
149
+ CFStringRef (*copyFormattingDesc)(CFTypeRef cf, CFDictionaryRef formatOptions); // str with retain
150
+ CFStringRef (*copyDebugDesc)(CFTypeRef cf); // str with retain
151
+ void (*reclaim)(CFTypeRef cf);
152
+ } CFRuntimeClass;
153
+
154
+ extern CFRuntimeClass * _CFRuntimeGetClassWithTypeID(CFTypeID typeID);
155
+
156
+ typedef void (*CFFinalizeFptr)(CFTypeRef);
157
+ static CFFinalizeFptr *gCFOriginalFinalizes;
158
+ static size_t gCFOriginalFinalizesSize;
159
+
160
+ #endif
161
+
162
+ #if COREFOUNDATION_HACK_LEVEL >= 1
163
+
164
+ extern Class *__CFRuntimeObjCClassTable;
165
+
166
+ #endif
167
+
168
+ static pthread_mutex_t gMutex;
169
+
170
+ #if __APPLE__
171
+ static CFMutableDictionaryRef gObjectWeakRefsMap; // maps (non-retained) objects to CFMutableSetRefs containing weak refs
172
+ #else
173
+ static NSMapTable *gObjectWeakRefsMap;
174
+ #endif
175
+
176
+ static NSMutableSet *gCustomSubclasses;
177
+ static NSMutableDictionary *gCustomSubclassMap; // maps regular classes to their custom subclasses
178
+
179
+ #if COREFOUNDATION_HACK_LEVEL >= 3
180
+ static CFMutableSetRef gCFWeakTargets;
181
+ static NSOperationQueue *gCFDelayedDestructionQueue;
182
+ #endif
183
+
184
+ + (void)initialize
185
+ {
186
+ if(self == [MAZeroingWeakRef class])
187
+ {
188
+ pthread_mutexattr_t mutexattr;
189
+ pthread_mutexattr_init(&mutexattr);
190
+ pthread_mutexattr_settype(&mutexattr, PTHREAD_MUTEX_RECURSIVE);
191
+ pthread_mutex_init(&gMutex, &mutexattr);
192
+ pthread_mutexattr_destroy(&mutexattr);
193
+
194
+ #if __APPLE__
195
+ gObjectWeakRefsMap = CFDictionaryCreateMutable(NULL, 0, NULL, &kCFTypeDictionaryValueCallBacks);
196
+ #else
197
+ gObjectWeakRefsMap = [[NSMapTable mapTableWithWeakToStrongObjects] retain];
198
+ #endif
199
+ gCustomSubclasses = [[NSMutableSet alloc] init];
200
+ gCustomSubclassMap = [[NSMutableDictionary alloc] init];
201
+
202
+ // see if the 10.7 ZWR runtime functions are available
203
+ // nothing special about objc_allocateClassPair, it just
204
+ // seems like a reasonable and safe choice for finding
205
+ // the runtime functions
206
+
207
+ // Farcaller: ZWR is disabled, as it doesn't work with RubyMotion reliably
208
+ objc_loadWeak_fptr = NULL;
209
+ objc_storeWeak_fptr = NULL;
210
+
211
+ #if COREFOUNDATION_HACK_LEVEL >= 3
212
+ gCFWeakTargets = CFSetCreateMutable(NULL, 0, NULL);
213
+ gCFDelayedDestructionQueue = [[NSOperationQueue alloc] init];
214
+ #endif
215
+ }
216
+ }
217
+
218
+ #if USE_BLOCKS_BASED_LOCKING
219
+ #define BLOCK_QUALIFIER __block
220
+ static void WhileLocked(void (^block)(void))
221
+ {
222
+ pthread_mutex_lock(&gMutex);
223
+ block();
224
+ pthread_mutex_unlock(&gMutex);
225
+ }
226
+ #define WhileLocked(block) WhileLocked(^block)
227
+ #else
228
+ #define BLOCK_QUALIFIER
229
+ #define WhileLocked(block) do { \
230
+ pthread_mutex_lock(&gMutex); \
231
+ block \
232
+ pthread_mutex_unlock(&gMutex); \
233
+ } while(0)
234
+ #endif
235
+
236
+ static void AddWeakRefToObject(id obj, MAZeroingWeakRef *ref)
237
+ {
238
+ #if __APPLE__
239
+ CFMutableSetRef set = (void *)CFDictionaryGetValue(gObjectWeakRefsMap, obj);
240
+ if(!set)
241
+ {
242
+ set = CFSetCreateMutable(NULL, 0, NULL);
243
+ CFDictionarySetValue(gObjectWeakRefsMap, obj, set);
244
+ CFRelease(set);
245
+ }
246
+ CFSetAddValue(set, ref);
247
+ #else
248
+ NSHashTable *set = [gObjectWeakRefsMap objectForKey:obj];
249
+ if (!set)
250
+ {
251
+ set = [NSHashTable hashTableWithWeakObjects];
252
+ [gObjectWeakRefsMap setObject:set forKey:obj];
253
+ }
254
+ [set addObject:ref];
255
+ #endif
256
+ }
257
+
258
+ static void RemoveWeakRefFromObject(id obj, MAZeroingWeakRef *ref)
259
+ {
260
+ #if __APPLE__
261
+ CFMutableSetRef set = (void *)CFDictionaryGetValue(gObjectWeakRefsMap, obj);
262
+ CFSetRemoveValue(set, ref);
263
+ #else
264
+ NSHashTable *set = [gObjectWeakRefsMap objectForKey:obj];
265
+ [set removeObject:ref];
266
+ #endif
267
+ }
268
+
269
+ static void ClearWeakRefsForObject(id obj)
270
+ {
271
+ #if __APPLE__
272
+ CFMutableSetRef set = (void *)CFDictionaryGetValue(gObjectWeakRefsMap, obj);
273
+ if(set)
274
+ {
275
+ NSSet *setCopy = [[NSSet alloc] initWithSet: (NSSet *)set];
276
+ [setCopy makeObjectsPerformSelector: @selector(_zeroTarget)];
277
+ [setCopy makeObjectsPerformSelector: @selector(_executeCleanupBlockWithTarget:) withObject: obj];
278
+ [setCopy release];
279
+ CFDictionaryRemoveValue(gObjectWeakRefsMap, obj);
280
+ }
281
+ #else
282
+ NSHashTable *set = [gObjectWeakRefsMap objectForKey:obj];
283
+ if (set)
284
+ {
285
+ NSArray *setContents = [set allObjects];
286
+ [setContents makeObjectsPerformSelector:@selector(_zeroTarget)];
287
+ [setContents makeObjectsPerformSelector:@selector(_executeCleanupBlockWithTarget:) withObject:obj];
288
+ [gObjectWeakRefsMap removeObjectForKey:obj];
289
+ }
290
+ #endif
291
+ }
292
+
293
+ static Class GetCustomSubclass(id obj)
294
+ {
295
+ Class class = object_getClass(obj);
296
+ while(class && ![gCustomSubclasses containsObject: class])
297
+ class = class_getSuperclass(class);
298
+ return class;
299
+ }
300
+
301
+ static Class GetRealSuperclass(id obj)
302
+ {
303
+ Class class = GetCustomSubclass(obj);
304
+ NSCAssert1(class, @"Coudn't find ZeroingWeakRef subclass in hierarchy starting from %@, should never happen", object_getClass(obj));
305
+ return class_getSuperclass(class);
306
+ }
307
+
308
+ static void CustomSubclassRelease(id self, SEL _cmd)
309
+ {
310
+ Class superclass = GetRealSuperclass(self);
311
+ IMP superRelease = class_getMethodImplementation(superclass, @selector(release));
312
+ WhileLocked({
313
+ ((void (*)(id, SEL))superRelease)(self, _cmd);
314
+ });
315
+ }
316
+
317
+ static void CustomSubclassDealloc(id self, SEL _cmd)
318
+ {
319
+ ClearWeakRefsForObject(self);
320
+ Class superclass = GetRealSuperclass(self);
321
+ IMP superDealloc = class_getMethodImplementation(superclass, @selector(dealloc));
322
+ ((void (*)(id, SEL))superDealloc)(self, _cmd);
323
+ }
324
+
325
+ static Class CustomSubclassClassForCoder(id self, SEL _cmd)
326
+ {
327
+ Class class = GetCustomSubclass(self);
328
+ Class superclass = class_getSuperclass(class);
329
+ IMP superClassForCoder = class_getMethodImplementation(superclass, @selector(classForCoder));
330
+ Class classForCoder = ((id (*)(id, SEL))superClassForCoder)(self, _cmd);
331
+ if(classForCoder == class)
332
+ classForCoder = superclass;
333
+ return classForCoder;
334
+ }
335
+
336
+ static void KVOSubclassRelease(id self, SEL _cmd)
337
+ {
338
+ IMP originalRelease = class_getMethodImplementation(object_getClass(self), @selector(MAZeroingWeakRef_KVO_original_release));
339
+ WhileLocked({
340
+ ((void (*)(id, SEL))originalRelease)(self, _cmd);
341
+ });
342
+ }
343
+
344
+ static void KVOSubclassDealloc(id self, SEL _cmd)
345
+ {
346
+ ClearWeakRefsForObject(self);
347
+ IMP originalDealloc = class_getMethodImplementation(object_getClass(self), @selector(MAZeroingWeakRef_KVO_original_dealloc));
348
+ ((void (*)(id, SEL))originalDealloc)(self, _cmd);
349
+ }
350
+
351
+ static void KVOSubclassRemoveObserverForKeyPath(id self, SEL _cmd, id observer, NSString *keyPath)
352
+ {
353
+ WhileLocked({
354
+ IMP originalIMP = class_getMethodImplementation(object_getClass(self), @selector(MAZeroingWeakRef_KVO_original_removeObserver:forKeyPath:));
355
+ ((void (*)(id, SEL, id, NSString *))originalIMP)(self, _cmd, observer, keyPath);
356
+
357
+ EnsureCustomSubclass(self);
358
+ });
359
+ }
360
+
361
+ static void KVOSubclassRemoveObserverForKeyPathContext(id self, SEL _cmd, id observer, NSString *keyPath, void *context)
362
+ {
363
+ WhileLocked({
364
+ IMP originalIMP = class_getMethodImplementation(object_getClass(self), @selector(MAZeroingWeakRef_KVO_original_removeObserver:forKeyPath:context:));
365
+ ((void (*)(id, SEL, id, NSString *, void *))originalIMP)(self, _cmd, observer, keyPath, context);
366
+
367
+ EnsureCustomSubclass(self);
368
+ });
369
+ }
370
+
371
+ #if COREFOUNDATION_HACK_LEVEL >= 3
372
+
373
+ static void CallCFReleaseLater(CFTypeRef cf)
374
+ {
375
+ mach_port_t thread = mach_thread_self(); // must "release" this
376
+
377
+ NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
378
+ SEL sel = @selector(releaseLater:fromThread:);
379
+ NSInvocation *inv = [NSInvocation invocationWithMethodSignature: [MAZeroingWeakRef methodSignatureForSelector: sel]];
380
+ [inv setTarget: [MAZeroingWeakRef class]];
381
+ [inv setSelector: sel];
382
+ [inv setArgument: &cf atIndex: 2];
383
+ [inv setArgument: &thread atIndex: 3];
384
+
385
+ NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithInvocation: inv];
386
+ [gCFDelayedDestructionQueue addOperation: op];
387
+ [op release];
388
+ [pool release];
389
+ }
390
+
391
+ static const void *kPCThreadExited = &kPCThreadExited;
392
+ static const void *kPCError = NULL;
393
+
394
+ static const void *GetPC(mach_port_t thread)
395
+ {
396
+ #if defined(__x86_64__)
397
+ x86_thread_state64_t state;
398
+ unsigned int count = x86_THREAD_STATE64_COUNT;
399
+ thread_state_flavor_t flavor = x86_THREAD_STATE64;
400
+ #define PC_REGISTER __rip
401
+ #elif defined(__i386__)
402
+ i386_thread_state_t state;
403
+ unsigned int count = i386_THREAD_STATE_COUNT;
404
+ thread_state_flavor_t flavor = i386_THREAD_STATE;
405
+ #define PC_REGISTER __eip
406
+ #elif defined(__arm__)
407
+ arm_thread_state_t state;
408
+ unsigned int count = ARM_THREAD_STATE_COUNT;
409
+ thread_state_flavor_t flavor = ARM_THREAD_STATE;
410
+ #define PC_REGISTER __pc
411
+ #elif defined(__ppc__)
412
+ ppc_thread_state_t state;
413
+ unsigned int count = PPC_THREAD_STATE_COUNT;
414
+ thread_state_flavor_t flavor = PPC_THREAD_STATE;
415
+ #define PC_REGISTER __srr0
416
+ #elif defined(__ppc64__)
417
+ ppc_thread_state64_t state;
418
+ unsigned int count = PPC_THREAD_STATE64_COUNT;
419
+ thread_state_flavor_t flavor = PPC_THREAD_STATE64;
420
+ #define PC_REGISTER __srr0
421
+ #else
422
+ #error don't know how to get PC for the current architecture!
423
+ #endif
424
+
425
+ kern_return_t ret = thread_get_state(thread, flavor, (thread_state_t)&state, &count);
426
+ if(ret == KERN_SUCCESS)
427
+ return (void *)state.PC_REGISTER;
428
+ else if(ret == KERN_INVALID_ARGUMENT)
429
+ return kPCThreadExited;
430
+ else
431
+ return kPCError;
432
+ }
433
+
434
+ static void CustomCFFinalize(CFTypeRef cf)
435
+ {
436
+ WhileLocked({
437
+ if(CFSetContainsValue(gCFWeakTargets, cf))
438
+ {
439
+ if(CFGetRetainCount(cf) == 1)
440
+ {
441
+ ClearWeakRefsForObject((id)cf);
442
+ CFSetRemoveValue(gCFWeakTargets, cf);
443
+ CFRetain(cf);
444
+ CallCFReleaseLater(cf);
445
+ }
446
+ }
447
+ else
448
+ {
449
+ void (*fptr)(CFTypeRef) = gCFOriginalFinalizes[CFGetTypeID(cf)];
450
+ if(fptr)
451
+ fptr(cf);
452
+ }
453
+ });
454
+ }
455
+
456
+ #elif COREFOUNDATION_HACK_LEVEL >= 2
457
+
458
+ static void CustomCFFinalize(CFTypeRef cf)
459
+ {
460
+ WhileLocked({
461
+ if(CFGetRetainCount(cf) == 1)
462
+ {
463
+ ClearWeakRefsForObject((id)cf);
464
+ void (*fptr)(CFTypeRef) = gCFOriginalFinalizes[CFGetTypeID(cf)];
465
+ if(fptr)
466
+ fptr(cf);
467
+ }
468
+ });
469
+ }
470
+ #endif
471
+
472
+ static BOOL IsTollFreeBridged(Class class, id obj)
473
+ {
474
+ #if COREFOUNDATION_HACK_LEVEL >= 1
475
+ CFTypeID typeID = CFGetTypeID(obj);
476
+ Class tfbClass = __CFRuntimeObjCClassTable[typeID];
477
+ return class == tfbClass;
478
+ #else
479
+ NSString *className = NSStringFromClass(class);
480
+ return [className hasPrefix:@"NSCF"] || [className hasPrefix:@"__NSCF"];
481
+ #endif
482
+ }
483
+
484
+ static BOOL IsConstantObject(id obj)
485
+ {
486
+ unsigned int retainCount = [obj retainCount];
487
+ return retainCount == UINT_MAX || retainCount == INT_MAX;
488
+ }
489
+
490
+ #if COREFOUNDATION_HACK_LEVEL >= 3
491
+ void _CFRelease(CFTypeRef cf);
492
+
493
+ + (void)releaseLater: (CFTypeRef)cf fromThread: (mach_port_t)thread
494
+ {
495
+ BOOL retry = YES;
496
+
497
+ while(retry)
498
+ {
499
+ BLOCK_QUALIFIER const void *pc;
500
+ // ensure that the PC is outside our inner code when fetching it,
501
+ // so we don't have to check for all the nested calls
502
+ WhileLocked({
503
+ pc = GetPC(thread);
504
+ });
505
+
506
+ if(pc != kPCError)
507
+ {
508
+ if(pc == kPCThreadExited || pc < (void *)CustomCFFinalize || pc > (void *)IsTollFreeBridged)
509
+ {
510
+ Dl_info info;
511
+ int success = dladdr(pc, &info);
512
+ if(success)
513
+ {
514
+ if(info.dli_saddr != _CFRelease)
515
+ {
516
+ retry = NO; // success!
517
+ CFRelease(cf);
518
+ mach_port_mod_refs(mach_task_self(), thread, MACH_PORT_RIGHT_SEND, -1 ); // "release"
519
+ }
520
+ }
521
+ }
522
+ }
523
+ }
524
+ }
525
+ #endif
526
+
527
+ static BOOL IsKVOSubclass(id obj)
528
+ {
529
+ #if KVO_HACK_LEVEL >= 1
530
+ return [obj respondsToSelector: @selector(_isKVOA)] && [obj _isKVOA];
531
+ #else
532
+ return [obj class] == class_getSuperclass(object_getClass(obj));
533
+ #endif
534
+ }
535
+
536
+ // The native ZWR capability table is conceptually a set of SHA1 hashes.
537
+ // Hashes are used instead of class names because the table is large and
538
+ // contains a lot of private classes. Embedding private class names in
539
+ // the binary is likely to cause problems with app review. Manually
540
+ // removing all private classes from the table is a lot of work. Using
541
+ // hashes allows for reasonably quick checks and no private API names.
542
+ // It's implemented as a tree of tables, where each individual table
543
+ // maps to a single byte. The top level of the tree is a 256-entry table.
544
+ // Table entries are a NULL pointer for leading bytes which aren't present
545
+ // at all. Other table entries can either contain a pointer to another
546
+ // table (in which case the process continues recursively), or they can
547
+ // contain a pointer to a single hash. In this second case, this indicates
548
+ // that this hash is the only one present in the table with that prefix
549
+ // and so a simple comparison can be used to check for membership at
550
+ // that point.
551
+ #if __APPLE__
552
+ static BOOL HashPresentInTable(unsigned char *hash, int length, struct _NativeZWRTableEntry *table)
553
+ {
554
+ while(length)
555
+ {
556
+ struct _NativeZWRTableEntry entry = table[hash[0]];
557
+ if(entry.ptr == NULL)
558
+ {
559
+ return NO;
560
+ }
561
+ else if(!entry.isTable)
562
+ {
563
+ return memcmp(entry.ptr, hash + 1, length - 1) == 0;
564
+ }
565
+ else
566
+ {
567
+ hash++;
568
+ length--;
569
+ table = entry.ptr;
570
+ }
571
+ }
572
+ return NO;
573
+ }
574
+ #endif
575
+
576
+ static BOOL CanNativeZWRClass(Class c)
577
+ {
578
+ #if __APPLE__
579
+ if(!c)
580
+ return YES;
581
+
582
+ const char *name = class_getName(c);
583
+ unsigned char hash[CC_SHA1_DIGEST_LENGTH];
584
+ CC_SHA1(name, strlen(name), hash);
585
+
586
+ if(HashPresentInTable(hash, CC_SHA1_DIGEST_LENGTH, _MAZeroingWeakRefClassNativeWeakReferenceNotAllowedTable))
587
+ return NO;
588
+ else
589
+ return CanNativeZWRClass(class_getSuperclass(c));
590
+ #else
591
+ return NO;
592
+ #endif
593
+ }
594
+
595
+ static BOOL CanNativeZWR(id obj)
596
+ {
597
+ return CanNativeZWRClass(object_getClass(obj));
598
+ }
599
+
600
+ static Class CreatePlainCustomSubclass(Class class)
601
+ {
602
+ NSString *newName = [NSString stringWithFormat: @"%s_MAZeroingWeakRefSubclass", class_getName(class)];
603
+ const char *newNameC = [newName UTF8String];
604
+
605
+ Class subclass = objc_allocateClassPair(class, newNameC, 0);
606
+
607
+ Method release = class_getInstanceMethod(class, @selector(release));
608
+ Method dealloc = class_getInstanceMethod(class, @selector(dealloc));
609
+ Method classForCoder = class_getInstanceMethod(class, @selector(classForCoder));
610
+ class_addMethod(subclass, @selector(release), (IMP)CustomSubclassRelease, method_getTypeEncoding(release));
611
+ class_addMethod(subclass, @selector(dealloc), (IMP)CustomSubclassDealloc, method_getTypeEncoding(dealloc));
612
+ class_addMethod(subclass, @selector(classForCoder), (IMP)CustomSubclassClassForCoder, method_getTypeEncoding(classForCoder));
613
+
614
+ objc_registerClassPair(subclass);
615
+
616
+ return subclass;
617
+ }
618
+
619
+ static void PatchKVOSubclass(Class class)
620
+ {
621
+ // NSLog(@"Patching KVO class %s", class_getName(class));
622
+ Method removeObserverForKeyPath = class_getInstanceMethod(class, @selector(removeObserver:forKeyPath:));
623
+ Method release = class_getInstanceMethod(class, @selector(release));
624
+ Method dealloc = class_getInstanceMethod(class, @selector(dealloc));
625
+
626
+ class_addMethod(class,
627
+ @selector(MAZeroingWeakRef_KVO_original_removeObserver:forKeyPath:),
628
+ method_getImplementation(removeObserverForKeyPath),
629
+ method_getTypeEncoding(removeObserverForKeyPath));
630
+ class_addMethod(class, @selector(MAZeroingWeakRef_KVO_original_release), method_getImplementation(release), method_getTypeEncoding(release));
631
+ class_addMethod(class, @selector(MAZeroingWeakRef_KVO_original_dealloc), method_getImplementation(dealloc), method_getTypeEncoding(dealloc));
632
+
633
+ class_replaceMethod(class,
634
+ @selector(removeObserver:forKeyPath:),
635
+ (IMP)KVOSubclassRemoveObserverForKeyPath,
636
+ method_getTypeEncoding(removeObserverForKeyPath));
637
+ class_replaceMethod(class, @selector(release), (IMP)KVOSubclassRelease, method_getTypeEncoding(release));
638
+ class_replaceMethod(class, @selector(dealloc), (IMP)KVOSubclassDealloc, method_getTypeEncoding(dealloc));
639
+
640
+ // The context variant is only available on 10.7/iOS5+, so only perform that override if the method actually exists.
641
+ Method removeObserverForKeyPathContext = class_getInstanceMethod(class, @selector(removeObserver:forKeyPath:context:));
642
+ if(removeObserverForKeyPathContext)
643
+ {
644
+ class_addMethod(class,
645
+ @selector(MAZeroingWeakRef_KVO_original_removeObserver:forKeyPath:context:),
646
+ method_getImplementation(removeObserverForKeyPathContext),
647
+ method_getTypeEncoding(removeObserverForKeyPathContext));
648
+ class_replaceMethod(class,
649
+ @selector(removeObserver:forKeyPath:context:),
650
+ (IMP)KVOSubclassRemoveObserverForKeyPathContext,
651
+ method_getTypeEncoding(removeObserverForKeyPathContext));
652
+
653
+ }
654
+ }
655
+
656
+ static void RegisterCustomSubclass(Class subclass, Class superclass)
657
+ {
658
+ [gCustomSubclassMap setObject: subclass forKey: (id <NSCopying>) superclass];
659
+ [gCustomSubclasses addObject: subclass];
660
+ }
661
+
662
+ static Class CreateCustomSubclass(Class class, id obj)
663
+ {
664
+ if(IsTollFreeBridged(class, obj))
665
+ {
666
+ #if COREFOUNDATION_HACK_LEVEL >= 2
667
+ CFTypeID typeID = CFGetTypeID(obj);
668
+ CFRuntimeClass *cfclass = _CFRuntimeGetClassWithTypeID(typeID);
669
+
670
+ if(typeID >= gCFOriginalFinalizesSize)
671
+ {
672
+ gCFOriginalFinalizesSize = typeID + 1;
673
+ gCFOriginalFinalizes = realloc(gCFOriginalFinalizes, gCFOriginalFinalizesSize * sizeof(*gCFOriginalFinalizes));
674
+ }
675
+
676
+ do {
677
+ gCFOriginalFinalizes[typeID] = cfclass->finalize;
678
+ } while(!OSAtomicCompareAndSwapPtrBarrier(gCFOriginalFinalizes[typeID], CustomCFFinalize, (void *)&cfclass->finalize));
679
+ #else
680
+ NSCAssert2(0, @"Cannot create zeroing weak reference to object of type %@ with COREFOUNDATION_HACK_LEVEL set to %d", class, COREFOUNDATION_HACK_LEVEL);
681
+ #endif
682
+ return class;
683
+ }
684
+ else if(IsKVOSubclass(obj))
685
+ {
686
+ PatchKVOSubclass(class);
687
+ return class;
688
+ }
689
+ else
690
+ {
691
+ return CreatePlainCustomSubclass(class);
692
+ }
693
+ }
694
+
695
+ static void EnsureCustomSubclass(id obj)
696
+ {
697
+ if(!GetCustomSubclass(obj) && !IsConstantObject(obj))
698
+ {
699
+ Class class = object_getClass(obj);
700
+ Class subclass = [gCustomSubclassMap objectForKey: class];
701
+ if(!subclass)
702
+ {
703
+ subclass = CreateCustomSubclass(class, obj);
704
+ RegisterCustomSubclass(subclass, class);
705
+ }
706
+
707
+ // only set the class if the current one is its superclass
708
+ // otherwise it's possible that it returns something farther up in the hierarchy
709
+ // and so there's no need to set it then
710
+ if(class_getSuperclass(subclass) == class)
711
+ object_setClass(obj, subclass);
712
+ }
713
+ }
714
+
715
+ static void RegisterRef(MAZeroingWeakRef *ref, id target)
716
+ {
717
+ WhileLocked({
718
+ EnsureCustomSubclass(target);
719
+ AddWeakRefToObject(target, ref);
720
+ #if COREFOUNDATION_HACK_LEVEL >= 3
721
+ if(IsTollFreeBridged(object_getClass(target), target))
722
+ CFSetAddValue(gCFWeakTargets, target);
723
+ #endif
724
+ });
725
+ }
726
+
727
+ static void UnregisterRef(MAZeroingWeakRef *ref)
728
+ {
729
+ WhileLocked({
730
+ id target = ref->_target;
731
+
732
+ if(target)
733
+ RemoveWeakRefFromObject(target, ref);
734
+ });
735
+ }
736
+
737
+ + (BOOL)canRefCoreFoundationObjects
738
+ {
739
+ return COREFOUNDATION_HACK_LEVEL >= 2 || objc_storeWeak_fptr;
740
+ }
741
+
742
+ + (id)refWithTarget: (id)target
743
+ {
744
+ return [[[self alloc] initWithTarget: target] autorelease];
745
+ }
746
+
747
+ - (id)initWithTarget: (id)target
748
+ {
749
+ if((self = [self init]))
750
+ {
751
+ if(objc_storeWeak_fptr && CanNativeZWR(target))
752
+ {
753
+ objc_storeWeak_fptr(&_target, target);
754
+ _nativeZWR = YES;
755
+ }
756
+ else
757
+ {
758
+ _target = target;
759
+ RegisterRef(self, target);
760
+ }
761
+ }
762
+ return self;
763
+ }
764
+
765
+ - (void)dealloc
766
+ {
767
+ if(objc_storeWeak_fptr && _nativeZWR)
768
+ objc_storeWeak_fptr(&_target, nil);
769
+ else
770
+ UnregisterRef(self);
771
+
772
+ #if NS_BLOCKS_AVAILABLE
773
+ [_cleanupBlock release];
774
+ #endif
775
+ [super dealloc];
776
+ }
777
+
778
+ - (NSString *)description
779
+ {
780
+ return [NSString stringWithFormat: @"<%@: %p -> %@>", [self class], self, [self target]];
781
+ }
782
+
783
+ #if NS_BLOCKS_AVAILABLE
784
+ - (void)setCleanupBlock: (void (^)(id target))block
785
+ {
786
+ block = [block copy];
787
+ [_cleanupBlock release];
788
+ _cleanupBlock = block;
789
+
790
+ if(objc_loadWeak_fptr && _nativeZWR)
791
+ {
792
+ // wrap a pool around this code, otherwise it artificially extends
793
+ // the lifetime of the target object
794
+ NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
795
+
796
+ id target = [self target];
797
+ if(target != nil) @synchronized(target)
798
+ {
799
+ static void *associatedKey = &associatedKey;
800
+ NSMutableSet *cleanupHelpers = objc_getAssociatedObject(target, associatedKey);
801
+
802
+ if(cleanupHelpers == nil)
803
+ {
804
+ cleanupHelpers = [NSMutableSet set];
805
+ objc_setAssociatedObject(target, associatedKey, cleanupHelpers, OBJC_ASSOCIATION_RETAIN);
806
+ }
807
+
808
+ _MAZeroingWeakRefCleanupHelper *helper = [[_MAZeroingWeakRefCleanupHelper alloc] initWithRef: self target: target];
809
+ [cleanupHelpers addObject:helper];
810
+
811
+ [helper release];
812
+ }
813
+
814
+ [pool release];
815
+ }
816
+ }
817
+ #endif
818
+
819
+ - (id)target
820
+ {
821
+ if(objc_loadWeak_fptr && _nativeZWR)
822
+ {
823
+ return objc_loadWeak_fptr(&_target);
824
+ }
825
+ else
826
+ {
827
+ BLOCK_QUALIFIER id ret;
828
+ WhileLocked({
829
+ ret = [_target retain];
830
+ });
831
+ return [ret autorelease];
832
+ }
833
+ }
834
+
835
+ - (void)_zeroTarget
836
+ {
837
+ _target = nil;
838
+ }
839
+
840
+ - (void)_executeCleanupBlockWithTarget: (id)target
841
+ {
842
+ #if NS_BLOCKS_AVAILABLE
843
+ if(_cleanupBlock)
844
+ {
845
+ _cleanupBlock(target);
846
+ [_cleanupBlock release];
847
+ _cleanupBlock = nil;
848
+ }
849
+ #endif
850
+ }
851
+
852
+ @end