iOS app crashes after adding ScanAPISDK - objective-c

I am trying to integrate socketscan SDK with my objective C iOS app. I downloaded scanapisdk, made a copy of scanapisdk inside my project main folder. I Added the files and library references to the Xcode project. compiled and build the app. run it on a device. The following code has been working with no problem for couple of years now.
I have a constant NSString declared as global variable in an m file
NSString *const kSymbology = #"Symbology";
and it is declared as an extern in an h file
extern NSString *const kSymbology;
Then in a database class there is this code fragment
NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
const char *Symbology = (const char*)sqlite3_column_text(statement, 0);
strSymbology = [NSString stringWithUTF8String:Symbology];
[dict setValue:strSymbology forKey:kSymbology];
[symbologies addObject:dict];
[dict release];
The code crashes on this line
[dict setValue:strSymbology forKey:kSymbology];
When I change it to
[dict setValue:strSymbologyAlias forKey:#"Symbology"];
The app doesn't crash.
It doesn't make sense to me. it seems that the error is somewhere else and it is showing here by accident. The only new thing I have added to my project are the references to the socketScan files and libraries. I don't even call any of the methods in the SDK. I commented out all the methods in ScanApiHelper.mm file, now the app doesn't crash. I started to put back some of the methods in ScanApiHelper.mm and I discovered that when any line that has SktClassFactory mentioned for example (for example [SktClassFactory createScanObject]) is alive, the app crashes.
Can that be related to not using CocoaPods to install the sdk?

It looks like a name space collision. The libScanApiCore.a has a variable named kSymbology and my global variable is also named kSymbology. Once I changed the variable name to ksymbology (lower case s) my app no longer crashes.

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

iTunes Mp3 Add Returning Nil Via Scripting Bridge In ObjC and Swift And Doesn't Import

I'm trying to import a downloaded MP3 into iTunes. My app is for OSX using Swift, and I've tried doing this both in Swift code and in ObjC using interoperability. I've generated the iTunes.h using sdef and sdp. I'm importing it in my Bridging Header for the Swift attempt and in my .m file for the ObjC attempt. I've added Scripting Bridge to my build.
This is my first program in Swift or ObjC (my experience is in C#) so this may be something simple I don't have the experience to realize.
Here is my ObjC code:
NSString *filepath = "~/Music/test.mp3";
iTunesApplication *iTunes = [SBApplication applicationWithBundleIdentifier:#"com.apple.iTunes"];
iTunesPlaylist *pl;
for (iTunesSource *source in sources) {
if([source kind] == iTunesESrcLibrary) {
SBElementArray *libraries = [source libraryPlaylists];
pl = [libraries objectAtIndex:1];
break;
}
}
iTunesTrack *track = [iTunes add:[NSArray arrayWithObject:[NSURL fileURLWithPath:filepath]] to: pl];
Everything compiles and runs, but the iTunes add returns nil (and more importantly, doesn't add to iTunes). I've tried using different Playlists from the Library, and difference Sources as well. I've tried using a different MP3 file. And as I said, I've written it in Swift as well. All give me the same result.
So apparently I was fetching the path for MyMusic incorrectly. You can't just use the tilde, you have to resolve it:
NSString *musicDirectory = [NSSearchPathForDirectoriesInDomains(NSMusicDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *filepath = [musicDirectory stringByAppendingPathComponent:#"test.mp3"];
Or with Swift:
let fileManager = NSFileManager.defaultManager()
let urls = fileManager.URLsForDirectory(.MusicDirectory, inDomains: .UserDomainMask)
let musicDirectory: NSURL = urls.first as? NSURL
let filepath = musicDirectory.URLByAppendingPathComponent("test.mp3")
At this point, I no longer needed to mess with any of the source searching either, I could just pass nil as the playlist:
iTunesTrack *track = [iTunes add:[NSArray arrayWithObject:[NSURL fileURLWithPath:filepath], nil] to: nil];
I still need an Objective C wrapper around the actual add method, however, which is connected to the Swift programming via the Bridging Header. If I try to use the add method in Swift directly, I get linker errors which seem unrelated to the original issue, so I won't address.

Overwriting SystemVersion.plist (Objective-c)

I'm writing an application that can change your Mac OS X version for a short period (few seconds) to launch an application that is to old or to new for your Version.
It reads the version from SystemVersion.plist (/System/Library/CoreServices/SystemVersion.plist) then sets the new required version.
Save (overwrite) the file and launch the app and then change the version back
to the orginal version.
This is the first part of the code (reads version and tries to overwrite)
// Get SystemVersion.plist as Dictionary
NSBundle *bundle = [NSBundle bundleWithPath:#"/System/Library/CoreServices"];
NSString *SystemVersionString = [bundle pathForResource:#"SystemVersion" ofType:#"plist"];
NSMutableDictionary *SystemVersionDir = [NSDictionary dictionaryWithContentsOfFile:SystemVersionString];
// Get current version (Systemversion, ProductUserVisibleVersion)
NSString *SystemVersion = [SystemVersionDir objectForKey:#"ProductVersion"];
// Get desired version from text field
NSString *DesiredVersion = [_Text_version stringValue];
[_Text_Info setStringValue:SystemVersion];
// Set new version
[SystemVersionDir setObject:DesiredVersion forKey:#"ProductVersion"];
// Write (save) plist
if([SystemVersionDir writeToFile:#"/System/Library/CoreServices/SystemVersion.plist" atomically:YES])
{
NSLog(#"YES");
}
else
{
NSLog(#"NO");
}
The problem is with the 'writeToFile'. If I write to my desktop or something everything goes fine and it creates a new file. Also if I copy the file to my desktop and overwrite it there it goes fine. But it needs to overwrite the one in /System/Library/CoreServices/.
I guess I need authorization or something. However I've looked into that but it seems an awful lot of work and my mind is mess because of that.
Isn't there a simpler way to do this, perhaps a class that helps?
Note: Mac OS X 10.8 Mountain Lion Developer Preview 3 (Xcode 4.3.1)

Move data (.app) to jailbroken phones /Applications dictionary?

I'm making an app (for Cydia) that allows the user to download other .app's and install them. Everything downloads fine but when I get to moving the file I hit a brick wall.
Code:
//Start Moving
NSString *str = textDownload.text;
str = [str stringByReplacingOccurrencesOfString:#".zip"
withString:#".app"];
NSString *placeToMoveFile = [NSString stringWithFormat:#"mv %#/%# /Applications/", documentsDirectory, str];
NSLog(placeToMoveFile);
const char *runCommand = [placeToMoveFile UTF8String];
system(runCommand);
I started getting creative after a bit (LOL)
I think the problem lays inside of system(). System doesn't even give an output to the console in the simulator. Just as a note the app does run as root so I shouldn't get permission errors with trying to move the file to /Applications.

NSBundle pathForResource failing in shell tool

I've noticed some weird behavior with NSBundle when using it in a
command-line program. If, in my program, I take an existing bundle and
make a copy of it and then try to use pathForResource to look up
something in the Resources folder, nil is always returned unless the
bundle I'm looking up existed before my program started. I created a
sample app that replicates the issue and the relevant code is:
int main(int argc, char *argv[])
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSString *exePath = [NSString stringWithCString:argv[0]
encoding:NSASCIIStringEncoding];
NSString *path = [exePath stringByDeletingLastPathComponent];
NSString *templatePath = [path stringByAppendingPathComponent:#"TestApp.app"];
// This call works because TestApp.app exists before this program is run
NSString *resourcePath = [NSBundle pathForResource:#"InfoPlist"
ofType:#"strings"
inDirectory:templatePath];
NSLog(#"NOCOPY: %#", resourcePath);
NSString *copyPath = [path stringByAppendingPathComponent:#"TestAppCopy.app"];
[[NSFileManager defaultManager] removeItemAtPath:copyPath
error:nil];
if ([[NSFileManager defaultManager] copyItemAtPath:templatePath
toPath:copyPath
error:nil])
{
// This call will fail if TestAppCopy.app does not exist before
// this program is run
NSString *resourcePath2 = [NSBundle pathForResource:#"InfoPlist"
ofType:#"strings"
inDirectory:copyPath];
NSLog(#"COPY: %#", resourcePath2);
[[NSFileManager defaultManager] removeItemAtPath:copyPath
error:nil];
}
[pool release];
}
For the purpose of this test app, let's assume that TestApp.app
already exists in the same directory as my test app. If I run this,
the 2nd NSLog call will output: COPY: (null)
Now, if I comment out the final removeItemAtPath call in the if
statement so that when my program exits TestAppCopy.app still exists
and then re-run, the program will work as expected.
I've tried this in a normal Cocoa application and I can't reproduce
the behavior. It only happens in a shell tool target.
Can anyone think of a reason why this is failing?
BTW: I'm trying this on 10.6.4 and I haven't tried on any other
versions of Mac OS X.
I can confirm that it is a bug in CoreFoundation, not Foundation. The bug is due to CFBundle code relying on a directory contents cache containing stale data. The code apparently assumes that neither the bundle directories nor their immediate parent directories will change during application runtime.
The CoreFoundation call corresponding to +[NSBundle pathForResource:ofType:inDirectory:] is CFBundleCopyResourceURLInDirectory(), and it exhibits the same misbehavior. (This is unsurprising, as -pathForResource:ofType:inDirectory: itself uses this call.)
The problem ultimately lies with _CFBundleCopyDirectoryContentsAtPath(). This is called during bundle loading and during all resource lookup. It caches information about the directories it looks up in contentsCache.
Here's the problem: When it comes time to get the contents of TestAppCopy.app, the cached contents of the directory containing TestApp.app don't include TestAppCopy.app. Because the cache ostensibly has the contents of that directory, only the cached contents are searched for TestAppCopy.app. When TestAppCopy.app is not found, the function takes that as a definitive "this path does not exist" and doesn't bother trying to open the directory:
__CFSpinLock(&CFBundleResourceGlobalDataLock);
if (contentsCache) dirDirContents = (CFArrayRef)CFDictionaryGetValue(contentsCache, dirName);
if (dirDirContents) {
Boolean foundIt = false;
CFIndex dirDirIdx, dirDirLength = CFArrayGetCount(dirDirContents);
for (dirDirIdx = 0; !foundIt && dirDirIdx < dirDirLength; dirDirIdx++) if (kCFCompareEqualTo == CFStringCompare(name, CFArrayGetValueAtIndex(dirDirContents, dirDirIdx), kCFCompareCaseInsensitive)) foundIt = true;
if (!foundIt) tryToOpen = false;
}
__CFSpinUnlock(&CFBundleResourceGlobalDataLock);
So, the contents array remains empty, gets cached for this path, and lookup continues. We now have cached the (incorrectly empty) contents of TestAppCopy.app, and as lookup drills down into this directory, we keep hitting bad cached information. Language lookup takes a stab when it finds nothing and hopes there's an en.lproj hanging around, but we still won't find anything, because we're looking in a stale cache.
CoreFoundation includes SPI functions to flush the CFBundle caches. The only place public API calls into them in CoreFoundation is __CFBundleDeallocate(). This flushes all cached information about the bundle's directory itself, but not its parent directory: _CFBundleFlushContentsCacheForPath(), which actually removes the data from the cache, removes only keys matching an anchored, case-insensitive search for the bundle path.
It would seem the only public way a client of CoreFoundation could flush bad information about TestApp.app's parent directory would be to make the parent directory a bundle directory (so TestApp.app lived alongside Contents), create a CFBundle for the parent bundle directory, then release that CFBundle. But, it seems that if you made the mistake of trying to work with the TestAppCopy.app bundle prior to flushing it, the bad data about TestAppCopy.app would not be flushed.
That sounds like a bug in the Foundation. The one key difference between a command line tool like that one and a Cocoa application is the run loop. Try refactoring the above into something like:
#interface Foo:NSObject
#end
#implementation Foo
- (void) doIt { .... your code from main() here .... }
#end
... main(...) {
Foo *f = [Foo new];
[f performSelector: #selector(doIt) withObject: nil afterDelay: 0.1 ...];
[[NSRunLoop currentRunLoop] run];
return 0; // not reached, I'd bet.
}
And see if that "fixes" it. It might. It might not (there are couple of other significant differences, obviously). In any case, do please file a bug via http://bugreport.apple.com/ and add the bug # as a comment.