I have a code with two targets that uses functions specific to iOS 4 and others compatible with 3.0+.
I would like to make a final revision on the code to see if everything is fine. In other words, to see if there's no function meant for iOS4 being called when compiling for iOS 3.x target.
Is there a way to list, during compilation, any functions being called that doesn't belong to the desired version for the target?
thanks in advance.
1) turn the compiler warnings all the way up
2) treat warnings as errors
3) change the base/target SDK to the earliest version you will support
4) clean
5) build
things such as implicit functions and undeclared selectors should now generate errors.
to avoid errors going forward, create a shim static library for the functions or objc methods you will need. this shim will implement two variations of the code, and will ultimately build against the latest headers. it will contain conditional runtime checks. for the objc class methods you can fake, use categories and use the category implementation where any warning is generated.
to illustrate using two versions of the code:
/*
illustration:
- sortByCoordinates: was added in OS 4, but we need to implement or approximate it for our projects targeting OS 3 (or throw out an alert in some cases)
- as long as you have to support OS 3 and build against the SDKs from time to time, the compiler will produce errors or warnings when a selector has not been declared, or if an object may not respond to a selector (assuming you have directed the compiler to inform you of this)
- so we put our conditionals/runtime checks in a library here, and update our calls from sortByCoordinates: to mon_sortByCoordinates:
*/
- (void)mon_sortByCoordinates:(EECoordinate*)coordinates
{
/* MONLib_OSVersion_4_0 implies some constant, indicating OS 4 */
/* MONLibGetCurrentRuntimeVersion() is a call we make at runtime which returns the OS version, such as MONLib_OSVersion_4_0 or MONLib_OSVersion_3_2 */
if (MONLib_OSVersion_4_0 <= MONLibGetCurrentRuntimeVersion()) {
/* they are using OS 4 (or greater) - call the OS version */
[self sortByCoordinates:coordinates];
}
else {
/*
%%%%%% < else implement our approximation here >
*/
}
}
finally, the compiler can't catch everything for you, due to objc's dynamic nature and the way objc programs are often written. sometimes it helps to be more verbose. simple example:
#interface MONBox
/* added in version 4 */
- (NSUInteger)count;
#end
NSUInteger beispiel() {
if (0) {
/* less safe -- the compiler will not flag this */
/* if [myNSArrayInstace objectAtIndex:0] is a MONBox then this will go undetected since the selector count may be matched to -[NSArray count] */
return [[myNSArrayInstaceOfMONBoxObjects objectAtIndex:0] count];
}
else {
/* more safe -- the compiler will flag this */
MONBox * box = [myNSArrayInstaceOfMONBoxObjects objectAtIndex:0];
return [box count];
}
}
I don't think there's an automated way to do this, as far as I know. I keep an old first-gen iPhone around for this purpose -- I'll run the app on the device and see what crashes. Not ideal but it works OK for smaller apps.
Related
I am trying to extend a class written in Obj-C and include an extension written in Swift that makes it conform to the UIDropInteractionDelegate, like so:
#available(iOS 11.0, *)
extension NoteEditViewController: UIDropInteractionDelegate {
#available(iOS 11.0, *)
public func dropInteraction(_ interaction: UIDropInteraction, sessionDidUpdate session: UIDropSession) -> UIDropProposal {
let operation: UIDropOperation
if session.localDragSession == nil {
operation = .forbidden
} else {
// If a local drag session exists, we only want to move an
// existing item in the pin board to a different location.
operation = .forbidden
}
return UIDropProposal(operation: operation)
}
#objc(setupDropInteractions)
#available(iOS 11.0, *)
func setupDropInteractions() {
// Add drop interaction
self.view.addInteraction(UIDropInteraction(delegate: self))
}
}
My problem is that Project_Name-Swift.h file contains the following code that will not compile:
#class UIDropInteraction;
#protocol UIDropSession;
#class UIDropProposal;
// This line is causing the issue saying "'UIDropInteractionDelegate' is partial: introduced in iOS 11.0"
#interface NoteEditViewController (SWIFT_EXTENSION(Bloomberg_Professional)) <UIDropInteractionDelegate>
- (UIDropProposal * _Nonnull)dropInteraction:(UIDropInteraction * _Nonnull)interaction sessionDidUpdate:(id <UIDropSession> _Nonnull)session SWIFT_WARN_UNUSED_RESULT SWIFT_AVAILABILITY(ios,introduced=11.0);
- (void)setupDropInteractions SWIFT_AVAILABILITY(ios,introduced=11.0);
#end
The compiler is complaining that the interface in that file is partial.
'UIDropInteractionDelegate' is partial: introduced in iOS 11.0
I assumed that including the #available(iOS 11.0, *) would generate a SWIFT_AVAILABILITY(ios,introduced=11.0) that would encapsulate the entire interface but I was wrong.
Is there a way to fix this?
UPDATE
I implemented a toy example.
Here is the toy ViewController:
Here is the swift extension:
And here is the generated dnd_toy-Swift.h file.
You were right that it is simply a warning but my problem is that we must treat all warnings as errors in our project.
Any ideas on how to get rid of this warning from here?
You've done everything correctly, and the complaint is accurate if not extremely obtuse.
Basically, since you've (correctly) marked that function as available in iOS 11, the compiler will warn you about the use of it in any code that is targeting something < iOS 11.
So you can make the complaint go away by setting your deployment target to iOS 11, which means users of iOS 10 simply won't be allowed to install it. Or, you can use the new (to obj-c) #available construct to guard against the use of the API.
if (#available(iOS 11, *)) {
[self setupDropInteractions];
}
This construct isn't supported by Xcode 8 since it's a recent back port of the #available construct provided by Swift.
Update
I'm clarifying where I'm coming from. It seems I haven't been able to reproduce the situation that the asker is experiencing, so I'm demonstrating what I have been able to do.
I can produce 2 different compiler warnings that are similar but it seems not identical to the original question.
Here is my generated objc interface from my_project-Swift.h:
#interface NoteEditViewController (SWIFT_EXTENSION(conditional_class_declaration)) <UIDropInteractionDelegate>
- (UIDropProposal * _Nonnull)dropInteraction:(UIDropInteraction * _Nonnull)interaction sessionDidUpdate:(id <UIDropSession> _Nonnull)session SWIFT_WARN_UNUSED_RESULT SWIFT_AVAILABILITY(ios,introduced=11.0);
- (void)setupDropInteractions SWIFT_AVAILABILITY(ios,introduced=11.0);
#end
Issue 1: Declaring and objc property to conform to the protocol
Issue 2: Using a method declared in the extension
With more information to reproduce the compilation error, I'll be able to help more.
Update 2: Hopefully the last
I found that the discrepancies in behavior between us was due to the fact that my project was created with Xcode 8.3 then migrated to 9. There seems to be a difference in build settings after these things happen. The setting in question is CLANG_WARN_UNGUARDED_AVAILABILITY, which I think is new for Xcode 9.
During migration the project ends up like this:
This maps to the term YES in the .pbxproject file
After new project creation:
This maps to the term YES_AGGRESSIVE is the .pbxproject file
This setting is discussed in WWDC2017 - What's new in LLVM, but nothing they say would suggest this minor behavioral difference. I'm guessing that it is a bug with clang and how it's handling the difference between the two settings (but I'd welcome other input). As I'm sure you've figured out, this code runs fine on iOS 10. Also, if you change the setting to simply "Yes", you will still get the correct warnings about iOS 11 APIs.
In ObjC, adding API_AVAILABLE(ios(11.0)) to end of the function definition will suppress the warning. Like this:
- (nullable UISwipeActionsConfiguration *)tableView:(UITableView *)tableView leadingSwipeActionsConfigurationForRowAtIndexPath:(NSIndexPath *)indexPath API_AVAILABLE(ios(11.0))
{
... function implementation ...
}
Xcode 9 beta 4 will build, if #available is added to each method. The build still fails if #available is added only at the extension level.
My program works perfectly. I assure you with my life, 0 bugs. Proudly, I tried to package the application as an .ipa file for ad-hoc distribution to my beta tester using TestFlight.
The program didn't work. Animations which are supposed to happen never happened. Network code breaks. The button to fade out the music beautifully didn't do anything at all.
It turns out that the culprit is the new and shiny blocks. When I test my program in the Simulator or on my device, I used the default "Debug" build configuration. But when I archive it for distribution (and I believe later for submission to the App Store), XCode uses another configuration which is "Release". Investigating further, the difference is due to the optimization level (you can find it on XCode's Build Settings): Debug uses None (-O0) but Release uses Fastest, Smallest (-Os). Little did I know, that it's Fastest, Smallest, and Doesn't Work (tm). Yes, blocks behave differently between those 2 configurations.
So, I set out to solve the problem. I've simplified my soon-to-change-the-world app into its bare bones, shown in the image I've attached to this post. The view controller has an instance variable x with initial value 0. If we press b, it will spawn a thread that will continuously check the value of x, changing the bottom label when x becomes 1. We can change the value of x using button a.
Here is my naive code (I'm using ARC btw):
#implementation MBIViewController
{
int _x;
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
_x = 0;
}
- (void)updateLabel
{
self.topLabel.text = [NSString stringWithFormat:#"x: %d", _x];
}
- (IBAction)buttonAPressed:(id)sender {
_x = 1;
[self updateLabel];
}
- (IBAction)buttonBPressed:(id)sender {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
while (_x != 1) {
// keep observing for value change
}
dispatch_async(dispatch_get_main_queue(), ^{
self.bottomLabel.text = #"b changed me becase x changed!";
});
});
}
#end
_x is an instance variable, so it is reasonable to think that the block will access it using a pointer to "self", not on a local copy. And it works on the debug configuration!
But it doesn't work on Release build. So perhaps the block is using a local copy after all? OK, so let's explicitly use self:
while (self->_x != 1) {
// keep observing for value change
}
Doesn't work either in Release. OK, so let's access the damn variable directly using pointer:
int *pointerToX = &_x;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
while (*pointerToX != 1) {
// keep observing for value change
}
// other codes
});
Still doesn't work. This is when it dawned to me that the smart optimizing compiler assumes that there is no possible way in this multithreaded world that the result of the comparison will change, so perhaps it substituted it to be always TRUE or some other voodoo.
Now, when I use this, things start working again:
while (_x != 1) {
// keep observing for value change
NSLog(#"%d", _x);
}
So, to bypass the compiler optimizing out the comparison, I resorted to making a getter:
- (int)x
{
return _x;
}
And then checking the value using that getter:
while (self.x != 1) {
// keep observing for value change
}
It now works, because self.x is actually a call to a function and the compiler is polite enough to let the function actually do its job. However, I think this is a rather convoluted way to do something so simple. Is there any other way you would have coded it, another pattern that you will use, if you are faced with the task of "observing for change of value inside a block"? Thanks a lot!
If you use a variable and do not modify it in a loop, the compiler optimization can cause the actual access to the variable to be optimized out, because your statement can be evaluated beforehand at compile time.
In order to prevent this, you can use the "volatile" keyword, which prevents the compiler from applying this type of optimization.
It does work with getters and setters, because then you need to send a message to your instance, which serves as a synchronization point.
Try declaring _x as follows:
__block int _x;
Normally variables that are also used in blocks are copied. This will indicate to the compiler that if _x is modified in the block the changes should be visible outside it. It might fix your problem.
Consider an app that needs to be compatible with iOS 5 and iOS 6.
Is there a way to mark the code that is there purely for iOS 5 compatibility, so that it appear as a compile error (or warning) when -eventually- the deployment target changes to iOS 6?
Something like this:
#IF_DEPLOYMENT_TARGET_BIGGER_THAN_IOS_5
#OUTPUT_ERROR_MESSAGE
#ENDIF
- (BOOL) shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation
{
return YES;
}
If not, what is the best alternative?
Try this:
#if __IPHONE_OS_VERSION_MIN_REQUIRED >= 60000
#warning This pre-6.0 code isn't needed anymore
#endif
- (BOOL)shouldAutorotateToInterfaceOrientation(UIInterfaceOrientation)toInterfaceOrientation {
return YES;
}
This code will cause a compiler warning once the Deployment Target is set to 6.0 or later.
#define MY_CONDITIONAL_DEPRECATED_ATTRIBUTE __deprecated
use it on all methods BUT until you need it turn it quiet
#define MY_CONDITIONAL_DEPRECATED_ATTRIBUTE
Consider looking at how Apple marks that sort of thing in their framework classes. It seems they're making use of the Availability.h and AvailabilityInternal.h classes in the SDK.
I have a set of classes that were created by www.sudzc.com (awesome WDSL web service proxy creation tool for iPhone/Flex/Javascript).
When I run the CMD+SHIFT+A to check for memory leaks I get the following message:
Object with +0 retain counts returned
to caller where a +1 (owning) retain
count is expected
Here is the method that it is returning that message for:
// Static method for initializing from a node.
+ (id) newWithNode: (CXMLNode*) node
{
return (id)[[[SoapObject alloc] initWithNode: node] autorelease];
}
I don't want to message with this code and it will need to be regenerated many times through the project, as the web services change and I need to update the proxy classes.
Any ideas?
Thanks in advance.
Jason
The analyzer is complaining because the memory management guide dictates that...
You “create” an object using a method whose name begins with “alloc” or “new” or contains “copy”'.
Cocoa and Objective-C rely heavily on convention, you should make every effort to follow that. Turn on "treat warnings as errors" and fix the problem. While you may be the only person working on this now, if at any point another developer were to use your methods, it is likely they would follow the memory management guidelines, and end up over-releasing the object returned by this method (and crashing the app).
The method is flagged because the method name has the 'new' prefix. The static analyzer is just commenting that applying normal method naming conventions one would expect that method to return an object that you are meant to release, and not an autoreleased object.
The "normal" naming convention for methods such as that is to prefix the method with the name of the class, for example if that method was defined for a class called Widget:
#interface Widget : NSObject {
}
+ (id)widgetWithNode:(CXMLNode*)node; // Returns an object that has been autoreleased.
- (id)initWithNode:(CXMLNode*)node; // Returns an object you are expected to release.
#end
If you're using the method correctly (that is, you're accounting for the fact that it returns an autoreleased object) then you can just ignore that warning.
If you have a method name that has to have something like "new" or "copy" in it, and you know the warning is invalid - you can eliminate the warning by including a hint to LLVM that the class is really OK.
In your header file, first add this (usually near the top but it could be anywhere):
#ifndef __has_feature // Optional.
#define __has_feature(x) 0 // Compatibility with non-clang compilers.
#endif
#ifndef NS_RETURNS_NOT_RETAINED
#if __has_feature(attribute_ns_returns_not_retained)
#define NS_RETURNS_NOT_RETAINED __attribute__((ns_returns_not_retained))
#else
#define NS_RETURNS_NOT_RETAINED
#endif
#endif
Then at the end of your method declaration add like so:
+ (id) newWithNode: (CXMLNode*) node NS_RETURNS_NOT_RETAINED;
You can find a list of other hints (really attributes) that you can pass to LLVM here:
http://clang-analyzer.llvm.org/annotations.html
HI all
I want to make one app for iPhone 2.2.* and for version 3.0.
Some method in 2.2* is deprecated in 3.0. ( like UITableViewCell setText and setLabel )
Is there any way to check which firmware version is used on iPhone and to set different method to use
You will need to use pre-processor directives for the conditional compilation such as __IPHONE_3_0 and build two separate executables.
For example:
#ifdef __IPHONE_3_0
// code specific to version 3
#else
// code specific to version 2
#end
If you need to detect the version at run-time you can use [[UIDevice currentDevice] systemVersion]. It returns the string with the current version of the iPhone OS.
As mentioned in the other referenced thread, while you can use pre-processor directives to generate two applications from one code base, you will still need two applications (one for 2.x and one for 3.x)
A compile time directive cannot be used to make a run time decision.
There's more detail in the other thread.
Alternate solution, just check using respondsToSelector. For example-
CGSize expectedLabelSize;
if ([subTitle respondsToSelector:#selector(sizeWithAttributes:)])
{
expectedLabelSize = [subTitle sizeWithAttributes:#{NSFontAttributeName:subTitleLabel.font}];
}else{
expectedLabelSize = [subTitle sizeWithFont:subTitleLabel.font constrainedToSize:subTitleLabel.frame.size lineBreakMode:NSLineBreakByWordWrapping];
}