I've been working on an app for OSX (not my own, one that I am to maintain) that needs Twitter integrations and found the STTwitter wrapper. It says it's compatible down to OSX 10.7. However, when I try to compile it, I run into several compilation issues that I've tracked I think I've tracked down to not being able to compile with Objective-C literal subscripts.
I tries using the workaround suggested across the web for adding my own interfaces, but that didn't seem to help.
The first compilation error I get is "Array subscript is not an integer" at the following chunk of code in NSError+STTwitter.m:
NSMutableDictionary *md = [NSMutableDictionary dictionary];
md[NSLocalizedDescriptionKey] = message;
if(underlyingError) md[NSUnderlyingErrorKey] = underlyingError;
if(rateLimitLimit) md[kSTTwitterRateLimitLimit] = rateLimitLimit;
if(rateLimitRemaining) md[kSTTwitterRateLimitRemaining] = rateLimitRemaining;
if(rateLimitResetDate) md[kSTTwitterRateLimitResetDate] = rateLimitResetDate;
If I comment that code out, just to see what happens (the literal substring I believe), I get more issues in STTwitterOS.m
NSString *value = [keyValue[1] stringByReplacingOccurrencesOfString:#"\"" withString:#""];
[md setObject:value forKey:keyValue[0]];
Those give "Bad receiver type NSArray" and "Sending NSArray to parameter of incompatible type 'id
Any help would be appreciated. My objective c coding isn't that great....
I'm attempting to use the RocketSocket library on a OS 10.7 but Xcode keeps complaining that the property base64Encoding not found in NSData and NSMutable regarding these lines in SRWebSocket.m:
return [[NSData dataWithBytes:md length:CC_SHA1_DIGEST_LENGTH] base64Encoding];
_secKey = keyBytes.base64Encoding;
After some googling, I found that base64Encoding is deprecated. I'm a relative noob to ObjC (and Mac in general). Anyone have any idea how to fix this problem?
Replace
_secKey = keyBytes.base64Encoding
by
_secKey = [keyBytes base64EncodedStringWithOptions:NSDataBase64Encoding64CharacterLineLength];
I upgraded my Xcode to Version 4.5.1 and suddenly previous app development had 'potentially insecure' during build.
it generally applies to declaring this type of construct:
NSString *userChoice = [NSString stringWithFormat:label1a.text]; //Warning at pulling string from label1a.text
[usrAnswer setObject:userChoice forKey:#"1"];
is this a serious warning? How do i rectify this?
You are not using a format string. Just do this:
NSString *userChoice = label1a.text;
I see a lot of people create strings with stringWithFormat:. Only use that when you are actually creating a string with format specifiers.
I've seen in some projects something like:
#if .....
code...
#endif
but i can't find it now...
Let's say, for example, if the app is running on 10.8 the app does 1 thing, if not the app does other thing.
Whats to code to check if it's running on 10.8?
Thanks.
You're probably asking the wrong question. Except in very rare cases, you should not care what system version the user is running. Instead, you should be checking if the specific thing you're interested in is available.
For instance, if Apple introduces a MagicHologram class in Mac OS X 10.9 that you want to use, you don't check if the user is running Mac OS X 10.9. Instead, you check if the MagicHologram class is available. If it is, you can use it. If not, it's not available. It doesn't even matter why. Maybe they're running 10.8. But maybe it's five years later, and Apple's decided to drop the MagicHologram class entirely.
(Also, keep in mind that you'd need to weak link to HologramKit, the library that provides the MagicHologram class.)
Likewise, if they introduce a new method to NSString, instead of checking the OS version you'd check if NSString knows about the new method.
That said, NSApplication.h includes an external constant called NSAppKitVersionNumber. You can compare this to constants like NSAppKitVersionNumber10_7 which (it should be noted) are numbers like 1138, not 10.7. There's only a few places this is appropriate, mostly where classes were private and undocumented but got major changes before being documented and becoming a part of the public parts of the SDK. Also, it might be helpful if you want to avoid a specific bug that's been fixed since.
To recap:
Detect individual classes and methods, which should cover 99.44% of your cases.
Use NSAppKitVersionNumber and NSAppKitVersionNumber10_7 to cover those cases where class or method detection would lie to you.
Those first two points cover all normal cases. You should go no further. But if you must have behaviour based on humane version, look at abarnert's answer below. It's the sanest way to get them.
Don't use operatingSystemVersionString, which is specifically listed as not safe for parsing.
References/more information:
SDK Compatibility Guide "Read this document if you want your application to target a specific version or multiple versions of iOS or Mac OS X."
Using SDK-Based Development Describes how to use weakly linked classes, methods, and functions to support running on multiple versions of an operating system.
A quick way to do it is:
if ( NSAppKitVersionNumber >= NSAppKitVersionNumber10_7 ) {
// Do stuff for Lion or later
}
More here.
You can see all the constants available in NSApplication.h, which you can get to by using Open Quickly... (Cmd-Shift O) in Xcode.
The header files provide for some surprisingly interesting reading material, if you are so inclined.
As others have said above (and I'd pick Steven Fisher's answer), you usually do not actually want to get the version number.
And if you only need to do comparisons against a major OS X version up to the version of the current SDK you're using, NSAppKitVersionNumber (as in Monolo's answer) is the right way to do it.
If you actually do need to get the version number for some reason (e.g., for recording analytics about your users, so you can decide when to stop supporting 10.6.0-10.6.5), here's how to do it:
#import <CoreServices/CoreServices.h>
SInt32 majorVersion, minorVersion, bugFixVersion;
Gestalt(gestaltSystemVersionMajor, &majorVersion);
Gestalt(gestaltSystemVersionMinor, &minorVersion);
Gestalt(gestaltSystemVersionBugFix, &bugFixVersion);
For 10.7.3, this gives majorVersion = 10, minorVersion = 7, bugFixVersion = 3.
The 10.7 documentation removed the paragraph that directly suggested Gestalt as the way to get OS version, but it's still not deprecated or legacy, and there's no other suggestions. In fact, every other way to get this information (parsing -[NSProcessInfo operatingSystemVersionString], calling sysctlbyname on "kern.osrelease" and converting Darwin kernel version to OS X version, etc.) is explicitly counter-indicated somewhere. So, this is the way to do it, if you really want to.
Just keep in mind that, as the release notes for System 6.0.4 said back in 1989, this new API may not be permanent and could be removed in a future version of the OS.
You can get the current release from the uname -r command (that's actually the kernel release, though it's easy to map onto Mac OS X versions), from the sw_vers command which gives you the release name, version and build identifier, or from the Gestalt() function (on current versions of Mac OS X, anyway). The safest way is probably to read the output of sw_vers which is in a stable format that's easy to parse.
Notice that you probably don't want to know what version of the OS you're on, though. What you probably want to do is to test whether a particular feature is available. You can do this by weak-linking frameworks, by weak class references, or by inspecting whether a class responds to selectors appropriate to the feature you're interested in.
As Steven Fisher said, you should not check for the system version but for the availability of the class or method you want to use.
The check if a specific class is available use
if ([NSHologram class]) {
// Create an instance of the class and use it.
} else {
// The Hologram class is not available.
}
To check if a specific method is available use
NSString* hologramText = #"Hologram";
if ([hologramText respondsToSelector:#selector(convertHologram)]) {
[hologramText convertHologram];
}
Yet for the method checking, the method must be available on the system where you build your app, or else you will get a compile error.
Here is code for how I do it. I love it this way, mainly because I don't have to A) Rely on an NSTask or B) Rely on any File I/O that many processes have access to.
static NSString* const kVarSysInfoVersionFormat = #"%#.%#.%# (%#)";
static NSString* const kVarSysInfoKeyOSVersion = #"kern.osrelease";
static NSString* const kVarSysInfoKeyOSBuild = #"kern.osversion";
- (NSString *) _strControlEntry:(NSString *)ctlKey {
size_t size = 0;
if ( sysctlbyname([ctlKey UTF8String], NULL, &size, NULL, 0) == -1 ) return nil;
char *machine = calloc( 1, size );
sysctlbyname([ctlKey UTF8String], machine, &size, NULL, 0);
NSString *ctlValue = [NSString stringWithCString:machine encoding:[NSString defaultCStringEncoding]];
free(machine); return ctlValue;
}
- (NSString *) getOSVersionInfo {
NSString *darwinVer = [self _strControlEntry:kVarSysInfoKeyOSVersion];
NSString *buildNo = [self _strControlEntry:kVarSysInfoKeyOSBuild];
if ( !darwinVer || !buildNo ) return nil;
NSString *majorVer = #"10", *minorVer = #"x", *bugFix = #"x";
NSArray *darwinChunks = [darwinVer componentsSeparatedByCharactersInSet:[NSCharacterSet punctuationCharacterSet]];
if ( [darwinChunks count] > 0 ) {
NSInteger firstChunk = [(NSString *)[darwinChunks objectAtIndex:0] integerValue];
minorVer = [NSString stringWithFormat:#"%ld", (firstChunk - 4)];
bugFix = [darwinChunks objectAtIndex:1];
} return [NSString stringWithFormat:kVarSysInfoVersionFormat, majorVer, minorVer, bugFix, buildNo];
}
Enjoy!
You can get OS version like this:
NSString *version = [[NSProcessInfo processInfo] operatingSystemVersionString];
NSLog(version);
Output:
And I see You want to get just version. It can be done like this:
NSString *version = [[NSProcessInfo processInfo] operatingSystemVersionString];
NSRange range = NSMakeRange(8, 4);
NSString *justVersion = [version substringWithRange: range];
NSLog(#"%#", justVersion);
Result:
And for checking:
if ([justVersion isEqualToString:#"10.7"]) {
code...
}
else {
...
}
Xcode looked at this line and did not complain. Project built, code crashed at runtime.
NSString *randomName = [NSString stringWithFormat:#"%#, %#, %#",
[randomAjectiveList objectAtIndex:ajectiveIndex],
[randomNounList objectAtIndex:nounIndex]];
Naturally, come to think about it, i have one too many "%#" in place, one more then actual arguments. Correct code should look as follows
NSString *randomName = [NSString stringWithFormat:#"%#, %#",
[randomAjectiveList objectAtIndex:ajectiveIndex],
[randomNounList objectAtIndex:nounIndex]];
I ask you though ... why didn't Xcode complain? Seems like such an obvious thing to do with param counters. Shouldn't this be checked at compile time? Is it specific to "%#", perhaps?
Please advise.
Based on a quick check, you're 100% right that this isn't checked at compile time, seemingly even by the static analyser. Conversely, NSLog is checked. So on my machine, with XCode 4.0.2, the following:
[NSString stringWithFormat:#"%d %# %#"];
NSLog(#"%d %# %#");
Produces a warning on the NSLog of "More '%' conversions than data arguments" but fails to comment on the NSString.
So, the difference could be fixed function calls versus dynamic calls. The compiler can't actually be completely certain where the NSString call will go because it's possible you'll have declared a category or used the low-level runtime to adjust the NSString selector table at runtime.
However, especially given the problems you'll almost immediately encounter if you start modifying the behaviour of the Foundation classes, like you I'd have expected at least a warning.