Do instance references really work in Swift? - objective-c

I wrote the Objective-C code first
NSMutableString *aStrValue = [NSMutableString stringWithString:#"Hello"];
NSMutableDictionary *aMutDict = [NSMutableDictionary dictionary];
[aMutDict setObject:aStrValue forKey:#"name"];
NSLog(#"Before %#",aMutDict);
[aStrValue appendString:#" World"];
NSLog(#"After %#",aMutDict);
I got the output as follows
2015-09-17 14:27:21.052 ShareIt[4946:129853] Before {
name = Hello;
}
2015-09-17 14:27:21.057 ShareIt[4946:129853] After {
name = "Hello World";
}
Means when I append a string to a Mutable string which is actually referred into a MutableDictionary, the change is getting reflected in Dictionary too..
But then I tried something same in Swift
var stringValue:String?
stringValue = "Hello"
var dict:Dictionary = ["name":stringValue!]
println(dict)
stringValue! += " World"
stringValue!.extend(" !!!!")
println(dict)
I seen the output in playground like this
My Questions are
Why the value that changed is not reflecting in a data structure like
Dictionary.
Does in Swift adding any key value really keeps the value or its
reference, if it's keeping the reference like objective-C then here what is my mistake?

Reference type
The different behaviours depends on the fact that in the Objective-C code you use NSMutableString that is a class.
This means that aMutDict and aStrValue are references to the same object of type NSMutableString. So the changes you apply using aStrValue are visibile by aMutDict.
Value type
On the other hand in Swift you are using the String struct. This is a value type. This means that when you copy the value from one variable to another, the change you do using the first variable are not visible to the second one.
The following example clearly describes the value type behaviour:
var word0 = "Hello"
var word1 = word0
word0 += " world" // this will NOT impact word1
word0 // "Hello world"
word1 // "Hello"
Hope this helps.

Strings in Swift (copy by value) are completely different than string in Objective C (copy by reference).
From Apple' Swift documentation:
Strings Are Value Types
Swift’s String type is a value type. If you create a new String value,
that String value is copied when it is passed to a function or method,
or when it is assigned to a constant or variable. In each case, a new
copy of the existing String value is created, and the new copy is
passed or assigned, not the original version. Value types are
described in Structures and Enumerations Are Value Types.
Swift’s copy-by-default String behavior ensures that when a function
or method passes you a String value, it is clear that you own that
exact String value, regardless of where it came from. You can be
confident that the string you are passed will not be modified unless
you modify it yourself.
Behind the scenes, Swift’s compiler optimizes string usage so that
actual copying takes place only when absolutely necessary. This means
you always get great performance when working with strings as value
types.

In swift, String is a Struct.
Structs are not reference types in Swift, thus it's copied when you setting it to a dictionary.

Related

Corrupted string when returning new string from objective-c to swift

I try to build a string from an enum in Objective-C:
+ (NSString *)getStringFromKey:(ITGenericNotification)key {
return [NSString stringWithFormat:#"notification%lu", (long)key];
}
This code worked really well for over a year
However when I try to call it from swift with:
let notificationKey = NSNotification.getStringFromKey(ITNotificationWithObject.DidChangedQuestion.hashValue)
I get a corrupted string :
What is happening ?
The value of ITGenericNotification seems to be correct,
ITGenericNotification is defined as typedef long ITGenericNotification; and is used as base type for an enum with typedef NS_ENUM(ITGenericNotification, ITNotificationWithObject)
And + (NSString *)getStringFromKey:(ITGenericNotification)key is implemented as a category to NSNotification
You probably want .rawValue, not .hashValue, to get the underlying
integer value of the enum.
(Also the debugger variables view is sometimes wrong. When in doubt,
add a println() or NSLog() to your code to verify the values of
your variables.)

What is the correct way to change a string value in Objective C?

Changing a mutable strings value in many languages is very easy:
myString = "foo"
myString = "bar"
However, while learning Objective C it seems like you have to really, really jump through hoops to change an existing string's value (This is often done with switch or if cases):
NSMutableString *myString;
myString = [NSMutableString stringWithString:#"foo"];
myString = [NSMutableString stringWithString:#"bar"];
Is there an better way to change a string's value?
myString = #"foo"
myString = #"bar"
is fine in obj-c too. Don't confuse changing object value (for which you need NSMutableString) and variable value.
EDIT. Well, maybe you really need to change existing object value, but it's unclear from your question. You don't usually need to mutate existing object for switches.
Objective-C doesn't force you to jump through any extra hoops relative to most languages. It's probably simply less clear what you're doing in other languages perhaps, because for the most part, it's the same.
For starters, Objective-C doesn't allow objects on the stack--only on the heap. This is true for all objects and there's nothing special about strings here. Other languages such as C++ allow objects on the stack.
When we create objects on the heap, our variable is merely a pointer to that object, so when we create an object we have to allocate memory for it.
When we assign a string object via the = operator, we've change the memory location our variable points too.
You can create strings as simply as this:
NSString *fooString = #"foo";
NSString *barString = #"bar";
Which really isn't any more complex then any language I've seen. It's just an extra #, how is this complicated?
CHANGING the value of a string, however, isn't so simple. NSString is immutable, so to change it's value, we have to create a new object on the heap, and point to that. We haven't changed the value, we've created a new object and pointed at that.
With NSMutableString however, there are a range of available methods for changing the actual value at the memory location we point to.
In your example
NSMutableString *myString;
myString = [NSMutableString stringWithString:#"foo"];
myString = [NSMutableString stringWithString:#"bar"];
the first two lines can be grouped together and the third can simply reduce to changing the string property of the mutable string, like this
NSMutableString *myString = [NSMutableString stringWithString:#"foo"];
myString.string = #"bar";
Anyway, mutable strings actually make sense only when you need to pass a reference away and allow it to be changed.
If you simply need to change the value of a NSString * pointer over time, just do
NSString *myString = #"foo";
myString = #"bar";
myString = #"baz";
and be done with it.
It depends on whether you insist on having a string where you can change the bytes inside it or only want to replace it entirely. Replacement of the whole thing doesn't need 'mutable' but if you'd like to replace it and be able to modify the bytes, then you want something like:
NSMutableArray *mstr = [#"foo" mutableCopy];
mstr = [#"bar" mutableCopy];

difficulty understanding syntax

In the tutorial there is the following declaration:
-(BOOL)writeToFile:(NSString *)path atomically:(BOOL)useAuxiliaryFile;
I'm interpreting this as writeToFile method returns a BOOL result. It takes 2 parameters. The first is an NSString reference. The second is the result of calling atomically and passing it a BOOL value.
Did I get that right?
The tutorial goes on to say you call the above method like this:
BOOL result = [myData writeToFile:#"/tmp/log.txt" atomically:NO];
which is find. But I wonder if I MUST use "atomically:NO"
Could I have done something like
resultOfAtomically = atomically:NO
BOOL result = [ myData writeToFile:#"/tmp/log.txt" resultOfAtomically ];
assuming I declared resultOfAtomically properly.
Also, does prepending # to "/tmp/log.txt" mean something like "give me the reference NOT the value" ?
The text atomically introduces the second parameter of the writeToFile:atomically: method, it is not a separate function. Therefore, you cannot call simply atomically:NO. This would be correct, however:
BOOL myBool = NO;
BOOL result = [myData writeToFile:#"/tmp/log.txt" atomically:myBool];
When talking about Objective-C methods, you would not call this method "writeToFile", you would call it "writeToFile:atomically:". By this syntax you know that the method expects two parameters (one for each colon).
Here's a way to think about how the method declaration breaks down:
-(BOOL)writeToFile:(NSString *)path atomically:(BOOL)useAuxiliaryFile;
- = defining an instance method (+ would be for a class method).
(BOOL) = returning a boolean value.
write = this method is about writing something (nothing magic, just a friendly word choice - could have been print or something else).
ToFile:(NSString *)path = the first parameter of the method, path, is an NSString pointer and the friendly text "ToFile" (again just a word choice, nothing special) hints that I need to provide a file path for that parameter.
atomically:(BOOL)useAuxiliaryFile; = the second parameter of the method, useAuxiliaryFile, is a boolean and the friendly text "atomically" (still nothing special) hints that the boolean value determines whether or not to write the file atomically.

Change pointer to NSString in Objective-C

I have been experimenting a bit with Objective-C and noted some, at least to me, rather strange behavior. First I define a pointer to an NSString and add it to an NSArray:
NSString *s = #"A";
NSArray *a = [NSArray arrayWithOject: s];
I then print out the value of s as well as the contents of a:
NSLog(#"%#", s);
NSLog(#"%#", myArray);
and in both cases the output is A. Now, if I change the pointer s, say
s = #"B";
then the two NSLog statements print out B and A, respectively.
That is, the pointer in my array still points to #"A". After spending years coding in Java, this is very surprising to me. Am I missing something really fundamental here?
Thanks,
Michael Knudsen
The easiest way to understand this is that #"A" creates a new NSString object.
In your code, you set the pointer s to point to this object, and then add the original object (not the pointer) to an array. You then change the address that the pointer points to to a new address.
If you want to change the original object, then use NSMutableString and modify the actual object (instead of changing the pointer to a new object) and they will both update as you expect.
Try:
NSMutableString *s = #"A";
// Add to array
[s setString:#"B"]
That is because NSStrings are immutable, as in Java, so you are not changing the contents of the object stored at address x (the one nsarrsy has), you are pointing s to address y.
The Java and Objective-C behaviors are the same. With the following Java code:
String foo = "foo";
Vector myVector = new Vector();
myVector.add(foo);
System.out.println(foo);
System.out.println(myVector);
foo = "bar";
System.out.println(foo);
System.out.println(myVector);
The following values are printed out:
foo
[foo]
bar
[foo]
Note: The same is true if I use String[] instead of Vector, but an NSArray is more like Vector than String[].
The easiest way to see this is to see that your s = #"B"; simply point s to another string object rather than modifying the original object.
So the array still contain #"A". When you add objects to NSArray you simply make a pointer in NSArray to point to #"A". That never changes.

How to use #encode() to get #"NSArray" in Objective-C

I'm using the runtime functions to get the type of a property (thanks to eJames for helping me to figure out this way).
The attribute string of the property looks like this:
T#"NSArray",&,Vstuff
I need to check if the property type is an array, at the moment I'm doing it like this:
- (BOOL)valueForKeyIsArray:(NSString *)key fromTagret:(id)target
{
NSString *lowerCaseKey = [self convertToKVCKey:key];
objc_property_t property = class_getProperty([target class], [lowerCaseKey UTF8String]);
NSString *propertyAttrs = [NSString stringWithUTF8String:property_getAttributes(property)];
NSString *encodedType = #"#\"NSArray\"";
NSRange range = [propertyAttrs rangeOfString:encodedType options:NSLiteralSearch];
return range.location != NSNotFound;
}
But since Apple can change the type definition string at any time, I would like to generate this #"NSArray" type string. I tried it with #encode(), but it did not work:
NSString *encodedType = [NSString stringWithUTF8String:#encode(NSArray *)];
So how can I generate this type string? Or is there a better way to check if this property attributes contain the array type?
There is no way to check this. In Objective-C source code the variables being typed as NSArray * is only there for the compiler to issue warnings. It has no meaning, and does not exist at runtime. If you mis-typed an NSArray as an NSString, you would get lots of warnings when compiling, but your code would behave exactly the same when run. At runtime all that is known is that the ivar/property is "an object".
Another way to think of it, is that once Objective-C is compiled, all object references are id references.
Just accept that if the runtime changes, your code will break, and move on. However, I think you might be miscategorizing ivars of type NSMutableArray *, CFArrayRef, or CFMutableArrayRef. You also seem to be assuming all keys correspond directly to a declared property.
The cleanest solution might be to assert that the sample object being used for the test (the target) must have a non-nil value for that key, and just grab the value and test that [[target valueForKey:key] isKindOfClass:[NSArray class]].