Sometimes it happens to me to observe my code and to say: “no, these things should be made differently”. Then I go into “refactoring”. I think this is an important step of every programmer. Don’t be afraid to refactor (don’t listen to the voice inside you: “… don’t touch that code! Nothing will work correctly after changing this single byte …”): code is something always in evolution (and in test …).
Seeing the code of Part 2 I don’t like two different callbacks to be passed to the start method:
1 2 |
parser.start(parserTestReader, { (error : NSError) in println(error) } ) { (photoResult : PhotoResult) -> Bool in ... |
Refactor: callbacks
We can merge the two callbacks into one.
Begin changing the name of the callback typealias from ParserNewPhoto to ParserCallback (more generic). Also change the enum type name we pass to the callback from PhotoResult to ParserResult.
Then we can use the same callback for all types of errors (see error type 1 and 2 of Part 2).
1 2 |
parser.start(parserTestReader) { (parserResult : ParserResult) -> Bool in ... |
But now we need a way to know, inside the callback, which kind of error I’m receiving (one from the parser loop, or one more generic, before starting the loop).
Let us to be more precise in the definition of Parser errors:
1 2 3 4 5 |
enum ParserError : Int { case ReadingData = 100 case ConvertingMainJsonObj case ConvertingAnElement } |
ConvertingMainJsonObj is returned if an error occurred converting the main JSON object.
ConvertingAnElement is returned if en element in the loop caused the error (and the process can optionally continue).
ReadingData is returned if there was an error from the reader callback. Note that in this case we pass also the underlying error (the one returned from ParserReader) in the userInfo dictionary:
1 |
let parseError = NSError(domain: "Parser", code: ParserError.ReadingData.rawValue, userInfo: [NSLocalizedDescriptionKey: "Error reading data", NSUnderlyingErrorKey: _error]) |
The call to the start method can now check precisely the error code in order to know the type of error and what went wrong.
Refactor: nested enums
The enums ParserReaderResult and ParserResult are really part of our Parser Interface. So we want to move them inside the class Parser.
1 2 3 4 5 6 7 8 9 10 11 12 |
class Parser { enum ReaderResult { case Value(NSData) case Error(NSError) } enum Result { case Value(Photo) case Error(NSError) } .... |
We also removed the Parser prefix, since now all the code outside Parser must refer to them as:
1 2 |
Parser.ReaderResult Parser.Result |
On the other hand, if we use them inside the Parser class we can refer to them simply as:
1 2 |
ReaderResult Result |
Access control
Regarding this, I miss one thing of Objective-C.
I like the fact that Swift has only one source file (no header) because this makes my projects cleaner and the managing of files simpler. But when I make a new class in Objective C I begin from the header (.h) clearly writing the API of the class (what other must see of it) and separating it from the private stuff and the implementation (all in .m file). I don’t like to have public (or internal) and private members in the same file. A developer studying the API of my parser will search it in the same file where he can find the implementation.
That said, the best we can do is to ask ourselves: what others must know of the Parser class? These must be public, all other things must be private.
public: start, the typealias Parser.Reader and Parser.Callback, the enums Parser.ReaderResult and Parser.Result.
private: StringFromJSON, DoubleFromJSON, handleData
Then move all the private stuff at the end of the file and mark them explicitly with:
1 2 3 4 |
//MARK: PUBLIC (internal) ... //MARK: PRIVATE ... |
The difference between public and internal is not important at this stage (see this for details).
For completeness set also the method readJsonFile in the View Controller to be private.
Getting data from the web
In order to check the real independence of the parser from the source of the data, we want to test it substituting the readJsonFile method with the getJsonFromWeb method.
This method is simple and uses the Cocoa class NSURLSession:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
private func getJsonFromWeb(jsonUrl : String)(completion : Parser.ReaderResult->()) { var fileData : NSData? var error : NSError? let url = NSURL(string: jsonUrl); if let _url = url { var request = NSURLRequest(URL: NSURL(string: jsonUrl)!) let task = NSURLSession.sharedSession().dataTaskWithRequest(request) { data, urlResponse, error in var result : Parser.ReaderResult if (error != nil) { result = Parser.ReaderResult.Error(error!) } else { result = Parser.ReaderResult.Value(data!) } completion(result) } task.resume() } else { error = NSError(domain: "ParserReader", code: 101, userInfo: [NSLocalizedDescriptionKey:"Wrong URL"]); completion(Parser.ReaderResult.Error(error!)) } } |
Now define a different reader function (we put the json file at the address http://www.tabasoft.it/ios/json/photos.json):
1 2 3 |
let parserTestReader = getJsonFromWeb("http://www.tabasoft.it/ios/json/photos.json") parser.start(parserTestReader) { (parserResult : Parser.Result) -> Bool in ... |
And all works in the same way as before, without touching the Parser class.
(this remind me of the Open/closed principle: our class Parser is open for extension, but closed for modification).
Custom operators
The last step of this post is to change the way we call StringFromJSON and DoubleFromJSON using operators.
We define a Swift custom operator that operates between two elements. These kind of operators are said infix. (Swift has also prefix and postfix operators acting on one single element, like the preincrement, postincrement etc..).
The custom operators must be declared in a global (file) scope (not inside a class).
We could put the declaration at the top of our Parser file, but we like to separate the declaration in a separate Swift file (Operators.swift). Create this new file, add it to the project and write in it:
1 |
infix operator >>> { associativity left precedence 150 } |
This declares the infix operator >>>. Our operator has a precedence of 150. This means that if it is near another operator, it must be evaluated first if the other operator has precedence < 150, after otherwise. And if the precedence is the same? Then the associativity says that the first operation to compute is that with the operand at the left (think to this like adding implicit parenthesis). We will remember this precedence when we will define more operators and combine them together.
Now we must write the implementation of the operator (in the file Operators.swift near the declaration):
1 2 3 4 5 6 7 8 |
func >>><A, B>(o: A?, f: A -> B?) -> B? { if let _o = o { return f(_o) } else { return .None } } |
Our first operand is o (an optional of generic type A). The second is a function getting one parameter (a generic A) as input and returning an optional of generic type B. The result of the operator is an optional of type B.
If o is nil or f returns nil, the result is nil (.None is an optional with nil value), otherwise the result is a value of type B.
Now we can convert the call:
1 2 |
if let _titolo = StringFromJSON(_jsonItem["titolo"]) { ... |
to
1 2 |
if let _titolo = _jsonItem["titolo"] >>> StringFromJSON { ... |
It seems like we are saying:
send _jsonItem[“titolo”] to StringFromJSON and get the result
Note that, because of our definition of the operator, now the StringFromJSON, if called, receives a real value, not an optional (the operator unwraps it). So we redefine the function without the optional parameter (just removed the ‘?’ from the type of the parameter):
1 2 3 |
private func StringFromJSON(ao : AnyObject) -> String? { return ao as? String } |
Do the same for DoubleFromJSON an add another function like StringFromJSON but for dictionary:
1 2 3 |
private func DictionaryFromJSON(ao : AnyObject) -> [String: AnyObject]? { return ao as? [String: AnyObject] } |
And the code to unwrap optionals becomes:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
if let _jsonItem = jsonItem >>> DictionaryFromJSON { var ok = false var toStop = false if let _titolo = _jsonItem["titolo"] >>> StringFromJSON { if let _autore = _jsonItem["autore"] >>> StringFromJSON { if let _latitudine = _jsonItem["latitudine"] >>> DoubleFromJSON { if let _longitudine = _jsonItem["longitudine"] >>> DoubleFromJSON { if let _data = _jsonItem["data"] >>> StringFromJSON { if let _descr = _jsonItem["descr"] >>> StringFromJSON { let photo = Photo(titolo: _titolo, autore: _autore, latitudine: _latitudine, longitudine: _longitudine, data: _data, descr: _descr) toStop = parserCallback(Result.Value(photo)) if toStop { break } ok = true } } } } } } |
This is something near what is called: Functional Programming. In this case we are using functions as first class citizens, we pass them around like we do for variables (this is one of the FP principles).
Many developers are asking if the Swift functional programming characteristics are something we will benefit from. Now I only want to inspect some of these in order to evaluate their usefulness (comments, as always, are welcome).
I’m not sure but I think that the operator we just defined (>>>) is similar to something called a MayBe Monad in some functional programming language (i.e. Haskell).
Apart from this we are now at a point of optimization and refactor of our parser but … the story is not finished.
It is enough for today, see you at the next part.
The code