Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save seanm/00938c5f6e456db8fcb1 to your computer and use it in GitHub Desktop.
Save seanm/00938c5f6e456db8fcb1 to your computer and use it in GitHub Desktop.
//
// NSObject+KVOWeakPropertyDebug.h
// KVOWeakPropertyDebug
//
// Created by Vladimir Grichina on 12.01.13.
// Copyright (c) 2013 Vladimir Grichina. All rights reserved.
//
#import <Foundation/Foundation.h>
@interface NSObject (KVOWeakPropertyDebug)
@end
//
// NSObject+KVOWeakPropertyDebug.m
// KVOWeakPropertyDebug
//
// Created by Vladimir Grichina on 12.01.13.
// Copyright (c) 2013-2014 Vladimir Grichina. All rights reserved.
//
#import "NSObject+KVOWeakPropertyDebug.h"
#import <objc/runtime.h>
static void MethodSwizzle(Class c, SEL origSEL, SEL overrideSEL)
{
Method origMethod = class_getInstanceMethod(c, origSEL);
Method overrideMethod = class_getInstanceMethod(c, overrideSEL);
if (class_addMethod(c, origSEL, method_getImplementation(overrideMethod), method_getTypeEncoding(overrideMethod))) {
class_replaceMethod(c, overrideSEL, method_getImplementation(origMethod), method_getTypeEncoding(origMethod));
} else {
method_exchangeImplementations(origMethod, overrideMethod);
}
}
static BOOL DoesPropertyTypeStringIncludeWeak(const char* string)
{
// Assume the string is ASCII.
// Traverse the string char by char, ignore everything before the first comma, return YES if a 'W' is found after it, NO otherwise.
size_t strLen = string ? strlen(string) : 0;
BOOL foundFirstComma = NO;
for (size_t i = 0; i < strLen; i++) {
if (string[i] == ',') {
foundFirstComma = YES;
}
else if (foundFirstComma && (string[i] == 'W')) {
return YES;
}
}
return NO;
}
@implementation NSObject (KVOWeakPropertyDebug)
+ (void)load
{
MethodSwizzle(self, @selector(addObserver:forKeyPath:options:context:), @selector(private_addObserver:forKeyPath:options:context:));
MethodSwizzle(self, @selector(addObserver:toObjectsAtIndexes:forKeyPath:options:context:), @selector(private_addObserver:toObjectsAtIndexes:forKeyPath:options:context:));
}
- (BOOL)private_isWeak:(NSString *)keyPath
{
BOOL isWeak = NO;
// TODO: Support complex keyPath variants. Until then, keep just what's before the first '.'
char* copy = strdup(keyPath.UTF8String);
assert(copy);
copy = strsep(&copy, ".");
assert(copy);
objc_property_t property = class_getProperty(self.class, copy);
if (property) {
const char* tmp = property_getAttributes(property);
isWeak = DoesPropertyTypeStringIncludeWeak(tmp);
}
free(copy);
return isWeak;
}
- (void)private_addObserver:(NSObject *)observer
forKeyPath:(NSString *)keyPath
options:(NSKeyValueObservingOptions)options
context:(void *)context
{
if ([self private_isWeak:keyPath]) {
NSLog(@"WARNING: object %p is observing '%@' property, which is weak and so isn't KVO-compliant", observer, keyPath);
}
[self private_addObserver:observer
forKeyPath:keyPath
options:options
context:context];
}
- (void)private_addObserver:(NSObject *)observer
toObjectsAtIndexes:(NSIndexSet *)indexes
forKeyPath:(NSString *)keyPath
options:(NSKeyValueObservingOptions)options
context:(void *)context
{
if ([self private_isWeak:keyPath]) {
NSLog(@"WARNING: object %p is observing '%@' property, which is weak and so isn't KVO-compliant", observer, keyPath);
}
[self private_addObserver:observer
toObjectsAtIndexes:indexes
forKeyPath:keyPath
options:options
context:context];
}
@end
@Thesaurus
Copy link

On 10.11 we need something like

static BOOL DoesPropertyTypeStringIncludeWeak(const char* string)
{
return strstr(string, ",W,") != NULL ? YES : NO; // 10.11 variant
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment