Can I enable warning for comparing a enum and a non enum values? - ios

I recently debugged an issue which was caused because an enum was being compared with a non-enum value. Here is a simplified example:
typedef NS_ENUM(NSInteger, MyType) {
TypeVal1,
};
...
MyType type = TypeVal1;
int randomValue = 0;
BOOL compareTypeAndPrimiative = (randomValue == typeA); // No warning
Is it possible to turn on a warning for this?
I could suppress if if needed by explicitly casting:
BOOL iKnowWhatImDoing = (randomValue == (int) typeA);

There is no support for this kind of warning because C enums are not strongly typed, and I believe the standards require them to be treated as ints (or unsigned ints). Comparing them with regular integers has always been allowed as part of the C standard, and a warning of this type would end up flagging a lot of correct code. I'm sure somebody can link to the appropriate section of the C standards.
Particularly with iOS and Apple APIs, you will find that enum values are often used as bitmasks. In these situations it is common to write code like this:
if ((value & flag) == kFlag) { ... }
You could argue that using enums for this purpose is a bad idea, but you would probably end up having to disable this warning for all sorts of code.

I just ran through LLVM manual and it seems there is no option for this. Then I tried to turn on all the compiler warnings (-Weverything), however there was no warning your enum case.
So the answer is no.
The best solution to avoid such bugs is to name the variables/constants appropiately. Make obvious what the variable/constant represent. Then the error in comparison should be obvious, too.

Related

Compiling lambda as Objective-C++ causes block cast

