I have to create a simple web server in Swift or Obj-C for an iOS application.
In fact it has to be the simplest web server ever because it just has to create a socket or whatever to listen to a web page request.
Then it has to provide the HTML string when the request comes.
That's all, no other feature from a real web server needed.
It has to respond only to a single type of requests
localhost:port/page_number.html
or with different alias, like
alias/page_number.html
Is it possible?
I read an example for Mac OS, with very short source code, but I also found examples for iOS that have many source code files and they are not simple at all.
The Mac OS example leverages Darwin library. Maybe it is so powerful that a simple web server is possible with a few instructions.
It is from Tiny http server engine written in Swift programming language
Here is the code:
import Darwin.C
let zero = Int8(0)
let transportLayerType = SOCK_STREAM // TCP
let internetLayerProtocol = AF_INET // IPv4
let sock = socket(internetLayerProtocol, Int32(transportLayerType), 0)
let portNumber = UInt16(4000)
let socklen = UInt8(socklen_t(MemoryLayout<sockaddr_in>.size))
var serveraddr = sockaddr_in()
serveraddr.sin_family = sa_family_t(AF_INET)
serveraddr.sin_port = in_port_t((portNumber << 8) + (portNumber >> 8))
serveraddr.sin_addr = in_addr(s_addr: in_addr_t(0))
serveraddr.sin_zero = (zero, zero, zero, zero, zero, zero, zero, zero)
withUnsafePointer(to: &serveraddr) { sockaddrInPtr in
let sockaddrPtr = UnsafeRawPointer(sockaddrInPtr).assumingMemoryBound(to: sockaddr.self)
bind(sock, sockaddrPtr, socklen_t(socklen))
}
listen(sock, 5)
print("Server listening on port \(portNumber)")
repeat {
let client = accept(sock, nil, nil)
let html = "<!DOCTYPE html><html><body style='text-align:center;'><h1>Hello from <a href='https://swift.org'>Swift</a> Web Server.</h1></body></html>"
let httpResponse: String = """
HTTP/1.1 200 OK
server: simple-swift-server
content-length: \(html.count)
\(html)
"""
httpResponse.withCString { bytes in
send(client, bytes, Int(strlen(bytes)), 0)
close(client)
}
} while sock > -1
But I know that iOS is advanced too, so maybe there is a very compact code to create that web server minimal capability on iOS.
Here is a very basic and simplified http server implementation in Swift that might be enough for your case if you do not want to use external libraries:
class PicoHttpServer {
private static var serverSocket: Int32?
private static let queue = DispatchQueue(label: "com.pico.http.server.queue")
private static let semaphore = DispatchSemaphore(value: 1)
static func httpOkResponse(html: String) -> String {
return "HTTP/1.1 200 OK\r\nServer: PicoHttpServer\r\nContent-Length: \(html.count)\r\n\r\n\(html)"
}
static func start(port: UInt16 = 7000, address: UInt32 = INADDR_LOOPBACK, requestHandler: #escaping ((String) -> String)) {
semaphore.wait()
let started = serverSocket != nil
semaphore.signal()
if started {
return
}
queue.async {
realStart(port: port, address: address, requestHandler: requestHandler)
}
}
static func stop() {
semaphore.wait()
if let serverSocker = serverSocket {
close(serverSocker)
}
serverSocket = nil
semaphore.signal()
}
private static func realStart(port: UInt16, address: UInt32, requestHandler: ((String) -> String)) {
let tcpSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)
if tcpSocket == -1 {
return
}
serverSocket = tcpSocket
var reuseOn = Int32(1)
setsockopt(tcpSocket, SOL_SOCKET, SO_REUSEADDR, &reuseOn, socklen_t(MemoryLayout.size(ofValue: reuseOn)))
var socketAddress = sockaddr_in()
socketAddress.sin_family = sa_family_t(AF_INET)
socketAddress.sin_port = port.bigEndian
socketAddress.sin_addr = in_addr(s_addr: address.bigEndian)
socketAddress.sin_zero = (0, 0, 0, 0, 0, 0, 0, 0)
let socklen = UInt8(socklen_t(MemoryLayout<sockaddr_in>.size))
let bindResult = withUnsafePointer(to: &socketAddress) { sockaddrInPtr -> Int32 in
let sockaddrPtr = UnsafeRawPointer(sockaddrInPtr).assumingMemoryBound(to: sockaddr.self)
return bind(tcpSocket, sockaddrPtr, socklen_t(socklen))
}
if bindResult == -1 {
return
}
let listenResult = listen(tcpSocket, 5)
if listenResult == -1 {
return
}
print("Server started")
while(true) {
semaphore.wait()
let stopped = serverSocket == nil
semaphore.signal()
if stopped {
break
}
let mtu = 65536
let client = accept(tcpSocket, nil, nil)
if client == -1 {
continue
}
var buffer = Data(repeating: 0, count: mtu)
let readResult = buffer.withUnsafeMutableBytes { pointer in
return read(client, pointer.baseAddress, mtu)
}
if readResult == -1 {
continue
}
let clientData = buffer.subdata(in: 0..<readResult)
let clientRequest = String(data: clientData, encoding: .utf8) ?? ""
let response = requestHandler(clientRequest)
response.withCString { bytes in
write(client, bytes, Int(strlen(bytes)))
close(client)
}
}
print("Server stopped")
}
}
Then you can use it as below to response with different html for each page number:
PicoHttpServer.start { request in
if request.hasPrefix("GET /1.html") {
return PicoHttpServer.httpOkResponse(html: "<html><body>Page 1</body></html>")
} else if request.hasPrefix("GET /2.html") {
return PicoHttpServer.httpOkResponse(html: "<html><body>Page 2</body></html>")
} else {
return PicoHttpServer.httpOkResponse(html: "<html><body>Other page</body></html>")
}
}
Note that if you want to open this from your app in Safari as you wrote in the comment to the question then you will probably also need to create a background task with UIApplication.shared.beginBackgroundTask so that your app can run this server at least for a moment when your iPhone app goes to background.
Related
I am parsing a certain json url data to plot in a map and I need to detect that I have all the data to show a spinner while nothing is happening. I have created a variable that goes from false to true after I have all the data but that variable only exists as true inside the for loop
This is part of the code that gets the data
import SwiftUI
import MapKit
var locationsFillTest : Int = 0
var allLocations = [MKPointAnnotation]()
var doneGettingData : Bool = false
struct MapView: UIViewRepresentable {
var startdate : String
func makeUIView(context: Context) -> MKMapView{
MKMapView(frame: .zero)
}
func makeCoordinator() -> MapViewCoordinator{
MapViewCoordinator(self)
}
func updateUIView(_ uiView: MKMapView, context: Context){
uiView.removeAnnotations(allLocations)
allLocations = []
doneGettingData = false
print("Done = \(doneGettingData)")
let url = URL(string: "https://XXXXXX")!
URLSession.shared.dataTask(with: url) {(data,response,error) in
do {
if let d = data {
let decodedLists = try JSONDecoder().decode(emsc.self, from: d)
DispatchQueue.main.async {
locationsFillTest = allLocations.count
doneGettingData = false
for locations in decodedLists.features {
let lat = Double(locations.properties.lat)
let long = Double(locations.properties.lon)
let annotation = MKPointAnnotation()
annotation.coordinate = CLLocationCoordinate2D(latitude: lat , longitude: long )
if locationsFillTest == 0 {
allLocations.append(annotation)}
}
uiView.addAnnotations(allLocations)
uiView.delegate = context.coordinator
uiView.showAnnotations(allLocations, animated: true)
doneGettingData = true
print("Done = \(doneGettingData)")
}
}else {
print("No Data")
}
} catch {
print("Error decoding JSON: ", error, response!)
}
}.resume()
}
}
The variable doneGettingData becomes false and true by watching the print but if I need to use it for example to create a spinner its false all the time since its only true inside.
How can I make it global ?
Thank you
Unless you have another declaration for doneGettingData inside the closure the instance level property is getting set to true. It may be getting set later than you expect though. Try the following to see when it changes (and to get you setup to react to those changes):
var doneGettingData : Bool = false {
didSet {
if doneGettingData {
print("The instance property doneGettingData is now true.")
} else {
print("The instance property doneGettingData is now false.")
}
}
}
You may want to make this into a custom enum though with cases along the lines of fetching, done, noData, and jsonError. Right now if there is no data you will never have a trigger to either retry, move on, notify the user, etc. The same applies when there is a decoding error. Or at the very least set the flag to true at the very end of the loop so something happens no matter what.
Something like:
enum DataCollectionState {
case fetching, done, noData, jsonError
var doneGettingData : DataCollectionState = fetching {
didSet {
switch doneGettingData {
case fetching:
// Show a spinner or something
case done:
// Hide the spinner
case noData:
// Tell the user there was no data? Try again?
case jsonError:
// Tell the user there was an error? Try again?
}
}
}
Note: I don't have Xcode open right now so syntax may not be exact.
I need to do some initialization in viewDidLoad after my json data has been loaded (using loadResultsFromItunesApi()). The problem is that it never finishes until later. Any references to the json data set within super.viewDidLoad bomb the application. The process is asynchronous as can be seen by the timing shown in the following debug print:
0 ET msec: 0.153
1 ET msec: 0.567
10 ET msec: 2.413
11 ET msec: 2.472
2 ET msec: 49.038
3 ET msec: 52.827
6 ET msec: 52.984
4 ET msec: 55.767
5 Lover
When I try to use the json data within super.viewDidLoad I get the following debug print:
0 ET msec: 0.133
1 ET msec: 0.506
10 ET msec: 2.725
Fatal error: Index out of range:
Thanking you ahead of time for any ideas!! Here is the code:
'''
import UIKit
struct Response: Codable {
var results: [Result]
}
struct Result: Codable {
var trackId: Int
var trackName: String
var collectionName: String
}
class ViewController: UIViewController {
var results = [Result]()
var start:Int64 = 0
#IBOutlet weak var resultsLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
start = Date().toMicroSeconds()
print("0 ",printMsecFromStart())
loadResultsFromItunesApi()
print("11 ",printMsecFromStart())
// This next statement bombs the app but okay later
//resultsLabel.text = results[0].trackName
}
#IBAction func printResults(_ sender: Any) {
resultsLabel.text = results[20].trackName
}
func loadResultsFromItunesApi() {
print("1 ",printMsecFromStart())
guard let url = URL(string: "https://itunes.apple.com/search?term=taylor+swift&entity=song") else {
print("Invalid URL"); return }
let request = URLRequest(url: url)
URLSession.shared.dataTask(with: request) { data, response, error in
print("2 ",self.printMsecFromStart())
if let data = data {
if let decodedResponse = try? JSONDecoder().decode(Response.self, from: data) {
print("3 ",self.printMsecFromStart())
DispatchQueue.main.async {
print("4 ",self.printMsecFromStart())
self.results = decodedResponse.results
print("5 ",self.results[0].collectionName)
}
print("6 ",self.printMsecFromStart())
return
}
print("8 ",self.printMsecFromStart())
}
print("9 ",self.printMsecFromStart())
print("Error: \(error?.localizedDescription ?? "error ??")")
}.resume()
print("10 ",printMsecFromStart())
}
func printMsecFromStart() -> String {
let et = Float(Date().toMicroSeconds() - start)/1000.0
return "ET msec: " + String(et)
}
func printDate(string: String) {
let date = Date()
let formatter = DateFormatter()
formatter.dateFormat = "HH:mm:ss.SSSS"
print(string + formatter.string(from: date))
}
}
extension Date {
func toMicroSeconds() -> Int64! {
return Int64(self.timeIntervalSince1970 * 1000000)
}
}
'''
Though it's possible to force viewDidLoad() to wait until the iTunes request completes with a DispatchGroup, I would not recommend blocking that method - if the request takes awhile it will make your app unresponsive.
Instead, put the downstream initialization you need to do in a separate func, and call it from that DispatchQueue.main.async block you already have written when the json data is returned.
If the problem you're trying to avoid is how the UI looks before the data is retrieved, there are a few ways to handle that including having your view content initially hidden and showing it (maybe even with a little animation) after the data is retrieved. If you find in testing there's usually a noticeable lag you could add an activity indicator in the view to show the user it's loading (without blocking the main thread).
Your URLRequest block currently isn't handling any request errors (apart from the print statement), so you might also create a func to handle errors and call it similarly.
I have been searching for an answer to this problem for weeks now. After I posted the question for help I ran across a post by Axel Kee 7 May 2019 on:
Download files sequentially using URLSession inside OperationQueue. https://fluffy.es/download-files-sequentially/
By using the approach provided by his class DownloadOperation : Operation {…} I was able to solve my problem. I understand Corbell’s warning, but since I need to do setup operations that depend on Api results this approach provided the solution I need. Maybe there is a better way than this, and maybe this would help someone else. It will also expose Axel Kee’s approach to downloading multiple files to others.
class ViewController: UIViewController {
var start:Int64 = 0
var urlLocal: URL?
var response:Response?
var session : URLSession!
var queue : OperationQueue!
override func viewDidLoad() {
// Do any additional setup after loading the view.
start = Date().toMicroSeconds()
print("0 ",printMiilliSecondsFStart())
session = URLSession(configuration: URLSessionConfiguration.default, delegate: nil, delegateQueue: nil)
queue = OperationQueue()
queue.maxConcurrentOperationCount = 1
print("100 Start downloadApi func at: ",printMiilliSecondsFStart())
response = downloadApi(response, stringUrl: "https://itunes.apple.com/search?term=taylor+swift&entity=song")
print("110 Return from downloadApi func at: ",printMiilliSecondsFStart())
print("120 Without DownloadOperation the next statements failed!!")
let txtFromDecodedApi = "Count: = " + String(self.response!.results.count) + "\nTrack Name: " + self.response!.results[1].trackName + "\nCollection Name: " + self.response!.results[1].collectionName
print(txtFromDecodedApi)
print("130 Success ",printMiilliSecondsFStart())
}
func getDocumentsDirectory() -> URL {
// find all possible documents directories for this user
let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
// just send back the first one, which ought to be the only one
return paths[0]
}
func downloadApi<T:Decodable> (_ t:T, stringUrl: String) -> T {
var apiReadNotCompleted:Bool = true
var apiReadCompleted = false
var decodedResult: T?
let completionOperation = BlockOperation {
print("6 ",self.printMiilliSecondsFStart())
print("6 Finished download all")
if let data = try? Data(contentsOf: self.urlLocal!) {
print("7 ",self.printMiilliSecondsFStart())
print("7 readApiJson() ",data[0],data[1],data[2],data[3])
// here is where you could write the Api download to
// local file of different types
// self.writeApiToLocalFile(data: data)
let decoder = JSONDecoder()
if let jsonDecoded = try? decoder.decode(type(of:t), from: data) {
decodedResult = jsonDecoded
print("8 ",self.printMiilliSecondsFStart())
}
print("9 ",self.printMiilliSecondsFStart())
}
//self.progressLabel.text = self.response!.results[0].trackName
print("10 ",self.printMiilliSecondsFStart())
DispatchQueue.main.async {
// Axel Kee in the class DownloadOperation : Operation
// I borrowed provided for a cancel operation
// self.cancelButton.isEnabled = false
}
print("11 ",self.printMiilliSecondsFStart())
apiReadCompleted = true
}
if let url = URL(string: stringUrl) {
print("1 ",printMiilliSecondsFStart())
let operation = DownloadOperation(session: self.session, downloadTaskURL: url, completionHandler: { (localURL, urlResponse, error) in
print("1a \nlocalURL ",localURL!)//; print("1aa \nurlResponse\n",urlResponse!,"\n\nend")
self.urlLocal = localURL
if error == nil {
print("2 ",self.printMiilliSecondsFStart())
DispatchQueue.main.async {
print("3 ",self.printMiilliSecondsFStart())
//self.progressLabel.text = "\(index + 1) / \(urls.count) files downloaded"
print("3a Api file download complete")
}
}
} )
print("4 ",printMiilliSecondsFStart())
completionOperation.addDependency(operation)
self.queue.addOperation(operation)
}
else {print("4a Bad url = ", stringUrl)}
print("5 Before end ",printMiilliSecondsFStart())
self.queue.addOperation(completionOperation)
// Axel Kee in the class DownloadOperation : Operation
// I borrowed provided for a cancel operation
// self.cancelButton.isEnabled = true
apiReadNotCompleted = true
apiReadCompleted = false
while (apiReadNotCompleted) {
if apiReadCompleted {
print("5 Middle Api file read ")
apiReadNotCompleted = false
}
}
print("5 After end and exit ",printMiilliSecondsFStart())
return decodedResult!
}
func printMiilliSecondsFStart() -> String {
let et = Float(Date().toMicroSeconds() - start)/1000.0
return "Elapsed Time in milliseconds: " + String(et)
}
func printDate(string: String) {
let date = Date()
let formatter = DateFormatter()
formatter.dateFormat = "HH:mm:ss.SSSS"
print(string + formatter.string(from: date))
}
}
extension Date {
func toMicroSeconds() -> Int64! {
return Int64(self.timeIntervalSince1970 * 1000000)
}
}
struct Response: Codable {
var results: [Result]
}
struct Result: Codable {
var trackId: Int
var trackName: String
var collectionName: String
}
// Copyright © 2019 fluffy. All rights reserved.
// Axel Kee 7 May 2019
// https://fluffy.es/download-files-sequentially/
// SequentialDownload-master
class DownloadOperation : Operation {
private var task : URLSessionDownloadTask!
enum OperationState : Int {
case ready
case executing
case finished
}
// default state is ready (when the operation is created)
var state : OperationState = .ready {
willSet {
self.willChangeValue(forKey: "isExecuting")
self.willChangeValue(forKey: "isFinished")
}
didSet {
self.didChangeValue(forKey: "isExecuting")
self.didChangeValue(forKey: "isFinished")
}
}
override var isReady: Bool { return state == .ready }
override var isExecuting: Bool { return state == .executing }
override var isFinished: Bool { return state == .finished }
init(session: URLSession, downloadTaskURL: URL, completionHandler: ((URL?, URLResponse?, Error?) -> Void)?) {
super.init()
// use weak self to prevent retain cycle
task = session.downloadTask(with: downloadTaskURL, completionHandler: { [weak self] (localURL, response, error) in
// if there is a custom completionHandler defined,
// pass the result gotten in downloadTask's completionHandler to the
// custom completionHandler
if let completionHandler = completionHandler {
// localURL is the temporary URL the downloaded file is located
completionHandler(localURL, response, error)
}
// set the operation state to finished once
// the download task is completed or have error
self?.state = .finished
})
}
override func start() {
// if the operation or queue got cancelled even
// before the operation has started, set the
// operation state to finished and return
if(self.isCancelled) {
state = .finished
return
}
// set the state to executing
state = .executing
print("000 Start downloading \(self.task.originalRequest?.url?.absoluteString ?? "")")
// start the downloading
self.task.resume()
}
override func cancel() {
super.cancel()
// cancel the downloading
self.task.cancel()
}
}
Results
0 Elapsed Time in milliseconds: 0.147
100 Start downloadApi func at: Elapsed Time in milliseconds: 1.473
1 Elapsed Time in milliseconds: 1.777
4 Elapsed Time in milliseconds: 26.885
5 Before end Elapsed Time in milliseconds: 26.963
000 Start downloading https://itunes.apple.com/search?term=taylor+swift&entity=song
1a
localURL file:///Users/charlescole/Library/Developer/CoreSimulator/Devices/FB473C00-0C85-4B30-8DD6-9D7408BA0BD2/data/Containers/Data/Application/6E87F4AE-59BC-4650-A5C2-70FEACEB516D/tmp/CFNetworkDownload_GFf5Jt.tmp
2 Elapsed Time in milliseconds: 183.764
6 Elapsed Time in milliseconds: 183.946
6 Finished download all
7 Elapsed Time in milliseconds: 184.147
7 readApiJson() 10 10 10 123
8 Elapsed Time in milliseconds: 186.018
9 Elapsed Time in milliseconds: 186.031
10 Elapsed Time in milliseconds: 186.039
11 Elapsed Time in milliseconds: 186.056
5 Middle Api file read
5 After end and exit Elapsed Time in milliseconds: 186.072
110 Return from downloadApi func at: Elapsed Time in milliseconds: 186.083
120 Without DownloadOperation the next statements failed!!
Count: = 50
Track Name: Shake It Off
Collection Name: 1989
130 Success Elapsed Time in milliseconds: 186.106
3 Elapsed Time in milliseconds: 194.89
3a Api file download complete
After update to swift3 and xcode8 this code does not work. Before update works fine. I can't find what's problem. I was trying update pods and run code in older version xcode but doesn't work. All works fine before apple start update to swift3.
This is a error witch is printed:
Error sending email: Optional(Error Domain=MCOErrorDomain Code=1 "A stable connection to the server could not be established." UserInfo={NSLocalizedDescription=A stable connection to the server could not be established.})
override func viewDidLoad() {
super.viewDidLoad()
let smtpSession = MCOSMTPSession()
smtpSession.hostname = "host"
smtpSession.username = "username"
smtpSession.password = "pass"
smtpSession.port = 465
smtpSession.authType = MCOAuthType.saslPlain
smtpSession.connectionType = MCOConnectionType.TLS
smtpSession.connectionLogger = {(connectionID, type, data) in
if data != nil {
if let string = NSString(data: data!, encoding: String.Encoding.utf8.rawValue){
print("Connectionlogger: \(string)")
}
}
}
let builder = MCOMessageBuilder()
builder.header.to = [MCOAddress(displayName: "tadeuszek666#gmail.com", mailbox: "tadeuszek666#gmail.com")]
builder.header.from = MCOAddress(displayName: "jan.fryderyk12#o2.pl", mailbox: "jan.fryderyk12#o2.pl")
builder.header.subject = "temat nowej wiadomosci"
builder.htmlBody = "cialo wiadomosci"
let rfc822Data = builder.data()
let sendOperation = smtpSession.sendOperation(with: rfc822Data)
sendOperation?.start { (error) -> Void in
if (error != nil) {
print("Error sending email: \(error)")
} else {
print("Successfully sent email!")
//ContainerViews.tabBarViewController?.selectedIndex = 0
}
}
}
Let me start out by stating I'm still unfamiliar with what I'm trying to do, but striving to get better!
I'm working on a project that I'm writing unit tests for and I'm having some trouble with how to approach the problem.
The method I'm testing utilizes a RESTAPI call to verify a users credentials. I'm not sure what the best way to unit test would be.
Here is the method I'm looking to make the Unit test for:
#IBAction func loginBtnActivate(sender: UIButton) {
let enteredEmail: String = emailField.text!
let enteredPassword: String = passwordField.text!
let testInfo:[String: AnyObject] = ["User": enteredEmail, "Password": enteredPassword]
RestApiManager.sharedInstance.postLogin(testInfo) { (json, statusCode) in
if statusCode == 200 {
let AuthToken: TokenObject = (TokenObject(json: json))
try! self.keychain.set(AuthToken.Authorization, key:"Authorization")
try! self.keychain.set(AuthToken.LifeTime, key: "LifeTime")
try! self.keychain.set(AuthToken.UUID, key: "UUID")
NSOperationQueue.mainQueue().addOperationWithBlock {
self.performSegueWithIdentifier("loginToMyHealth", sender: nil)
}
} else if statusCode == 401 {
self.incorrectLoginAlert()
} else if statusCode == 503 {
print("Service Unavailable Please Try Again Later")
}
}
}
This is currently the approach I'm taking:
func testLoginInfoMatchesDataOnServer(){
let enteredEmail: String = "user"
let enteredPassword: String = "password"
let testInfo:[String: AnyObject] = ["User": enteredEmail, "Password": enteredPassword]
RestApiManager.sharedInstance.postLogin(testInfo) { (json, statusCode) in
XCTAssert(statusCode == 200, "statusCode is not matching the server data")
}
I'm simply verifying that the Rest call is successful and that the credentials are matching the JSON. The XCTAssert call doesn't appear to be working correctly. No matter what I put as the first parameter, XCTAssert doesn't affect whether the test is successful or not.
Example, if I put:
XCTAssert(false, "statusCode is not matching the server data")
The test will still pass regardless of what I put. If I place the Assert function outside the brackets then it appears the variable "statusCode" is out of scope so I'm stuck with a
Use of unresolved identifier 'statusCode'.
func testLoginInfoMatchesDataOnServer(){
let enteredEmail: String = "user"
let enteredPassword: String = "password"
let testInfo:[String: AnyObject] = ["User": enteredEmail, "Password": enteredPassword]
RestApiManager.sharedInstance.postLogin(testInfo) { (json, statusCode) in
}
XCTAssert(statusCode == 200, "statusCode is not matching the server data")
}
I was looking at this guide for help.. Would this be a better approach for what I'm trying to do?
http://roadfiresoftware.com/2016/06/how-do-you-unit-test-rest-calls-in-swift/
Again my understanding of some core concepts might be entirely off so I really appreciate your advice here!
Thanks in advance!
Sean W.
First Few problems with your code
function test(){
RestApiManager.sharedInstance.postLogin(testInfo) { (json, statusCode) in
}//PostLogin call ends
XCTAssert(statusCode == 200, "statusCode is not matching the server data") // Here StatusCode is not accessible as outside the block
}// end function
If you want to use Status Code you should do
function test(){
RestApiManager.sharedInstance.postLogin(testInfo) { (json, statusCode) in
XCTAssert(statusCode == 200, "statusCode is not matching the server data") // Here StatusCode is accessible inside the block
}//PostLogin call ends
}// end function
But this will fail since you need to wait for the response. That can be done using
waitForExpectationsWithTimeout(5)
So proper way to call this would be --
function test(){
let URL = NSURL(string: "http://google.com/")!
let expectation = expectationWithDescription("GET \(URL)")
RestApiManager.sharedInstance.postLogin(testInfo) { (json, statusCode) in
XCTAssert(statusCode == 200, "statusCode is not matching the server data") // Here StatusCode is accessible inside the block
expectation.fulfill()
}//PostLogin call ends
waitForExpectationsWithTimeout(5){ error in
if let error = error {
print("Error: \(error.localizedDescription)")
}
}//Wait block end
}// end function
Important lines here are
expectation.fulfill() // tells process is complete
and
waitForExpectationsWithTimeout(5){} //tells wait for 5secs or throw error
For more info
I am trying to read an online JSON file using the sample found here. It seems quite straight forward except when I run it. It keeps failing at dataTaskWithURL call and the error.localizedDescription is "The operation couldn't be completed. (NSURLErrorDomain -1005.)" I have looked up the error code and it refers to NSURLErrorNetworkConnectionLost but I am connected to internet and when I try the same url in a browser, I receive the JSON result.
func searchItunes() {
let urlPath = "http://itunes.apple.com/search?term=JQ+Software&media=software"
let url: NSURL = NSURL(string: urlPath)
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithURL(url, completionHandler: {
data, response, error in
if(error != nil) {
println(error.localizedDescription)
}
else {
var err: NSError?
var jsonResult = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error: &err) as NSDictionary
if(err != nil) {
println("JSON Error \(err!.localizedDescription)")
}
else {
// process data here.
}
}
})
task.resume()
}
This should work, but note that your pasting above has an extra }) in the else block and an additional } below. With that I get the above to work successfully. If your brackets match up in your code then there may be some kind of intermediary proxy that is being used in your browser but not swift, or vice versa, that's causing the change to occur.
Note that the background thread will take a brief time to resolve, so you won't see anything if your program terminates before the thread finishes downloading in the background.
I was using the iOS Simulator and I realized that although my laptop is connected to the internet, the simulator is not because of the order of services in the System Preferences \ Network WiFi is not on the top. Apparently, the simulator does not try to find the first device which is actually connected! So I re-ordered the services and now the code works fine.
//this is swift program
struct Stack<Element>{
var brackets = [Element]()
mutating func push(bracket:Element){
brackets.append(bracket)
}
mutating func pop() -> Element{
return brackets.removeLast()
}
}
var stackOfCharacter = Stack<String>()
var arrayCharacter: [String] = ["[","(",")","]"]
for symbol in arrayCharacter{
if symbol == "{" {
stackOfCharacter.push(bracket: symbol)
}
else if symbol == "[" {
stackOfCharacter.push(bracket:symbol)
}
else if symbol == "(" {
stackOfCharacter.push(bracket:symbol)
}
else if symbol == "}"{
if stackOfCharacter.brackets.count != 0 {
var topItem = stackOfCharacter.brackets[stackOfCharacter.brackets.count - 1]
if topItem == "{" {
var element = stackOfCharacter.pop()
}
}
}
else if symbol == ")" {
if stackOfCharacter.brackets.count != 0 {
var topItem = stackOfCharacter.brackets[stackOfCharacter.brackets.count - 1]
if topItem == "(" {
var element = stackOfCharacter.pop()
}
}
}
else if symbol == "]" {
if stackOfCharacter.brackets.count != 0 {
var topItem = stackOfCharacter.brackets[stackOfCharacter.brackets.count - 1]
if topItem == "[" {
var element = stackOfCharacter.pop()
}
}
}
else {
stackOfCharacter.push(bracket:symbol)
}
}
var count = stackOfCharacter.brackets.isEmpty
if count {
print ("valid ")
}
else {
print ("Invalid")
}