Why does Swift have both NSDictionary and Dictionary classes? - ios

I'm new here so please help me figure this out. I wonder why does Swift have both NSDictionary and Dictionary classes? My question applies also to the other standard classes like String/NSString, Array/NSArray etc. I'm asking this because i have problem in my code, having to do a lot of casts.
For example, i found the method to load a plist file into a dictionary only in NSDictionary contentsOfFile and not in the native Dictionary class, the latter being the 'advertised' way to go with Swift.
Is this an implementation problem (Swift being new and all) or are there more, deeper, reasons for having the old Objective-C classes? What are the differences?

Both types are there in order to use Objective-C code in swift. Swift array and dictionary are generic type and can hold non-class instances (enum, struct), while NSArray/NSDictonary can only hold NSObject.

In order to simplify your question lets take an example of NSString
NSString is class for a long time in iOS development, it is the predecessor of String
String is the new Swift specific string class where as NSString is the older objective C version and there is a way to convert or bridge between the two,
As per Apple “The entire NSString API is available to call on any String value you create”
now why you want to do this because String class does’t necessarily have all the methods that is on NSString at the moment, so if you want to use method of NSString you can convert a String to NSString and take advantage of those methods
for example
// Double conversion
var strCon = Double(("3.14" as NSString).doubleValue)
// Contains String
var stack = "Stack overflow"
(stack as NSString).containsString("Stack")
// Substring
var overflow = (stack as NSString).substringToIndex(8)
In short if you are trying to do something and there doesn’t seem to be method available on String I recommend to take a look at NSString as there are tons of methods there and chances are it will help you out
same is the case with NSDictionary, NSArray and so on…
I hope it helps

Related

Swift collection that has defined types and copies by reference?