I have a non-copyable C++ lambda which captures a unique_ptr, and certain situations when compiling with Apple Clang as Objective-C++ cause the lambda to get converted to a block pointer, at which point the compilation fails due to an attempted copy of the lambda. A simple example is as follows:
int main(int argc, const char * argv[])
{
std::unique_ptr<int> myHeapInt = std::make_unique<int>(4);
int myStackInt = 0;
auto myLambda = [&, myHeapInt = std::move(myHeapInt)]()
{
myStackInt = *myHeapInt;
};
if(bool(myLambda)) //Error ar this point
{
*myHeapInt = 5;
}
std::invoke(myLambda);
return 0;
}
The error is as follows:
Call to implicitly-deleted copy constructor of 'const lambda...
Implicit capture of lambda object due to conversion to block pointer here
Is there a way around this conversion?
What is that bool(myLambda)? I have no clue.
The only thing you can do with a lambda is evoke it: myLambda(). You cannot test for whether it exists or anything.
So, I'm not entirely seeing the relevance of Objective-C++ here, as this code doesn't compile as C++ either:
objc++-noncopy-lambda.cpp:15:9: error: cannot convert '(lambda at objc++-noncopy-lambda.cpp:9:21)' to 'bool' without a conversion operator
if (bool(myLambda))
^~~~~~~~~~~~~
1 error generated.
The error message is different; I assume there's some attempt at implicitly converting lambdas to blocks in Objective-C++, I've tried to stay away from weird edge cases like that, but it seems that in the absence of an operator bool it might try the conversion to a block first.
Either way the code you're attempting to write doesn't make any sense and the compiler is correctly rejecting it.
I see in the comments that you're actually trying to do something different. Could you perhaps post a reduced version of the code you're actually trying to write, which supposedly compiles as C++ but not as Objective-C++?
I was trying to compile a templated header-file class replacement for std::function (github.com/Naios/function2) as Objective-C++, which implements a vtable and optimises it if the callable implements operator bool() or can be converted to bool.
In the end I just decided to disable this optimisation if compiled as Objective-C++ as converting to a block pointer is by design in Clang for block-lambda interoperability (http://clang.llvm.org/docs/LanguageExtensions.html#interoperability-with-c-11-lambdas).

Access to iOS SDK constant via name (reflection)

Inside of iOS SDK, lots of constants defined by Apple can be found looking something like this:
extern const CFStringRef kSomeReallyNiceConstant
__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_8_0);
If I check for presence of this constant standard way:
if (NULL == &kSomeReallyNiceConstant)
I am pretty much referencing it and in order for my code to compile properly, I need at least iOS SDK 8.0 or higher in this case.
When it comes to objects and methods, reflection approach works nicely with usage of NSClassFromString, respondsToSelector and performSelector.
Is there a chance to use some kind of reflection (access to string constant by name) in attempt to get it's value if it exists (or none if it doesn't)?
I know that I can use macros to check for iOS version and execute different code paths based on that information, but I don't want to use that approach.
I managed to do this with pointer:
#include <dlfcn.h>
// ...
int *pointer = dlsym(RTLD_SELF, "kSomeReallyNiceConstant");
if (pointer) {
NSLog(#"Thing exists!");
} else {
NSLog(#"Nope, doesn't exist!");
}
but I am not sure if this is something that would cause app rejection. Do you maybe know?
Regardless of this pointer approach, I'm curious to hear if there's any other way to achieve this?
Nothing better than suggested solution found on this topic.
#include <dlfcn.h>
// ...
int *pointer = dlsym(RTLD_SELF, "kSomeReallyNiceConstant");
if (pointer) {
NSLog(#"Thing exists!");
} else {
NSLog(#"Nope, doesn't exist!");
}

Objective-C pointer and swift

I'm following an apple document, but unfortunately the examples are written on objective-c, but I have confidence with Swift language and can not understand the meaning of some things, in particular, in this example:
void RunLoopSourcesPerformRoutine (void *info){
RunLoopSource* obj = (RunLoopSource*)info;
[obj sourceFired];
}
this line: RunLoopSource* obj = (RunLoopSource*)info;
the parameter: void *info indicates that info is a pointer to void, then I can put the address of any type of data structure, following various apple documents I saw that the translation of this : void *info into swift language is :
info: UnsafeMutableRawPointer?
Now, the RunLoopSource* obj = (RunLoopSource*)info; line indicates that obj is a variable of type: RunLoopSource, and to this is assigned the value of (RunLoopSource *) info, but precisely What does it mean this statement? : (RunLoopSource *) info, and how it translates in swift language ?
Swift really hates pointer. These 2 lines of code can be converted to Swift as
func RunLoopSourcesPerformRoutine(info: UnsafeMutableRawPointer) {
let obj = info.assumingMemoryBound(to: RunLoopSource.self)
obj.pointee.sourceFired()
}
This specific expression is a "typecast": it's saying that info, which is declared to be a pointer-to-unknown-anything (void *) is actually known by the programmer to be a pointer to a RunLoopSource. This forcibly changes the type of the expression to make the compiler happy as it is assigned to obj.
It is equivalent to using as! in Swift and is idiomatic when you know the semantics of a void * but the syntax doesn't capture it.
(This attempts to answer your question as stated but I'm not sure if you are looking for more information. If so, please clarify and me or someone more expert in unsafe pointers in Swift can help out.)
What you are dealing with (void *info) is a C pointer-to-void, which arrives into Swift as a form of UnsafeRawPointer. This means that type info has been cast away and that memory is being managed elsewhere.
In order to work with this thing as what you believe it to be, i.e. a RunLoopSource, you need to characterize it explicitly as a RunLoopSource. In C, you would cast, as in the example code you posted: (RunLoopSource*)info. In Swift, you rebind.
Observe that in your case this whole thing has been made just a little more complicated by the fact that this UnsafeMutableRawPointer has been wrapped in an Optional, and will have to be unwrapped before you can do anything at all.
Assuming, then, in your case, that info is really an UnsafeMutableRawPointer? bound to a RunLoopSource, you can say:
let rlsptr = info!.assumingMemoryBound(to: RunLoopSource.self)
let rls = rlsptr.pointee
Now rls is a RunLoopSource and you can work with it however you like. Keep in mind, however, that the memory is unmanaged, so you should work with it only here and now.
EDIT By the way, Apple has a really nice document on this entire matter: https://swift.org/migration-guide/se-0107-migrate.html

Clang nullability warnings and how to approach them

Recently I turned on CLANG_WARN_NULLABLE_TO_NONNULL_CONVERSION in Xcode and I am overwhelmed with nullability related warnings in my Objective-C code. The one warning type that is most prevalent is Implicit conversion from nullable pointer 'TypeA * _Nullable' to non-nullable pointer type 'TypeA * _Nonnull'.
I started my attempt to remove with these warnings by creating a local of the same type in a method as described here.
https://www.mail-archive.com/xcode-users%40lists.apple.com/msg02260.html
This article says by first using a local, that object is of attribute unspecified nullable, so it can be used as legit parameter to the methods expecting nonnull.
But I feel this is a cop out move and really not solving the issue in any beneficial way.
Has anyone gone through this exercise already? I would be grateful if you can share a strategy that you took.
Actually, I have messed around with that topic for a little bit. I wanted to improve nullability situation in a somewhat big project (make it more 'swiftier'). Here is what I found.
Firstly, you should turn on CLANG_WARN_NULLABLE_TO_NONNULL_CONVERSION (-Wnullable-to-nonnull-conversion)
Secondly, about
first using a local, that object is of attribute unspecified nullable,
so it can be used as legit parameter to the methods expecting nonnull.
This smells bad, and I created a macro called NONNUL_CAST(). Here is example how to implement it:
#define NONNUL_CAST(__var) ({ NSCAssert(__var, #"Variable is nil");\
(__typeof(*(__var))* _Nonnull)__var; })
Here you can see hacky __typeof(*(__var))* _Nonnull)__var, but it is not so bad. If __var is of type A* _Nullable we dereference __var, so it's type now just A, after we make reference again, but _Nonnull, and get nonnull __var as answer. Of course we assert to, in case something goes wrong.
Thirdly, you must specify nullabilty on every local variable, and you should put all your code in NS_ASSUME_NONNULL_BEGIN/END, like this:
NS_ASSUME_NONNULL_BEGIN
<your_code_goes_here>
NS_ASSUME_NONNULL_END`
You put there EVERY LINE (except imports) of your code, in .h and .m files. That will assume that all your arguments of methods, return types and properties are nonnull. If you want to make it nullable, put nullable there.
So, all done, now what?
Here is example of typical usage:
- (Atype*)makeAtypeWithBtype:(nullable BType*)btype {
Atype* _Nullable a = [btype makeAtype];
if (a) {
// All good here.
return NONNUL_CAST(a);
} else {
// a appeared as nil. Some fallback is needed.
[self reportError];
return [AtypeFactory makeDeafult];
}
}
Now you have more robust nullability situation. May be it is not looking nicely, but it is objective-c, so nothing to complain about.
Not every warning makes sense. Sometimes it's a shortcoming in the compiler. For instance, this code doesn't need a warning.
- (nullable id)transformedValue:(nullable id)value {
id result = value != nil ? UIImageJPEGRepresentation(value, 1.0) : nil;
return result;
}
We are checking to see if it's null! What more can we do? Why create an extra pointer?
So, we do this:
- (nullable id)transformedValue:(nullable id)value {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wnullable-to-nonnull-conversion"
id result = value != nil ? UIImageJPEGRepresentation(value, 1.0) : nil;
#pragma clang diagnostic pop
return result;
}
Why is this the proper answer?
First, it's OK to be smarter than the compiler. You don't want to start trashing your code, just because of a bogus warning.
This solution specifies the exact warning message to suppress, and it suppresses it for just one line.

warning on typedef enum when converting app to 64-bit

I am converting my iOS app to 64-bit. I have the latest Xcode 5.1 (beta 4) installed.
When I compiled the app, I received over 100 warnings and most of them are pretty easy to fix. However, I have a warning on the following code:
+ (CommentResponseStatus)commentReponseStatusCodeWithStatusString:(NSString *)_status
{
NSArray *commentStatusString = [NSArray arrayWithObjects:#"success", #"needConfirmation", #"stopped", nil];
return [commentStatusString indexOfObject:_status];
}
Where CommentResponseStatus is declared as:
typedef enum {
success,
needConfirmation,
stopped
} CommentResponseStatus;
I have a warning "Implicit conversion loses integer precision: 'NSUInteger' (aka 'unsigned long') to 'CommentResponseStatus'"
The warning is on the line return [commentStatusString indexOfObject:_status];
In NSArray we have - (NSUInteger)indexOfObject:(id)anObject;
I am confused about this warning and don't know for now how to fix it. Any quick help would be appreciated.
According to apple docs about 64-bit changes.
Enumerations Are Also Typed : In the LLVM compiler, enumerated types can
define the size of the enumeration. This means that some enumerated
types may also have a size that is larger than you expect. The
solution, as in all the other cases, is to make no assumptions about a
data type’s size. Instead, assign any enumerated values to a variable
with the proper data type
To solve this, create enumeration with type as below syntax.
typedef NS_ENUM(NSUInteger, CommentResponseStatus) {
success,
needConfirmation,
stopped
};
or
typedef enum CommentResponseStatus : NSUInteger {
success,
needConfirmation,
stopped
} CommentResponseStatus;

Resources