Can not find selector when exchange method - ios

I come across a problem when trying to exchange method:
#implementation LoginViewModel
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Method fromMethod = class_getClassMethod([NSURL class], #selector(URLWithString:));
Method toMethod = class_getClassMethod([self class], #selector(TempURLWithString:));
method_exchangeImplementations(fromMethod, toMethod);
}
});
}
+ (NSURL *)TempURLWithString:(NSString *)URLString {
NSLog(#"url: %#", URLString);
return [LoginViewModel TempURLWithString:URLString];
}
When calling [NSURL URLWithString:], I successfully get the parameter in the exchanged method TempURLWithString:. But it crashed when returning the result from the original implementation:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '+[LoginViewModel
URLWithString:relativeToURL:]: unrecognized selector sent to class
0x10625ff80'
What I want to do is modifying the url String when init NSURL, any one can give me some help, thanks!

The implementation of +[NSURL URLWithString:] is basically the following:
+ (NSURL *)URLWithString:(NSString *)string {
return [self URLWithString:string relativeToURL:nil];
}
The important thing to note here is that the self refers to the NSURL class.
However, when you call [LoginViewModel TempURLWithString:URLString], the self in the original URLWithString: method is now a reference to the LoginViewModel class, meaning that when the original implementation calls [self URLWithString:string relativeToURL:nil], that call gets dispatched to +[LoginViewModel URLWithString:relativeToURL:], which doesn't exist (hence the exception).
You can fix this by also adding a stub for URLWithString:relativeToURL to your class which just forwards the call to +[NSURL URLWithString:relativeToURL:]:
#implementation LoginViewModel
+ (NSURL *)URLWithString:(NSString *)string relativeToURL:(nullable NSURL *)relativeURL {
return [NSURL URLWithString:string relativeToURL:relativeURL];
}
#end

Related

Objective C: Method Swizzling of UITableViewDelegate method

I am using method swizzling in my project using (JRSwizzle). I have implemented swizzling for "UIViewController" and "UIButton". It works fine.
But i am having trouble in swizzling a UITableViewDelegate method "willDisplayCell". Can anyone provide me a working sample of this?
As i understand,
Step 1: We need to create a category of the class we want to swizzle.
Example: UIViewController+Swizzle or UIButton+Swizzle
Step 2: Implement the +load and swizzleMethod like below
+ (void)load
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSError *error;
BOOL result = [[self class] jr_swizzleMethod:#selector(viewWillAppear:) withMethod:#selector(xxx_viewWillAppear) error:&error];
if (!result || error) {
NSLog(#"Can't swizzle methods - %#", [error description]);
}
});
}
- (void)xxx_viewWillAppear
{
[self xxx_viewWillAppear]; // this will call viewWillAppear implementation, because we have exchanged them.
//TODO: add customer code here. This code will work for every ViewController
NSLog(#"xxx_viewWillAppear for - %#", NSStringFromClass(self.class));
}
But, if i create a category for "UITableViewController" and do the method swizzle like below,
+ (void)load
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSError *error;
BOOL result = [[self class] jr_swizzleMethod:#selector(willDisplayCell:) withMethod:#selector(xxx_willDisplayCell) error:&error];
if (!result || error) {
NSLog(#"Can't swizzle methods - %#", [error description]);
}
});
}
- (void)xxx_willDisplayCell
{
}
The BOOL result is always "NO" and says something like "method not found or available". That may be because "willDisplayCell" is a delegate method of "UITableViewDelegate" and not "UITableViewController"
That being the case, i cannot do step 1 above. That is, i cannot create a category for "UITableViewDelegate". XCode doesn't even allow that.
And this is were i am stuck. Completely clueless on how to do this.
Can someone help here!!

Subclassing RKObjectManager multiple times

