Objective C: Method Swizzling of UITableViewDelegate method - ios

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!!

Related

Get specific instance of class from inside super class class method

+ (NSURLSessionDataTask *)login:(NSString*)email andPassword:(NSString*)password andCallback:(void (^)(NSArray *responseArray, NSError *error))block {
if(![self hasInternet]){return nil;}
NSLog(#"Session.login");
[APIClient sharedClient].requestSerializer = [AFJSONRequestSerializer serializer];
[[APIClient sharedClient].requestSerializer setValue:email forHTTPHeaderField:#"email"];
[[APIClient sharedClient].requestSerializer setValue:password forHTTPHeaderField:#"password"];
[[APIClient sharedClient].requestSerializer setValue:#"poop" forHTTPHeaderField:#"apikey"];
return [[APIClient sharedClient] POST:#"/login" parameters:nil progress:nil success:^(NSURLSessionDataTask * __unused task, id JSON) {
NSLog(#"session.loginWithEmail.response:%#",JSON);
if([JSON objectForKey:#"user"]){
NSMutableDictionary *user=[[NSMutableDictionary alloc] initWithDictionary:[[JSON objectForKey:#"user"] copy]];
[user setObject:password forKey:#"password"];
[[Session sharedInstance] startSession:user];
if([[Session sharedInstance] isSessionActive]){
if([JSON objectForKey:#"req_onboarding"]){
NSLog(#"session.onboard!=nil");
[Session sharedInstance].requiredOnboarding=[JSON objectForKey:#"req_onboarding"];
}
if (block) {
NSLog(#"session.login.block.success");
block(nil, nil);
}
}else{
NSLog(#"Failed to set session");
}
}
} failure:^(NSURLSessionDataTask *__unused task, NSError *error) {
if (block) {
NSLog(#"Session.login.Fail");
block([NSArray array], error);
}
}];
}
I needed a sub-class-able singleton in order to be able to have a abstracted session manager that does most of the lifting,but can still be subclassed so that multiple sessions can co-exist and still have the power of being available throughout my app. Im building somewhat of a demo of all my apps which is why this functionality is important.
All was going well until I realized that my api methods that are hosted in the super session class were referencing the singleton itself to set the session, this is a problem bc sharedInstance is referenced like so:
+ (instancetype)sharedInstance
{
NSLog(#"[Master sharedInstance]");
id sharedInstance = nil;
#synchronized(self) {
NSLog(#"MS | synchronized(self)");
NSString *instanceClass = NSStringFromClass(self);
// Looking for existing instance
sharedInstance = [_sharedInstances objectForKey:instanceClass];
// If there's no instance – create one and add it to the dictionary
if (sharedInstance == nil) {
NSLog(#"MS | sharedInstance == nil");
sharedInstance = [[super allocWithZone:nil] init];
[_sharedInstances setObject:sharedInstance forKey:instanceClass];
NSLog(#"MS | SharedInstances:%#",_sharedInstances);
}
}
return sharedInstance;
}
When it was just the one Session singleton I could get away with doing this in class methods: [Session sharedInstance] isSessionActive]
but now, its essential that [______ sharedInstance] isSessionActive];
is a reference to the specific subclass calling the class method. Is it possible to retrieve reference the specific instance from within this class method shy of sending it as a param?
It looks like the aim is to distribute a singleton per subclass of the super (probably abstract) class. The sharedInstance code doesn't quite do that because this line:
sharedInstance = [[super allocWithZone:nil] init];
will create instances only of the superclass. I think you want instances of the subclasses so that you get access to the overridden data and behavior.
If I understand your aim correctly, then the fix is simple:
sharedInstance = [[self allocWithZone:nil] init]; // notice "self"
With this, when you send sharedInstance to ClassA, you'll get a (single) instance of ClassA. When you send it to ClassB, you'll get a (single) instance of ClassB. With change I suggest, those will really be instances of the subclasses A and B, not an instance of the class they both inherit from. If they've each overridden isSessionActive or any other superclass method, the caller will get the distinct, overridden implementation based on which class singleton they ask for.

Parse saveInBackground when iPhone is locked?

I want to be able to save a users information while the iOS device is locked. I'm using background modes but the saveInBackground only seems to work for about 5 seconds then it stops saving. When re open the app it saves them all at once. is there a way to keep them saving when it's locked?
Thanks
**Cloud Code (JS) **
Parse.Cloud.define("createSale", function(request, response) {
var SaleClass = Parse.Object.extend("Sale");
var sale = new SaleClass();
sale.set("contractorId", request.params.contractorId);
sale.set("subtotal", request.params.subtotal);
sale.save(null, {
success: function (sale) {
console.log(responseData);
response.success(responseData);
},
error: function (error) {
response.error(error);
console.log(error);
}
});
});
Share Instance of ParseCloudFunctions (Obj-C)
I created a class that holds all my Parse Cloud Code Functions. I create a shared instance of this class and have an instance method that calls the Parse Cloud Code:
#import <Parse/Parse.h>
+ (ParseCloudFunctions *) sharedInstance {
static ParseCloudFunctions *sharedInstance;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[ParseCloudFunctions alloc] init];
});
return sharedInstance;
}
- (void) createSaleWithParameters: (NSDictionary *) parameters block:(void (^)(id object, NSError *error)) block{
[PFCloud callFunctionInBackground:#"createSale" withParameters:parameters block:block];
}
Whenever you want call the parse cloud code, you just do:
NSDictionary *parameters = #{
#"contratorId":contractorId,
#"subtotal":subtotal,
};
[[ParseCloudFunctions sharedInstance] createSaleWithParameters:parameters block:^(id object, NSError *error) {
if(error){
//Handle Error
}
if(object){
//Do stuff
}
}];
The saving is handled on the server side, so you just need a bit of time to call the cloud code and you'll be good.

openParentApplication:reply: error with asynchronous network call in containing app

I'm getting stuck with an error when using my Watchkit Application. When I launch it, I ask the containing iOS app to get some data from network. The problem is that I get an error saying the containing app never calls 'reply()' :o But looking at my code, it should call it.
I tried to debug every step from openParentApplication to the 'reply()' call, and it seems to work well =X
Here is my code in the Watchkit extension
- (void)initDiaporamasWithSuccess:(void (^)())success andFailure:(void (^)(NSError*))failure {
NSLog(#"Ask to load diapos");
__weak typeof(self) weakSelf = self;
[WKInterfaceController openParentApplication:#{#"watchKit": #"watchKit.initDiapos"} reply:^(NSDictionary *replyInfo, NSError *error) {
if (error) {
NSLog(#"%#", error);
if (failure) {
failure(error);
}
return;
}
NSLog(#"got items : %#", replyInfo[#"diapos"]);
weakSelf.diaporamas = replyInfo[#"diapos"];
[weakSelf setDiaporama:replyInfo[#"firstDiapo"] AtIndex:0];
if (success) {
success();
}
}];
}
The result should be an NSDictionary containing an NSArray with some diaporamas basic informations, and an object (Diapo) containing the full informations of the first diaporama (e.g. self.diaporamas[0])
And here is the code in the containing app's AppDelegate :
- (void)application:(UIApplication *)application handleWatchKitExtensionRequest:(NSDictionary *)userInfo reply:(void (^)(NSDictionary *))reply {
// Maybe we could handle multiple watchKit extension calls that way ?
// Something like a key-value 'protocol' to run the right block of code
NSString *watchKitCall = userInfo[#"watchKit"];
NSLog(#"watchKit handled");
if ([watchKitCall isEqualToString:#"watchKit.initDiapos"]) {
[AppDelegate watchInitialObjects:^(NSDictionary *info) {
NSLog(#"Managed to get initial infos");
reply(info);
} failure:^(NSError *error) {
NSLog(#"Fail : %#", error);
reply(#{#"error": error});
}];
}
}
+ (void) watchInitialObjects:(void (^)(NSDictionary *info))success failure:(void (^)(NSError *error))failure {
NSDictionary *parameters = #{#"site" : #(14), #"limit" : #(10)};
[AppDelegate requestDiapoListWithParams:parameters success:^(NSArray *items) {
if ([items count] == 0)
{
NSError *error = [NSError errorWithDomain:#"com.domain.app" code:404 userInfo:nil];
failure(error);
return;
}
Diapo *firstDiapo = [items firstObject];
[AppDelegate requestDiapoDetailWithDiapo:firstDiapo success:^(Diapo *diapo) {
if (!diapo)
{
NSError *error = [NSError errorWithDomain:#"com.domain.app" code:404 userInfo:nil];
failure(error);
return;
}
NSDictionary *result = #{
#"firstDiapo" : diapo,
#"diapos" : items
};
success(result);
} failure:^(NSError *error) {
failure(error);
}];
} failure:^(NSError *error) {
failure(error);
}];
}
In the watchKitHandler, I call watchInitialObjects to get the diaporamas array and the first diaporama's informations.
In the watchInitialObjects, I make a first network call to get the array, and on success, I make an other network call to get the firs diaporama informations.
To make the calls and map the JSON into objects, I use RESTKit
I really don't get what could be the error =x
UPDATE
I forgot to write the error I get, here it is :
Error Domain=com.apple.watchkit.errors Code=2 "The UIApplicationDelegate in the iPhone App never called reply() in -[UIApplicationDelegate application:handleWatchKitExtensionRequest:reply:]" UserInfo=0x7fcb53e12830 {NSLocalizedDescription=The UIApplicationDelegate in the iPhone App never called reply() in -[UIApplicationDelegate application:handleWatchKitExtensionRequest:reply:]}
And I kept trying to know why I get this error, and I think I found it:
It seems that there is a (very little) timeout to do the work in the containing app. But I mapped the JSON data I received directly in the containing app and then, send those custom objects in the reply(). But when I removed the mapping part, it worked well !
So...that's why I think that was the problem =X
Does anybody could approve my thoughts or corrects me ?
After hours of searching and testing different codes, I finally found my problem...and it's obvious when we read the Apple documentation about 'application:handleWatchKitExtensionRequest:reply:' seriously...
here is the answer : (it's in the documentation)
The contents of the dictionary must be serializable to a property list file.
Which means that objects can ONLY be dictionaries, arrays, strings, numbers (integer and float), dates, binary data, or Boolean values
...I feel dumb ><

Calling objective-C typedef block from swift

I'm trying to call a method from swift.
The method is in a singleton written in objective-C
the block in the header file:
typedef void(^VPersonResultBlock)(Person *person, NSError *error);
- (void)askForMe:(VPersonResultBlock)block;
and here's the implementation of that method.
- (void)askForMe:(VPersonResultBlock)block
{
if (_me) block(_me,nil);
else {
[Person getMeWithBlock:^(PFObject *person, NSError *error) {
if (!error) {
_me = (Person *)person;
block(_me,nil);
}
else if (error) {
block(nil,error);
}
else {
NSDictionary *userInfo = #{
NSLocalizedDescriptionKey: NSLocalizedString(#"Operation was unsuccessful.", nil),
NSLocalizedFailureReasonErrorKey: NSLocalizedString(#"The operation failed to retrieve the user.", nil),
NSLocalizedRecoverySuggestionErrorKey: NSLocalizedString(#"Check your network connection and try again", nil)
};
NSError *error = [[NSError alloc] initWithDomain:#"VisesAsyncErrorDomain" code:-10 userInfo:userInfo];
block(nil,error);
}
}];
}
}
In Objective-C, I can call this and it autocompletes without confusion.
[[VDataStore instance] askForMe:^(Person *person, NSError *error) {
// do things with myself that aren't strange
}];
Now let's say I want to call the same method from swift. The bridging header is setup, with the header file imported, but swift's expectation is confusing.
VDataStore.askForMe(VDataStore)
This is what shows up in the autocomplete options
(VPersonResultBlock!) -> Void askForMe(self: VDataStore)
what I was hoping for, was for this to autocomplete into a closure, and although it appears to see all of the information correctly, what it's expecting isn't lining up with what objective-C seems to understand.
How do I call this properly from swift?
Directly translate your ObjC calling code to Swift is
VDataStore.instance().askForMe() {
person, error in
// do things with myself that aren't strange
}
Your problem is that askForMe is instance method but you are accessing from class object VDataStore.askForMe. Swift will give you a function object that takes an instance as input.

Getting access to a sharedInstance from within a Class method

I am converting a project to an SDK. I need to convert several instance methods to class methods. I am getting a compiler warning about using "self". The warning is "Incompatible pointer types initializing Store* with an expression of Class. This Store class is a singleton sharedInstance.
I have method like this in my class Store:
+ (void) dispatchStoreSource {
__weak Store *ref = self; <--- issue is here
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSError *error = nil;
NSArray *things = [ref fetchThings:&error];
//dispatch back to main queue
if (![ref updateSource:source forUser:user error:&error]) {
dispatch_async(dispatch_get_main_queue(), ^{
result(nil, error);
});
} else {
dispatch_async(dispatch_get_main_queue(), ^{
result(source, nil);
});
}
});
}
What is the proper way to fix this? Should it I do this?
__weak Store *ref = [Store sharedInstance];
Your ref is pointer to an object of Store class. But self in your class method doesn't point to an allocated object of your class (= your singleton), it's your Class, IOW Store (not object, but Class). If you have implemented sharedInstance class method, like this ...
+ (instancetype)sharedInstance {
static Story *instance;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [[self alloc] init];
});
return instance;
}
... just do this ref = [self sharedInstance];
Yes, You should go with __weak Store *ref = [Store sharedInstance];
Otherwise Let's use your original static reference of Store.
Example :
static Store = _store = nil;
__weak Store * ref = _store;

Resources