10 Libraries, die Architektur und Entwicklung einer iOS-App enorm verbessern

Die Verwendung von Libraries kann die Konzeption und Entwicklung von Apps erheblich erleichtern: Der Code wird besser strukturiert, übersichtlich und kürzer. In einem vorhergehenden Blogbeitrag haben wir bereits die 10 wichtigsten Libraries für Android-Entwickler vorgestellt. Nun wollen wir einen Blick auf das Angebot an iOS-Libriaries werfen. Wir haben uns auf GitHub umgesehen und für Euch die 10 besten Libraries ausgesucht, die Euch beim Programmieren von iPhone- und iPad-Apps Arbeit abnehmen.

1. Awesome Swift

Wie für viele andere Technologien auch, gibt es eine "Awesome"-Liste für Swift, die ständig aktuell Swift Frameworks und sonstigen Resourcen nach Kategorien sortiert bereitstellt. Ein Blick dorthin lohnt sich immer, wenn man sich einen Überblick verschaffen möchte, welche Integration sich für ein aktuelles Projekt lohnen könnte.

2. Alamofire

Alamofire ist eine Library für elegante API Requests, die vor allem durch die Interaktion mit anderen Frameworks (wie z.B. JASON und PromiseKit) hervorsticht. Auch die Liste der Features in Bezug auf Security, Routing und Retrying beeindruckt sehr.

let headers: HTTPHeaders = [
    "Authorization": "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==",
    "Accept": "application/json"
]
 
Alamofire.request("https://httpbin.org/headers", headers: headers)
.responseJSON { response in debugPrint(response)
}

3. EasyMapping

Die Kommunikation mit einem Backend ist in der modernen App-Entwicklung zu einem Standardfall geworden. Nun stellt sich die Aufgabe, die Antworten des Servers in ein Objektmodell umzuwandeln um sie in der Applikation verwenden zu können, und natürlich auch Objektmodelle wieder in ein passendes Format für das Backend aufzubereiten.

Genau hier setzt EasyMapping an. Auf eine sehr einfache Art und Weise ermöglicht es diese Umwandlung, die wir Entwickler Mapping nennen.

Im Gegensatz zu vielen ähnlichen Libraries setzt EasyMapping nicht voraus, dass das eigene Objektmodell von bestimmten Basisklassen erbt, sondern bietet seine Funktionalität über Protokolle an. Der Charme dieses Ansatzes ist schnell erklärt: Gibt es irgendwann mal die SuperEvenEasierMapping Library, so muss man nicht eine ganze Vererbungshierarchie umbauen, sondern ganz easy ein anderes Protokoll implementieren, und kann seine Modell-Klasse ohne Änderungen weiter verwenden.

class Car: EKObjectModel {
    var model: String!
    var year: String!
    var createdAt: NSDate!
}
 
extension Car {
    override class func objectMapping() -> EKObjectMapping {
        var mapping = EKObjectMapping(objectClass: self)
        mapping.mapPropertiesFromArray(["model","year"])
        return mapping
    }
}

4. JASON

JASON ist eine der vielen JSON Libraries und legt den Fokus auf Schnelligkeit und Eleganz. Die Integration mit Alamofire und anderen Libraries macht dieses Framework nahezu unschlagbar.

Alamofire.request(.GET, peopleURL).responseJASON { response in
    if let json = response.result.value {
        let people = json.map(Person.init)
        print("people: \(people)")
    }
}

5. Locksmith

Locksmith ist ein Wrapper für den Schlüsselbund, also die sichere Ablage von simplen Daten auf dem iOS-Gerät, und zeichnet sich durch die starke Typisierung der Ergebnisse beim Abruf der Daten aus. Die Implementierung über Protokolle macht dabei die Integration in vorhandene Projekte um einiges leichter.

struct TwitterAccount: CreateableSecureStorable, GenericPasswordSecureStorable {
    let username: String
    let password: String
 
    // Required by GenericPasswordSecureStorable
    let service = "Twitter"
    var account: String { return username }
 
    // Required by CreateableSecureStorable
    var data: [String: AnyObject] {
        return ["password": password]
    }
}
let account = TwitterAccount(username: "it-economics", password: "1234")
try account.createInSecureStore()

6. Magical Record

Wer jemals mit Core Data gearbeitet hat kennt die Schmerzen die damit verbunden sind diesen Objektgraphen zu bändigen. Magical Record versucht diese Schmerzen zu lindern, indem es das Active Record Design Pattern benutzt, um die Interaktion mit Core Data deutlich zu vereinfachen.

Die Library bietet eine Fülle von nützlichen Tools und Abstraktionen, um Core Data so erscheinen zu lassen, wie es von Apple hätte entwickelt werden sollen. Ein Muss für jeden App-Entwickler!

class SomeModel: NSManagedObject {
  
    @NSManaged var uuid: String
    @NSManaged var name: String
  
    class func create(uuid: NSString, name: NSString) {
        var res = SomeModel.MR_createEntity() as Flashcardpack
          
        res.uuid = uuid
        res.name = name
 
        // Save
        NSManagedObjectContext.MR_defaultContext().MR_saveToPersistentStoreAndWait()
        return res
    }
 
}
let allModels = SomeModel.MR_findAll()

7. PromiseKit

