Method Swizzling isEqualToString - objective-c

I'm running into some odd behavior when trying to Method Swizzle isEqualToString: on the NSString class. Here is the code in question:
#import <Foundation/Foundation.h>
#import <objc/objc-runtime.h>
#interface NSString (SwizzleString)
- (BOOL) custom_isEqualToString:(NSString *)aString;
- (NSRange)custom_rangeOfString:(NSString *)aString;
#end
#implementation NSString (SwizzleString)
- (BOOL) custom_isEqualToString:(NSString *)aString;
{
NSLog(#"Inside custom_isEqualToString method definition");
return [self custom_isEqualToString:aString];
}
- (NSRange)custom_rangeOfString:(NSString *)aString;
{
NSLog(#"Inside custom_rangeOfString method definition");
return [self custom_rangeOfString:aString];
}
#end
int main(int argc, const char * argv[])
{
Method m1, m2;
m1 = class_getInstanceMethod([NSString class], #selector(isEqualToString:));
m2 = class_getInstanceMethod([NSString class], #selector(custom_isEqualToString:));
method_exchangeImplementations(m1, m2);
m1 = class_getInstanceMethod([NSString class], #selector(rangeOfString:));
m2 = class_getInstanceMethod([NSString class], #selector(custom_rangeOfString:));
method_exchangeImplementations(m1, m2);
NSString *foo = #"Foo";
// Does not log anything, is still using isEqualToString: implementation
[foo isEqualToString:#"Foo"];
// Also does not log anything, since it is using the method implementation from isEqualToString:
[foo custom_isEqualToString:#"Foo"];
// Does log something because rangeOfString now uses custom_rangeOfString IMP
[foo rangeOfString:#"Foo"];
// Does not log anything because it uses the method implementation from rangeOfString:
[foo custom_rangeOfString:#"Foo"];
}
isEqualToString: and rangeOfString: are both defined in a category on NSString called (NSStringExtensionMethods), so I included the rangeOfString swizzle to show that I'm swizzling methods correctly and specifically an NSString object successfully so I could eliminate questions about class cluster problems.
When I produce the assembly for the code above, instead of seeing normal objc_msgSend calls I instead see stuff like l_objc_msgSend_fixup_isEqualToString_. This led me to finding out more about the objective-c vtable, in which it seems isEqualToString: can be found:
static const char * const defaultVtable[] = {
"allocWithZone:",
"alloc",
"class",
"self",
"isKindOfClass:",
"respondsToSelector:",
"isFlipped",
"length",
"objectForKey:",
"count",
"objectAtIndex:",
"isEqualToString:",
"isEqual:",
"retain",
"release",
"autorelease",
};
I've been digging through the objective-c source and the internet all day on how it would be possible to somehow still be able to swizzle isEqualToString:.

As you're aware, this is a class cluster. You need the actual class, not the public class, so just ask the string object that you have:
NSString *foo = #"Foo";
m1 = class_getInstanceMethod([foo class], #selector(isEqualToString:));
m2 = class_getInstanceMethod([foo class], #selector(custom_isEqualToString:));
method_exchangeImplementations(m1, m2);
There's also the fragile alternative of using the class name itself:
NSClassFromString(#"__NSCFConstantString")
NSClassFromString(#"__NSCFString")

Related

In a macOS Objective-C application, I have subclassed NSMutableSet for enforcing an equality different from isEqual. Is my implementation fine?

In my macOS Objective-C application, I have created a subclass of NSMutableSet. What I want to achieve is a NSMutableSet that does not use isEqual: as the comparing strategy. Specifically, The set will contain objects of type NSRunningApplication, and I want the set to work based on the equality of the objects bundle identifiers. Following is my implementation:
Header file:
#import <Cocoa/Cocoa.h>
NS_ASSUME_NONNULL_BEGIN
#interface BundleIdentifierAwareMutableSet : NSMutableSet
#property (atomic, strong) NSMutableSet *backStorageMutableSet;
#property (atomic, strong) NSMutableArray *backStorageMutableArray;
#end
NS_ASSUME_NONNULL_END
Implementation file:
#import "BundleIdentifierAwareMutableSet.h"
#implementation BundleIdentifierAwareMutableSet
#synthesize backStorageMutableSet;
- (instancetype)init {
self = [super init];
if (self) {
self.backStorageMutableSet = [[NSMutableSet alloc] init];
self.backStorageMutableArray = [[NSMutableArray alloc] init];
}
return self;
}
- (NSUInteger)count {
return [self.backStorageMutableArray count];
}
- (NSRunningApplication *)member:(NSRunningApplication *)object {
__block NSRunningApplication *returnValue = nil;
[self.backStorageMutableArray enumerateObjectsUsingBlock:^(NSRunningApplication * _Nonnull app, NSUInteger __unused idx, BOOL * _Nonnull stop) {
if ([app.bundleIdentifier isEqualToString:[object bundleIdentifier]]) {
returnValue = app;
if (![app isEqual:object]) {
NSLog(#"An ordinary set would have not considered the two objects equal.");
}
*stop = YES;
}
}];
return returnValue;
}
- (NSEnumerator *)objectEnumerator {
self.backStorageMutableSet = [NSMutableSet setWithArray:self.backStorageMutableArray];
return [self.backStorageMutableSet objectEnumerator];
}
- (void)addObject:(NSRunningApplication *)object {
NSRunningApplication *app = [self member:object];
if (app == nil) {
[self.backStorageMutableArray addObject:object];
}
}
- (void)removeObject:(NSRunningApplication *)object {
NSArray *snapShot = [self.backStorageMutableArray copy];
[snapShot enumerateObjectsUsingBlock:^(NSRunningApplication * _Nonnull currentApp, NSUInteger __unused idx, BOOL * _Nonnull __unused stop) {
if ([[currentApp bundleIdentifier] isEqualToString:[object bundleIdentifier]]) {
[self.backStorageMutableArray removeObject:currentApp];
if (![currentApp isEqual:object]) {
NSLog(#"An ordinary set would have not considered the two objects equal.");
}
}
}];
}
This seems to work, and indeed, When applicable, Xcode logs that an ordinary NSMutableSet would have not considered two members equal. I would like to bring this implementation to the Production App, but I am afraid I have not considered something important, since this is the first time I subclass NSMutableSet. For example, I am worried about the following method:
- (NSEnumerator *)objectEnumerator {
self.backStorageMutableSet = [NSMutableSet setWithArray:self.backStorageMutableArray];
return [self.backStorageMutableSet objectEnumerator];
}
This is the only use I do of the backStorageMutableSet since the rest is backed to the array. Is this fine or can bring troubles ? Will other parts of the subclass bring problems ? Any help will be greatly appreciated. Thanks
Don't do this. Subclassing collections should be the last resort. It can have implications on performance, ... Try to use highest possible abstraction and go down if it doesn't work for you for some reason.
Wrapper object
Wrap the NSRunningApplication in another object and provide your own hash & isEqual: methods.
Application.h:
#interface Application: NSObject
#property (nonatomic, strong, readonly, nonnull) NSRunningApplication *application;
#end
Application.m:
#interface Application ()
#property (nonatomic, strong, nonnull) NSRunningApplication *application;
#end
#implementation Application
- (nonnull instancetype)initWithRunningApplication:(NSRunningApplication *_Nonnull)application {
if ((self = [super init]) == nil) {
// https://developer.apple.com/documentation/objectivec/nsobject/1418641-init?language=objc
//
// The init() method defined in the NSObject class does no initialization; it simply
// returns self. In terms of nullability, callers can assume that the NSObject
// implementation of init() does not return nil.
return nil;
}
self.application = application;
return self;
}
// https://developer.apple.com/documentation/objectivec/1418956-nsobject/1418795-isequal?language=objc
- (BOOL)isEqual:(id)object {
if (![object isKindOfClass:[Application class]]) {
return NO;
}
Application *app = (Application *)object;
return [self.application.bundleIdentifier isEqualToString:app.application.bundleIdentifier];
}
// https://developer.apple.com/documentation/objectivec/1418956-nsobject/1418859-hash?language=objc
- (NSUInteger)hash {
return self.application.bundleIdentifier.hash;
}
#end
Toll-free bridging & CFMutableSetRef
CFSet is bridged with the NSSet, CFMutableSet is bridged with the NSMutableSet, etc. It means that you can create a set via Core Foundation
API and then use it as NSSet for example. Core Foundation is a powerful
framework which exposes more stuff to you.
You can provide a custom set of callbacks for the CFSet.
/*!
#typedef CFSetCallBacks
Structure containing the callbacks of a CFSet.
#field version The version number of the structure type being passed
in as a parameter to the CFSet creation functions. This
structure is version 0.
#field retain The callback used to add a retain for the set on
values as they are put into the set. This callback returns
the value to store in the set, which is usually the value
parameter passed to this callback, but may be a different
value if a different value should be stored in the set.
The set's allocator is passed as the first argument.
#field release The callback used to remove a retain previously added
for the set from values as they are removed from the
set. The set's allocator is passed as the first
argument.
#field copyDescription The callback used to create a descriptive
string representation of each value in the set. This is
used by the CFCopyDescription() function.
#field equal The callback used to compare values in the set for
equality for some operations.
#field hash The callback used to compare values in the set for
uniqueness for some operations.
*/
typedef struct {
CFIndex version;
CFSetRetainCallBack retain;
CFSetReleaseCallBack release;
CFSetCopyDescriptionCallBack copyDescription;
CFSetEqualCallBack equal;
CFSetHashCallBack hash;
} CFSetCallBacks;
There're predefined sets of callbacks like:
/*!
#constant kCFTypeSetCallBacks
Predefined CFSetCallBacks structure containing a set of callbacks
appropriate for use when the values in a CFSet are all CFTypes.
*/
CF_EXPORT
const CFSetCallBacks kCFTypeSetCallBacks;
Which means that you're not forced to provide all of them, but you're free to modify just some of them. Let's prepare two callback functions:
// typedef CFHashCode (*CFSetHashCallBack)(const void *value);
CFHashCode runningApplicationBundleIdentifierHash(const void *value) {
NSRunningApplication *application = (__bridge NSRunningApplication *)value;
return [application.bundleIdentifier hash];
}
// typedef Boolean (*CFSetEqualCallBack)(const void *value1, const void *value2);
Boolean runningApplicationBundleIdentifierEqual(const void *value1, const void *value2) {
NSRunningApplication *application1 = (__bridge NSRunningApplication *)value1;
NSRunningApplication *application2 = (__bridge NSRunningApplication *)value2;
return [application1.bundleIdentifier isEqualToString:application2.bundleIdentifier];
}
You can use them in this way:
- (NSMutableSet<NSRunningApplication *> *_Nullable)bundleIdentifierAwareMutableSetWithCapacity:(NSUInteger)capacity {
// > Predefined CFSetCallBacks structure containing a set of callbacks
// > appropriate for use when the values in a CFSet are all CFTypes.
//
// Which means that you shouldn't bother about retain, release, ... callbacks,
// they're already set.
//
// CFSetCallbacks can be on stack, because this structure is copied in the
// CFSetCreateMutable function.
CFSetCallBacks callbacks = kCFTypeSetCallBacks;
// Overwrite just the hash & equal callbacks
callbacks.hash = runningApplicationBundleIdentifierHash;
callbacks.equal = runningApplicationBundleIdentifierEqual;
// Try to create a mutable set.
CFMutableSetRef set = CFSetCreateMutable(kCFAllocatorDefault, capacity, &callbacks);
if (set == NULL) {
// Failed, do some error handling or just return nil
return nil;
}
// Transfer the ownership to the Obj-C & ARC => no need to call CFRelease
return (__bridge_transfer NSMutableSet *)set;
}
&
NSMutableSet<NSRunningApplication *> *set = [self bundleIdentifierAwareMutableSetWithCapacity:50];
[set addObjectsFromArray:[[NSWorkspace sharedWorkspace] runningApplications]];
NSLog(#"%#", set);

Potential problems in objective-c code

Here is a small piece of code. Posted by Russian company Yandex as a part of their interview. What are potential problems here? It looks very simple, should be hidden problems I can not see.
First header
//Foo.h
#import <Cocoa/Cocoa.h>
#interface Foo : NSObject
{
NSString* str;
static int i = 0;
}
- (NSString*) str;
#end
Another file
//Foo.m
#import "Foo.h"
#implementation
- (id) init
{
return [self initWithStr:"number:" someInt:6];
}
- (id) initWithStr:(NSString*)theStr someInt:(int)value
{
self = [super init];
str = [NSString stringWithFormat:#"%#%d", theStr, value];
return self;
}
- (NSString*) str
{
return str;
}
- (void) setStr:(NSString*)theStr
{
str = theStr;
}
#end
And the last file
//main.m
#import <Cocoa/Cocoa.h>
#import "Foo.h"
int main(int argc, char *argv[])
{
Foo objA;
NSLog([objA str]);
[objA setStr:#"hello world!"];
NSLog([objA str]);
Foo* objB = [[Foo alloc] init];
Foo* objC = [[Foo alloc] initWithStr:#"My magic number:" value:265];
objB = objC;
NSLog([objB str]);
[objA release];
[objB release];
[objC release];
return 0;
}
In another file:
#implementation
implementation of what? must specify.
In the last file:
Foo objA;
NSLog([objA str]);
[objA setStr:#"hello world!"];
NSLog([objA str]);
This will crash, local variable Foo objA is not initialized, it would be fine it was set to nil, since messages to nil are ok in objective c but it is not.
Here:
[objA setStr:#"hello world!"];
That method will give a compile warning since that method is not declared in the interface, but it will still call the method.
Here:
- (id) init
{
return [self initWithStr:"number:" someInt:6];
}
Missing # for the string #"number:"
Here:
objB = objC;
You just leaked objB, since there is now no valid reference to release the previous allocation.
[objA release];
This was never allocated!
[objB release];
[objC release];
The second one will crash since they both refer to the same object, and the retain count is only 1.
The first file also has some potential issues such as declaring a method that appears to be a getter without declaring a property for the ivar, same with the setter, would be better to just declare a property.
#interface Foo : NSObject
{
NSString* str;
static int i = 0;
}
You cann't define static int i = 0; here. Type name does not allow storage class to be specified Foo.h
Also, the setter needs to release the previous string and retain the new one.
- (void) setStr:(NSString*)theStr
{
if(str) {
[str release];
}
str = [theStr retain];
}

NSArray of weak references (__unsafe_unretained) to objects under ARC

I need to store weak references to objects in an NSArray, in order to prevent retain cycles. I'm not sure of the proper syntax to use. Is this the correct way?
Foo* foo1 = [[Foo alloc] init];
Foo* foo2 = [[Foo alloc] init];
__unsafe_unretained Foo* weakFoo1 = foo1;
__unsafe_unretained Foo* weakFoo2 = foo2;
NSArray* someArray = [NSArray arrayWithObjects:weakFoo1, weakFoo2, nil];
Note that I need to support iOS 4.x, thus the __unsafe_unretained instead of __weak.
EDIT (2015-02-18):
For those wanting to use true __weak pointers (not __unsafe_unretained), please check out this question instead: Collections of zeroing weak references under ARC
As Jason said, you can't make NSArray store weak references. The easiest way to implement Emile's suggestion of wrapping an object inside another object that stores a weak reference to it is the following:
NSValue *value = [NSValue valueWithNonretainedObject:myObj];
[array addObject:value];
Another option: a category that makes NSMutableArray optionally store weak references.
Note that these are "unsafe unretained" references, not self-zeroing weak references. If the array is still around after the objects are deallocated, you'll have a bunch of junk pointers.
The solutions to use a NSValue helper or to create a collection (array, set, dict) object and disable its Retain/Release callbacks are both not 100% failsafe solutions with regard to using ARC.
As various comments to these suggestions point out, such object references will not work like true weak refs:
A "proper" weak property, as supported by ARC, has two behaviors:
Doesn't hold a strong ref to the target object. That means that if the object has no strong references pointing to it, the object will be deallocated.
If the ref'd object is deallocated, the weak reference will become nil.
Now, while the above solutions will comply with behavior #1, they do not exhibit #2.
To get behavior #2 as well, you have to declare your own helper class. It has just one weak property for holding your reference. You then add this helper object to the collection.
Oh, and one more thing: iOS6 and OSX 10.8 supposedly offer a better solution:
[NSHashTable weakObjectsHashTable]
[NSPointerArray weakObjectsPointerArray]
[NSPointerArray pointerArrayWithOptions:]
These should give you containers that hold weak references (but note matt's comments below).
An example (updated 2 Feb 2022)
#import <Foundation/Foundation.h>
static BOOL didDealloc = NO;
#interface TestClass : NSObject
#end
#implementation TestClass
-(void)dealloc {
didDealloc = YES;
}
#end
int main(int argc, const char * argv[]) {
NSPointerArray *pa = [NSPointerArray weakObjectsPointerArray];
#autoreleasepool {
TestClass *obj = TestClass.new;
[pa addPointer:(__bridge void * _Nullable)(obj)]; // stores obj as a weak ref
assert([pa pointerAtIndex:0] != nil);
assert(!didDealloc);
} // at this point the TestClass obj will be deallocated
assert(didDealloc);
assert([pa pointerAtIndex:0] == nil); // verify that the weak ref is null now
return 0;
}
If you run this you'll find that after adding the TestClass object to the pointer array pa, then releasing that object again, the pointer (which is internally a weak object ref) is now set to null as desired.
However, note that calling [pa compact] at the end will not remove the nil pointer as I'd have expected.
I am new to objective-C, after 20 years of writing c++.
In my view, objective-C is excellent at loosely-coupled messaging, but horrible for data management.
Imagine how happy I was to discover that xcode 4.3 supports objective-c++!
So now I rename all my .m files to .mm (compiles as objective-c++) and use c++ standard containers for data management.
Thus the "array of weak pointers" problem becomes a std::vector of __weak object pointers:
#include <vector>
#interface Thing : NSObject
#end
// declare my vector
std::vector<__weak Thing*> myThings;
// store a weak reference in it
Thing* t = [Thing new];
myThings.push_back(t);
// ... some time later ...
for(auto weak : myThings) {
Thing* strong = weak; // safely lock the weak pointer
if (strong) {
// use the locked pointer
}
}
Which is equivalent to the c++ idiom:
std::vector< std::weak_ptr<CppThing> > myCppThings;
std::shared_ptr<CppThing> p = std::make_shared<CppThing>();
myCppThings.push_back(p);
// ... some time later ...
for(auto weak : myCppThings) {
auto strong = weak.lock(); // safety is enforced in c++, you can't dereference a weak_ptr
if (strong) {
// use the locked pointer
}
}
Proof of concept (in the light of Tommy's concerns about vector reallocation):
main.mm:
#include <vector>
#import <Foundation/Foundation.h>
#interface Thing : NSObject
#end
#implementation Thing
#end
extern void foo(Thing*);
int main()
{
// declare my vector
std::vector<__weak Thing*> myThings;
// store a weak reference in it while causing reallocations
Thing* t = [[Thing alloc]init];
for (int i = 0 ; i < 100000 ; ++i) {
myThings.push_back(t);
}
// ... some time later ...
foo(myThings[5000]);
t = nullptr;
foo(myThings[5000]);
}
void foo(Thing*p)
{
NSLog(#"%#", [p className]);
}
example log output:
2016-09-21 18:11:13.150 foo2[42745:5048189] Thing
2016-09-21 18:11:13.152 foo2[42745:5048189] (null)
If you do not require a specific order you could use NSMapTable with special key/value options
NSPointerFunctionsWeakMemory
Uses weak read and write barriers appropriate for ARC or GC. Using NSPointerFunctionsWeakMemory object references will turn to NULL on last release.
I believe the best solution for this is to use NSHashTable or NSMapTable. the Key or/and the Value can be weak. You can read more about it here: http://nshipster.com/nshashtable-and-nsmaptable/
To add weak self reference to NSMutableArray, create a custom class with a weak property as given below.
NSMutableArray *array = [NSMutableArray new];
Step 1: create a custom class
#interface DelegateRef : NSObject
#property(nonatomic, weak)id delegateWeakReference;
#end
Step 2: create a method to add self as weak reference to NSMutableArray. But here we add the DelegateRef object
-(void)addWeakRef:(id)ref
{
DelegateRef *delRef = [DelegateRef new];
[delRef setDelegateWeakReference:ref]
[array addObject:delRef];
}
Step 3: later on, if the property delegateWeakReference == nil, the object can be removed from the array
The property will be nil, and the references will be deallocated at proper time independent of this array references
The simplest solution:
NSMutableArray *array = (__bridge_transfer NSMutableArray *)CFArrayCreateMutable(nil, 0, nil);
NSMutableDictionary *dictionary = (__bridge_transfer NSMutableDictionary *)CFDictionaryCreateMutable(nil, 0, nil, nil);
NSMutableSet *set = (__bridge_transfer NSMutableSet *)CFSetCreateMutable(nil, 0, nil);
Note: And this works on iOS 4.x too.
No, that's not correct. Those aren't actually weak references. You can't really store weak references in an array right now. You need to have a mutable array and remove the references when you're done with them or remove the whole array when you're done with it, or roll your own data structure that supports it.
Hopefully this is something that they'll address in the near future (a weak version of NSArray).
I've just faced with same problem and found that my before-ARC solution works after converting with ARC as designed.
// function allocates mutable set which doesn't retain references.
NSMutableSet* AllocNotRetainedMutableSet() {
CFMutableSetRef setRef = NULL;
CFSetCallBacks notRetainedCallbacks = kCFTypeSetCallBacks;
notRetainedCallbacks.retain = NULL;
notRetainedCallbacks.release = NULL;
setRef = CFSetCreateMutable(kCFAllocatorDefault,
0,
&notRetainedCallbacks);
return (__bridge NSMutableSet *)setRef;
}
// test object for debug deallocation
#interface TestObj : NSObject
#end
#implementation TestObj
- (id)init {
self = [super init];
NSLog(#"%# constructed", self);
return self;
}
- (void)dealloc {
NSLog(#"%# deallocated", self);
}
#end
#interface MainViewController () {
NSMutableSet *weakedSet;
NSMutableSet *usualSet;
}
#end
#implementation MainViewController
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
weakedSet = AllocNotRetainedMutableSet();
usualSet = [NSMutableSet new];
}
return self;
}
- (IBAction)addObject:(id)sender {
TestObj *obj = [TestObj new];
[weakedSet addObject:obj]; // store unsafe unretained ref
[usualSet addObject:obj]; // store strong ref
NSLog(#"%# addet to set", obj);
obj = nil;
if ([usualSet count] == 3) {
[usualSet removeAllObjects]; // deallocate all objects and get old fashioned crash, as it was required.
[weakedSet enumerateObjectsUsingBlock:^(TestObj *invalidObj, BOOL *stop) {
NSLog(#"%# must crash here", invalidObj);
}];
}
}
#end
Output:
2013-06-30 00:59:10.266 not_retained_collection_test[28997:907]
constructed 2013-06-30 00:59:10.267
not_retained_collection_test[28997:907] addet to
set 2013-06-30 00:59:10.581 not_retained_collection_test[28997:907]
constructed 2013-06-30 00:59:10.582
not_retained_collection_test[28997:907] addet to
set 2013-06-30 00:59:10.881 not_retained_collection_test[28997:907]
constructed 2013-06-30 00:59:10.882
not_retained_collection_test[28997:907] addet to
set 2013-06-30 00:59:10.883 not_retained_collection_test[28997:907]
deallocated 2013-06-30 00:59:10.883
not_retained_collection_test[28997:907]
deallocated 2013-06-30 00:59:10.884
not_retained_collection_test[28997:907]
deallocated 2013-06-30 00:59:10.885
not_retained_collection_test[28997:907] * -[TestObj
respondsToSelector:]: message sent to deallocated instance 0x1f03c8c0
Checked with iOS versions 4.3, 5.1, 6.2.
Hope it will be useful to somebody.
If you need zeroing weak references, see this answer for code you can use for a wrapper class.
Other answers to that question suggest a block-based wrapper, and ways to automatically remove zeroed elements from the collection.
If you use a lot this comportment it's indicated to your own NSMutableArray class (subclass of NSMutableArray) which doesn't increase the retain count.
You should have something like this:
-(void)addObject:(NSObject *)object {
[self.collection addObject:[NSValue valueWithNonretainedObject:object]];
}
-(NSObject*) getObject:(NSUInteger)index {
NSValue *value = [self.collection objectAtIndex:index];
if (value.nonretainedObjectValue != nil) {
return value.nonretainedObjectValue;
}
//it's nice to clean the array if the referenced object was deallocated
[self.collection removeObjectAtIndex:index];
return nil;
}
I think an elegant solution is what Mr. Erik Ralston propose on his Github repository
https://gist.github.com/eralston/8010285
this are the essential steps:
create a category for NSArray and NSMutableArray
in the implementation create a convenience class with a weak property. Your category will assign the objects to this weak property.
.h
#import <Foundation/Foundation.h>
#interface NSArray(WeakArray)
- (__weak id)weakObjectForIndex:(NSUInteger)index;
-(id<NSFastEnumeration>)weakObjectsEnumerator;
#end
#interface NSMutableArray (FRSWeakArray)
-(void)addWeakObject:(id)object;
-(void)removeWeakObject:(id)object;
-(void)cleanWeakObjects;
#end
.m
#import "NSArray+WeakArray.h"
#interface WAArrayWeakPointer : NSObject
#property (nonatomic, weak) NSObject *object;
#end
#implementation WAArrayWeakPointer
#end
#implementation NSArray (WeakArray)
-(__weak id)weakObjectForIndex:(NSUInteger)index
{
WAArrayWeakPointer *ptr = [self objectAtIndex:index];
return ptr.object;
}
-(WAArrayWeakPointer *)weakPointerForObject:(id)object
{
for (WAArrayWeakPointer *ptr in self) {
if(ptr) {
if(ptr.object == object) {
return ptr;
}
}
}
return nil;
}
-(id<NSFastEnumeration>)weakObjectsEnumerator
{
NSMutableArray *enumerator = [[NSMutableArray alloc] init];
for (WAArrayWeakPointer *ptr in self) {
if(ptr && ptr.object) {
[enumerator addObject:ptr.object];
}
}
return enumerator;
}
#end
#implementation NSMutableArray (FRSWeakArray)
-(void)addWeakObject:(id)object
{
if(!object)
return;
WAArrayWeakPointer *ptr = [[WAArrayWeakPointer alloc] init];
ptr.object = object;
[self addObject:ptr];
[self cleanWeakObjects];
}
-(void)removeWeakObject:(id)object
{
if(!object)
return;
WAArrayWeakPointer *ptr = [self weakPointerForObject:object];
if(ptr) {
[self removeObject:ptr];
[self cleanWeakObjects];
}
}
-(void)cleanWeakObjects
{
NSMutableArray *toBeRemoved = [[NSMutableArray alloc] init];
for (WAArrayWeakPointer *ptr in self) {
if(ptr && !ptr.object) {
[toBeRemoved addObject:ptr];
}
}
for(WAArrayWeakPointer *ptr in toBeRemoved) {
[self removeObject:ptr];
}
}
#end

Giving each subclass its own copy of a class variable

I have the following class in my iOS application (it is like an abstract class from the Java world).
#implementation WSObject
static NSDictionary* _dictionary = nil;
+(NSDictionary*) dictionary {
if (_dictionary == nil) {
_dictionary = [NSKeyedUnarchiver unarchiveObjectWithFile:[self localStorePath]];
}
return _dictionary;
}
...
#end
I then have multiple classes which implement this above WSObject with the class method dictionary. The problem is, that each of these classes should have their own _dictionary, but they are all sharing the same object from the super class. I could, of course, copy to all the subclasses, but that would break the reusability. Besides this getter, there are other class methods in WSObject which mutate the dictionary. Because of this, there would be a several class methods which should be in every subclass.
How can I solve this in a smart way? Please tell me if my description is insufficient.
Associative references seem like they'll do the trick. You can essentially tack some storage on to the class object itself. (I'm using NSStrings here, in place of the dictionaries you want to use, just for demonstration.)
Superclass:
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#interface Stuper : NSObject
// Accessor method for the "class variable"
+ (NSString *) str;
// Analog to your +localStorePath
+ (NSString *) quote;
#end
#import "Stuper.h"
// The doc suggests simply using the address of a static variable as the key.
// This works fine, even though every class is (as in your problem) using
// the same key, because we are associating to a different class each time.
static char key;
#implementation Stuper
+ (NSString *) str {
NSString * s = objc_getAssociatedObject(self, &key);
if( !s ){
s = [self quote];
// You'll probably want to use OBJC_ASSOCIATION_RETAIN for your dictionary.
// self inside a class method is the class object; use that as
// the associator. The string is now tied to the associator, i.e.,
// has the same lifetime.
objc_setAssociatedObject(self, &key, s, OBJC_ASSOCIATION_COPY);
}
return s;
}
+ (NSString *) quote {
return #"It was the best of times, it was the worst of times.";
}
#end
Subclass:
#import "Stuper.h"
#interface Stub : Stuper #end
#import "Stub.h"
#implementation Stub
+ (NSString *) quote {
return #"Call me Ishmael.";
}
#end
Trying this out:
#import <Foundation/Foundation.h>
#import "Stuper.h"
#import "Stub.h"
int main (int argc, const char * argv[])
{
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
NSLog(#"%#", [Stuper str]);
NSLog(#"%#", [Stub str]);
[pool drain];
return 0;
}
Each class object now has its own string, associated with it.
2011-12-05 23:11:09.031 SubClassVariables[36254:903] It was the best of times, it was the worst of times.
2011-12-05 23:11:09.034 SubClassVariables[36254:903] Call me Ishmael.
The only downside here is that you'll have to call the accessor method every time you want the object; you don't have a pointer you can use directly. You can call objc_getAssociatedObject in the superclass as an accessor, too, of course, since it has access to key.
In order to give each subclass its own dictionary, store a second dictionary object in your primary dictionary using the class name as the key. For example:
static NSMutableDictionary *_dictionary = nil;
+ (NSDictionary*)dictionary
{
if (_dictionary == nil)
_dictionary = [[NSKeyedUnarchiver unarchiveObjectWithFile:[self localStorePath]] mutableCopy];
NSString *key = NSStringFromClass( [self class] );
if ( [_dictionary objectForKey:key] == nil )
[_dictionary setObject:[NSMutableDictionary dictionary] forKey:key];
return [_dictionary objectForKey:key];
}
Perhaps you can return a copy of the dictionary
#implementation WSObject
static NSDictionary* _dictionary = nil;
+(NSDictionary*) dictionary {
if (_dictionary == nil) {
_dictionary = [NSKeyedUnarchiver unarchiveObjectWithFile:[self localStorePath]];
}
return [_dictionary copy];
}
...
#end
Keep in mind that if you modify _dictionary you will get a copy of that modified dictionary which may differ from what is on disk.
How often is this being called? is it really necessary to cache the file contents in this static _dictionary object?
Why not just fetch it every time form disk, assuming it isn't too often that performance comes into question.
#implementation WSObject
+(NSDictionary*) dictionary {
return [NSKeyedUnarchiver unarchiveObjectWithFile:[self localStorePath]];
}
...
#end

selector not sent by methodSignatureForSelector to forwardInvocation

I'm trying to change which method is called on an object for the sake of learning.
Here is my code.
#import <Foundation/Foundation.h>
#interface A : NSObject
-(void) say: (NSString*) s;
-(NSMethodSignature*) methodSignatureForSelector: (SEL) aSelector;
-(void) forwardInvocation: (NSInvocation*) invocation;
#end
#implementation A
-(void) say: (NSString*) s {
printf( "say \"%s\"", [s UTF8String] );
}
-(NSMethodSignature*) methodSignatureForSelector: (SEL) aSelector {
return [A instanceMethodSignatureForSelector: #selector(say:)];
}
-(void) forwardInvocation: (NSInvocation*) invocation {
// [invocation setSelector:#selector(say:)];
[invocation invokeWithTarget:self];
}
#end
int main(int args, char* argv[]){
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
A* myObject = [[[A alloc] init] autorelease];
[myObject tryToSay:#"Hello strange method!"];
[pool release];
return 0;
}
I want to use -say: no matter which method is attempted used on objects of the class A.
The result is a Segfault.
It seems the invocation object sent to forwardInvocation still has #selector(tryToSay:) as its selector. Is it not supposed to get the same selector that was set in methodSignatureForSelector?
No, it's not. NSMethodSignature doesn't encode the selector. It encodes the signature of the method instead. This means things like the type and number of arguments. It's also not going to work correctly. If someone tries to invoke a method like -setInteger: which takes an NSUInteger, it will be passed to your method -say: which is expecting an NSString*. But it won't get an NSString*, it will get an NSUInteger, and any attempt to dereference that will crash (unless it's 0).