swizzle NSMutableArray addObject: failed - ios

I failed to swizzle NSMutableArray addObject. code as follows:
Method ori_Mehtod = class_getInstanceMethod([self class], #selector(addObject:));
Method swi_Method = class_getInstanceMethod([self class], #selector(swi_addObject:));
method_exchangeImplementations(ori_Mehtod, swi_Method);
But, I have found a useful way, as follows:
Method orig = class_getInstanceMethod(NSClassFromString(#"__NSArrayM"), NSSelectorFromString(#"addObject:"));
Method override = class_getInstanceMethod(NSClassFromString(#"__NSArrayM"), #selector(addObject_override:));
method_exchangeImplementations(orig, override);
I guess there are something wrong in [self class]. But I don't know where is the problem.

NSMutableArray is a class cluster. Instances are always of a hidden class type (such as the __NSArrayM that you found), so swizzling the NSMutableArray class itself does little good.

I think you're looking for a way to swizzle cluster, have a look at NSObjectSafe, it's a tiny open source framework which hook most of commonly used function of Foundation container such as [NSMutableArray addObject:]

ClassClusters
Refer to the SafeKit Library

Related

How to initialize an NSArray of NSString?

I'm using a property of NSArray type. Then I'm trying to initialize or setting values for the NSArray. When I use shorthand assignment, I'm getting the output. But when I'm trying with long initialization style, I'm not getting the result. What should be the right way for the latter??
Here is the code snippet:
#property NSArray * moods;
//shorthand assignment
self.moods=#[#"Happy",#"Sad"];
NSLog(#"Hello %#",[self moods]);
This is working. But when I tried:
//long initialization style
[[self moods]initWithObjects:#"Happy",#"Sad", nil];
NSLog(#"Hello %#",[self moods]);
This isn't doing the same way. Suggest me something please.
The second example should be:
self.moods = [[NSArray alloc] initWithObjects:#"Happy",#"Sad", nil];
alloc must always be called before init to actually allocate the memory for the object. [self moods] is going to return nil until you assign something to self.moods.
Edit:
If you really want to avoid the assignment by property dot notation syntax for whatever reason, you can use the setter method instead:
[self setMoods: [[NSArray alloc] initWithObjects:#"Happy",#"Sad", nil]];
The answer above is completely correct. I would love just to add a comment for the sake of completeness but I can't so I'll add an extra answer to give all the options.
You can use the convenience initializers if you always get confused with the order of the alloc and init. Or if you want to have cleaner code.
self.moods = [NSArray arrayWithObjects:#"Happy",#"Sad", nil];
But the answer above it's perfect and I personally prefer the more explicit alloc init pattern.
just some alternative approach without dots... ;)
[self setMoods:#[#"Happy", #"Sad"];

Expected identifier error when calling a singleton method iOS

I'm trying to call a method from a class that uses a singleton pattern on a different class. I'm using a tutorial that doesn't use a singleton that works great, but I can't seem to implement what I need. I keep getting an error that says Expected identifier. I'm assuming this is probably due to syntax, but I can't seem to resolve it. The POI is an NSManagedObject subclass from core data.
This is my version using a singleton. The error points at the bracket before DataSource.
NSArray *itemGroups = [POI [DataSource sharedInstance] fetchDistinctItemGroupsInManagedObjectContext:managedObjectContext];
Here is the equivalent (from the tutorial) without a singleton
NSArray *itemGroups = [POI fetchDistinctItemGroupsInManagedObjectContext:managedObjectContext];
If you want be able to use POI class in this way:
NSArray *itemGroups = [POI fetchDistinctItemGroupsInManagedObjectContext:managedObjectContext];
then I will suggest to add such code:
// POI.h
+ (NSArray *)fetchDistinctItemGroupsInManagedObjectContext:(NSManagedObjectContext *)context;
// POI.m
+ (NSArray *)fetchDistinctItemGroupsInManagedObjectContext:(NSManagedObjectContext *)context {
return [[DataSource sharedInstance] fetchDistinctItemGroupsInManagedObjectContext: context];
}
You should have somethig like: [[POI sharedInstance] fetchDistinctItemGroupsInManagedObjectContext:managedObjectContext];

Crashing On CopyWithZone: Method

I have created one class which is subclass of NSObject(nsme as GroupClass).In that class I have created one property which is belonging from 'id' ,like
#property(nonatomic,retain)id myObj;
Now I am standing on way where I have one mutable array,that array contains instances of my GroupClass.so I am getting one copy in similar way
GroupClass* objG=[array objectAtIndex:i];
now I want one another copy of objG.I searched .And I found NSCopying Protocol.So I added as delegate NSCopying to GroupClass and also added copyWithZone method.Here it is
-(id)copyWithZone:(NSZone *)zone
{
GroupClass *copy = [[[self class] allocWithZone: zone] init];
copy.myObj=[myObj copyWithZone: zone];
return copy;
}
Here I need deep copy.but it is always crashing after allocation line.Please help me.Thanking You.
Not all NSObject subclasses adopt the NSCopying protocol. The problem sounds like you are sending this message to an object which doesn't recognise it.
It doesn't matter that your GroupClass adopts NSCopying, that is not the problem. You are calling copyWithZone: on myObj, whatever that is.
This line of code:
copy.myObj=[myObj copyWithZone: zone];
Is almost certainly what is causing the crash.
EDIT
Not knowing what myObj is, I would recommend that if it is possible, and isn't a bad idea, that you subclass whatever the class is.
If it is a UIView for example, I would subclass it like so:
#class JVView : UIView <NSCopying>
I would then implement the copyWithZone: method appropriately, copying any of the properties of the instance that you feel are necessary.

Objective-C: Mutating parameters

Is it safe to have a method edit a parameter and not be inside a category.
For example, just having a method like this in a ViewController subclass:
-(void)editArray:(NSMutableArray*)array
{
[array removeLastObject];
}
And calling [self editArray:(someArray)]; inside the view controller where someArray is a strong atomic property. It seems to work when I test it, I just don't know if this is discouraged. I know I could easily do this in a category, I just want to know if something like this is safe.
This is safe, as long you're not modifying the reference, just the contents of the parameter object. E.g. assigning to array inside the method would not work, you'd need to pass it by reference instead. As regards style, consider how the errors are returned from many Cocoa Touch framework methods:
NSError *error = nil;
[NSFileManager defaultManager] removeItemAtPath:filePath error:&error];
Here, too, the parameter object, in this case &error, is modified inside the method. So I think you're good from the style perspective as well :)
If you change the array while it is being mutated Cocoa will throw an exception *** Collection <__NSArrayM: 0x7f96432004c0> was mutated while being enumerated.
Consider the code:
NSMutableArray* a = [[NSMutableArray alloc] initWithArray:#[#"a", #"b", #"c"]];
for (id x in a) {
[a removeLastObject];
}
In this case it would be unsafe and cause your application to crash. Abstracting the removeLastObject message into another message doesn't really change anything.
It will also not be safe if done on a background thread.

Strange behavoir when decoding an NSArray via NSSecureCoding

i spent all afternoon banging my head against the wall trying to figure out why decoding of this class was failing. the class has a property that is an NSArray of Foo objects. Foo conforms to NSSecureCoding, and i have successfully encoded and decoded that class by itself. i was getting an error in initWithCoder: that said failed to decode class Foo. through some experimentation, i discovered that i needed to add [Foo class] to initWithCoder: in order for it to work. maybe this will help someone else who's having the same problem. my question is, why is this necessary? i found no suggestion that this is necessary in apple's documentation.
#import "Foo.h"
#interface MyClass : NSObject <NSSecureCoding>
#property (nonatomic) NSArray *bunchOfFoos;
#end
#implementation MyClass
static NSString *kKeyFoo = #"kKeyFoo";
+ (BOOL) supportsSecureCoding
{
return YES;
}
- (void) encodeWithCoder:(NSCoder *)encoder
{
[encoder encodeObject:self.bunchOfFoos forKey:kKeyFoo];
}
- (id) initWithCoder:(NSCoder *)decoder
{
if (self = [super init])
{
[Foo class]; // Without this, decoding fails
_bunchOfFoos = [decoder decodeObjectOfClass:[NSArray class] forKey:kKeyFoo];
}
return self;
}
#end
For those who are still struggling with this: #Ben H's solution didn't solve my problem. And I keep having the following error message:
Terminating app due to uncaught exception 'NSInvalidUnarchiveOperationException', reason: >'value for key 'NS.objects' was of unexpected class 'ClassA'. Allowed classes are '{(
NSArray
)}'.'
And finally, I realized that for custom classes. You have to use the following function instead decodeObjectOfClasses:
- (id)decodeObjectOfClasses:(NSSet *)classes forKey:(NSString *)key
And you to pass a NSSet of all possible classes in the NSArray to the function above! I am not sure why #Ben H could solve the issue by simply adding a [Foo class] outside of the function. Maybe it is a compiler issue. But anyway, if his solution doesn't work, try this one as well.
I've just encountered similar issue and that was weird and extremely time consuming. I wanted to test my class to be NSSecureCoded correctly with Specta/Expecta. So I've implemented everything as needed specifying class when decoded. At the end of my trials I got weirdest exception:
value for key 'key' was of unexpected class 'MyClass'. Allowed classes are '{(
MyClass
)}'.
Test looked something like that:
MyClass *myClassInstance = ...
NSMutableData *data = [NSMutableData data];
NSKeyedArchiver *secureEncoder = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data];
[secureEncoder setRequiresSecureCoding:YES]; // just to ensure things
NSString *key = #"key";
[secureEncoder encodeObject:myClassInstance forKey:key];
[secureEncoder finishEncoding];
NSKeyedUnarchiver *secureDecoder = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];
[secureDecoder setRequiresSecureCoding:YES];
MyClass *decodedInstance = [secureDecoder decodeObjectOfClass:[MyClass class] forKey:key]; // exception here
[secureDecoder finishDecoding];
...expect...
While plain NSCoding (requiresSecureCoding = NO) test succeeded, NSSecureCoding tests kept failing. After vast range of trials I found solution for that, just a single line:
[secureDecoder setClass:[MyClass class] forClassName:NSStringFromClass([MyClass class])];
After that all my tests succeeded, objects were created as expected.
I'm not sure why did that happened, my guess would be that class is not visible as Ben H suggested and it uses something like NSClassFromString(#"MyClass"). The above code worked fine in AppDelegate. MyClass was from development pods I'm developing.
i think i may have figured this out. without the line [Foo class], there is no reference to the class Foo in this file. because of this, i believe the compiler is optimizing the Foo class out, and then the Foo objects within the array cannot be decoded. having [Foo class] in there prevents this.
Yuchen's answer is/was on the right track but the important thing to know is that the NSSet parameter needs to include the class for the collection in addition to the custom class, like so:
_bunchOfFoos = [decoder decodeObjectOfClasses:[NSSet setWithObjects:[NSArray class], [Foo class], nil] forKey:kKeyFoo];
At least that's what seems to be working for me at this point...

Resources