Determining from Objective-c if a Swift property is declared as dynamic - ios

I have been trying for some time to inspect a Swift class, and determine if any of the properties are declared as dynamic. My example class is as below:
class SwiftTestClass : DBObject {
dynamic var SwiftTestString : String!
dynamic var SwiftTestNumber : NSNumber!
dynamic var lowercaseField : String!
var nonDynamicVariable : String!
func testThyself() {
SwiftTestClass.query().fetchLightweight().removeAll()
let newObject = SwiftTestClass();
newObject.SwiftTestString = "hello, world"
newObject.SwiftTestNumber = 123
newObject.lowercaseField = "lowercase"
newObject.nonDynamicVariable = "should not be persisted"
newObject.commit()
let result = SwiftTestClass.query().fetch().firstObject;
print(result)
}
}
I am basically trying to pick out the fact that the property nonDynamicVariable is not declared as dynamic as the rest of them are.
DBObject is a subclass of NSObject.
I have tried:
Looking at the type encoding of the property, they are identical (type for type)
Seeing if they have a difference in the method implementations, they do not. (e.g. class_getMethod), the dynamic properties still have getter/setter methods.
Grabbing the Ivars to see if there is any difference there
Looking at all of the property attributes, also identical.
What I do know:
If I try to class_replaceMethod for the <propertyName>/set<propertyName>, it works for a dynamic property (as you would expect, because it adds objc compatibility) but fails to work (but does replace?, well, the memory address of the method changes!) or be actioned on the non dynamic property.
Does anyone know how to differentiate the two property declarations in swift from objc?
Thanks

Related

Why #Objc dynamic variable not accepting optional Int [duplicate]

Within a Swift class derived from an Obj-C based framework (but could just as easily be a Swift class with an #objc attribute) I declare two stored properties:
var optInt: Int?
var optString: String?
Only optString is being exposed to Obj-C via the generated -Swift.h header.
String? is presumably fine because it is exposed using an NSString object which can be nil, so the bridging has a way to represent no value.
If I remove the ? from optInt it's exposed with an NSInteger type, so I can see that for non-optional integers it avoids objects and bridges value type to value type, but does this literally mean that an Int? can't be exposed?
I can't seem to find any documentation that explicitly says this is the case. There is a whole list of incompatible Swift features here that this doesn't appear on: Using Swift from Objective-C
The use case here is the classic situation requiring the passing of a numeric ID which could legitimately be zero. In the pre-Swift world NSNumber and nil is exactly how I went about implementing this, but it just feels wrong to be trying to migrate a class to Swift but then hanging on to Obj-C types within the Swift class specifically for this reason.
I suppose I had envisaged that an Int? unlike Int would bridge as an NSNumber in the background, with its potentially nil value feeding the "has no value" element of the optional in Swift.
Is there anything I'm missing here? To reiterate, can a Swift Optional Int (Int?) be exposed to Objective-C via bridging?
The problem with exposing an Int? property as NSNumber* is that you could store a non-Int-compatible NSNumber to it from Objective-C.
You can make a computed NSNumber? property to wrap your Int? property. The getter would simply return the Int? variable. The setter would set the Int variable from -[NSNumber integerValue].
Here's a concrete answer for the solution described above:
private var _number: Int?
public var number: NSNumber? {
get {
return _number as NSNumber?
}
set(newNumber) {
_number = newNumber?.intValue
}
}
// In case you want to set the number in the init
public init(number: NSNumber?) {
_number = number?.intValue
}

"fatal error: array cannot be bridged from Objective-C"—Why are you even trying, Swift?

I have declared a Swift protocol:
protocol Option {
var name: String { get }
}
I declare multiple implementations of this protocol—some classes, some enums.
I have a view controller with a property declared as so:
var options: [Option] = []
When I try and set this property to an array of objects that implement the Option protocol in another VC's prepareForSegue, I get a runtime error:
fatal error: array cannot be bridged from Objective-C
Why doesn't this work? The compiler has all the information it needs, and I don't understand what Objective-C has to do with it at all—my project contains only Swift files, and these arrays aren't coming in or out of any framework methods that would necessitate them being bridged to NSArray.
I have found a solution. It is quite... unsatisfying, but it works. Where I set the array on the destination view controller I do:
destinationViewController.options = options.map({$0 as Option})
the compiler knows I'm passing in an Array of things that implement Option
You've let slip there a very revealing remark, which suggests the source of the issue. An "Array of things that implement Option" is not an Array of Option.
The problem is with the type of options back at the point where you create it (in prepareForSegue). You don't show that code, but I am betting that you fail to cast / type it at that point. That's why the assignment fails. options may be an array of things that do in fact happen to adopt Option, but that's not enough; it must be typed as an array of Option.
So, back in prepareForSegue, form your options like this:
let options : [Option] = // ... whatever ...
Now you will be able to assign it directly to destinationViewController.options.
Here's a quick test case (in a playground; I detest playgrounds, but they can have their uses):
protocol Option {
var name : String {get}
}
class ViewController : UIViewController {
var options : [Option] = []
}
enum Thing : Option {
var name : String {
get {
return "hi"
}
}
case Thing
}
let vc = ViewController()
let options : [Option] = [Thing.Thing]
vc.options = options // no problem
(I also tested this in an actual app with an actual prepareForSegue, and it works fine.)
I was having the same problem and fixed it marking my protocol with #objc, in your case it would look like this
#objc protocol Option {
var name: String { get }
}
Got the solution from this answer
This one also works fine
destinationViewController.options = options.map{$0}

