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];
Related
I implementing custom subclass of the NSURLProtocol. I need to store my NSURLProtocol instance inside NSMutableURLRequest. Since [NSURLProtocol setProperty:forKey:inRequest:] raises warning Invalid protocol-property list if you try to store non-plist-serializable object, I do it like this:
- (void)startLoading {
...
// when I need to store an NSURLProtocol subclass
[NSURLProtocol setProperty:[NSNumber numberWithLongLong:(long long)self] forKey:#"WebProxyURLProtocol" inRequest:mutableRequest];
...
}
and
// when I need to get an NSURLProtocol subclass back in NSURLSessionDelegate
- (NSURLProtocol *)protocolForTask:(NSURLSessionTask *)task {
NSNumber *number = [NSURLProtocol propertyForKey:#"WebProxyURLProtocol" inRequest:task.originalRequest];
return (__bridge NSURLProtocol *)(void *)number.longLongValue;
}
This works quite well. But is it safe and correct way to solve my problem, or I sometimes can get already deallocated object? Thanks!
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
This question already has answers here:
How do I declare an array as a constant in Objective-c?
(6 answers)
Closed 9 years ago.
In my app I refer to a number of the same string variables from lots of different view controllers and I have created a number of global NSStrings using this method:
OpeningController.h (before #interface)
extern NSString *stringName;
OpeningController.m (after the #interface { } #end)
NSString *stringName =#"On";
I can then refer to/alter stringName anywhere in my application.
I want to be able to do the same with an array of strings but when I try the following I get the error Initializer is not a compile-time constant.
How do I achieve what I am trying to achieve?
OpeningController.h
extern NSArray *arrayName;
OpeningController.m (after the #interface { } #end)
NSArray *arrayName = [NSArray arrayWithObjects:
#"String1",
#"String2",
#"String3",
#"String4",
nil];
Assuming openingController is a class name (hint: it should be OpeningController) then you can initialize the array within the class's +initialize method, which will be invoked as soon as the class is referenced at runtime:
OpeningController.m:
#import "OpeningController.h"
NSArray *arrayName = nil;
#implementation OpeningController
+ (void)initialize {
arrayName = [NSArray arrayWithObjects:
#"String1",
#"String2",
#"String3",
#"String4",
nil];
}
....
#end
EDIT: Note that this isn't a particularly good example as referencing the array without referencing OpeningController first will access the un-initialized array. A better approach is a singleton pattern:
OpeningController.h:
#interface OpeningController : UIViewController // Not sure of the subclass
+ (NSArray *)array;
...
#end
OpeningController.m:
#import "OpeningController.h"
#implementation OpeningController
+ (NSArray *)array {
static NSArray *array = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
array = [NSArray arrayWithObjects:
#"String1",
#"String2",
#"String3",
#"String4",
nil];
});
return array;
}
...
#end
Don't use shared global variables. They create very strong coupling between your different classes, and force all sorts of interdependencies. That leads very quickly to spaghetti-code.
Instead, create a data container singleton. That is a singleton object (do a google search on the singleton design pattern in Objective C, or even search here) that contain properties that you want to share.
Then any time you want to read or write a global variable, you use your singleton's accessor method to fetch the singleton and then invoke the desired property.
NSUserDefaults is an example of a singleton in Apple's frameworks. The code
[NSUserDefaults sharedUserDefaults] is a class method call that returns a pointer to the user defaults singleton.
Your code might look something like [MyDataSingleton sharedDataSingleton].
You mus init the array, otherwise it will not work.
You can create a instance of the class and get that instance everytime you want.
If you have questions please tell me :)
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.
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...