What's the Enum value difference between 'k' and 'v' in Swift? - ios

I just updated the latest Xcode today, when I build my project, the project was occurred an error.
Like this:
let playerStatus: BJYPlayerStatus = .playing // ambiguous use of 'playing'
The enum defined like this:
typedef NS_ENUM (NSInteger, BJVPlayerStatus) {
BJVPlayerStatus_playing,
// other cases...
BJVPlayerStatus_Playing DEPRECATED_MSG_ATTRIBUTE("use `BJVPlayerStatus_playing`") =
BJVPlayerStatus_playing
// other deprecated cases...
};
It is ambiguous about the 'playing'.
I don't know how to write to distinguish both of the '.playing'.
Thanks for your answer!

First, to answer the question in the title. "K" means an enum case (they don't use "C" because it's used for "class" already). "V" means a var (or let), i.e. a property.
The Objective-C enum gets treated like this in Swift:
// This is not real Swift code, for illustrative purposes only
enum BJVPlayerStatus : Int {
// This was BJVPlayerStatus_playing
case playing
// ...
// This was BJVPlayerStatus_Playing
#available(*, deprecated, message: "use `BJVPlayerStatus_playing`")
var playing: BJVPlayerStatus {
return BJVPlayerStatus.playing // returns the *case* .playing
}
// ...
}
The point here is that the deprecated BJVPlayerStatus_Playing is treated as a computed property in Swift, rather than an enum case, hence the "V". This is because you wrote
BJVPlayerStatus_Playing = BJVPlayerStatus_playing
in your Objective-C code.
Anyway, both the uppercase and lowercase name translates to playing in Swift, and that causes the conflict. You need to either:
use separate header files for the Swift and Objective-C code, so the Swift code doesn't see the deprecated cases, or
rename the Swift name for the deprecated case to something else using NS_SWIFT_NAME.
BJVPlayerStatus_Playing NS_SWIFT_NAME(deprecatedPlaying) DEPRECATED_MSG_ATTRIBUTE("use `BJVPlayerStatus_playing`") =
BJVPlayerStatus_playing

Related

Accessing NS_OPTIONS in swift option unavailable