Swift dynamic variable can't be of type Printable

I have a Swift project that contains two UITableViewControllers. The second UITableViewController is linked to a MVC model called Model. According to the UITableViewCell I select in the first UITableViewController, I want to initialize some properties of Model with Ints or Strings. Therefore, I've decided to define those properties with Printable protocol type. In the same time, I want to perform Key Value Observing on one of these properties.
Right now, Model looks like this:
class Model: NSObject {
let title: String
let array: [Printable]
dynamic var selectedValue: Printable //error message
init(title: String, array: [Printable], selectedValue: Printable) {
self.title = title
self.array = array
self.selectedValue = selectedValue
}
}
The problem here is that the following error message appears on the selectedValue declaration line:
Property cannot be marked dynamic because its type cannot be
represented in Objective-C
If I go to the Xcode Issue Navigator, I can also read the following line:
Protocol 'Printable' is not '#objc'
Is there any workaround?
There is no way to do what you want. Non-#objc protocols cannot be represented in Objective-C. One reason is that Non-#objc protocols can represent non-class types (and indeed, you said that you wanted to use it for Int and String, both non-class types), and protocols in Objective-C are only for objects.
KVO is a feature designed for Objective-C, so you must think about what you expect it to see from the perspective of Objective-C. If you were doing this in Objective-C, you would not want to have a property that could either be an object like id or a non-object like int -- you can't even declare that. Instead, as you said in your comment, you probably want it to be just objects. And you want to be able to use Foundation's bridging to turn Int into NSNumber * and String into NSString *. These are regular Cocoa classes that inherit from NSObject, which implements Printable.
So it seems to me you should just use NSObject or NSObjectProtocol.
Unfortunately ObjC does not treat protocols as types, they are just a convenient way of grouping members. Under the covers they are of type Any, so regretfully you will have to make the property Any and cast to Printable.
The best I can thing of is:
dynamic var selectedValue: Any
var printableValue : Printable {
get {
return (Printable)selectedValue
}
set {
selectedValue = newValue
}
}

Struct value types in Swift

I understand the difference between 'Value Types' and 'Reference Types'. I know 'Structures' are 'Value Types' and according to the Swift documentation all the values stored by the structure are themselves value types. Now my question is what if I have a stored property in a Struct that is an instance of a class. In that case, would the whole class would be copied or just its address?
Any help would be appreciated.
It copies the pointer to the instance. I just tested this in a playground.
struct MyStruct {
var instance: MyClass
}
class MyClass {
var name: String
init(name: String) {
self.name = name
println("inited \( self.name )") // Prints "inited Alex" only once
}
}
var foo = MyClass(name: "Alex") // make just one instance
var a = MyStruct(instance: foo) // make a struct that contains that instance
var b = a // copy the struct that references the instance
foo.name = "Wayne" // Update the instance
// Check to see if instance was updated everywhere.
a.instance.name // Wayne
b.instance.name // Wayne
What is different though, is that it's now two different references to the same object. So if you change one struct to a different instance, you are only hanging it for that struct.
b.instance = MyClass(name: "Vik")
// a and b no longer reference the same instance
a.instance.name // Wayne
b.instance.name // Vik
The playground is a great way to test out questions like these. I did not know the answer definitively when I read this question. But now I do :)
So don't be afraid to go play.
I think you misread the documentation. According to the The Swift Programming Language,
All structures and enumerations are value types in Swift. This means that any structure and enumeration instances you create—and any value types they have as properties—are always copied when they are passed around in your code.
Since classes are reference types, not value types, they are not copied even if they are properties of a value type, so only the address is copied.