I have a subclass called MyObjectManager which inherits from RKObjectManager
in MyObjectManager I initialize RestKit (set base url etc etc)
I then subclass MyObjectManager into two different classes:
UserManager and GameManager. Each implement different functions
at first I call UserManager sharedManager and it works fine. I later called GameManager sharedManager with a relevant function, but I get a "unrecognized selector sent to instance" error as it refers to the latter as a UserManager.
I read in the guidelines that it's accepted to subclass RKObjectManager several times, what am I missing?
MyObjectManager implementation:
static MyObjectManager *sharedManager = nil;
#implementation MyObjectManager
+ (instancetype)sharedManager {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSString *baseUrl = [NSString stringWithFormat:#"http://ip/api"];
NSURL *url = [NSURL URLWithString:baseUrl];
sharedManager = [self managerWithBaseURL:url];
sharedManager.requestSerializationMIMEType = RKMIMETypeJSON;
[sharedManager setupRequestDescriptors];
[sharedManager setupResponseDescriptors];
});
return sharedManager;
}
That's because RKObjectManager +sharedManager is a class method. It will instantiate the shared manager on first call and then return it on further calls. Your first call to sharedManager did instantiate a RKObjectManager and your call to GameManager sharedManager did return the same instance. This instance being of class RKObjectManager , it doesn't implement GameManager's functions - thus the unrecognized selector sent to instance error.
That's perfectly acceptable to subclass RKObjectManager but you should probably also subclass the class method +sharedManager inside GameManager to return your own static instance instead of relying on RKObjectManager's one.
EDIT : Based on your edit, you should try to subclass +managerWithBaseURL inside GameManager so that is returns an instance of GameManager instead of RKObjectManager.

Calling dispatch_async in a for loop

I'm trying to call a dispatch_async queue inside a for-loop. A new queue is added for every loop.
queue1 = dispatch_queue_create("com..queue1", DISPATCH_QUEUE_CONCURRENT);
for(NSDictionary *dictInfo in dataArray) {
dispatch_async(queue1,^(void) {
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init];
HeaderValueGenerator *valueGenerator = [HeaderValueGenerator instance] ;
valueGenerator.apiPath = #"path";
valueGenerator.request = request;
[request addValue:[valueGenerator createSignature] forHTTPHeaderField:#"X-Authorization-Client"];
//request is used to call the server. (asynchronous call and hence inside dispatch_async)
}
}
The request is used to make asynchronous call to the server. The server responds with mismatched signature. The createSignature method is not run for some of the requests.
The following is a thread safe singleton. I'm using the singleton as a shared resource for various NSURLRequest for different API calls. So I can't be defining it in all the request objects rather call them using this singleton.
HeaderValueGenerator.h
#import <CommonCrypto/CommonHMAC.h>
#interface HeaderValueGenerator : NSObject
#property (nonatomic,strong) NSString *apiPath;
#property (nonatomic,strong) NSMutableURLRequest *request;
+ (HeaderValueGenerator *)instance;
- (NSString *)createSignature;
#end
HeaderValueGenerator.m
+ (HeaderValueGenerator *)sharedInstance {
static HeaderValueGenerator *sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [[self alloc] init];
});
return sharedInstance;
}
- (NSString *)createSignature {
// String hashing and data encoding the header values for special header fields
return SignedString;
}
When this program runs through the for loop, a few of the loops doesn't execute the createSignature method. As a result I get a bad response from the server. Is there a better way to use dispatch_async?
Is there a deadlock?
EDIT
-(void)createSignature{
#synchronized(self) {
// ... do stuff ...
}
}
Will this help? Defining the method inside synchronized(self)?

class tracking and limiting instances with an NSSet

