Categories in Objective-C aren't working - objective-c

I'm developing an iOS application that needs to deploy to iOS 3.1.3. I need to extend some of the functionality of the NSData class and am using the following code inside NSData+Base64 (truncated to show the interesting part):
NSData+Base64.h
[...]
#interface NSData (Base64)
+ (NSData *)dataFromBase64String:(NSString *)aString;
- (NSString *)base64EncodedString;
#end
NSData+Base64.m
#implementation NSData (Base64)
[...]
//
// base64EncodedString
//
// Creates an NSString object that contains the base 64 encoding of the
// receiver's data. Lines are broken at 64 characters long.
//
// returns an autoreleased NSString being the base 64 representation of the
// receiver.
//
- (NSString *)base64EncodedString
{
size_t outputLength;
char *outputBuffer =
NewBase64Encode([self bytes], [self length], true, &outputLength);
NSString *result =
[[[NSString alloc]
initWithBytes:outputBuffer
length:outputLength
encoding:NSASCIIStringEncoding]
autorelease];
free(outputBuffer);
return result;
}
#end
However, when I try to message this selector:
NSData *HMAC = [[NSData alloc] initWithBytes:cHMAC length:sizeof(cHMAC)];
NSString *hash = [HMAC base64EncodedString];
I get the following error:
-[NSConcreteData base64EncodedString]: unrecognized selector sent to instance 0x6146e70
2010-11-09 13:44:41.443 SpringboardApplication[21318:40b] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[NSConcreteData base64EncodedString]: unrecognized selector sent to instance 0x6146e70'
I read a lot about iOS 3.1.x having problems with categories. I tried adding the flags -all_load and -ObjC (both separately and together) to no avail. I would really appreciate some direction of how to get this selector to work.
Thanks!

It really seems like your category isn't being compiled or linked into the same target that you're using it from. You should make sure that NSData+Base64.m is marked to be compiled by the same target that it's being used from by getting info on the two files and comparing the targets they're assigned to.
A test you can perform is to add a line with an #error error message to NSData+Base64.m, which will cause the build to fail when it gets to that file. Like this:
#error We're now compiling NSData+Base64.m
Then look and see which target fails to compile.

I had the same issue with ARC project which was linking with non-ARC module having category extension.
Fixed the issue by adding "Other Linker Flags: -all_load" in parent ARC project.

Have you #imported the header file for your category? I know it sounds simple, but I forget nearly every time.

There is a great post on The Carbon Emitter about about handling categories in iOS. It details an easy way to handle importing categories to your project.
Make a file containing all of your category imports, in this example it is Extensions.h:
#import "NSDate+Formatting.h"
#import "UIFonts+MyFonts.h"
#import "UIViewController+Tourbot.h"
Add import your file in AppName-Prefix.pch:
#import <Availability.h>
#ifndef __IPHONE_3_0
#warning "This project uses features only available in iPhone SDK 3.0 and later."
#endif
#ifdef __OBJC__
#import <UIKit/UIKit.h>
#import <Foundation/Foundation.h>
#import <QuartzCore/QuartzCore.h>
#import <CoreText/CoreText.h>
#import "Extensions.h" // Add import here
#endif

In My case when I got this error I simply added the .m file in the Compiled resources, and it get worked. This can be achieved by selecting target project->Build Phases->Compile Sources. Then you click on the + button from its bottom left. In this case you may add 'NSData+Base64.m' file to the compile sources. Then you clean your project and run. I guess this may help.

Related

Objective-C library category methods not working in Swift app environment

