Is it possible to swizzle the addObject: method of NSMutableArray?
Here is what I am trying.
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#implementation NSMutableArray (LoggingAddObject)
+ (void)load {
Method addObject = class_getInstanceMethod(self, #selector(addObject:));
Method logAddObject = class_getInstanceMethod(self, #selector(logAddObject:));
method_exchangeImplementations(addObject, logAddObject);
Method original = class_getInstanceMethod(self, #selector(setObject:atIndexedSubscript:));
Method swizzled = class_getInstanceMethod(self, #selector(swizzled_setObject:atIndexedSubscript:));
method_exchangeImplementations(original, swizzled);
}
- (void)logAddObject:(id)anObject {
[self logAddObject:anObject];
NSLog(#"Added object %# to array %#", anObject, self);
}
-(void)swizzled_setObject:(id)obj atIndexedSubscript:(NSUInteger)idx
{
NSLog(#"This gets called as expected!!-----");
[self swizzled_setObject:obj atIndexedSubscript:idx];
}
I am able to swizzle some of the methods like setObject:atIndexedSubscript: but I am worried that I cant do it do the addObject: and others.
I think the below can not be swizzled? Can someone explain why ? what I am doing wrong and or a way around this?
/**************** Mutable Array ****************/
#interface NSMutableArray : NSArray
- (void)addObject:(id)anObject;
- (void)insertObject:(id)anObject atIndex:(NSUInteger)index;
- (void)removeLastObject;
- (void)removeObjectAtIndex:(NSUInteger)index;
- (void)replaceObjectAtIndex:(NSUInteger)index withObject:(id)anObject;
#end
You can try this with NSProxy, but I don't suggest you to use it on production code because:
it will break something (some framework may require NSMutableArray to throw exception when add nil into it to prevent more serious error later. i.e. Fail fast)
it is slow
If you really want to avoid nil checking, I suggest you to make a subclass of NSMutableArray and use it everywhere in your code. But really? There are so many ObjC code using NSMutableArray, most of them doesn't need this feature. So why you are so special?
#import <objc/runtime.h>
#interface XLCProxy : NSProxy
+ (id)proxyWithObject:(id)obj;
#end
#implementation XLCProxy
{
id _obj;
}
+ (void)load
{
Method method = class_getClassMethod([NSMutableArray class], #selector(allocWithZone:));
IMP originalImp = method_getImplementation(method);
IMP imp = imp_implementationWithBlock(^id(id me, NSZone * zone) {
id obj = ((id (*)(id,SEL,NSZone *))originalImp)(me, #selector(allocWithZone:), zone);
return [XLCProxy proxyWithObject:obj];
});
method_setImplementation(method, imp);
}
+ (id)proxyWithObject:(id)obj
{
XLCProxy *proxy = [self alloc];
proxy->_obj = obj;
return proxy;
}
- (void)forwardInvocation:(NSInvocation *)invocation
{
[invocation setTarget:_obj];
[invocation invoke];
const char *selname = sel_getName([invocation selector]);
if ([#(selname) hasPrefix:#"init"] && [[invocation methodSignature] methodReturnType][0] == '#') {
const void * ret;
[invocation getReturnValue:&ret];
ret = CFBridgingRetain([XLCProxy proxyWithObject:CFBridgingRelease(ret)]);
[invocation setReturnValue:&ret];
}
}
-(NSMethodSignature *)methodSignatureForSelector:(SEL)sel
{
return [_obj methodSignatureForSelector:sel];
}
- (Class)class
{
return [_obj class];
}
- (void)addObject:(id)obj
{
[_obj addObject:obj ?: [NSNull null]];
}
- (BOOL)isEqual:(id)object
{
return [_obj isEqual:object];
}
- (NSUInteger)hash {
return [_obj hash];
}
// you can add more methods to "override" methods in `NSMutableArray`
#end
#interface NSMutableArrayTests : XCTestCase
#end
#implementation NSMutableArrayTests
- (void)testExample
{
NSMutableArray *array = [NSMutableArray array];
[array addObject:nil];
[array addObject:#1];
[array addObject:nil];
XCTAssertEqualObjects(array, (#[[NSNull null], #1, [NSNull null]]));
}
#end
You can iterate over all registered classes, check if current class is a subclass of NSMutableArray, if so, swizzle.
I would advice against it, rather act on case-by-case basis to have more predictable behavior - you never know which other system frameworks rely in this particular behavior (e.g. I can see how CoreData might rely on this particular behavior)
You can swizzle any NSMutableArray method in the following way:
#implementation NSMutableArray (Swizzled)
+ (void)load
{
Method orig = class_getInstanceMethod(NSClassFromString(#"__NSArrayM"), NSSelectorFromString(#"addObject:"));
Method override = class_getInstanceMethod(NSClassFromString(#"__NSArrayM"), #selector(addObject_override:));
method_exchangeImplementations(orig, override);
}
- (void)addObject_override:(id)anObject
{
[self addObject_override:anObject];
NSLog(#"addObject called!");
}
Related
Is there possibility to create custom annotation which will check if parameter of method is empty array or empty string? Something like #NotEmpty in Java. I already use _Nonnull and check parameter with NSParameterAssert but I am curious does we can write custom annotation?
Thanks.
You can use macros to define an inline function.
#define isNil(x) nil ==x
Objective-C has no customizable annotations of this sort, but one of its major strengths is the versatility of its runtime.
And so, if we really wanted to, we could implement this with a wrapper class type:
#interface NotEmpty<Object> : NSProxy
#property(readonly,copy) Object object;
+ (instancetype)notEmpty:(Object)object;
- (instancetype)initWithObject:(Object)object;
#end
#implementation NotEmpty {
id _object;
}
- (id)object {
return _object;
}
+ (instancetype)notEmpty:(id)object {
return [[self alloc] initWithObject:object];
}
- (instancetype)initWithObject:(id)object {
if ([object respondsToSelector:#selector(length)]) {
NSParameterAssert([object length] != 0);
} else {
NSParameterAssert([object count] != 0);
}
_object = [object copy];
return self;
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector {
if (selector == #selector(object)) {
return [NSMethodSignature signatureWithObjCTypes:"#:"];
} else {
return [_object methodSignatureForSelector:selector];
}
}
- (void)forwardInvocation:(NSInvocation *)invocation {
invocation.target = _object;
[invocation invoke];
}
#end
#interface SomeClass : NSObject
#end
#implementation SomeClass
- (void)method:(NotEmpty<NSString*> *)nonEmptyString {
// Call NSString methods, option 1
unsigned long length1 = [(id)nonEmptyString length];
// Call NSString methods, option 2
unsigned long length2 = nonEmptyString.object.length;
// Note that just printing `nonEmptyString` (not nonEmptyString.object)
// will print an opaque value. If this is a concern, then also forward
// #selector(description) from -methodSignatureForSelector:.
NSLog(#"Received: %# (length1: %lu length2: %lu)", nonEmptyString.object, length1, length2);
}
#end
int main() {
SomeClass *sc = [SomeClass new];
[sc method:[NotEmpty notEmpty:#"Not an empty string"]];
// [sc method:[NotEmpty notEmpty:#""]]; // Raises error
}
Do note that this will cause some small performance penalties.
I use NSProxy to mock a class, and want to hook all invocation of the class. But only methods called outside the class are hooked, without methods called inside the class. Below is something like my code:
In my AppDelegate.m, TBClassMock is subclass of NSProxy
TBClassMock *mock = [[TBClassMock alloc] init];
TBTestClass *foo = [[TBTestClass alloc] init];
mock.target = foo;
foo = mock;
[foo outsideCalled];
In my TBTestClass.m
- (void)outsideCalled
{
[self insideCalled];
}
- (void)insideCalled
{
}
In my TBClassMock.m
- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector
{
NSLog(#"Signature: %#", NSStringFromSelector(selector));
return [self.target methodSignatureForSelector:selector];
}
-(void)forwardInvocation:(NSInvocation*)anInvocation
{
//... Do other things
[anInvocation invokeWithTarget:self.target];
}
Then I can log the invocation of [foo outsideCalled], but can't log the invocation of [self insideCalled].
I aim to do something in all invocations of the class in //... Do other things, and this way seems failed. Any explanations about this and any other method to implement my requirement? I just don't want to swizzle all methods of the class using method_exchangeImplementations as I think it's too fussy and not a good way.
I guess you've misunderstood the concept of NSProxy. The NSProxy stands for a class, it isn't anything in itself. The way a NSProxy is supposed to be used is, you just call all the methods on proxy as if it were the class it is standing for.
In your code, instead of:
foo = mock;
[foo outsideCalled];
Do:
[mock outsideCalled];
This is my entire code:
#import <Foundation/Foundation.h>
#interface TBTestClass : NSObject
- (void)outsideCalled;
- (void)insideCalled;
#end
#implementation TBTestClass
- (void)outsideCalled;
{
NSLog(#"TBTestClass:outsideCalled");
[self insideCalled];
}
- (void)insideCalled;
{
NSLog(#"TBTestClass:insideCalled");
}
#end
#interface TBClassMock : NSProxy
#property (retain) id target;
#end
#implementation TBClassMock
#synthesize target;
- (void)dealloc
{
self.target = nil;
}
- (id)init
{
self.target = nil;
return self;
}
-(void)forwardInvocation:(NSInvocation *)anInvocation
{
if([self.target respondsToSelector:[anInvocation selector]]){
[anInvocation invokeWithTarget:self.target];
}
}
- (NSMethodSignature*)methodSignatureForSelector:(SEL)selector
{
return [self.target methodSignatureForSelector:selector];
}
#end
int main(int argc, const char **argv)
{
#autoreleasepool {
TBClassMock *mock = [[TBClassMock alloc] init];
TBTestClass *foo = [[TBTestClass alloc] init];
mock.target = foo;
[mock outsideCalled];
[mock release];
[foo release];
}
return 0;
}
Output:
2014-10-23 05:35:06.043 a.out[25483:507] TBTestClass:outsideCalled
2014-10-23 05:35:06.047 a.out[25483:507] TBTestClass:insideCalled
*I definitely need a break... cause was simple - array was not allocated... Thanks for help. Because of that embarrassing mistake, I flagged my post in order to delete it. I do not find it useful for Users ;) *
I have just tried to create a singleton class in iOS, but I probably I am making a mistake. Code (no ARC is a requirement):
#import "PeopleDatabase.h"
#import "Person.h"
#import <Foundation/Foundation.h>
#interface PeopleDatabase : NSObject{objetive
NSMutableArray* _arrayOfPeople;
}
+(PeopleDatabase *) getInstance;
#property (nonatomic, retain) NSMutableArray* arrayOfPeople;
#end
--
#implementation PeopleDatabase
#synthesize arrayOfPeople = _arrayOfPeople;
static PeopleDatabase* instance = nil;
-(id)init{
if(self = [super init]) {
Person* person = [[[Person alloc] initWithName:#"John" sname:#"Derovsky" descr:#"Some kind of description" iconName:#"johnphoto.png" title:Prof] retain];
[_arrayOfPeople addObject:person];
NSLog(#"array count = %d", [_arrayOfPeople count]); // <== array count = 0
[person release];
}
return self;
}
+(PeopleDatabase *)getInstance {
#synchronized(self)
{
if (instance == nil)
NSLog(#"initializing");
instance = [[[self alloc] init] retain];
NSLog(#"Address: %p", instance);
}
return(instance);
}
-(void)dealloc {
[instance release];
[super dealloc];
}
#end
When invoking getInstance like here:
PeopleDatabase *database = [PeopleDatabase getInstance];
NSLog(#"Adress 2: %p", database);
Address 2 value the same value as in getInstance.
The standard way of creating a singleton is like...
Singleton.h
#interface MySingleton : NSObject
+ (MySingleton*)sharedInstance;
#end
Singleton.m
#import "MySingleton.h"
#implementation MySingleton
#pragma mark - singleton method
+ (MySingleton*)sharedInstance
{
static dispatch_once_t predicate = 0;
__strong static id sharedObject = nil;
//static id sharedObject = nil; //if you're not using ARC
dispatch_once(&predicate, ^{
sharedObject = [[self alloc] init];
//sharedObject = [[[self alloc] init] retain]; // if you're not using ARC
});
return sharedObject;
}
#end
Check this apple doc on how to create singleton instance:
https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/CocoaFundamentals/CocoaObjects/CocoaObjects.html
#synchronized(self)
{
if (instance == nil)
NSLog(#"initializing");
instance = [[[self alloc] init] retain];
NSLog(#"Address: %p", instance);
}
You appear to be missing your braces for that if statement. As written, the only thing you do different when instance == nil is emit a log message.
After web reading and personal practicing, my current singleton implementation is:
#interface MySingleton
#property myProperty;
+(instancetype) sharedInstance;
#end
#implementation MySingleton
+ (instancetype) sharedInstance
{
static dispatch_once_t pred= 0;
__strong static MySingleton *singletonObj = nil;
dispatch_once (&pred, ^{
singletonObj = [[super allocWithZone:NULL]init];
singletonObj.myProperty = initialize ;
});
return singletonObj;
}
+(id) allocWithZone:(NSZone *)zone
{
return [self sharedInstance];
}
-(id)copyWithZone:(NSZone *)zone
{
return self;
}
this is a thread safe implementation and avoids the risk to create new objects by calling "alloc init" on your class. Attributes initialization has to occur inside the block, not inside "init" override for similar reasons.
This is an error that can be avoided by some disziplined convention which is to always use curly brackets followed by if and else.
+(PeopleDatabase *)getInstance {
#synchronized(self)
{
if (instance == nil)
NSLog(#"initializing");
instance = [[[self alloc] init] retain];
NSLog(#"Address: %p", instance);
}
return(instance);
}
If instance is nil then the very next statement and only that is executed. And that is the nslog and not the allocation. Then instance is allocated anyway, regardless wether it was used before or not. This will provide you with a new singleton on each call. BTW that causes a leak.
+(PeopleDatabase *)getInstance {
#synchronized(self)
{
if (instance == nil) {
NSLog(#"initializing");
instance = [[[self alloc] init] retain];
NSLog(#"Address: %p", instance);
}
}
return(instance);
}
But this error came in while debugging. It may confuse you but does not solve your original problem. Please add an alloc and init and retain for _arrayOfPeople as well.
-(id)init{
if(self = [super init]) {
Person* person = [[[Person alloc] initWithName:#"John" sname:#"Derovsky" descr:#"Some kind of description" iconName:#"johnphoto.png" title:Prof] retain];
_arrayOfPeople = [[[NSMutableArray alloc] init] retain]; //dont forget the release
[_arrayOfPeople addObject:person];
NSLog(#"array count = %d", [_arrayOfPeople count]); // <== array count = 1 !!!
[person release];
}
return self;
}
In your code _arrayOfPeople is nil and addObject is sent to nil which does not cause an abort but does not do anything either. Then count is sent to nil wich returns 0/nil.
in this function +(PeopleDatabase *)getInstance i think you need to place curly Braces correctly : like this
+(PeopleDatabase *)getInstance {
#synchronized(self)
{
if (instance == nil)
{
NSLog(#"initializing");
instance = [[[self alloc] init] retain];
NSLog(#"Address: %p", instance);
}
return instance ;
}
}
While implementing a subclass of NSArray (a class cluster), I was surprised to see that my overridden description method was not called. Can somebody explain what is happening here?
#interface MyArrayClassCluster : NSArray
#end
#implementation MyArrayClassCluster
{
NSArray *_realArray;
}
// Implement the class cluser stuff here
- (NSUInteger)count
{
return [_realArray count];
}
- (id)objectAtIndex:(NSUInteger)index
{
return [_realArray objectAtIndex:index];
}
// lifeCycle
- (id)initWithItems:(NSArray *)items
{
self = [super init];
_realArray = [items retain];
return self;
}
- (void)dealloc
{
[_realArray release];
[super dealloc];
}
- (NSString *)description
{
return [NSString stringWithFormat:#"My Custom Array: %p, objs:%#", self, _realArray];
}
#end
int main(int argc, const char * argv[])
{
#autoreleasepool {
NSArray *a = #[#1, #2, #3];
NSLog(#"a: %#", a);
MyArrayClassCluster *clzCluster = [[MyArrayClassCluster alloc] initWithItems:a];
NSLog(#"clzCluster: %#", clzCluster);
}
return 0;
}
Output
2013-01-29 18:52:38.704 ClassClusterTester[31649:303] a: (
1,
2,
3
)
2013-01-29 18:52:38.707 ClassClusterTester[31649:303] clzCluster: (
1,
2,
3
)
The link #Rob pointed to had the correct answer at the bottom. An obscure fact: if something implements descriptionWithLocale:, NSLog will call that instead. Since my class is a subclass of NSArray, and NSArray implements that, my version of description was not called.
HI, I have a NSMutableDicitionary contains both lowercase and uppercase keys. So currently i don't know how to find the key in the dictionary irrespective key using objective c.
Categories to the rescue.
Ok, so it's an old post...
#interface NSDictionary (caseINsensitive)
-(id) objectForCaseInsensitiveKey:(id)aKey;
#end
#interface NSMutableDictionary (caseINsensitive)
-(void) setObject:(id) obj forCaseInsensitiveKey:(id)aKey ;
#end
#implementation NSDictionary (caseINsensitive)
-(id) objectForCaseInsensitiveKey:(id)aKey {
for (NSString *key in self.allKeys) {
if ([key compare:aKey options:NSCaseInsensitiveSearch] == NSOrderedSame) {
return [self objectForKey:key];
}
}
return nil;
}
#end
#implementation NSMutableDictionary (caseINsensitive)
-(void) setObject:(id) obj forCaseInsensitiveKey:(id)aKey {
for (NSString *key in self.allKeys) {
if ([key compare:aKey options:NSCaseInsensitiveSearch] == NSOrderedSame) {
[self setObject:obj forKey:key];
return;
}
}
[self setObject:obj forKey:aKey];
}
#end
enjoy.
Do you have control over the creation of the keys? If you do, I'd just force the keys to either lower or upper case when you're creating them. This way when you need to look up something, you don't have to worry about mixed case keys.
You can do this to get the object as an alternative to subclassing.
__block id object;
[dictionary enumerateKeysAndObjectsWithOptions:NSEnumerationConcurrent
UsingBlock:^(id key, id obj, BOOL *stop){
if ( [key isKindOfClass:[NSString class]] ) {
if ( [(NSString*)key caseInsensitiveCompare:aString] == NSOrderedSame ) {
object = obj; // retain if wish to.
*stop = YES;
}
}
}];
You can use a #define shorthand if you find yourself doing this a lot in your code.
Don't think there's any easy way. Your best option might be to create a subclass of NSMutableDictionary, and override the objectForKey and setObject:ForKey methoods. Then in your overridden methods ensure that all keys are converted to lowercase (or uppercase), before passing them up to the superclass methdods.
Something along the lines of the following should work:
#Interface CaseInsensitveMutableDictionary : MutableDictionary {}
#end
#implementation CaseInsensitveMutableDictionary
- (void) setObject: (id) anObject forKey: (id) aKey {
[super setObject:anObject forKey:[skey lowercaseString]];
}
- (id) objectForKey: (id) aKey {
return [super objectForKey: [aKey lowercaseString]];
}
#end