Explorar el Código

Removed NSRegularExpression dependency from HttpServer.
Introduced a first version of new routing mechanism ( ~Sinatra ).

Damian Kołakowski hace 10 años
padre
commit
df24cedc2b

+ 24 - 50
Sources/Swifter/DemoServer.swift

@@ -10,30 +10,41 @@ public func demoServer(publicDir: String?) -> HttpServer {
     let server = HttpServer()
     
     if let publicDir = publicDir {
-        server["/resources/(.+)"] = HttpHandlers.directory(publicDir)
+        server["/resources/:file"] = HttpHandlers.directory(publicDir)
+    }
+    
+    server["/files/:path"] = HttpHandlers.directoryBrowser("~/")
+
+    server["/"] = { r in
+        var listPage = "Available services:<br><ul>"
+        listPage += server.routes.map({ "<li><a href=\"\($0)\">\($0)</a></li>"}).joinWithSeparator("")
+        return .OK(.Html(listPage))
     }
     
-    server["/files(.+)"] = HttpHandlers.directoryBrowser("~/")
     server["/magic"] = { .OK(.Html("You asked for " + $0.url)) }
     
-    server["/test"] = { request in
+    server["/test/:param1/:param2"] = { r in
         var headersInfo = ""
-        for (name, value) in request.headers {
+        for (name, value) in r.headers {
             headersInfo += "\(name) : \(value)<br>"
         }
         var queryParamsInfo = ""
-        for (name, value) in request.urlParams {
+        for (name, value) in r.urlParams {
             queryParamsInfo += "\(name) : \(value)<br>"
         }
-        return .OK(.Html("<h3>Address: \(request.address)</h3><h3>Url:</h3> \(request.url)<h3>Method: \(request.method)</h3><h3>Headers:</h3>\(headersInfo)<h3>Query:</h3>\(queryParamsInfo)"))
+        var pathParamsInfo = ""
+        for token in r.params {
+            pathParamsInfo += "\(token.0) : \(token.1)<br>"
+        }
+        return .OK(.Html("<h3>Address: \(r.address)</h3><h3>Url:</h3> \(r.url)<h3>Method: \(r.method)</h3><h3>Headers:</h3>\(headersInfo)<h3>Query:</h3>\(queryParamsInfo)<h3>Path params:</h3>\(pathParamsInfo)"))
     }
     
-    server["/params/(.+)/(.+)"] = { request in
-        var capturedGroups = ""
-        for (index, group) in request.capturedUrlGroups.enumerate() {
-            capturedGroups += "Expression group \(index) : \(group)<br>"
-        }
-        return .OK(.Html("Url: \(request.url)<br>Method: \(request.method)<br>\(capturedGroups)"))
+    server["/demo"] = { r in
+        return .OK(.Html("<center><h2>Hello Swift</h2><img src=\"https://devimages.apple.com.edgekey.net/swift/images/swift-hero_2x.png\"/><br></center>"))
+    }
+    
+    server["/raw"] = { request in
+        return HttpResponse.RAW(200, "OK", ["XXX-Custom-Header": "value"], [UInt8]("Sample Response".utf8))
     }
     
     server["/json"] = { request in
@@ -43,49 +54,12 @@ public func demoServer(publicDir: String?) -> HttpServer {
     server["/redirect"] = { request in
         return .MovedPermanently("http://www.google.com")
     }
-    
+
     server["/long"] = { request in
         var longResponse = ""
         for k in 0..<1000 { longResponse += "(\(k)),->" }
         return .OK(.Html(longResponse))
     }
-    
-    server["/demo"] = { request in
-        return .OK(.Html("<center><h2>Hello Swift</h2>" +
-            "<img src=\"https://devimages.apple.com.edgekey.net/swift/images/swift-hero_2x.png\"/><br>" +
-            "</center>"))
-    }
-    
-    server["/login"] = { request in
-        switch request.method.uppercaseString {
-            case "GET":
-                if let rootDir = publicDir {
-                    if let html = NSData(contentsOfFile:"\(rootDir)/login.html") {
-                        var array = [UInt8](count: html.length, repeatedValue: 0)
-                        html.getBytes(&array, length: html.length)
-                        return HttpResponse.RAW(200, "OK", nil, array)
-                    } else {
-                        return .NotFound
-                    }
-                }
-            case "POST":
-                let formFields = request.parseForm()
-                return HttpResponse.OK(.Html(formFields.map({ "\($0.0) = \($0.1)" }).joinWithSeparator("<br>")))
-            default:
-                return .NotFound
-        }
-        return .NotFound
-    }
-    
-    server["/raw"] = { request in
-        return HttpResponse.RAW(200, "OK", ["XXX-Custom-Header": "value"], [UInt8]("Sample Response".utf8))
-    }
-    
-    server["/"] = { request in
-        var listPage = "Available services:<br><ul>"
-        listPage += server.routes.map({ "<li><a href=\"\($0)\">\($0)</a></li>"}).joinWithSeparator("")
-        return .OK(.Html(listPage))
-    }
 
     return server
 }

+ 6 - 16
Sources/Swifter/HttpHandlers.swift

@@ -15,11 +15,11 @@ public class HttpHandlers {
     public class func directory(dir: String) -> ( HttpRequest -> HttpResponse ) {
         return { request in
             
-            guard let localPath = request.capturedUrlGroups.first else {
+            guard let localPath = request.params.first else {
                 return HttpResponse.NotFound
             }
             
-            let filesPath = dir.stringByExpandingTildeInPath.stringByAppendingPathComponent(localPath)
+            let filesPath = dir + "/" + localPath.1
             
             let cachedBody = cache.objectForKey(filesPath) as? NSData
             
@@ -74,9 +74,9 @@ public class HttpHandlers {
     }
     
     public class func directoryBrowser(dir: String) -> ( HttpRequest -> HttpResponse ) {
-        return { request in
-            if let pathFromUrl = request.capturedUrlGroups.first {
-                let filePath = dir.stringByExpandingTildeInPath.stringByAppendingPathComponent(pathFromUrl)
+        return { r in
+            if let (_, value) = r.params.first {
+                let filePath = dir + "/" + value
                 let fileManager = NSFileManager.defaultManager()
                 var isDir: ObjCBool = false;
                 if ( fileManager.fileExistsAtPath(filePath, isDirectory: &isDir) ) {
@@ -84,7 +84,7 @@ public class HttpHandlers {
                         do {
                             let files = try fileManager.contentsOfDirectoryAtPath(filePath)
                             var response = "<h3>\(filePath)</h3></br><table>"
-                            response += files.map({ "<tr><td><a href=\"\(request.url)/\($0)\">\($0)</a></td></tr>"}).joinWithSeparator("")
+                            response += files.map({ "<tr><td><a href=\"\(r.url)/\($0)\">\($0)</a></td></tr>"}).joinWithSeparator("")
                             response += "</table>"
                             return HttpResponse.OK(.Html(response))
                         } catch  {
@@ -103,13 +103,3 @@ public class HttpHandlers {
         }
     }
 }
-
-private extension String {
-    var stringByExpandingTildeInPath: String {
-        return (self as NSString).stringByExpandingTildeInPath
-    }
-    
-    func stringByAppendingPathComponent(str: String) -> String {
-        return (self as NSString).stringByAppendingPathComponent(str)
-    }
-}

+ 3 - 3
Sources/Swifter/HttpParser.swift

@@ -31,16 +31,16 @@ class HttpParser {
         let headers = try readHeaders(socket)
         if let contentLength = headers["content-length"], let contentLengthValue = Int(contentLength) {
             let body = try readBody(socket, size: contentLengthValue)
-            return HttpRequest(url: path, urlParams: urlParams, method: method, headers: headers, body: body, capturedUrlGroups: [], address: nil)
+            return HttpRequest(url: path, urlParams: urlParams, method: method, headers: headers, body: body, address: nil, params: [:])
         }
-        return HttpRequest(url: path, urlParams: urlParams, method: method, headers: headers, body: nil, capturedUrlGroups: [], address: nil)
+        return HttpRequest(url: path, urlParams: urlParams, method: method, headers: headers, body: nil, address: nil, params: [:])
     }
     
     private func extractUrlParams(url: String) -> [(String, String)] {
         guard let query = url.split("?").last else {
             return []
         }
-        return query.split("&").map { (param:String) -> (String, String) in
+        return query.split("&").map { (param: String) -> (String, String) in
             let tokens = param.split("=")
             guard tokens.count >= 2 else {
                 return ("", "")

+ 1 - 1
Sources/Swifter/HttpRequest.swift

@@ -13,8 +13,8 @@ public struct HttpRequest {
     public let method: String
     public let headers: [String: String]
     public let body: String?
-    public var capturedUrlGroups: [String]
     public var address: String?
+    public var params: [String: String]
     
     public func parseForm() -> [(String, String)] {
         if let body = body {

+ 55 - 0
Sources/Swifter/HttpRouter.swift

@@ -0,0 +1,55 @@
+//
+//  HttpRouter.swift
+//  Swifter
+//  Copyright (c) 2015 Damian Kołakowski. All rights reserved.
+//
+
+import Foundation
+
+public class HttpRouter {
+    
+    private var handlers: [(pattern: [String], handler: HttpServer.Handler)] = []
+    
+    public func routes() -> [String] {
+        return handlers.map { $0.pattern.joinWithSeparator("/") }
+    }
+    
+    public func register(path: String, handler: HttpServer.Handler) {
+        handlers.append((path.split("/"), handler));
+        handlers.sortInPlace { $0.0.pattern.count < $0.1.pattern.count }
+    }
+    
+    public func select(url:String) -> ([String:String], HttpServer.Handler)? {
+        let urlTokens = url.split("/")
+        for (pattern, handler) in handlers {
+            if let params = matchParams(pattern, valueTokens: urlTokens) {
+                return (params, handler)
+            }
+        }
+        return nil
+    }
+    
+    public func matchParams(patternTokens: [String], valueTokens: [String]) -> [String: String]? {
+        var params = [String: String]()
+        for index in 0..<valueTokens.count {
+            if index >= patternTokens.count {
+                return nil
+            }
+            let patternToken = patternTokens[index]
+            let valueToken = valueTokens[index]
+            if patternToken.isEmpty {
+                if  patternToken != valueToken {
+                    return nil
+                }
+            }
+            if patternToken.characters.first == ":" {
+                params[patternToken.substringFromIndex(patternToken.characters.startIndex.successor())] = valueToken
+            } else {
+                if patternToken != valueToken {
+                    return nil
+                }
+            }
+        }
+        return params
+    }
+}

+ 9 - 50
Sources/Swifter/HttpServer.swift

@@ -12,7 +12,8 @@ public class HttpServer
     
     public typealias Handler = HttpRequest -> HttpResponse
     
-    private var handlers: [(expression: NSRegularExpression, handler: Handler)] = []
+    private var router = HttpRouter()
+    
     private var listenSocket: Socket = Socket(socketFileDescriptor: -1)
     private var clientSockets: Set<Socket> = []
     private let clientSocketsLock = 0
@@ -21,22 +22,15 @@ public class HttpServer
     
     public subscript (path: String) -> Handler? {
         set {
-            do {
-                let regex = try NSRegularExpression(pattern: path, options: self.expressionOptions)
-                if let newHandler = newValue {
-                    self.handlers.append(expression: regex, handler: newHandler)
-                    // Longer patterns will have higher priority.
-                    self.handlers = self.handlers.sort { $0.0.pattern > $1.0.pattern }
-                }
-            } catch  {
-                print("Could not register handler for: \(path), error: \(error)")
-            }
+            router.register(path, handler: newValue!)
+        }
+        get {
+            return nil
         }
-        get { return nil }
     }
     
     public var routes:[String] {
-        return self.handlers.map { $0.expression.pattern }
+        return router.routes()
     }
     
     public func start(listenPort: in_port_t = 8080) throws {
@@ -53,9 +47,8 @@ public class HttpServer
                     while let request = try? httpParser.readHttpRequest(socket) {
                         let keepAlive = httpParser.supportsKeepAlive(request.headers)
                         let response: HttpResponse
-                        if let (expression, handler) = self.findHandler(request.url) {
-                            let capturedUrlsGroups = self.captureExpressionGroups(expression, value: request.url)
-                            let updatedRequest = HttpRequest(url: request.url, urlParams: request.urlParams, method: request.method, headers: request.headers, body: request.body, capturedUrlGroups: capturedUrlsGroups, address: socketAddress)
+                        if let (params, handler) = self.router.select(request.url) {
+                            let updatedRequest = HttpRequest(url: request.url, urlParams: request.urlParams, method: request.method, headers: request.headers, body: request.body, address: socketAddress, params: params)
                             response = handler(updatedRequest)
                         } else {
                             response = HttpResponse.NotFound
@@ -88,40 +81,6 @@ public class HttpServer
         }
     }
     
-    private let matchingOptions = NSMatchingOptions(rawValue: 0)
-    private let expressionOptions = NSRegularExpressionOptions(rawValue: 0)
-    
-    private func findHandler(url:String) -> (NSRegularExpression, Handler)? {
-        if let u = NSURL(string: url), path = u.path {
-            for handler in self.handlers {
-                if handler.expression.numberOfMatchesInString(path, options: self.matchingOptions, range: HttpServer.asciiRange(path)) > 0 {
-                    return handler
-                }
-            }
-        }
-        return nil
-    }
-    
-    private func captureExpressionGroups(expression: NSRegularExpression, value: String) -> [String] {
-        guard let u = NSURL(string: value), path = u.path else {
-            return []
-        }
-        var capturedGroups = [String]()
-        if let result = expression.firstMatchInString(path, options: matchingOptions, range: HttpServer.asciiRange(path)) {
-            let nsValue: NSString = path
-            for i in 1..<result.numberOfRanges {
-                if let group = nsValue.substringWithRange(result.rangeAtIndex(i)).stringByRemovingPercentEncoding {
-                    capturedGroups.append(group)
-                }
-            }
-        }
-        return capturedGroups
-    }
-    
-    private class func asciiRange(value: String) -> NSRange {
-        return NSMakeRange(0, value.lengthOfBytesUsingEncoding(NSASCIIStringEncoding))
-    }
-    
     private class func lock(handle: AnyObject, closure: () -> ()) {
         objc_sync_enter(handle)
         closure()

+ 10 - 0
Swifter.xcodeproj/project.pbxproj

@@ -11,6 +11,10 @@
 		7AE893EA1C05127900A29F63 /* SwifteriOS.h in Headers */ = {isa = PBXBuildFile; fileRef = 7AE893E91C05127900A29F63 /* SwifteriOS.h */; settings = {ATTRIBUTES = (Public, ); }; };
 		7AE893FE1C0512C400A29F63 /* SwifterMac.h in Headers */ = {isa = PBXBuildFile; fileRef = 7AE893FD1C0512C400A29F63 /* SwifterMac.h */; settings = {ATTRIBUTES = (Public, ); }; };
 		7AE8940D1C05151100A29F63 /* Launch Screen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 7AE8940C1C05151100A29F63 /* Launch Screen.storyboard */; };
+		7C67C3471C17542E007B98E8 /* HttpRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C67C3461C17542E007B98E8 /* HttpRouter.swift */; };
+		7C67C3481C17542E007B98E8 /* HttpRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C67C3461C17542E007B98E8 /* HttpRouter.swift */; };
+		7C67C3491C17542E007B98E8 /* HttpRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C67C3461C17542E007B98E8 /* HttpRouter.swift */; };
+		7C67C34A1C17542E007B98E8 /* HttpRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C67C3461C17542E007B98E8 /* HttpRouter.swift */; };
 		7C71C5B01A1D52F800682BF0 /* login.html in CopyFiles */ = {isa = PBXBuildFile; fileRef = 98630C061A1C9A9D00478D08 /* login.html */; };
 		7C71C5B11A1EC49B00682BF0 /* logo.png in CopyFiles */ = {isa = PBXBuildFile; fileRef = 7CB102DF1A17381D00CBA3B4 /* logo.png */; };
 		7CA4813E19A2EA8D0030B30D /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CA4813D19A2EA8D0030B30D /* main.swift */; };
@@ -97,6 +101,7 @@
 		7AE893FD1C0512C400A29F63 /* SwifterMac.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SwifterMac.h; sourceTree = "<group>"; };
 		7AE893FF1C0512C400A29F63 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
 		7AE8940C1C05151100A29F63 /* Launch Screen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = "Launch Screen.storyboard"; sourceTree = "<group>"; };
+		7C67C3461C17542E007B98E8 /* HttpRouter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HttpRouter.swift; sourceTree = "<group>"; };
 		7C839B6E19422CFF003A6950 /* SwifterSampleiOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SwifterSampleiOS.app; sourceTree = BUILT_PRODUCTS_DIR; };
 		7CA4813B19A2EA8D0030B30D /* SwifterSampleOSX */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = SwifterSampleOSX; sourceTree = BUILT_PRODUCTS_DIR; };
 		7CA4813D19A2EA8D0030B30D /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = "<group>"; };
@@ -238,6 +243,7 @@
 			isa = PBXGroup;
 			children = (
 				7CEAF84C1C14B29B003252DE /* DemoServer.swift */,
+				7C67C3461C17542E007B98E8 /* HttpRouter.swift */,
 				7CEAF84D1C14B29B003252DE /* HttpHandlers.swift */,
 				7CEAF84E1C14B29B003252DE /* HttpParser.swift */,
 				7CEAF84F1C14B29B003252DE /* HttpRequest.swift */,
@@ -431,6 +437,7 @@
 				7CEAF85D1C14B29B003252DE /* HttpParser.swift in Sources */,
 				7CEAF8551C14B29B003252DE /* DemoServer.swift in Sources */,
 				7CD6DABB1C15C48500A04931 /* String+Misc.swift in Sources */,
+				7C67C3491C17542E007B98E8 /* HttpRouter.swift in Sources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -446,6 +453,7 @@
 				7CEAF85E1C14B29B003252DE /* HttpParser.swift in Sources */,
 				7CEAF8561C14B29B003252DE /* DemoServer.swift in Sources */,
 				7CD6DABC1C15C48500A04931 /* String+Misc.swift in Sources */,
+				7C67C34A1C17542E007B98E8 /* HttpRouter.swift in Sources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -457,6 +465,7 @@
 				7CEAF8531C14B29B003252DE /* DemoServer.swift in Sources */,
 				7CEAF8671C14B29B003252DE /* HttpServer.swift in Sources */,
 				7CD6DAB91C15C48500A04931 /* String+Misc.swift in Sources */,
+				7C67C3471C17542E007B98E8 /* HttpRouter.swift in Sources */,
 				7CEAF8571C14B29B003252DE /* HttpHandlers.swift in Sources */,
 				7CEAF85B1C14B29B003252DE /* HttpParser.swift in Sources */,
 				7CEAF86B1C14B29B003252DE /* Socket.swift in Sources */,
@@ -474,6 +483,7 @@
 				7CEAF8541C14B29B003252DE /* DemoServer.swift in Sources */,
 				7CEAF8681C14B29B003252DE /* HttpServer.swift in Sources */,
 				7CD6DABA1C15C48500A04931 /* String+Misc.swift in Sources */,
+				7C67C3481C17542E007B98E8 /* HttpRouter.swift in Sources */,
 				7CEAF8581C14B29B003252DE /* HttpHandlers.swift in Sources */,
 				7CEAF85C1C14B29B003252DE /* HttpParser.swift in Sources */,
 				7CEAF86C1C14B29B003252DE /* Socket.swift in Sources */,

BIN
Swifter.xcodeproj/project.xcworkspace/xcuserdata/damiankolakowski.xcuserdatad/UserInterfaceState.xcuserstate


+ 96 - 0
Swifter.xcodeproj/xcuserdata/damiankolakowski.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist

@@ -814,5 +814,101 @@
             landmarkType = "5">
          </BreakpointContent>
       </BreakpointProxy>
+      <BreakpointProxy
+         BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
+         <BreakpointContent
+            shouldBeEnabled = "No"
+            ignoreCount = "0"
+            continueAfterRunningActions = "No"
+            filePath = "Sources/Swifter/HttpRouter.swift"
+            timestampString = "471300719.120407"
+            startingColumnNumber = "9223372036854775807"
+            endingColumnNumber = "9223372036854775807"
+            startingLineNumber = "22"
+            endingLineNumber = "22"
+            landmarkName = "select(_:)"
+            landmarkType = "5">
+         </BreakpointContent>
+      </BreakpointProxy>
+      <BreakpointProxy
+         BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
+         <BreakpointContent
+            shouldBeEnabled = "No"
+            ignoreCount = "0"
+            continueAfterRunningActions = "No"
+            filePath = "Sources/Swifter/HttpRouter.swift"
+            timestampString = "471300719.576639"
+            startingColumnNumber = "9223372036854775807"
+            endingColumnNumber = "9223372036854775807"
+            startingLineNumber = "23"
+            endingLineNumber = "23"
+            landmarkName = "select(_:)"
+            landmarkType = "5">
+         </BreakpointContent>
+      </BreakpointProxy>
+      <BreakpointProxy
+         BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
+         <BreakpointContent
+            shouldBeEnabled = "No"
+            ignoreCount = "0"
+            continueAfterRunningActions = "No"
+            filePath = "Sources/Swifter/HttpHandlers.swift"
+            timestampString = "471302341.920768"
+            startingColumnNumber = "9223372036854775807"
+            endingColumnNumber = "9223372036854775807"
+            startingLineNumber = "79"
+            endingLineNumber = "79"
+            landmarkName = "directoryBrowser(_:)"
+            landmarkType = "5">
+         </BreakpointContent>
+      </BreakpointProxy>
+      <BreakpointProxy
+         BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
+         <BreakpointContent
+            shouldBeEnabled = "No"
+            ignoreCount = "0"
+            continueAfterRunningActions = "No"
+            filePath = "Sources/Swifter/HttpHandlers.swift"
+            timestampString = "471302440.943472"
+            startingColumnNumber = "9223372036854775807"
+            endingColumnNumber = "9223372036854775807"
+            startingLineNumber = "78"
+            endingLineNumber = "78"
+            landmarkName = "directoryBrowser(_:)"
+            landmarkType = "5">
+         </BreakpointContent>
+      </BreakpointProxy>
+      <BreakpointProxy
+         BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
+         <BreakpointContent
+            shouldBeEnabled = "No"
+            ignoreCount = "0"
+            continueAfterRunningActions = "No"
+            filePath = "Sources/Swifter/HttpParser.swift"
+            timestampString = "471302639.772698"
+            startingColumnNumber = "9223372036854775807"
+            endingColumnNumber = "9223372036854775807"
+            startingLineNumber = "22"
+            endingLineNumber = "22"
+            landmarkName = "readHttpRequest(_:)"
+            landmarkType = "5">
+         </BreakpointContent>
+      </BreakpointProxy>
+      <BreakpointProxy
+         BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
+         <BreakpointContent
+            shouldBeEnabled = "Yes"
+            ignoreCount = "0"
+            continueAfterRunningActions = "No"
+            filePath = "Sources/Swifter/HttpHandlers.swift"
+            timestampString = "471302923.536711"
+            startingColumnNumber = "9223372036854775807"
+            endingColumnNumber = "9223372036854775807"
+            startingLineNumber = "18"
+            endingLineNumber = "18"
+            landmarkName = "directory(_:)"
+            landmarkType = "5">
+         </BreakpointContent>
+      </BreakpointProxy>
    </Breakpoints>
 </Bucket>

+ 2 - 5
SwifterSampleiOS/Base.lproj/Main.storyboard

@@ -1,8 +1,8 @@
 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="9059" systemVersion="15B42" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="BYZ-38-t0r">
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="9531" systemVersion="15B42" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="BYZ-38-t0r">
     <dependencies>
         <deployment identifier="iOS"/>
-        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="9049"/>
+        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="9529"/>
     </dependencies>
     <scenes>
         <!--View Controller-->
@@ -19,7 +19,6 @@
                         <subviews>
                             <button opaque="NO" contentMode="scaleToFill" misplaced="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="MAP-74-h0L">
                                 <rect key="frame" x="259" y="285" width="82" height="30"/>
-                                <animations/>
                                 <state key="normal" title="Stop Server">
                                     <color key="titleShadowColor" white="0.5" alpha="1" colorSpace="calibratedWhite"/>
                                 </state>
@@ -29,13 +28,11 @@
                             </button>
                             <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="yQl-Ci-dgA">
                                 <rect key="frame" x="272" y="303" width="46" height="30"/>
-                                <animations/>
                                 <state key="normal" title="Button">
                                     <color key="titleShadowColor" white="0.5" alpha="1" colorSpace="calibratedWhite"/>
                                 </state>
                             </button>
                         </subviews>
-                        <animations/>
                         <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
                         <constraints>
                             <constraint firstAttribute="centerY" secondItem="MAP-74-h0L" secondAttribute="centerY" id="Fgf-8c-jVA"/>