I'd like my class to detect that a new instance is equivalent (vis a vis isEqual: and hash) to some existing instance, and create only unique instances. Here's code that I think does the job, but I'm concerned it's doing something dumb that I can't spot...
Say it's an NSURLRequest subclass like this:
// MyClass.h
#interface MyClass : NSMutableURLRequest
#end
// MyClass.m
#implementation MyClass
+ (NSMutableSet *)instances {
static NSMutableSet *_instances;
static dispatch_once_t once;
dispatch_once(&once, ^{ _instances = [[NSMutableSet alloc] init];});
return _instances;
}
- (id)initWithURL:(NSURL *)URL {
self = [super initWithURL:URL];
if (self) {
if ([self.class.instances containsObject:self])
self = [self.class.instances member:self];
else
[self.class.instances addObject:self];
}
return self;
}
// Caller.m
NSURL *urlA = [NSURL urlWithString:#"http://www.yahoo.com"];
MyClass *instance0 = [[MyClass alloc] initWithURL: urlA];
MyClass *instance1 = [[MyClass alloc] initWithURL: urlA]; // 2
BOOL works = instance0 == instance1; // works => YES, but at what hidden cost?
Questions:
That second assignment to self in init looks weird, but not insane.
Or is it?
Is it just wishful coding to think that second alloc (of instance1) gets magically cleaned up?
It's not insane, but in manual retain/release mode, you do need to release self beforehand or you'll leak an uninitialized object every time this method is run. In ARC, the original instance will automatically be released for you.
See #1.
BTW, for any readers who usually stop at one answer, bbum's answer below includes a full working example of a thread-safe implementation. Highly recommended for anyone making a class that does this.
Thought of a better way (original answer below the line) assuming you really want to unique by URL. If not, this also demonstrates the synchronization primitive use.
#interface UniqueByURLInstances:NSObject
#property(strong) NSURL *url;
#end
#implementation UniqueByURLInstances
static NSMutableDictionary *InstanceCache()
{
static NSMutableDictionary *cache;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
cache = [NSMutableDictionary new];
});
return cache;
}
static dispatch_queue_t InstanceSerializationQueue()
{
static dispatch_queue_t queue;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
queue = dispatch_queue_create("UniqueByURLInstances queue", DISPATCH_QUEUE_SERIAL);
});
return queue;
}
+ (instancetype)instanceWithURL:(NSURL*)URL
{
__block UniqueByURLInstances *returnValue = nil;
dispatch_sync(InstanceSerializationQueue(), ^{
returnValue = [InstanceCache() objectForKey:URL];
if (!returnValue)
{
returnValue = [[self alloc] initWithURL:URL];
}
});
return returnValue;
}
- (id)initWithURL:(NSURL *)URL
{
__block UniqueByURLInstances* returnValue = self;
dispatch_sync(InstanceSerializationQueue(), ^{
returnValue = [InstanceCache() objectForKey:URL];
if (returnValue) return;
returnValue = [super initWithURL:URL];
if (returnValue) {
[InstanceCache() setObject:returnValue forKey:URL];
}
_url = URL;
});
return returnValue;
}
- (void)dealloc {
dispatch_sync(InstanceSerializationQueue(), ^{
[InstanceCache() removeObjectForKey:_url];
});
// rest o' dealloc dance here
}
#end
Caveat: Above was typed into SO -- never been run. I may have screwed something up. It assumes ARC is enabled. Yes, it'll end up looking up URL twice when using the factory method, but that extra lookup should be lost in the noise of allocation and initialization. Doing that means that the developer could use either the factory or the initializer and still see unique'd instances but there will be no allocation on execution of the factory method when the instance for that URL already exists.
(If you can't unique by URL, then go back to your NSMutableSet and skip the factory method entirely.)
What Chuck said, but some additional notes:
Restructure your code like this:
+(NSMutableSet*)instances
{
static NSMutableSet *_instances;
dispatch_once( ...., ^{ _instances = [[NSMutableSet alloc] init];});
return instances;
}
Then call that method whenever you want access to instances. It localizes all the code in one spot and isolates it from +initialize (which isn't really a big deal).
If your class may be instantiated from multiple threads, you'll want to surround the check-allocate-or-return with a synchronization primitive. I would suggest a dispatch_queue.

ios singleton class crashes my app

I have a problem with an singleton pattern.
I have read the following tutorials about singleton classes and have created my own.
http://www.galloway.me.uk/utorials/singleton-classes/
http://www.johnwordsworth.com/2010/04/iphone-code-snippet-the-singleton-pattern/
The first time i build & run the app it works like it should. No problems at all!
But when i rebuild the app the singleton class does not work properly anymore. The first init works like it should but when i call it again after a button click it crashes my app.
My singleton class:
BPManager.h
#interface BPManager : NSObject {
NSString *dbPath;
}
#property (nonatomic, retain) NSString *dbPath;
+ (id)bpManager;
- (void)initDatabase:(NSString *)dbName;
- (int)getQuestions;
#end
BPManager.m
static BPManager *sharedMyManager = nil;
#implementation BPManager
#synthesize dbPath;
- (void)initDatabase:(NSString *)dbName
{
dbPath = dbName;
}
-(int)getQuestions
{
NSLog(#"getQuestions");
}
- (id)init {
if ((self = [super init])) {
}
return self;
}
+ (BPManager *) bpManager {
#synchronized(self) {
if(sharedMyManager != nil) return sharedMyManager;
static dispatch_once_t pred; // Lock
dispatch_once(&pred, ^{ // This code is called at most once per app
sharedMyManager = [[BPManager alloc] init];
});
}
return sharedMyManager;
}
- (void)dealloc {
[dbPath release];
[super dealloc];
}
When i call the following code when building my interface, the app creates the singleton:
BPManager *manager = [BPManager bpManager];
[manager initDatabase:#"database.db"];
Note: At this point i can create references to the class from other files as well. But when i click on a button it seems to loose his references.
But when a button is clicked, the following code is ecexuted:
BPManager *manager = [BPManager bpManager];
int count = [manager getQuestions];
The app should get the sharedInstance. That works, only the parameters (like dbPath) are not accessible. Why is that?
Edit:
after some research, i have changed the method to:
+ (BPManager *) bpManager {
#synchronized(self) {
if(sharedMyManager != nil) return sharedMyManager;
static dispatch_once_t pred; // Lock
dispatch_once(&pred, ^{ // This code is called at most once per app
sharedMyManager = [[BPManager alloc] init];
});
}
return sharedMyManager;
}
But the problem is not solved
How about
#interface BPManager : NSObject
#property (nonatomic, copy) NSString *dbName;
#property (nonatomic, assign) int questions;
-(id) initWithDBName:(NSString*) dbName {
#end
#import "BPManager.h"
#implementation BPManager
#synthesize dbName=_dbName, questions;
+(BPManager *)singleton {
static dispatch_once_t pred;
static BPManager *shared = nil;
dispatch_once(&pred, ^{
shared = [[BPManager alloc] initWithDBName:#"database.db"];
});
return shared;
}
-(id) initWithDBName:(NSString*) dbName {
self = [super init]
if (self) self.dbName = dbName;
return self;
}
-(void)dealloc {
[_dbName release];
[super dealloc];
}
#end
BPManager *manager = [BPManager singleton];
int count = [manager questions];
The static is private to the implementation file but no reason it should be even accessible outside the singleton method. The init overrides the default implementation with the default implementation so it's useless. In Objective-C you name the getter with the var name (count), not getCount. Initializing a class twice causes an undefined behaviour. No need to synchronize or check for if==nil when you are already using dispatch_once, see Care and Feeding of Singletons. NSString should always use copy instead retain in #property. You don't need the dealloc because this is going to be active forever while your app is running, but it's just there in case you want to use this class as a non singleton . And you probably are as good with this class being an ivar in your delegate instead a singleton, but you can have it both ways.
I'm not sure whether it's the (complete) answer, but one major flaw is that you're using instance variables (self, super) in a class method, +(id)bpManager; I'm actually surprised it let you compile that at all. Change the #synchronized(self) to #synchronized(sharedMyManager), and the [[super alloc...] init] to [[BPManager alloc...] init]. And, writing that just made me realize that the problem looks like accessing a subclassed method on an object instantiated as the superclass, but that should have been overwritten in the dispatch. Shouldn't you really only need one of those anyway, why double-init like that? (And while we're there, that's a memory leak - init'd in the if() and then overwritten in the closure without releasing it.)
The solution of Jano must work well. I use this way too to create singleton object. And I don't have any problem.
For your code, I think that if you use #synchronized (it's not necessary cause your have dispatch_once_t as Jano said), you should not call return in #synchronized.
+ (BPManager *) bpManager {
#synchronized(self) {
if(sharedMyManager == nil) {
static dispatch_once_t pred; // Lock
dispatch_once(&pred, ^{ // This code is called at most once per app
sharedMyManager = [[BPManager alloc] init];
});
}
}
return sharedMyManager;
}

Resources