I defined an NS_OPTIONS in Objective-C .h file:
typedef NS_OPTIONS (NSInteger, Options){
OptionsOne,
OptionsTwo,
OptionsThree
};
Now when accessing from Swift:
public func myFunc() -> Options {
return [.one, .two]
}
I am getting this error:
'one' is unavailable: use [] to construct an empty option set.
But I am not getting this error for .two or .three. It appears only for the first option.
By default, in Swift 3, an NS_OPTIONS enumerand equating to 0 is not imported into Swift by name. You have to use [] in Swift to get it.
When you changed the enumerand's value to 1, the name was imported.
If you think about it, this makes perfect sense. NS_OPTIONS is for bitmasks. Thus, if (let's say) .one is 0 and .two is 1, there is no useful meaning to the expression [.one, .two] because there is no information added by the presence of the .one.
What you were doing, on the other hand, was always a misuse of NS_OPTIONS, since it was not a bitmask. Your modification turned it into one. (Objective-C does not magically generate bitmask-appropriate values for you.)
I have found the solution to this to be adding explicit bitmask value to the options:
typedef NS_OPTIONS (NSInteger, Options){
OptionsOne = 1 << 0,
OptionsTwo = 1 << 1,
OptionsThree = = 1 << 2
};
and the error went away.

How to convert 'void *' return from a C function to 'UnsafeMutableRawPointer' in Swift 3?

I'm trying to convert a lua bridge from Swift 2 to Swift 3. I am not the original author so there are aspects of the library I don't know very well and the original author seems not interested to continue working on the project. I have most of the conversion done but there remain one place I'm stuck and could not figure out. I've tried searching on SO and on the Internet but could not find anything that could help me solve the problem.
If anyone is interested in looking at the full source code, here is my fork of the project on github: https://github.com/weyhan/lua4swift (My changes is in the Swift3 branch)
Allow me setup the context to the error I'm stuck on. There is a Userdata class, specifically in the method userdataPointer<T>() -> UnsafeMutablePointer<T> the c function lua_touserdata returns the block address of userdata as a void * pointer type.
Original code written in Swift 2:
public class Userdata: StoredValue {
public func userdataPointer<T>() -> UnsafeMutablePointer<T> {
push(vm)
let ptr = lua_touserdata(vm.vm, -1)
vm.pop()
return UnsafeMutablePointer<T>(ptr)
}
public func toCustomType<T: CustomTypeInstance>() -> T {
return userdataPointer().memory
}
public func toAny() -> Any {
return userdataPointer().memory
}
override public func kind() -> Kind { return .Userdata }
}
After the conversion with Xcode 8 migration tool, Xcode is complaining about the return line with error Cannot invoke initializer for type 'UnsafeMutablePointer<T>' with an argument list of type '(UnsafeMutableRawPointer?)':
return UnsafeMutablePointer<T>(ptr)
I've fixed it with:
return (ptr?.assumingMemoryBound(to: T.self))!
Following that above change, now Xcode 8 is now complaining about the calling statement in createCustomType:
public func createCustomType<T: CustomTypeInstance>(setup: (CustomType<T>) -> Void) -> CustomType<T> {
lua_createtable(vm, 0, 0)
let lib = CustomType<T>(self)
pop()
setup(lib)
registry[T.luaTypeName()] = lib
lib.becomeMetatableFor(lib)
lib["__index"] = lib
lib["__name"] = T.luaTypeName()
let gc = lib.gc
lib["__gc"] = createFunction([CustomType<T>.arg]) { args in
let ud = args.userdata
// ******* Here's the line that is causing problem in Swift 3
(ud.userdataPointer() as UnsafeMutablePointer<Void>).destroy()
// *******
let o: T = ud.toCustomType()
gc?(o)
return .Nothing
}
if let eq = lib.eq {
lib["__eq"] = createFunction([CustomType<T>.arg, CustomType<T>.arg]) { args in
let a: T = args.customType()
let b: T = args.customType()
return .Value(eq(a, b))
}
}
return lib
}
Where I'm getting stuck is the line :
(ud.userdataPointer() as UnsafeMutablePointer<Void>).destroy()
I believe the original author is attempting to clear the memory block where the pointer returned by userdataPointer() call is pointing to.
With the Xcode 8 auto migration tool the above line is converted as below:
(ud.userdataPointer() as UnsafeMutableRawPointer).deinitialize()
However Xcode now is then complains that Cannot convert call result type 'UnsafeMutablePointer<_>' to expected type 'UnsafeMutableRawPointer'.
From my research, the change to the return line in userdataPointer seems correct, so I think the issue is with the cast to UnsafeMutableRawPointer. I've tried dropping the cast to UnsafeMutableRawPointer and invoke ud.userdataPointer().deinitialize() directly but I get this error Generic parameter 'T' could not be inferred.
Other things I've tried is to convert the UnsafeMutablePointer to UnsafeMutableRawPointer but It always result in Xcode 8 complaining one thing or another. Any suggestion on how to get this to work?
As you may have already found out, Swift 3 attempts to provide better type safety when it comes to pointers. UnsafeMutablePointer can now only represent a pointer to an instance of a known type. In Swift 2, a C void * was represented by UnsafeMutablePointer<Void>, allowing void and non-void pointers to be treated in the same way, including trying to call a de-initializer of the pointed-to type, which is what the destroy() method in the problematic line of code does:
(ud.userdataPointer() as UnsafeMutablePointer<Void>).destroy()
In Swift 3 the de-initializer on the pointee is called using the deinitialize() method of the UnsafeMutablePointer structure. It appears that the migration assistant got confused. The line
(ud.userdataPointer() as UnsafeMutableRawPointer).deinitialize()
makes little sense because (1) UnsafeMutablePointer cannot be converted using as to UnsafeMutableRawPointer;
(2) UnsafeMutableRawPointer has not deinitialize() method. In Swift 3, UnsafeMutableRawPointer is a special type to represent void*. It is actually quite understandable why the migration tool made this mistake: it blindly replaced destroy() with deinitialize() and UnsafeMutablePointer<Void> with the corresponding Swift 3 type UnsafeMutableRawPointer, without realizing that the conversion would not work.
I don't quite understand why calling destroy() on a void pointer would work in Swift 2. Maybe this was a bug in the program or some compiler trick allowed the correct de-initializer to be called. Without knowing enough about the code, I can't be more specific than to suggest analyzing it to figure out what is the type pointed to by that pointer on which destroy() was called. For example, if we know for sure that it is always the placeholder type T used on the following line:
let o: T = ud.toCustomType()
then the offending line simply becomes
(ud.userdataPointer() as UnsafeMutablePointer<T>).deinitialize()
We need the conversion in the parentheses to allow the compiler to infer the generic parameter.
Thank you for bringing up an interesting problem. BTW, once you get over this obstacle, you are likely to run into other problems. One thing that jumps out is that UnsafeMutablePointer has no .memory in Swift 3; you'll have to use .pointee instead.
Here's an update. After playing with Swift 2.2 on Linux, I realize that calling destroy() on an UnsafeMutablePointer<A> cast as UnsafeMutablePointer<Void> won't call A's deinitializer, even if it has one. So, you have to be careful with that line...
Try creating an instance of UnsafeMutableRawPointer instead of trying to cast it:
UnsafeMutableRawPointer<T>(ud.userdataPointer()).destroy()

How to declare variable and enum declaration in one line?

Is there a way to simplify this into one line in Swift 2.0?
enum Direction {
case Up
case Down
}
var panDirection: Direction?
Something like this, which doesn't work:
var panDirection = enum Direction {
case Up
case Down
}
Even if you could do it, those are not the same at all. enum is an object type, like class. In your first example, panDirection is an instance of the Direction enum. In your second example, if it could compile and run, panDirection would end up as the enum itself (the type, not an instance of the type) — which is not at all what you want.
Thus, what you are trying to do is to declare a type in the middle of a line. You can't do that. The rules for where you can declare a type are very clear and very strict.
Note, however, that you can declare a type within another type, or even purely locally, e.g. within a function's code. Thus, for example, you can declare the type temporarily as a way of passing data around inside a function. Nutty but legal:
func myCoolFunction(up:Bool) {
enum Direction : String {
case Up
case Down
}
let dir : Direction = (up ? .Up : .Down)
print("user wants \(dir)")
}
No, an enum type must be declared separately before it can be used as a variable's type.

Can we add #objc for swift function that returns optional value?

I have a swift method like:
public func xIndexAtPoint(point: CGPoint) -> Int?
I want to expose it to Objective-C runtime, so I add #objc before it.
Method cannot be marked #objc because its result type cannot be represented in Objective-C
I am not sure why optional value is not allowed, since Apple does not
mention it in https://developer.apple.com/library/ios/documentation/Swift/Conceptual/BuildingCocoaApps/MixandMatch.html
You’ll have access to anything within a class or protocol that’s marked with the #objc attribute as long as it’s compatible with Objective-C. This excludes Swift-only features such as those listed here:
Generics
Tuples
Enumerations defined in Swift
Structures defined in Swift
Top-level functions defined in Swift
Global variables defined in Swift
Typealiases defined in Swift
Swift-style variadics
Nested types
Curried functions
Int is a structure defined in Swift which listed in Swift-only features
https://developer.apple.com/library/prerelease/ios/documentation/Swift/Reference/Swift_Int_Structure/index.html#//apple_ref/swift/struct/s:Si
So the answer is No
you need to break it into two function or some other workaroud
An Objective-C function can't return a pointer to an Int. You will need to create a wrapper function like:
#objc(xIndexAtPoint:)
public func _xIndexAtPoint(point: CGPoint) -> Int {
return xIndexAtPoint(point: point) ?? NSNotFound
}
Then in Objective-C, you can test for NSNotFound
NSInteger i = [foo xIndexAtPoint:CGPointMake(10, 10)];
if(i == NSNotFound) {
}

Swift set content compression resistance

Using Swift again, am I missing something?
I have this line:
self.itemDescription?.setContentCompressionResistancePriority(UILayoutPriorityRequired, forAxis: UILayoutConstraintAxis.Vertical);
But Xcode is giving me an error:
Undefined symbols for architecture i386: "_UILayoutPriorityRequired"
Another one of Swift's quirks?
Solution: replace UILayoutPriorityRequired with 1000
self.itemDescription?.setContentCompressionResistancePriority(1000, forAxis: UILayoutConstraintAxis.Vertical);
This is not a bug. It is a misunderstanding of how Objective-C libraries are imported into Swift. It should be understood how Swift imports code (even from the Apple UIKIt Libraries) from Objective-C into Swift.
UILayoutPriority is a float. In Objective-C, a couple of values have been pre-defined for us. The pre-defined values appear to be an enum. We might expect that the same enums would be available to us in Swift.
The documentation does seem to imply an enum:
SWIFT (Documentation)
typealias UILayoutPriority = Float
OBJECTIVE-C (Documentation)
enum {
UILayoutPriorityRequired = 1000,
UILayoutPriorityDefaultHigh = 750,
UILayoutPriorityDefaultLow = 250,
UILayoutPriorityFittingSizeLevel = 50,
};
typedef float UILayoutPriority;
But in Xcode, if you ask to see the defintion of one of these enum values (UILayoutPriorityRequired, for example), you will see that they are actually defined in the header file as constant floats.
OBJECTIVE-C (Header file)
typedef float UILayoutPriority;
static const UILayoutPriority UILayoutPriorityRequired NS_AVAILABLE_IOS(6_0) = 1000; // A required constraint. Do not exceed this.
static const UILayoutPriority UILayoutPriorityDefaultHigh NS_AVAILABLE_IOS(6_0) = 750; // This is the priority level with which a button resists compressing its content.
static const UILayoutPriority UILayoutPriorityDefaultLow NS_AVAILABLE_IOS(6_0) = 250; // This is the priority level at which a button hugs its contents horizontally.
So although we may like to think of the pre-defined layout priorities as enum values (as the documentation suggests) the layout priorities are not really defined as enums; they are defined as constant floats.
A hint that there is a mis-match -- for anyone that knows the C programming language -- is that a C enum may only contain int values. The following is legal and will compile:
enum myEnum {
JGCEnum_one = 1, // this is O.K.
JGCEnum_two,
JGCEnum_three
} JGCEnum;
But we can not define floats as values for C enums. The following will not compile:
enum myEnum {
JGCEnum_one = 1.5, // compile error
JGCEnum_two,
JGCEnum_three
} JGCEnum;
Objective-C enums are the same as C enums (Swift enums are different). It is important to know if we are dealing with actual integers or floats. Because integers can be defined using the NS_ENUM macro, which can then be imported to Swift as a Swift enum.
The iBook says
Swift imports as a Swift enumeration any C-style enumeration marked with the NS_ENUM macro. This means that the prefixes to enumeration value names are truncated when they are imported into Swift, whether they’re defined in system frameworks or in custom code.
Excerpt From: Apple Inc. “Using Swift with Cocoa and Objective-C.” iBooks
That means that if UILayoutPriority had been defined as an integer using the NS_ENUM macro, it would have been imported into Swift as Swift enum. This is the case for UILayoutConstraintAxis.
SWIFT (Documentation)
enum UILayoutConstraintAxis : Int {
case Horizontal
case Vertical
}
OBJECTIVE-C (Documentation)
enum {
UILayoutConstraintAxisHorizontal = 0,
UILayoutConstraintAxisVertical = 1
};
typedef NSInteger UILayoutConstraintAxis;
Looking at the Objective-C header file confirms what the documentation says.
OBJECTIVE-C (Header File)
//
// UIView Constraint-based Layout Support
//
typedef NS_ENUM(NSInteger, UILayoutConstraintAxis) {
UILayoutConstraintAxisHorizontal = 0,
UILayoutConstraintAxisVertical = 1
};
So there are at least two ways to know if a pre-defined value you are used to using in Objective-C is available in Swift:
check the documentation
check the header file in Objective-C (found by right-clicking the value and then selecting "Jump to Definition")
There is one more way to see if a typedef you are used to using is a constant or an enum. In code, test to see if the address of the constant exists. Constants have a memory address, while enums do not. See the code below.
// this line will compile and run just fine.
// UILayoutPriorityDefaultHigh is a constant and has a memory address
// the value will be true if the device is running iOS 6.0 or later
// and false otherwise
BOOL predefinedValueIsAvailable = (NULL != &UILayoutPriorityDefaultHigh);
// this line will not compile
// UILayoutConstraintAxisHorizontal is an enum (NOT a constant)
// and does not have a memory address
predefinedValueIsAvailable = (NULL != &UILayoutConstraintAxisHorizontal);
References
Using Layout Priority in Swift
Xcode Documentation (iOS 8.2)
Apple Inc. “Using Swift with Cocoa and Objective-C.” iBooks
One thing to keep in mind is that, even though UILayoutPriority is an alias to Float, you're using Swift and you can use an extension to create semantic constants for these values:
extension UILayoutPriority {
static var Low: Float { return 250.0 }
static var High: Float { return 750.0 }
static var Required: Float { return 1000.0 }
}
Then you're code can read as:
self.itemDescription?.setContentCompressionResistancePriority(UILayoutPriority.Required, forAxis: .Vertical);
I've done this myself in one of my projects and thought it may be useful.
You can also write this:
self.itemDescription?.setContentCompressionResistancePriority(UILayoutPriority(<float number>), forAxis: .Vertical)
Example:
self.itemDescription?.setContentCompressionResistancePriority(UILayoutPriority(750), forAxis: .Vertical)

Resources