In converting old projects from Objective-C to Swift, I've mostly been able to use Dictionary in place of NSMutableDictionary. But in some cases, it's a hassle or uses a lot of extra memory to have the Dictionaries copying by value.
I thought I could simply change some Dictionary collections to NSMutableDictionary to make them objects that copy by value, but I don't see a way to specify the key and value types. This works in Objective-C:
NSMutableDictionary<NSString*, NSString*> *dict = [NSMutableDictionary dictionary];
But this gives an error "Cannot specialize non-generic type 'NSMutableDictionary'" in Swift:
let dict: NSMutableDictionary<String, String> = [:]
Is there a way to specify the types so I don't have to be constantly casting the values I get out of the dictionary?
Alternatively, is there another kind of collection object that supports key and value types like Dictionary but copies by reference like NSMutableDictionary?
UPDATE
I tried using NSMapTable as suggested in the comment below. That's missing some features of NSDictionary (e.g., it doesn't conform to IteratorProtocol), so I made a subclass to try making a drop-in replacement for Dictionary. I then ran into problems making my subclass generic since Swift and Objective-C have different support for that.
Since that would either require a lot of casting (or making a different subclass for each type of data I wanted to store in the dictionary), I then tried just using NSMutableDictionary and casting all the values when I read them. Unfortunately, after all that work, I couldn't see any difference in memory usage compared to using Dictionary.
So I guess having collections that copy by value isn't really my problem. It shouldn't be since I'm never retaining anything for very long, but I didn't see these memory problems until I migrated from Objective-C. I'll have to do more testing and explore other solutions.
The Objective-C specification:
NSMutableDictionary<NSString*, NSString*>
Is not a true implementation of Generics. It simply gives hints to the compiler that the dictionary will contain strings as the keys and values and the compiler can tell you, at compile time, if you make a simple mistake.
At the end of the day, there is nothing at runtime to enforce those type specifications. An NSDictionary (mutable or not) will have id for the keys, and id for the values and the runtime format will not change. That's why you can get away with using [NSMutableDictionary dictionary] to initialize all NSDictionaries... the type spec only has meaning at compile time.
In contrast when you use a identical syntax in Swift, say Dictionary<String, Int>, you are using true generics. The runtime representation of the dictionary may change depending on what key and value types you use.
In other words, in spite of similarities in their in Syntax, the <type, type> construct in Objective-C and in Swift mean VERY different things.
In Swift's Eyes, an NSDictionary (mutable or not) is simply a NSObject, just like every other NSObject so NSDictionary<NSString, NSString> is a nonsensical use of the generic type specification syntax - you're saying you want to use generics with a type that is not a generic type (hence the error).
There is no Swift syntax (that I'm aware of) that lets you specify the type you'd like to stand in for NSObject in things like NSDictionaries and NSArrays. You're going to have to use casting.
Even in Objective-C the type specs mean nothing and it's possible to squeeze something in there that doesn't belong. Consider:
NSDictionary<NSString *, NSString *> *myDictionary = [NSMutableDictionary dictionary];
((NSMutableDictionary *)myDictionary)[[NSNumber numberWithInt: 3]] = [NSURL URLWithString: #"http://www.apple.com"];
Here I declare the dict to use Strings, then shove in a number and a URL. The only way to guard against this would be to check the types, that is to do typecasting (or at least type-checking), for each key and value. Most folks code doesn't do that because it would be a pain, but the only way to get true safety.
Swift, in contrast, focus on the safety right up front. It's one of the defining differences between Swift an Objective-C. So you have to go through the pain if you insist on using "unsafe" Objective-C types.

Swift Transfer -> Array to ObjectiveC

I have problem and i cannot fix it by using google.
I have string array (written in swift). But i have objectiveC files with chart functions.
I need transfer whole swift array into objectiveC file.
Example of array:
for i in 0...11 {
mainSelectionMonthArrayValues.append(
String(WatchlistViewController().fetchDataMonth(
type:1, month: i+1, year: 2017)
)
)
}
Given you are not very specific about the code that "does not work" or the specific error you get it is hard to answer anything precisely. But given what Google finds in books for "cocoa pass swift string array to objective-c" you will have
To call an NSArray function on a Swift array you may have to cast to NSArray
so it is a pretty safe bet you will have to do this too if you want to pass mainSelectionMonthArrayValues to an Objective-C method (with an appropriate interface). Casting to an Objective-C class will not be free in most cases, but it is likely to be a constant time operation. Note however, that you will have to coerce if your object needs to be mutable on the Objective-C end. So try passing your array using something like
objCRef.callObjCMethod(mainSelectionMonthArrayValues as NSArray)
If this again "does not work" then you should provide us with more info on the kind of error you experience.

NSString Subclass or Wrapper Class or Category

I'm currently helping a client that needs to change the language in their app due to certain governmental guidelines (medical vs wellness wording). Their app is huge and all the strings are contained in the code i.e. (stringWithFormat/hardcoded), none of it is in an external table. Meaning this would be a huge manual task.
At a undetermined point in the future the client believes they will receive approval to return to their current wording and will want to switch the strings back. Most of the changes will literally be switching a single problematic word to a less problematic word.
I thought that maybe if I could change the strings at run time based on a bool switch that it might eliminate the manual work involved and it would let me switch the language back when needed.
First attempt:
+ (instancetype)stringWithFormat:(NSString *)format, ...
{
va_list args;
va_start(args,format);
//todo check flag if we're changing the language
//todo replace problematic word from 'format'
NSString *result = [NSString stringWithFormat:format,args];
return result;
}
I first quickly coded up a category to override stringWithFormat to replace problematic words. I forgot that I would lose the original implementation of stringWithFormat. This resulted in endless recursion.
Next Attempt (subclass):
I started an attempt to subclass NSString but hit a stackoverflow post saying that if my solution was to subclass a class cluster then I didn't understand my problem because subclassing a class cluster is almost never done.
Final Option (wrapper):
My final attempt would be to write a wrapper class but that kind of defeats the original purpose which was to avoid having to manually seek out each string in the app.
I'm not really sure how to approach this problem anymore. What do I do if I need to add/override functionality to one of the core classes.
There is nothing wrong with the idea of your first attempt, just a little mistake in its implementation. Change:
NSString *result = [NSString stringWithFormat:format,args];
to:
NSString *result = [NSString alloc] initWithFormat:format arguments:args];
which is the expansion of stringWithFormat: and the interception will work.
Thoughts about class clusters are a red herring in this particular situation, the front class for a cluster (NSString) must provide implementations for class methods (+stringWithFormat:), so you can use a simple category to intercept them.
However, having intercepted +stringWithFormat: be careful. A simple test will show you it is used a lot by the frameworks and you do not wish to break them - as my first simple test did by simply changing "d" to "c", which changes "window" to "wincow", which in turn broke the binding setup of Xcode's default app which binds the property "window"...
If you are changing health-related words you might be OK, whole strings would be better.
A better approach might be to simply write a RE to match all the literal strings in the code and replace them by function(string) for some function (not method) you write to do the conversion.
HTH
There is a much simpler solution that seems like a better fit. Use NSLocalizedString, with keys instead of actual strings:
displayString *NSString = NSLocalizedString(#"displayString", nil);
cancelButtonTitle *NSString = NSLocalizedString(#"cancelButtonTitle", nil);
Then create a Localizable.strings file in your app, and define the actual values that should be displayed:
"displayString" = "The string to display in English"
"cancelButtonTitle" = "Cancel"
You put the Localizable.strings file in your app bundle and the app uses it to do string replacements at runtime.
You can also define different versions of Localizable.strings for different languages, so, for example, if the user has set their language to Spanish, the call to NSLocalizedString() gives you the Spanish-language version.
(As you've mentioned, NSString is a class cluster. That means that it is a public interface to a variety of different private subclasses. You can't be sure what private subclass you get when you create an NSString, and that makes it a bad idea to try to subclass it.)
For hardcoded strings you have no other way but to modify those manually by assigning it to a string converter class of some sort. So those for:
yourmom.text = #"Hi Mom";
yourdad.text = [NSString stringWithFormat:#"%# and Dad!",yourmom.text];
You need to change these kind of assignments to something like
yourmom.text = [StringConverter string:#"Hi Mom"];
yourdad.text = [StringConverter string:#"%# and Dad!" placeHolder:yourmom.text];
As for strings in storyboards or xibs, you can change them by iterations loop in viewdidload. Good luck.

What is NSMutable Array/String/URL?

Sometimes I get errors when defining a variable as NSArray/String/URL, it can often be solved by changing it to NSMutableArray/String/URL.
What is the difference between them? People say you can't change the value of a NSString, but why not since I defined it as a variable with a var?
Please use Swift 2 code to explain.
Here's code example:
enum Router: URLRequestConvertible {
static let baseURLString = "https://api.500px.com/v1"
static let consumerKey = "My_Key"
case PopularPhotos(Int)
case PhotoInfo(Int, ImageSize)
case Comments(Int, Int)
var URLRequest: NSURLRequest {
//Error: Type does not conform to protocol 'URLRequestConvertible' with Alamofire.
//I solved this problem by changing it to NSMutableRequest.
let (path, parameters) : (String, [String: AnyObject]) = {
switch self {
//3 cases.
}
}()
let URL = NSURL(string: Router.baseURLString)
let URLRequest = NSURLRequest(URL: URL!.URLByAppendingPathComponent(path))
let encoding = Alamofire.ParameterEncoding.URL
return encoding.encode(URLRequest, parameters: parameters).0
}
}
NS types are coming from Cocoa (some would say Objective-c, but actually they don't exist in Objective-C so they were created in the NextStep libraries and then used by Apple in Cocoa after they bought NextStep -> hence the NS prefix)
NSArray/String/URL are similar to let myVar = "aString"
and
NSMutableArray/String/URL are similar to var myVar = "aString"
the first one cannot be changed after it's set the second one can be. Why are you using the Cocoa/Objective-C types and not native Swift types ?
EDIT (see comments)
in Swift you can define a string:
var anEmtpyString = String()
or
var anEmtpyString = ""
in the second case Swift is intelligent enough to deduct that the type is a String. If you use 'var' the string is Mutable, meaning you can change it afterwards. So I can do :
var myString = "first text"
myString = "another text"
if you're using 'let' you create a references to the string, also called Immutable
let myString = "first text"
myString = "another text" // <- the compiler won't accept this !
In those examples I have used the native String type from Swift, which exists in both Swift 1.2 and 2
I could also use the Cocoa/Objective types NSString, NSMutableString but most of the time there is no need for it. It can be handy if you're using frameworks which are written in Objective-C. It's easier then to you since you don't have to cast from String to NSString/NSMutableString. (which is of course possible)
In Objective-C there is no let or var keyword so there you used NSString (for let) and NSMutableString (for var).
The same goes for NSArray/NSMutableArray
More info : https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/StringsAndCharacters.html
PS the answer from gnasher729 is a bit more technical and tell you how it goes around behind the scenes. It al has to do with pointers, which were very present in Objective-C but are much more hidden in Swift. If you come from a background where you have learned pointers it will make thing clearer, if not I'm not sure if it'll help you. If you want me to expand on this please tell.
A.
In Objective-C many types of your daily routine are not built-in types. That means that they are not part of the programming language.
Therefore the framework Foundation defines that types as classes as any other class. There is no difference to classes like NSImage, NSManagedObject, NSWhatever. You do not have a language level documentation. They are documented as part of the Framework. You can treat them as every other class including subclassing, categories on it and so on. There is simply nothing special with them. (You could completely replace them, but there is little reason to do so.)
B.
In Objective-C every instance object is allocated on heap ("dynamically"). Since instances of NSString are usual instance objects, you do the same with them. The return value of such a construction is not an instance object, but a reference to an instance object. That means that all instance objects including instances of NSString are treated "by reference".
When you define a variable with the "type of string" you do not define a string instance, but a reference to the string:
NSString *referenceToString;
This referene is always a variable. In Swift it would look like this:
var string : String;
The analogon to a const in Objective-C is defined with the key word const.
NSString * const referenceToString;
But there is a big difference: Since the variable you declare is a reference, the constness can be related to the reference or to the object. In the first case you couldn't change the reference, in the latter case you could not change the object. The above syntax means: Constant reference to an NSString instance. Therefore you are not allowed to assign a value to that reference. (I. e. referencing another string object.)
These constant references are not very popular in Objective-C, because in most cases the constness of the reference is meaningless.
The constness of the string object itself is modeled by the type of the string object. NSString means constant string object, NSMutableString means variable string object.
Therefore, if you want to have a sting that can be mutated, use NSMutableString instead of NSString.
NSMutableString *referenceToAMutableString;
NSString *referenceToAnImmutableString;
There are value objects and reference objects. For example, Int and String are value objects, NSNumber and NSString are reference object. Swift struct instance are value objects, Swift class instances are reference objects.
For a reference object, the variable is not the object. It is a reference to the object. So a "let" variable cannot be changed, which means it refers to one object and cannot be made to refer to another object. A "var" variable can be changed, which means it can be made to refer to a different object. But both say nothing about the object itself. An NSArray object cannot be modified, an NSMutableArray can be modified. Whether you use a let or var variable to refer to the object only means the variable cannot or can be made to refer to a different variable, it doesn't say whether you can modify the object itself.

Objective-C: NSArray of Custom Class?

I am trying to learn iOS, I come from Java background where we can have lists/arrays of specific classes like
List<String> l = new ArrayList<>();
List<MyClass> l = new ArrayList<>();
Looking at Objective-C, I can make use of NSArray class to make immutable arrays, but how do I specify that this NSArrray is strictly of type MyClass?
Now Xcode 7 supports some kind of generics for standard collections(e.g. NSArrays). So you can make an array and provide kind of storing objects like this:
NSArray<NSString*> *myArrayOfStrings;
As far as I know, there isn't a built-in mechanism for specifying the type of objects put into NSArrays in Objective-C, but it looks like Swift does what you're looking for if that helps at all:
https://developer.apple.com/library/prerelease/mac/documentation/Swift/Conceptual/Swift_Programming_Language/CollectionTypes.html#//apple_ref/doc/uid/TP40014097-CH8-XID_172
On a slightly related note, there's a decent write-up here about enforcing inserting objects of only a certain type into a subclassed NSMutableArray and throwing exceptions if trying to insert the wrong object types:
NSMutableArray - force the array to hold specific object type only
Sadly there are no generics in Objective-C.

Resources