Reputation: 21540
I am currently working on an iOS app which has to parse a URL. The URLs that have to be parsed always have the same format.
Example of such a URL:
myprotocol://label?firstname=John&lastname=Smith
My first thought was to write a "parser" class which is initialised with the String of the URL and has getter methods to return the label
, firstname
and lastname
parsed from the URL.
Pseudocode of such a class:
import Foundation
class URLParser {
var url: URL
init(url: String) {
self.url = URL(string: url)
}
func label() -> String? {
// code to determine label
}
func firstname() -> String? {
// code to determine firstname
}
func firstname() -> String? {
// code to determine lastname
}
}
But then I read more about Swift extensions and saw them being used and recommended in several places.
For my use case I could create an extension for the URL
class that might look like this:
import Foundation
extension URL {
var label: String {
// code to determine label
}
var firstname: String {
// code to determine label
}
var lastname: String {
// code to determine label
}
}
Using an extension like this seems like a good idea on the one hand, because it feels very lightweight and precise. But on the other hand it also feels a bit wrong since the values label
, firstname
and lastname
do not have anything to do with the general idea of a URL. They are specific to my use case.
I would like to clearly communicate in my code what is happening and using classes like MyUseCaseURLParser
or extended classes (inheritance) of URL
called MyUseCaseURL
seem to do that better.
But this is all based on past programming experiences in other languages.
What is the Swifty way of doing something like this and organising the code around it? Are there better ways to do this other than using extensions and classes?
Upvotes: 4
Views: 1072
Reputation: 130162
There are two things that have to be represented:
Your first solution is fine but you shouldn't call it URLParser
. The parsing happens there but the class doesn't represent a parser, it represents the result.
I would simplify it to:
class MyProtocolData {
init?(url: URL) {
// parsing happens here, it's a failable initializer
// no need to store the URL
}
let label: String
let firstName: String
let lastName: String
}
You could also use a struct
.
The second solution is also fine. Apple has also used such a solution (e.g. on NSDictionary
). However, the problem with such a solution is having the properties on all URL
instances, even the ones that don't belong to your scheme. Also, the URL will have to be parsed again every time you access the functions (or properties).
My first solution using extension
would be:
extension URL {
func parseMyProtocol() -> (label: String, firstName: String, lastName: String)? {
}
}
Note that we separate parsing from the result. Also note that the name of the method is not generic and it won't conflict with other parsing method.
My second solution would be to replace the result tuple with a struct:
struct MyProtocolData {
let label: String
let firstName: String
let lastName: String
}
extension URL {
func parseMyProtocol() -> MyProtocolData? {}
}
You can probably think of better names depending on what the URL represents, e.g. UserData
and parseUserData
.
As you can see, there are multiple solutions, all idiomatic Swift. There is nothing un-Swifty on using classes.
Upvotes: 2
Reputation: 5285
In your example an extension is not a good fit for this. An extension would be used if you wanted to provide a function to check if a URL contains a parameter. A better way is to use a struct with a failable initializer and make it immutable by using let.
struct CustomURL {
let url: URL
let label: String
let firstname: String
let lastname: String
init?(url: URL) {
//Check if url contains all elements you need, if not return nil
}
}
Upvotes: 1