I have an Objective-C iOS app that is composed of a workspace (AuthPort.xcworkspace) with a project (AuthPort.xcodeproj) and an Objective-C SDK project ("ANet SDK.xcodeproj") which builds a library called “libAnet SDK.a”).
“libAnet SDK.a” is linked in with the AuthPort target Build Phases.
In the SDK, there are some categories on NSString which get built into “libAnet SDK.a”. They are named:
NSString+stringWithXMLTag.h and NSString+stringWithXMLTag.m
.
.
.
etc.
Some code in the AuthPort.xcodeproj calls code in the library which in turn calls these category methods.
It all works smoothly in the above Objective-C environment!
Now…I am re-writing the AuthPort code in Swift in a new workspace with a new project and also using the Objective-C SDK which builds “libAnet SDK.a”
So, I created a new Swift Xcode project called AuthPort.xcodeproj and added it to a new workspace called AuthPort.xcworkspace. I also added the authorize.net SDK ("ANet SDK.xcodeproj", written in Objective-C) to the project.
So far, so good. I created an AuthPort-Bridging-Header.h file and imported all the headers from the SDK (without the category headers - more on this later). I also linked in “libAnet SDK.a” in the Build Phases of the AuthPort target.
AuthPort builds and runs with no errors or warnings.
Now, in viewDidLoad of the primary view controller I added code to access library methods to register a new device with the company, authorize.net:
var r: MobileDeviceRegistrationRequest = MobileDeviceRegistrationRequest()
an.mobileDeviceRegistrationRequest(r)
These two calls are the same library methods in both Objective-C and Swift versions. The last method calls other library methods which call methods from the NSString category above.
When I put breakpoints in a category method in the Objective-C environment (all Objective-C code in app and library), the program breaks as expected and I can step through the code.
When I put breakpoints in a category method in the Swift environment (Swift app project with Objective-C library), the debugger never enters a category method. At one point in the execution, the app SIGABRT’s with the following:
2015-04-21 12:25:47.388 AuthPort[4743:2404231] +[NSString stringWithXMLTag:andValue:]: unrecognized selector sent to class 0x38450e18
2015-04-21 12:25:47.390 AuthPort[4743:2404231] * Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '+[NSString stringWithXMLTag:andValue:]: unrecognized selector sent to class 0x38450e18'
* First throw call stack:
(0x29a0a5a7 0x3792fc77 0x29a0f989 0x29a0d949 0x2993eb68 0xd9e4b 0xc301f 0xdd507 0xc4725 0xc44b1 0xb8f28 0xb9e60 0x2cf0eb65 0x2cf0e8d5 0x2d031c43 0x2d031ac7 0x2d03196b 0x2cf1b5f1 0x2d0357b3 0x2d03491f 0x2cf2433b 0x2cf159e1 0x2cf15003 0x2cf14e6b 0x2cf148ef 0x2cf121ef 0x2cf7c031 0x2d16e34f 0x2d170781 0x2d17b1a9 0x2d16f063 0x302880a1 0x299d125d 0x299d0521 0x299cecbf 0x2991bb51 0x2991b963 0x2cf72b8f 0x2cf6dc91 0xb5290 0x37ed8aaf)
libc++abi.dylib: terminating with uncaught exception of type NSException
(lldb)
The [NSString stringWithXMLTag:andValue:] is from the category added by the library onto NSString.
The library method stringOfXMLRequest calls the category method stringWithXMLTag:andValue:
- (NSString *) stringOfXMLRequest {
NSString *s = [NSString stringWithFormat:#""
#"<merchantAuthentication>"
#"%#" //name (optional)
#"%#" //fingerPrint type Object #"%#" //transactionKey or
#"%#" //sessionToken or
#"%#" //password
#"%#" //mobileDeviceId (optional)
#"</merchantAuthentication>",
(self.name ? [NSString stringWithXMLTag:#"name" andValue:self.name] : #""),
(self.fingerPrint ? [self.fingerPrint stringOfXMLRequest] : #""),
(self.transactionKey ? [NSString stringWithXMLTag:#"transactionKey" andValue:self.transactionKey] : #""),
(self.sessionToken ? [NSString stringWithXMLTag:#"sessionToken" andValue:self.sessionToken] : #""),
(self.password ? [NSString stringWithXMLTag:#"password" andValue:self.password] : #""),
(self.mobileDeviceId ? [NSString stringWithXMLTag:#"mobileDeviceId" andValue:self.mobileDeviceId] : #"")];
return s;
}
The SIGABRT doesn't happen until the second call is made to stringWithXMLTag:andValue: for the transitionKey.
The exact same library code is run in both environments.
Here’s the code from stringWithXMLTag:andValue: which is never seen in the debugger:
+ (NSString *)stringWithXMLTag:(NSString *)t andValue:(NSString *)v {
if (v == nil) {
return #"";
}
else {
v = [NSString stringWithEscapedXMLValue:v];
return [NSString stringWithFormat:#"<%#>%#</%#>", t, v, t];
}
}
The problem is solely within the library code where a library method calls another library method which calls a library category method which is never entered in the debugger in the Swift version.
I also tried adding the category header files in the bridging header and adding -ObjC and -all_load to the Other Linker Flags for AuthPort. No luck.
I’ve searched the net for 2 days with no luck. Hopefully one of you will have seen this behavior.
I'm building using Xcode 6.3 and iOS 8.3 (deploying on iOS 8.2).
Thanks.
Check that:
you've added you category to the Umbrella Framework of your Framework project. Use #import "your.h"
I've marked the .h file from the category as public. Select the .h > Identity and Type > Target Membership > Public
In your project, set correct Framework Search Paths

'unrecognized selector sent to instance'?

I'm new to Objective C and having trouble understanding why I am getting this error. I've checked other similar questions, but haven't been able to resolve the issue.
The error is "-[NSConcreteMutableData base64Decoded]: unrecognized selector sent to instance 0x6e15610"
Here is a snippet of the problem code, where the call to base64Decoded is causing the crash.
#import "DDData.h"
- (NSString *)decodeBase64:(NSString *)input
{
NSData* dataDecoded = [[input dataUsingEncoding:NSUTF8StringEncoding] base64Decoded];
return [NSString stringWithUTF8String:[dataDecoded bytes]];
}
And in DDData.h:
#import <Foundation/Foundation.h>
#interface NSData (DDData)
- (NSData *)base64Decoded;
#end
and DDData.m:
#implementation NSData (DDData)
- (NSData *)base64Decoded
{
// Excluding function code, as it never gets to here
}
#end
Just a note that the Project has ARC enabled. Any ideas as to what might be the issue here? Thanks.
EDIT: I have adjusted the above code to help debug the error:
NSData* dataDecoded = [input dataUsingEncoding:NSUTF8StringEncoding];
[dataDecoded base64Decoded];
dataDecoded gets a value from dataUsingEncoding, it is not nil when the call to base64Decoded is made. When I step over to the called to base64Decoded, it crashes.
Insert a break point in your code and step through it and you'll see exactly where it breaks.
You may also want to check that the DDData files are properly included in your project by looking at the target membership of those files, the .m should be ticked.
SimonH pointed out the solution correctly in one of the sub-comments. I was having the same problem with a custom method i defined in a NSData category. The solution better explained:
Make sure the .m file is included in the projects Build Phases->Compile Sources.
Right click on the .m file in the project navigator and click "Show file Inspector". Under File Inspector make sure you check the target you are building for otherwise it wont be included and the calling that method will crash.
You get that kind of message if you try to execute an undefined method on an object. Try it like this:
NSData *dataDecoded = [[input dataUsingEncoding:NSUTF8StringEncoding] base64EncodedString];
There is no base64Decoded method as far as I know, but there is base64EncodedString. So when you send the base64Decoded message to your NSData object, it isn't recognized because it's simply not there.
Follow below debugging steps to resolve it.
Put breakpoints in your code and check step by step where that is breaking.
Also, check if you have added DDData.m source file in your project target properly.
You should also check the object presence before using it. Check below sample code.
- (NSString *)decodeBase64:(NSString *)input {
if(input) {
NSData *utfData = [input dataUsingEncoding:NSUTF8StringEncoding];
if(utfDFata) {
NSData* dataDecoded = [utfDFata base64Decoded];
return [NSString stringWithUTF8String:[dataDecoded bytes]];
}
}

PhoneGap plugin: AudioEncode success callback never called

I'm using the AudioEncode plugin for PhoneGap (Cordova) on iOS. After updating a couple of lines for a newer version of Cordova, it appears to be correctly encoding wav files as m4a. In the Xcode console I see:
AVAssetExportSessionStatusCompleted
doing success callback
When I look at the file system on the phone, the wav file has indeed become a m4a. However, the success callback (where I upload the file to a server) is never run. This is the relevant code in the plugin:
-(void) doSuccessCallback:(NSString*)path {
NSLog(#"doing success callback");
NSString* jsCallback = [NSString stringWithFormat:#"%#(\"%#\");", self.successCallback, path];
[self writeJavascript: jsCallback];
[self.successCallback release];
}
My code in the app goes like this:
function encodeSuccess (path) {
console.log('Audio encoded to M4A! Preparing to upload...')
// file transfer code...
}
console.log('Preparing to encode audio file...')
window.plugins.AudioEncode.encodeAudio(entry.fullPath, encodeSuccess, fail)
I'm assuming the doSuccessCallback function in the plugin needs to be updated, but I don't have experience with Objective C or PhoneGap plugins, so I'm stuck at this point.
Any ideas?
UPDATE
In the Objective C function posted above, I tried logging self.successCallback, and it logged as <null>. Then I went up to the top of the main encodeAudio function, and the argument which is assigned to self.successCallback ([arguments objectAtIndex:1]) also logs as <null>. So, it seems to me that the callbacks are not being passed into the main function successfully.
This is the AudioEncode.h file, maybe someone can spot the problem here:
#interface AudioEncode : CDVPlugin {
NSString* successCallback;
NSString* failCallback;
}
#property (nonatomic, retain) NSString* successCallback;
#property (nonatomic, retain) NSString* failCallback;
- (void)encodeAudio:(NSArray*)arguments withDict:(NSDictionary*)options;
Ok, I figured this out by reading the basic examples in the Cordova Plugin Development Guide closely. The problem was with the ordering of parameters for cordova.exec(), which must have changed recently.
I plan to submit a pull request on GitHub with a working version of the plugin, but for now, here's the basic solution.
Before asking this question, I had updated the imports in AudioEncode.h from #import <PhoneGap/PGPlugin.h> to:
#import <Cordova/CDVPlugin.h>
#import <Cordova/CDVPluginResult.h>
Any reference to PGPlugin should also be updated to CDVPlugin, and PhoneGap should become cordova.
Here's the crux of the problem: in AudioEncode.js, cordova.exec() (where the original plugin calls PhoneGap.exec()) needs to be called like this:
AudioEncode.prototype.encodeAudio = function(audioPath, successCallback, failCallback) {
cordova.exec(successCallback, failCallback, "AudioEncode", "encodeAudio", [audioPath]);
};
If you don't order the parameters like this, the callbacks won't be passed in (although audioPath was...). Look at the docs for more details, but the parameters have to be the two callbacks first, the module name, the module action, and finally an array of extra parameters.
Then, you'll need to read in the parameters in the main encodeAudio function like this:
self.callback = [[arguments objectAtIndex:0] retain];
NSString* audioPath = [arguments objectAtIndex:1];
Note that there is only one callback object now, which contains references to the success and fail callbacks. This means that whenever the plugin sets up variables for successCallback and failCallback, you now only need callback (e.g. #synthesize callback). This is also declared in the AudioEncode.h file with #interface and #property.
Now, when actually firing the callbacks & returning data (in the doSuccessCallback and doFailCallback functions), you need to use CDVPluginResult, like this:
CDVPluginResult* pluginResult = nil;
NSString* javaScript = nil;
pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:path];
javaScript = [pluginResult toSuccessCallbackString:self.callback];
[self writeJavascript: javaScript];
[self.callback release];
Until I get the updated module up on GitHub, this should help anyone to get the plugin working.

Crash with SBJSON library

following code is copied from Stig Brautaset JSON library - I removed the comments to be more clear.
#interface NSString (NSString_SBJSON)
- (id)JSONFragmentValue;
- (id)JSONValue;
#end
I have a crash when I call:
NSString *jsonString = [request responseString];
...
NSDictionary *results = [jsonString JSONValue];//here is the crash with invalid selector.
I added this:
if (![jsonString respondsToSelector:#selector(JSONValue)])
{
NSLog(#"fix this!!!\n");
}
and the message is shown. Do you have any idea why this function in not called?
Thanks!
EDIT: I compile JSON framework as a static lib. The call is made in the same lib.
When I compile it as exe it runs without any problem.
You need to both link your static library and your final executable with the -ObjC linker option.
The -JSONValue method is implemented as a category on the NSString class in the file NSString+SBJSON.m file. Because of how the dynamic runtime works in Objective-C, categories defined in static libraries don't get loaded up unless you link with the -Objc flag. If the category doesn't get loaded, then dynamic binding fails, and you get the dreaded "doesn't respond to selector" exception.

twitpic API GSTwitPicEngine error while setAccessToken

I tried to use GSTwitPicEngine class https://github.com/Gurpartap/GSTwitPicEngine for sharing image on twitter
but its giving error
As we have to set [twitpicEngine setAccessToken:token];
if I am right, I am using _accessToken of class SA_OAuthTwitterEngine as token
_accessToken was private in class SA_OAuthTwitterEngine, I set it to public and also
#property (nonatomic, readonly) OAToken *accessToken;
#synthesize accessToken = _accessToken;
and then in action
twitPicEngine = [GSTwitPicEngine twitpicEngineWithDelegate:self];
[twitPicEngine setAccessToken:twitterObj.accessToken];
[twitPicEngine uploadPicture:[shareDict objectForKey:#"image"] withMessage:[shareDict objectForKey:#"image_msg"]];
But Application getting crash while setAccessToken with log
-[OAToken parameters]: unrecognized selector sent to instance 0x6327e30
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[OAToken parameters]: unrecognized selector sent to instance 0x6327e30'
Please help if you able to find any thing wrong
Amit Battan
Unfortunately, GSTwitPicEngine is not using the same version of oauth-consumer than the fabulous Twitter+Oauth library (SAOAuthTwitterEngine). I assume you are using that library for posting messages to twitter.
The crash is because OAToken from Twitter+Oauth doesn´t implement the "parameters" method.
Today I spent the entire morning tweaking the several libraries to avoid crashes.
Here you can download a sample project I created for posting a twitpic photo to twitter with a message.
TestTwitpic
The project has all the latest versions of all libraries from github.
Instructions for making my TestTwitpic project work:
In TestTwitpic-Prefix.pch set the variables for:
#define kTwitterOAuthConsumerKey #""
#define kTwitterOAuthConsumerSecret #""
#define kTwitPicAPIKey #""
In RootViewController you can change these lines to your needs. To change the photo:
//change [UIImage imageNamed:#"image.jpg"] for whatever UIImage you want to upload
//change #"my photo" for whatever title you want for your photo in twitpic website
[twitpicEngine uploadPicture:[UIImage imageNamed:#"image.jpg"] withMessage:#"my photo"];
and this one to change message sent to twitter:
//I post to twitter the title of the photo and the twitpic url but you can post whatever you want
[engine sendUpdate:[NSString stringWithFormat:#"%# %#", [[[response objectForKey:#"request"] userInfo] objectForKey:#"message"], [[response objectForKey:#"parsedResponse"] objectForKey:#"url"]]];
If you want to create your own project based on this sample. Do the following:
Import into your project (drag and drop) the Twitpic folder with all the libraries that are inside.
Add these frameworks to your project:
CoreGraphics
libz.1.2.3
libxml2
MobileCoreServices
SystemConfiguration
CFNetwork
Security
In Build Settings add "$SDKROOT/usr/include/libxml2" to the "Header Search Paths" (mark it as recursive)
In Build Settings add -lxml2 to "Other linker flags"
If you want to know what I did for fixing the libraries, I will tell you more or less what I remember I did:
Import Twitter+Oauth, GSTwitPicEngine, OARequestHeader, TouchJSON, and ASIHTTPRequest libraries.
Set GSTwitPicEngine to use TouchJSON instead of YAJL.
Added the category NSString+URLEncoding from oauth-consumer github project as OARequestHeader was missing it.
Modified all occurrences of NSDictionary* foo = [toke parameters] inside OARequestHeader with the line:
NSDictionary *foo = [NSDictionary dictionaryWithObject:[token key] forKey:#"oauth_token"];
Created a method getAccessToken in SAOAuthTwitterEngine to return the private _accessToken variable.
In requestFinished: inside GSTwitPicEngine
change the line:
response = [[CJSONDeserializer deserializer] deserialize:responseString error:&error];
with the line:
response = [[CJSONDeserializer deserializer] deserialize:[responseString dataUsingEncoding:NSUTF8StringEncoding] error:&error];
as GSTwitPicEngine wrongly assumed deserialize:error: method accepts an NSString instead of an NSData.
You can set Access token & Access Secret for twitpic after getting from twitter engine i.e
SA_OAuthTwitterEngine.m
(void) setAccessToken: (OAServiceTicket *) ticket withData: (NSData *) data
Create object of "GSTwitPicEngine.m" class in "SA_OAuthTwitterEngine.m" & pass Access token & Access Secret to "GSTwitPicEngine.m" and set delegate methods of "GSTwitPicEngine.m" in "SA_OAuthTwitterEngine.m"
And one more change...
must cross check URLEncoding while genrating OAuth Header i.e
[requestHeader generateRequestHeaders]
this will be according to "OAuthConsumer" classes in Twitter Library