PromiseKit versucht asynchrone Tasks in einem Projekt ein wenig überschaubarer zu machen, indem es "Promises" und die Keywords firstly, when, then und always einführt. Diese lassen sich hintereinander aufreihen und machen so bspw. die Struktur eines Netzwerkaufrufs und die anschließende Verarbeitung der empfangenen Daten deutlich. Extrem mächtig wird diese Library im Zusammenspiel mit Alamofire und JASON sowie mit den zusätzlich bereitgestellten Integrationen mit MapKit und CoreLocation.

firstly {
    when(URLSession.dataTask(with: url).asImage(), CLLocationManager.promise())
}.then { image, location -> Void in
    self.imageView.image = image
    self.label.text = "\(location)"
}.always {
    UIApplication.shared.isNetworkActivityIndicatorVisible = false
}.catch { error in
    UIAlertView(/*…*/).show()
}

8. Siesta

Spricht man heutzutage mit einem Backend, so ist die Wahrscheinlichkeit sehr hoch, dass es sich um eine REST Schnittstelle handelt. Nun gibt es ja viele Libraries und Frameworks, die einen bei der REST Kommunikation unterstützen, allerdings sind die meisten davon so gemünzt, dass sie die Netzwerk-Requests in den Vordergrund stellen.

Siesta ist anders: Hier ist die jeweilige REST Ressource der Hauptplayer. Alles Notwendige, um diese Ressourcen anzusprechen sowie zu verwalten, kapselt Siesta in einer Blackbox.

Als Enwickler steht einem damit ein elegantes Interface zur Verfügung, das folgende Fragen beantworten kann:

  • Was sind die neusten Daten für eine bestimmte Ressource bzw. gibt es überhaupt Daten?
  • Ist bei der Kommunikation ein Fehler aufgetreten?
  • Ist gerade eine Anfrage für eine bestimmte Ressource in Bearbeitung?

Dies alles in Verbindung mit einer geschickten Observer-Pattern Implementierung in Siesta macht die Arbeit mit REST Schnittstellen zu einem Vergnügen.

let MyAPI = Service(baseURL: "https://api.example.com")
 
MyAPI.resource("/profile").addObserver(owner: self) { [weak self] resource, _ in
    self?.nameLabel.text = resource.jsonDict["name"] as? String
    self?.colorLabel.text = resource.jsonDict["favoriteColor"] as? String
 
    self?.errorLabel.text = resource.latestError?.userMessage
}

9. SwifterSwift

SwifterSwift erweitert Swift Typen um immens nützliche Funktionen und Properties, deren Einsatz man kaum von nativen Funktionen und Properties unterscheiden kann. Sollte einem beim Programmieren also häufig eine bestimmte Eigenschaft eines Typs abgehen, dann wird man hier sehr wahrscheinlich fündig.

[1, 2, 3, 4, 5].contains([1, 2]) // true
[1, 2, 2, 3, 4, 2, 5].indexes(of: 2) // [1, 2, 5]
var date = Date() // "Jan 12, 2017, 7:07 PM"
date.isInToday // true
date.unixTimestamp // 1484233862.826291
date.add(.month, value: 2) // "Mar 12, 2017, 7:07 PM"
let dict: [String : Any] = ["testKey": "testValue", "testArrayKey": [1, 2, 3, 4, 5]]
dict.jsonString() // "{"testKey":"testValue","testArrayKey":[1,2,3,4,5]}"
let string = "Hello World!"
string.length // 12

10. Typhoon

Man kennt es ja, am Anfang sind die Applikationen noch relativ klein und übersichtlich - mit der Zeit kommen immer mehr Features und Komponenten hinzu, die verwaltet werden wollen. Als guter Entwickler beginnt man nun die Komponenten klein und wiederverwendbar zu machen, was dazu führt, dass es plötzlich noch viel mehr Teile im Puzzle gibt, die man irgendwie zusammenbringen muss.

An dieser Stelle kommt einem Dependecy Injection und somit Typhoon zu Hilfe. 

Typhoon bietet eine elegante Möglichkeit, die Abhängigkeiten der einzelnen Komponenten zueinander zu definieren, und stellt sicher, dass diese Abhängigkeiten zur passenden Zeit an der richtigen Stelle geladen werden und somit zur Verfügung stehen.

Das tolle an diesem Framework ist, dass es im Gegensatz zu anderen ähnlichen Helfern nicht auf ein XML-Format oder eine eigene DSL (Domain Specific Language) setzt - nein, mit Typhoon werden die Abhängigkeiten in Swift definiert. Warum das super ist?  Zum einen muss man keine neue Sprache lernen und zum anderen man kann seine Definitionen mit den üblichen Mitteln debuggen.

class CoreComponents: Assembly {
         
    func manWith(_ name: String) -> Definition {
        return Definition(withClass: Man.self) {
            $0.injectProperty("name", with: name)
            $0.setScope(Definition.Scope.ObjectGraph)
            $0.injectProperty("brother", with: self.man())
        }
    }
     
    func man() -> Definition {
        return Definition(withClass: Man.self) { configuration in
            configuration.injectProperty("name", with: "John")
            configuration.injectProperty("brother", with: self.manWith("Alex"))
        }
    }
  
    func manWithInitializer() -> Definition {
        return Definition(withClass: Man.self) {
            $0.setScope(Definition.Scope.Prototype)
            $0.useInitializer("init(withName:)", with: { (m) in
                m.injectArgument("Tom")
            })
            $0.injectMethod("setAdultAge")
            $0.injectMethod("setValues(_:withAge:)") { (m) in
                m.injectArgument("John")
                m.injectArgument(21)
            }
        }
    }
 
}