Strange behaviour when naming variable in lowerCamelCase

I came across a strange behaviour in Swift while programming a Master-Detail application.
Here's the scenario:
It's a simple Task Manager application. I have two text controls (TaskName, TaskDescription) on the TaskDetailView and two string variables with the same name but in lowerCamelCase (taskName, taskDescription) declared in the TaskDetailViewController.
#IBOutlet var TaskName:UITextField! //UpperCamelCase
#IBOutlet var TaskDescription:UITextView! //UpperCamelCase
var taskName:String? //lowerCamelCase
var taskDescription:String? //lowerCamelCase
I am setting the values of Text controls on ViewDidLoad() as usual:
override func viewDidLoad() {
super.viewDidLoad()
TaskName.text = taskName
TaskDescription.text = taskDescription
}
And I am passing the data in prepareForSegue (from TaskListViewController) as usual:
override func prepareForSegue(segue: UIStoryboardSegue!, sender: AnyObject!) {
if(segue.identifier == "TaskListSegue"){
let detailViewController = segue.destinationViewController as ToDoTaskViewController
let (task, desc) = m_ToDoListManager.GetTask(TaskListView.indexPathForSelectedRow().row)
println("selected \(task) \(desc)")
detailViewController.taskName = task
detailViewController.taskDescription = desc
}
}
The way everything is implemented is correct.
But now when you run the application, the values of text controls are not set.
In fact, the values of the variables also are not set.
What must be happening here?
I have already investigated this problem and also came up with a solution (see my answer below). Please also see Martin R's answer below for a detailed explanation. I just wanted to share this with everyone. I am not sure if anyone has come across this issue.
Update:
Here's the actual code:https://github.com/Abbyjeet/Swift-ToDoList
Here is an explanation:
Your Swift class is (ultimately) a subclass of NSObject.
Therefore the properties are Objective-C properties with getter and setter method.
The name of the setter method for a property is built by capitalizing the first
letter of the property name, e.g. property "foo" has the setter method setFoo:
As a consequence, the setter method for both properties TaskName and taskName is called setTaskName:.
In an Objective-C file, you would get a compiler error
synthesized properties 'taskName' and 'TaskName' both claim setter 'setTaskName:' - use of this setter will cause unexpected behavior
but the Swift compiler does not notice the conflict.
A small demo of the problem:
class MyClass : NSObject {
var prop : String?
var Prop : String?
}
let mc = MyClass()
mc.prop = "foo"
mc.Prop = "bar"
println(mc.prop) // bar
println(mc.Prop) // nil
In your case
TaskName.text = ...
sets the "taskName" property, not the "TaskName". The properties have different type,
so that the behavior is undefined.
Note that the problem does only occur for "Objective-C compatible" properties. If you remove the
NSObject superclass in above example, the output is as expected.
Conclusion: You cannot have two Objective-C properties that differ only in the
case of the first letter. The Swift compiler should fail with an error here (as the
Objective-C compiler does).
The problem you were facing with was not connected to the swift language. Method prepareForSegue is called before loadView. That mean UITextField and UITextView are not initialized yet. That's why fields were not initialized.
You also asked: Why compiler doesn't show any error? That's because any selector performed on nil object doesn't throw an exception. So for example (sorry for obj-c):
UITextField *tf = nil;
[tf setText:#"NewText"];
Will not show any error.
As you said on your own answer to solve your problem you need to add additional fields to your destination controller (copy-paste):
var tAskName:String? //cUstomCamelCase
var tAskDescription:String? //cUstomCamelCase
Why is it happening?
I believe that internally Swift is using lowerCamelCase for text controls names which are not yet initialized and thus failing to set the values. But it is also strange that I didn't get any kind of error.
How did I solve it?
I know that the Swift is case-sensitive. So that was not the issue. So I just changed the case of one letter and named the variables as (tAskName, tAskDescription) and the values were set as expected.
#IBOutlet var TaskName:UITextField! //UpperCamelCase
#IBOutlet var TaskDescription:UITextView! //UpperCamelCase
var tAskName:String? //cUstomCamelCase
var tAskDescription:String? //cUstomCamelCase
So the conclusion is that if I have a control named TaskName, I cannot have a variable named as taskName

Resources