3

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, firstnameand 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?

Jens
  • 20,533
  • 11
  • 60
  • 86
  • Possible duplicate of [Where to implement Swift protocols?](http://stackoverflow.com/questions/37281671/where-to-implement-swift-protocols) – mfaani Apr 10 '17 at 20:33
  • 1
    @Honey This question is not about _where_ to implement _protocols_ but more about the basics of Swift code organisation for use cases like the one I described. – Jens Apr 10 '17 at 20:34
  • 2
    It sounds like the majority of your problem can be handled via builtin classes like URLComponents: https://developer.apple.com/reference/foundation/urlcomponents – Connor Neville Apr 10 '17 at 20:39
  • @ConnorNeville Thanks for the link. It is definitely something that could help me at least in some cases. But the real URLs I have to parse contain a few more "problems" that have to be handled. I wanted to "hide" the logic to handle those problems in some form so that the API consumer can easily get the information. And my question is more about the underlying problem: How to solve issues like this in general in Swift. What is the preferred way of experienced Swift developers. – Jens Apr 10 '17 at 20:43
  • Why not subclass `URL`? – Paulw11 Apr 10 '17 at 21:16
  • @Paulw11 that was one of the options I was considering. But would that be the _Swift_ way of doing something like this? Since I am trying to learn Swift as a new language I am also interested in the best practices concerning Swift to avoid writing code that looks like it was written in another language and then just ported to Swift. – Jens Apr 10 '17 at 21:19
  • 2
    There are certainly idioms that are considered "Swifty", but in my opinion this is just a design issue. You should implement your chosen design in idiomatic Swift, but don't bend your design to comply with some approach suggested by users of the language. This is a classic use-case of inheritance; you have an object that does what you want but needs some specialisation. By subclassing `URL` you can pass it to any method that expects a `URL` without needing to continually wrap/unwrap the "inner" `URL` as you would with another approach – Paulw11 Apr 10 '17 at 21:24
  • 1
    @Paulw11 `URL` is a `struct` though, you cannot subclass it. – Sulthan Apr 11 '17 at 06:37
  • @Sulthan, You can inherit from `NSURL` – Paulw11 Apr 11 '17 at 07:26
  • @Paulw11 I know that but that's a rather different thing. Most Swift APIs take only `URL` and that means you will have to add `as URL` everywhere. – Sulthan Apr 11 '17 at 07:55

2 Answers2

1

There are two things that have to be represented:

  1. The act of parsing
  2. The result of parsing

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.

Sulthan
  • 128,090
  • 22
  • 218
  • 270
  • Great answer. But I need to ask as to why do you say that this class is not a parser? What characteristics would do you define for calling a class a parser? Imo, it is a URLParser that returns a result as a custom struct. – jarora Apr 11 '17 at 06:23
  • Obviously, what is the `firstName` of a parser? Some person has `firstName` but parser does not have a first name :) A parser is a transformer of some input to some output but your class actually represents the output. Yes, parsing happens there but somewhere inside. Instead, you could have something like `func getResult()` on the parser but accessing the result properties directly seems rather strange. – Sulthan Apr 11 '17 at 06:31
  • Thank you for your answer. I like the approach to combine the `struct` and the `extension` and your reasoning about the naming. I still will wait for further answers, but so far it seems like the best answer. – Jens Apr 11 '17 at 07:49
0

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
    }
}
totiDev
  • 5,189
  • 3
  • 30
  • 30
  • Thanks for your advice. The thing about `structs` and `classes` is that structs use value semantics and classes use reference semantics. In general I do not need identity for this particular use case so I could use a `struct`. But on the other hand I have at least three `Strings` to handle besides a few integers and `enums`. So I am a bit worried about `Stack` vs. `Heap` allocation. In general `structs` would be better because they are allocated only on the stack. But this seems to change if the `struct` has to many values that have to be allocated on the `Heap`. – Jens Apr 10 '17 at 20:47