I have a library that was compiled against Apple's LLVM 4.2 compiler (Base SDK 6.1). In it there is object subscripting.
Imagine that my library has only one class with one method. That method does this:
NSLog(#"****** preTests");
NSDictionary *dictTest = #{ #1 : #1 };
NSLog(#"Initialized Dictionary");
NSArray *arrayTest = #[ #1, #2, #3 ];
NSLog(#"Initialized Array");
NSLog(#"****** arrayTest[1] = %#", arrayTest[1]); // First use of subscripting
NSLog(#"****** dictTest[#1] = %#", dictTest[#1]);
Now I create a new project and link this library in. In my application delegate, I call this method. I compile this application with the GCC LLVM 4.2 compiler. It compiles and links fine.
This application will run without error on iOS 6+. This application will crash on iOS 5 at the "First use of subscripting" (above).
2013-07-03 09:15:51.050 GCCTest[167:707] -[__NSArrayI objectAtIndexedSubscript:]: unrecognized selector sent to instance 0x381fb0
Compile it with the Apple LLVM 4.2 compiler and it will run normally.
objectAtIndexedSubscript: is a method made publicly available in iOS 6 and it is my understanding that it what the syntactic sugar of myArray[0] gets translated to.
Can someone help me understand why I see a crash with GCC and not Apple with iOS 5? I'm guessing it has to do with some macros somewhere... Could this be made not to crash with GCC without editing the code of my library?
According to the "Objective-C Feature Availability Index", NSArray subscripting requires at least LLVM Compiler 4.0.
Starting with iOS 6, NSArray has a objectAtIndexedSubscript: method. For iOS 5,
this method is supplied by the static Arclite library that is linked into the application
(see e.g. How to enable the new Objective-C object literals on iOS? and the links given in the answer).
But that is a Clang only feature, GCC does not support ARC.
So I do not see how you could use array subscripting if the main application is compiled and linked with GCC.
Related
I have a Swift framework which is managed via Cocoapods and contains an extension like so:
public extension UIImage {
public static func maskedImageWithColor( color: UIColor, forImageNamed image: UIImage) {
// Implementation
}
}
When I write it in Objective C in my main application I get no compiler errors or warnings.
// No problems here!
[UIImage maskedImageWithColor:UIColor.blackColor() forImageNamed:#"myImage"];
When I run the app, however, it explodes in a mess of 'Unrecognised Selector' errors.
The weird thing is, when the framework is contained within the project and added directly to the relevant parts of build phases (i.e. not managed by Cocoapods), it all works as expected.
Other aspects of the framework—classes, enums, etc.—all work fine too, it just seems to be extensions and only when they're in an framework managed by Cocoapods.
Any ideas on what I might be missing here?
This is because the linker is not loading category methods form static library by default. If you are adding category methods(extension in swift) with static library, add -ObjC to other linker flags in your targets build settings.
Read more here
Already checked this question: Weak linking UIPopoverBackgroundView
and already read: http://www.marco.org/2010/11/22/supporting-older-versions-of-ios-while-using-new-apis#fnref:1
I have a custom PopoverBackgroundView declared in a .h and implemented in a .m file. Then, in just one file, I instantiate it like this
self.settingsPopover.popoverBackgroundViewClass = [CustomPopoverBackgroundView class];
I´ve tried doing it like marco says in the link above:
if ([UIPopoverBackgroundView class] != nil) {
self.settingsPopover.popoverBackgroundViewClass = [CustomPopoverBackgroundView class];
}
But I get the same launch error when I run in a 4.3 ipad simulator
dyld: Symbol not found: _OBJC_CLASS_$_UIPopoverBackgroundView
My base sdk is IOS 5.1, and my target deployment is 5.1 as well. Im using LLVM compiler 4.0.
Any ideas? Thanks a lot!
Have you tried using respondsToSelector with the relevant UIPopoverController setBackgroundViewClass method? Remember that properties automatically generate setter and getter methods that you can use in addition to the normal property syntax.
The reason why you're still getting linker errors is because you're still trying to call a method on that class, which doesn't exist.
If it's a case that the entire class doesn't exist, Apple recommends using NSClassFromString(#"UIPopoverController") and checking if the returned result is nil.
When I create a new project with Xcode 4.4 and add these lines:
NSDictionary *test = #{ #"key" : #"test value" };
NSString *value = test[#"key"];
NSLog(#"value is: %#", value);
it compiles with no warnings and executes as expected.
Adding the same lines to an existing project produces the compiler error:
NSString *value = test[#"key"]; <-- Expected method to read dictionary element not found on object of type 'NSDictionary *'
I compared both projects' target build settings but nothing leapt out at me.
Update:
The new project that successfully compiled was for OSX. I tried another new one for iOS with the above lines and it fails to compile, same as my pre-existing (iOS) project.
This has nothing to do with old vs. new project, but rather is a factor of the SDK you use. The problem you're running into is that while this is a compiler feature, it requires SDK support. The iOS 5 SDK does not provide that support, though the iOS 6 SDK does.
For that reason, now you should just use the iOS 6 SDK. Read on if you want to use object subscripting with the iOS 5 SDK.
All you need to do is add a header file so that the compiler will try the call. There's no need to add an implementation; it's handled automatically by arclite. (If you are not using ARC, you will have to force the linker to include arclite. But you still don't have to actually switch to it.)
Create a new interface file, NSObject+subscripts.h.
#if __IPHONE_OS_VERSION_MAX_ALLOWED < 60000
#interface NSDictionary(subscripts)
- (id)objectForKeyedSubscript:(id)key;
#end
#interface NSMutableDictionary(subscripts)
- (void)setObject:(id)obj forKeyedSubscript:(id <NSCopying>)key;
#end
#interface NSArray(subscripts)
- (id)objectAtIndexedSubscript:(NSUInteger)idx;
#end
#interface NSMutableArray(subscripts)
- (void)setObject:(id)obj atIndexedSubscript:(NSUInteger)idx;
#end
#endif
I've put this chunk on github.
Note: I used to suggest adding the required methods to NSObject before explaining how to add them only to the relevant objects. In retrospect, I believe this was an error on my part; it led to errors being caught at runtime rather than compile time, unlike the approach now presented here. That approach is still on my blog, but I now believe it to be more of a cool hack than a useful approach.
Source:
Peter Steinberger, Using Subscripting With Xcode 4.4 and iOS 4.3+
I'm attempting to use this approach, described by Marco Arment, for checking if a class exists before using it. With the correct settings, classes are automatically weak-linked when it's appropriate. As Marco describes, "you can safely subclass or have pointers to whatever you want (as long as you’re careful not to instantiate them when they’re not available)".
My app runs fine on iOS 5. I've followed the conditions mentioned at the link:
Base SDK is Latest iOS (iOS 5.1)
Deployment Target is iOS 4.0
Compiler for C/C++/Objective-C is Apple LLVM compiler 3.1 (also tried LLVM GCC 4.2)
Any time I reference NSMetadataQuery I'm making sure the class exists first:
if ([NSMetadataQuery class] != nil) …
Despite all this my app crashes immediately on launch if I try to run it on an iPod touch with iOS 4.2.1:
dyld: Symbol not found: _OBJC_CLASS_$_NSMetadataQuery
I've tried commenting out all the code any my app runs fine. As soon as I add back in a single reference to NSMetadataQuery, it crashes. I've even tried this:
if ([NSMetadataQuery class] != nil) NSLog(#"OK");
Simply including that line, and no other reference to NSMetadataQuery, crashes the app. Even more strange, checking for other iOS 5 classes doesn't cause any problems:
if ([UIDictationPhrase class] != nil) NSLog(#"OK");
That works fine, as expected.
I have been able to work around the problem using the uglier NSClassFromString() to make sure the class exists, but I'd love to know why the other approach isn't working.
I don't have an explanation to this but I ran into the same problem as you. No matter what I/you do, NSMetadataQuery just won't be weak linked...
Refer to this answer, which is really the best one in another question:
https://stackoverflow.com/a/8426591/129202
In short, other auto weak linking seems to work, it's just NSMetadataQuery* that you have to remove from source and replace with id. Instantiate the class with NSClassFromString() etc. No hiccups on other classes like UIDocument however so you can safely use those in the normal sweat free way.
NSMetadataQuery is available in iOS 5.0 and above, so any versions below that has no clue as to what it is. By merely using it in your code, the class name will be added to a symbol table and looked-up when the app launches.
Now I'm involved in a project which needs to download a huge amount of images from server. Following the recommendation online, I tried the ASIHttpRequest. But when I copied all the necessary classes into my project, I got 30+ errors in those classes. Most of the errors are about using retain, release or autorelease. Because I'm using Xcode 4.2.1, explicit retain, release and autorelease is forbidden. But some other errors are quite ridiculous.
for example, in class ASIDataCompressor.m, following method should return NSData
- (NSData *)compressBytes:(Bytef *)bytes length:(NSUInteger)length error:(NSError **)err shouldFinish:(BOOL)shouldFinish
but I see one portion of the method return NO
if (status == Z_STREAM_END) {
break;
} else if (status != Z_OK) {
if (err) {
*err = [[self class] deflateErrorWithCode:status];
}
return NO;
}
Some other classes also have similar problems.
My questions are:
Did I download the wrong package?
How to let compiler ignore those explicit retain, release and autorelease?
"Xcode 4.2.1, explicit retain, release and autorelease is forbidden" because you have ARC enabled when you created your project. Disable ARC.
ASIDataCompressor.m method looks fine here. Don't know how you got it wrong.
Since you just picked up ASIHTTP in your project I would recommend switching to AFNetworking because read this: [request release]
As stated above, when using ARC, you cannot use (and no longer need) retain/release/etc.
If you want your overall project to still have ARC, you can disable it for that one file.
When you migrate a project to use ARC, the -fobjc-arc compiler flag is set as the default for all Objective-C source files. You can disable ARC for a specific class using the -fno-objc-arc compiler flag for that class.
In Xcode, go to target Build Phases tab > open the Compile Sources group to reveal the source file list > double-click the file for which you want to set the flag > enter -fno-objc-arc in the pop-up panel, then click Done.