Objective-C Runtime API in swift - swift

I try to use the old Objective-C runtime APIs in swift. But I can't get the correct result:
import Foundation
class MyClass
{
let a : UInt32 = 10
func test(){ }
}
var obj = MyClass()
println("%#",class_getSuperclass(MyClass)) //ExistentialMetatype
println("%#",objc_getClass(UnsafePointer<Int8>(unsafeAddressOf(obj)))) //nil
The result of first println is "ExistentialMetatype". The second println gives nil. My Questions are:
What is the type of "ExistentialMetatype"?
I also tried other runtime APIs but they all failed. So far, I can't find any Apple docs related to this topic. And It seems like the only way to get the meta-class(isa pointer) information is through reverse engineering.
Does this mean Swift prevents us from accessing the meta-class at runtime?
The above code is running on XCode 6.1.1

Related

Cannot access enum case's rawvalue defined in a global constants file

I have a global constants file: Constants.swift in an iOS app project. Xcode version is 11.1.
The code in this file:
import Foundation
struct Constants {
enum DayOfTheWeekend : Int {
case Saturday = 1
case Sunday = 2
}
}
In a different file in the same iOS app project, I have this code in a func within a class:
let day = Constants.DayOfTheWeekend.Saturday.rawvalue
And I get this error:
Value of type 'Constants.DayOfTheWeekend' has no member 'rawvalue'
If I put enum outside of the struct in the same Constants.swift file, I still get the same error.
When I type "Constants.DayOfTheWeekend.Saturday.", Xcode autocomplete feature suggests only "self" and "hashvalue". There is not any rawvalue option.
Where is my mistake?
The syntax is rawValue. See The Swift Programming Language: Enumerations: Raw Values.
Why do you think Xcode autocomplete doesn't suggest that?
It does:
But sometimes autocomplete gets confused, especially if there are some errors elsewhere in one’s code. It also won’t work if the file with the enumeration hasn’t been saved. And sometimes it just gets sufficiently confused that you have to empty the derived data folder and restart Xcode.

Only classes that inherit from NSObject can be declared #objc

I have to set value to property by string name representation.
import Foundation
#objc class A:NSObject {
var x:String = ""
}
var a = A()
a.x = "ddd"
print(a.x)
a.setValue("zzz", forKey:"x")
print(a.x)
And getting strange errors during compilation:
main.swift:4:2: error: only classes that inherit from NSObject can be declared #objc
#objc class A:NSObject {
~^~~~~
main.swift:13:1: error: value of type 'A' has no member 'setValue'
a.setValue("zzz", forKey:"x")
^ ~~~~~~~~
Does anyone know what is happening?
PS: reproducible on Swift 4.0 & 3.1.1 (Ubuntu 16.04.3 LTS)
Edited:
import Foundation
#objc class A:NSObject {
#objc dynamic var x:String = ""
}
var a = A()
a.x = "ddd"
print(a.x)
a.setValue("zzz", forKeyPath:"x")
print(a.x)
Output:
error: only classes that inherit from NSObject can be declared #objc
#objc class A:NSObject {
error: property cannot be marked #objc because its type cannot be represented in Objective-C
#objc dynamic var x:String = ""
note: Swift structs cannot be represented in Objective-C
#objc dynamic var x:String = ""
error: value of type 'A' has no member 'setValue'
a.setValue("zzz", forKeyPath:"x")
EDIT 2:
Just trying like "c-style":
func set<T>(_ val:T, forKey key:String) {
print("SET:\(self) \(key) to \(val)")
let ivar: Ivar = class_getInstanceVariable(type(of: self), key)!
let pointerToInstanceField:UnsafeMutableRawPointer = Unmanaged.passRetained(self).toOpaque().advanced(by: ivar_getOffset(ivar))
let pointer = pointerToInstanceField.assumingMemoryBound(to: T.self)
pointer.pointee = val
}
It works well, but causes bad access in the recursive calls. Probably some retain/release issues. Will dig dipper. Also does not work on Linux (as mentioned in answers)
Documentation
Swift without the Objective-C Runtime: Swift on Linux does not depend
on the Objective-C runtime nor includes it. While Swift was designed
to interoperate closely with Objective-C when it is present, it was
also designed to work in environments where the Objective-C runtime
does not exist.
https://swift.org/blog/swift-linux-port/
Which is clear, provided that it states:
value of type 'A' has no member 'setValue'
It basically tells that there is no KVC mechanism underneath. setValue method comes from Objective-C runtime, which is absent on Linux. Thus, it's a no-go and what you're trying to accomplish is simply not possible.
Other than that, the following rule is applied on systems with Obj-C runtime environment:
Key-Value Coding with Swift
Swift objects that inherit from NSObject or one of its subclasses are
key-value coding compliant for their properties by default. Whereas in
Objective-C, a property’s accessors and instance variables must follow
certain patterns, a standard property declaration in Swift
automatically guarantees this. On the other hand, many of the
protocol’s features are either not relevant or are better handled
using native Swift constructs or techniques that do not exist in
Objective-C. For example, because all Swift properties are objects,
you never exercise the default implementation’s special handling of
non-object properties.
Also: Requiring Dynamic Dispatch
Swift APIs that are callable from Objective-C must be available
through dynamic dispatch. However, the availability of dynamic
dispatch doesn’t prevent the Swift compiler from selecting a more
efficient dispatch approach when those APIs are called from Swift
code.
You use the #objc attribute along with the dynamic modifier to require
that access to members be dynamically dispatched through the
Objective-C runtime. Requiring this kind of dynamic dispatch is rarely
necessary. However, it is necessary when using APIs like key–value
observing or the method_exchangeImplementations function in the
Objective-C runtime, which dynamically replace the implementation of a
method at runtime.
Declarations marked with the dynamic modifier must also be explicitly
marked with the #objc attribute unless the #objc attribute is
implicitly added by the declaration’s context. For information about
when the #objc attribute is implicitly added, see Declaration
Attributes in The Swift Programming Language (Swift 4).
Elements must also be declared dynamic in order to be KVO-compatible (for KVC, inheriting from NSObject is enough):
#objc dynamic var x:String = ""
If String doesn't work out, then try going with NSString.
If neither helps, this seems to be a Linux-specific issue, which doesn't appear to support KVC/KVO mechanism (which is also understandable).
P.S. With the code provided, your issue reproduced in Xcode on Mac, too.

Segmentation fault in Swift compiler when converting to Swift 4 syntax

I have some Swift code that was written in Swift 3.2, and I just attempted to use the automatic syntax converter in Xcode 9.0 to update to Swift 4.0.
It actually found nothing that needed to be converted, but during the code analysis when the compiler compiles the project, the compiler crashes with a segmentation fault. I can't share the full source code, so I created a simple example which demonstrates the same issue, and confirmed that this also produces a crash in a newly-created Swift project.
The compiler crash only appears when joining values together using string interpolation. To reproduce:
Create a new Xcode project, and set Swift version to 3.2
Create a new Swift class
Paste the following code into the new class:
public class SomeClass: NSObject {
static let defaultStringPrefix = "abc"
var a: String = ""
var b: String = ""
var c: String = ""
lazy var concatStr: String = {
let str = "\(defaultStringPrefix)/\(self.a)/\(self.b)/\(self.c)/somepage.html"
return str
}()
}
Attempt to perform the Swift 3.2 to Swift 4.0 conversion -> This will fail and a segfault will appear with the following error:
Command failed due to Segmentation fault: 11
Stack dump:
0....(a huge load of arguments to the swift compiler)
1. While type-checking getter for concatStr at /Users/craigrouse/mobiledev/test/test/SomeClass.swift:17:14
2. While type-checking declaration 0x7ff292a86400 in module 'test'
3. While type-checking expression at [/Users/craigrouse/mobiledev/test/test/SomeClass.swift:17:34 - line:20:7] RangeText="{
let str = "\(a)/\(b)/\(c)/somepage.html"
return str
}()"
I've tried various versions of this, and if I remove the string interpolation, the problem goes away. Obviously I could avoid using the auto conversion, and set the Swift version to 4 manually in the project settings, but I'm sure others will encounter this mysterious error, and I'd like to find out what's causing it. Is there a problem with my syntax?
Thanks.

swift method_exchangeImplementations not work

swift code is below:
func swizzleMethod()
{
let method:Method = class_getInstanceMethod(object_getClass(self), Selector("function1"))
self.function1()
let swizzledMethod:Method = class_getInstanceMethod(object_getClass(self), Selector("function2"))
method_exchangeImplementations(method, swizzledMethod)
self.function1()
}
func function1()
{
print("function1 log")
}
func function2()
{
print("function2 log")
}
it logs:
function1 log
function1 log
/////
my environment is swift based project, xcode7.2
This always run into the funtion1 method body, so I think it exchanged failed, this two method is in the same class, anyone know why?
I get the answer, add "dynamic" keyword in front of method name!!!
like this:
dynamic func function1()
{
print("function1 log")
}
dynamic func function2()
{
print("function2 log")
}
Reason:
Swift optimizes code to call direct memory addresses instead of looking up the method location at runtime as in Objective-C. so we need tell the code run treat it as Objective-C code.
Answer Detail:
Method swizzling is a technique that substitutes one method implementation for another. If you're not familiar with swizzling, check out this blog post. Swift optimizes code to call direct memory addresses instead of looking up the method location at runtime as in Objective-C. So by default swizzling doesn’t work in Swift classes unless we:
1. Disable this optimization with the dynamic keyword. This is the preferred choice, and the choice that makes the most sense if the codebase is entirely in Swift.
2. Extend NSObject. Never do this only for method swizzling (use dynamic instead). It’s useful to know that method swizzling will work in already existing classes that have NSObject as their base class, but we're better off selectively choosing methods with dynamic .
3. Use the #objc annotation on the method being swizzled. This is appropriate if the method we would like to swizzle also needs to be exposed to Objective-C code at the same time.
thanks to the article: 15 Tips to Become a Better Swift Developer

Swift avspeechsynthesizer different languages

utterance.voice = [AVSpeechSynthesisVoice voiceWithLanguage:#"en-au"];
This exact line of code is how you can change the languages in obj-c. But i was wondering if anyone, could tell me how so it is implemented in swift. in documentation this line of code is used...
init!(language language: String!) -> AVSpeechSynthesisVoice
But i can't understand where i would implement it :/
Where you see init(paramName: ParamType) in the Swift interface for a type named Type, you call it with the syntax Type(paramName: paramValue). This is right at the top of the chapter on Initialization in The Swift Programming Language, which I'd recommend reading before getting more than trivially into Cocoa development with Swift.
Also worth reading is the section on Initialization in Using Swift with Cocoa and Objective-C, which repeats the above and also gives you the general rule for how ObjC initializers and factory methods automatically map to Swift initializers: if you have a ObjC class named Foo with the initializer initWithBar: and/or the factory class method fooWithBar:, it maps to the Swift initializer init(bar:) and you call it with the syntax Foo(bar: someBarValue).
So:
utterance.voice = AVSpeechSynthesisVoice(language: "en-au") // g'day, mate
Note that this specific initializer is of the form init! — that means that the underlying ObjC code can return nil, and that Swift wraps the result of the initializer call in an Implicitly Unwrapped Optional. Since AVSpeechUtterance.voice can accept an optional (including one with a nil value), you're in the clear. But if that ever changes, or if you need to deal with APIs that explicitly require a non-nil voice, you should check that optional; e.g.:
if let voice = AVSpeechSynthesisVoice(language: "en-au") {
// do something with voice
} else {
// pick another one, maybe?
}