I have the below code which obtains the access token from a facebook login and returns it as 'accessToken'. Once I have the access token I wish to pass this to my server in a request and return the response as an array.
The issue I have is that the request to the server executes before the accessToken is obtained. I have looked into closure statements but I cannot see a way where I can order the execution of the functions without ending up nesting. I don't mind nesting in this instance but in future if I have say 5 functions this will begin to look messy.
Am I approaching this in the best way by using classes and functions? Usually when I code in swift all the code relevant to the viewController would be contained in 1 file, but as the project gets larger I am looking to implement a more OOP approach to make the project more manageable. How would I best achieve this?
import Foundation
import UIKit
class registrationPage: UIViewController {
#IBAction func facebookButton(_ sender: Any) {
// Get the access token from facebook
let accessToken = facebookLogin().login()
// Get the users currency, langage and locale.
let currency = Locale.current.currencyCode ?? "GBP"
let language = Locale.current.languageCode ?? "GB"
let region = Locale.current.regionCode ?? "GB"
let params = "accessToken=\(accessToken)¤cy=\(currency)&language=\(language)®ion=\(region)"
let resultArray = database().connect(endPoint: "loginfb.php?", params: "\(params)")
print(resultArray)
}
}
class facebookLogin {
var response = ""
func login(completion: (_ result: String) -> Void) {
let loginManager = LoginManager()
loginManager.logIn(readPermissions:[ReadPermission.publicProfile, ReadPermission.email], viewController: registrationPage() as UIViewController) {
loginResult in switch loginResult {
case .failed:
self.response = "ERROR"
case .cancelled:
self.response = "ERROR"
case .success:
self.response = "\(String(describing: FBSDKAccessToken.current().tokenString!))"
print(self.response)
}
}
completion(self.response)
}
}
loginManager.logIn is asynchronous, thats why it takes a closure. You can synchronize the call or as you said use nested closures where one calls the next.
To make let accessToken = facebookLogin().login() synchronous with DispatchGroup:
class facebookLogin {
func login() -> String {
let loginManager = LoginManager()
var response = ""
let group = DispatchGroup()
group.enter() // loginManager.logIn
loginManager.logIn(readPermissions:[ReadPermission.publicProfile, ReadPermission.email], viewController: registrationPage() as UIViewController) {
loginResult in switch loginResult {
case .failed:
self.response = "ERROR"
case .cancelled:
self.response = "ERROR"
case .success:
self.response = "\(String(describing: FBSDKAccessToken.current().tokenString!))"
print(self.response)
}
group.leave() // loginManager.logIn
}
group.wait()
return response
}
}
If you don't like the facebookLogin().login() { accessToken in ... } syntax, you could put the { accessToken in ... } part into its own function
func callServer(accessToken: String) {
// Get the users currency, langage and locale.
let currency = Locale.current.currencyCode ?? "GBP"
let language = Locale.current.languageCode ?? "GB"
let region = Locale.current.regionCode ?? "GB"
let params = "accessToken=\(accessToken)¤cy=\(currency)&language=\(language)®ion=\(region)"
let resultArray = database().connect(endPoint: "loginfb.php?", params: "\(params)")
print(resultArray)
}
and call it with
#IBAction func facebookButton(_ sender: Any) {
// Get the access token from facebook
facebookLogin().login(completion: callServer(accessToken:))
}
Related
I have been trying to pass the response of my AF.request statement to my viewModel but am unable to understand still? I need to pass my response to my ViewModel and then use it to display in the tableView.
This is my Service Class:
Service
class Service {
fileprivate var baseUrl = ""
//https://api.themoviedb.org/3/tv/76479?api_key=3d0cda4466f269e793e9283f6ce0b75e&language=en-US
init(baseUrl: String) {
self.baseUrl = baseUrl
}
var tvShowDetails = TVShowModel()
func getTVShowDeet(completionHandler: #escaping ()-> TVShowModel){
let request = AF.request(self.baseUrl)
.validate()
.responseDecodable(of: TVShowModel.self) { (response) in
guard let tvShow = response.value else {return}
return tvShow
print("printing response", tvShow)
}
}
}
ViewModel
func getTVShowDetails(){
service.getTVShowDeet{
print(self.response)
self.delegate?.reloadTable()
self.headerDelegate?.configureHeader()
print("prinitn respinse in VM", self.response)
}
}
Model
struct TVShowModel : Decodable {
let id : Int?
let original_name : String?
let overview : String?
enum CodingKeys: String, CodingKey {
case id = "id"
case original_name = "original_name"
case overview = "overview"
}
init(){
id = nil
original_name = nil
overview = nil
}
}
Networking requests are asynchronous meaning we don't know when they'll complete so we need to use completion handlers instead of returning from the function (unless you use Async/Await). Something along the lines of this should work:
Service
func getTVShowDeet(completionHandler: #escaping (TVShowModel) -> Void) {
let request = AF.request(self.baseUrl)
.validate()
.responseDecodable(of: TVShowModel.self) { (response) in
guard let tvShow = response.value else { return }
completionHandler(tvShow)
}
}
ViewModel
func getTVShowDetails() {
service.getTVShowDeet { [weak self] tvShow in
// Here you may need to store the tvShow object somewhere to use in your tableView datasource.
self?.delegate?.reloadTable()
self?.headerDelegate?.configureHeader()
}
}
Today again one combine problem I currently run in and I hope that someone of you can help. How can normal unit tests be written for ObservableObjects classes which contain #Published attributes? How can I subscribe in my test to them to get the result object which I can assert?
The injected mock for the web service works correctly, loadProducts() function set exactly the same elements from the mock in the fetchedProducts array.
But I don't know currently how to access this array in my test after it is filled by the function because it seems that I cannot work with expectations here, loadProducts() has no completion block.
The code looks like this:
class ProductsListViewModel: ObservableObject {
let getRequests: GetRequests
let urlService: ApiUrls
private let networkUtils: NetworkRequestUtils
let productsWillChange = ObservableObjectPublisher()
#Published var fetchedProducts = [ProductDTO]()
#Published var errorCodeLoadProducts: Int?
init(getRequestsHelper: GetRequests, urlServiceClass: ApiUrls = ApiUrls(), utilsNetwork: NetworkRequestUtils = NetworkRequestUtils()) {
getRequests = getRequestsHelper
urlService = urlServiceClass
networkUtils = utilsNetwork
}
// nor completion block in the function used
func loadProducts() {
let urlForRequest = urlService.loadProductsUrl()
getRequests.getJsonData(url: urlForRequest) { [weak self] (result: Result<[ProductDTO], Error>) in
self?.isLoading = false
switch result {
case .success(let productsArray):
// the products filled async here
self?.fetchedProducts = productsArray
self?.errorCodeLoadProducts = nil
case .failure(let error):
let errorCode = self?.networkUtils.errorCodeFrom(error: error)
self?.errorCodeLoadProducts = errorCode
print("error: \(error)")
}
}
}
}
The test I try to write looks like this at the moment:
import XCTest
#testable import MyProject
class ProductsListViewModelTest: XCTestCase {
var getRequestMock: GetRequests!
let requestManagerMock = RequestManagerMockLoadProducts()
var productListViewModel: ProductsListViewModel!
override func setUp() {
super.setUp()
getRequestMock = GetRequests(networkHelper: requestManagerMock)
productListViewModel = ProductsListViewModel(getRequestsHelper: getRequestMock)
}
func test_successLoadProducts() {
let loginDto = LoginResponseDTO(token: "token-token")
UserDefaults.standard.save(loginDto, forKey: CommonConstants.persistedLoginObject)
productListViewModel.loadProducts()
// TODO access the fetchedProducts here somehow and assert them
}
}
The Mock looks like this:
class RequestManagerMockLoadProducts: NetworkRequestManagerProtocol {
var isSuccess = true
func makeNetworkRequest<T>(urlRequestObject: URLRequest, completion: #escaping (Result<T, Error>) -> Void) where T : Decodable {
if isSuccess {
let successResultDto = returnedProductedArray() as! T
completion(.success(successResultDto))
} else {
let errorString = "Cannot create request object here"
let error = NSError(domain: ErrorDomainDescription.networkRequestDomain.rawValue, code: ErrorDomainCode.unexpectedResponseFromAPI.rawValue, userInfo: [NSLocalizedDescriptionKey: errorString])
completion(.failure(error))
}
}
func returnedProductedArray() -> [ProductDTO] {
let product1 = ProductDTO(idFromBackend: "product-1", name: "product-1", description: "product-description", price: 3.55, photo: nil)
let product2 = ProductDTO(idFromBackend: "product-2", name: "product-2", description: "product-description-2", price: 5.55, photo: nil)
let product3 = ProductDTO(idFromBackend: "product-3", name: "product-3", description: "product-description-3", price: 8.55, photo: nil)
return [product1, product2, product3]
}
}
Maybe this article can help you
Testing your Combine Publishers
To solve your issue I will use code from my article
typealias CompetionResult = (expectation: XCTestExpectation,
cancellable: AnyCancellable)
func expectValue<T: Publisher>(of publisher: T,
timeout: TimeInterval = 2,
file: StaticString = #file,
line: UInt = #line,
equals: [(T.Output) -> Bool])
-> CompetionResult {
let exp = expectation(description: "Correct values of " + String(describing: publisher))
var mutableEquals = equals
let cancellable = publisher
.sink(receiveCompletion: { _ in },
receiveValue: { value in
if mutableEquals.first?(value) ?? false {
_ = mutableEquals.remove(at: 0)
if mutableEquals.isEmpty {
exp.fulfill()
}
}
})
return (exp, cancellable)
}
your test needs to use this function
func test_successLoadProducts() {
let loginDto = LoginResponseDTO(token: "token-token")
UserDefaults.standard.save(loginDto, forKey: CommonConstants.persistedLoginObject)
/// The expectation here can be extended as needed
let exp = expectValue(of: productListViewModel .$fetchedProducts.eraseToAnyPublisher(), equals: [{ $0[0].idFromBackend == "product-1" }])
productListViewModel.loadProducts()
wait(for: [exp.expectation], timeout: 1)
}
The easy and clearest way for me is simply to test #published var after X seconds. An example bellow :
func test_successLoadProducts() {
let loginDto = LoginResponseDTO(token: "token-token")
UserDefaults.standard.save(loginDto, forKey: CommonConstants.persistedLoginObject)
productListViewModel.loadProducts()
// TODO access the fetchedProducts here somehow and assert them
let expectation = XCTestExpectation()
DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
XCTAssertEqual(self.productListViewModel.fetchedProducts, ["Awaited values"])
expectation.fulfill()
}
wait(for: [expectation], timeout: 5.0)
}
I hope that helps !
I am trying to build a small currency converter and the problem is that my completionHandler does not work. As a result, the input Currency does not change instantly after the function was executed
I have already tried to implement a completionHandler; however, had no success yet
class CurrencyExchange: ViewController {
//Outlets
#IBOutlet weak var lblCurrency: UILabel!
#IBOutlet weak var segOutputCurrency: UISegmentedControl!
#IBOutlet weak var txtValue: UITextField!
#IBOutlet weak var segInputCurrency: UISegmentedControl!
//Variables
var inputCurrency: String!
var currencyCNY: Double!
var currencyEUR: Double!
var currencyGBP: Double!
var currencyJPY: Double!
override func viewDidLoad() {
super.viewDidLoad()
self.navigationController?.isNavigationBarHidden = true
}
#IBAction func btnConvert(_ sender: Any) {
assignOutput()
if txtValue.text == "" {
self.lblCurrency.text = "Please insert value"
} else {
let inputValue = Double(txtValue.text!)!
if segOutputCurrency.selectedSegmentIndex == 0 {
let output = Double(inputValue * currencyCNY!)
self.lblCurrency.text = "\(output)¥"
} else if segOutputCurrency.selectedSegmentIndex == 1 {
let output = Double(inputValue * currencyEUR!)
self.lblCurrency.text = "\(output)€"
} else if segOutputCurrency.selectedSegmentIndex == 2 {
let output = Double(inputValue * currencyGBP!)
self.lblCurrency.text = "\(output)"
} else if segOutputCurrency.selectedSegmentIndex == 3 {
let output = Double(inputValue * currencyJPY!)
self.lblCurrency.text = "\(output)"
}
}
}
func assignOutput() {
let currencies = ["EUR", "JPY", "CNY", "USD"]
inputCurrency = currencies[segInputCurrency.selectedSegmentIndex]
Alamofire.request("https://api.exchangeratesapi.io/latest?base=\(inputCurrency!)").responseJSON { (response) in
let result = response.result
let jsonCurrencies = JSON(result.value!)
let dictContent = jsonCurrencies["rates"]
self.currencyCNY = dictContent["CNY"].double
self.currencyEUR = dictContent["EUR"].double
self.currencyGBP = dictContent["GBP"].double
self.currencyJPY = dictContent["JPY"].double
}
}
}
The expected result is that everytime the btnConvert function is called the assignInput and assignOutput functions are called and the variables are set to the right values. I am a beginner so any help would be highly appreciated.
You need a completion handler in assignOutput(), I added also the minimum error handling to avoid crashes
//Variables
var inputCurrency = ""
var currencyCNY = 0.0
var currencyEUR = 0.0
var currencyGBP = 0.0
var currencyJPY = 0.0
#IBAction func btnConvert(_ sender: Any) {
assignOutput() { success in
if success {
if txtValue.text!.isEmpty {
self.lblCurrency.text = "Please insert value"
} else {
if let inputValue = Double(txtValue.text!) {
if segOutputCurrency.selectedSegmentIndex == 0 {
let output = Double(inputValue * currencyCNY)
self.lblCurrency.text = "\(output)¥"
} else if segOutputCurrency.selectedSegmentIndex == 1 {
let output = Double(inputValue * currencyEUR)
self.lblCurrency.text = "\(output)€"
} else if segOutputCurrency.selectedSegmentIndex == 2 {
let output = Double(inputValue * currencyGBP)
self.lblCurrency.text = "\(output)"
} else if segOutputCurrency.selectedSegmentIndex == 3 {
let output = Double(inputValue * currencyJPY)
self.lblCurrency.text = "\(output)"
}
} else {
self.lblCurrency.text = "Please enter a number"
}
}
} else {
self.lblCurrency.text = "Could not receive the exchange rates"
}
}
}
func assignOutput(completion: #escaping (Bool) -> Void) {
let currencies = ["EUR", "JPY", "CNY", "USD"]
inputCurrency = currencies[segInputCurrency.selectedSegmentIndex]
Alamofire.request("https://api.exchangeratesapi.io/latest?base=\(inputCurrency)").responseJSON { (response) in
if let result = response.result.value {
let jsonCurrencies = JSON(result)
let dictContent = jsonCurrencies["rates"]
self.currencyCNY = dictContent["CNY"].double
self.currencyEUR = dictContent["EUR"].double
self.currencyGBP = dictContent["GBP"].double
self.currencyJPY = dictContent["JPY"].double
completion(true)
} else {
completion(false)
}
}
}
The basic idea of a completion handler is that you have some asynchronous method (i.e., a method that finishes later) and you need to give the caller the opportunity to supply what it wants the asynchronous method to do when it’s done. So, given that assignOutput is the asynchronous method, that’s the method that you would refactor with a completion handler escaping closure.
Personally, I’d configure this escaping closure to return a Result type:
For example:
func assignOutput(completion: #escaping (Result<[String: Double]>) -> Void) {
let inputCurrency = ...
Alamofire.request("https://api.exchangeratesapi.io/latest?base=\(inputCurrency)").responseJSON { response in
switch response.result {
case .failure(let error):
completion(.failure(error))
case .success(let value):
let jsonCurrencies = JSON(value)
guard let dictionary = jsonCurrencies["rates"].dictionaryObject as? [String: Double] else {
completion(.failure(CurrencyExchangeError.currencyNotFound)) // this is just a custom `Error` type that I’ve defined
return
}
completion(.success(dictionary))
}
}
}
And then you could use it like so:
assignOutput { result in
switch result {
case .failure(let error):
print(error)
case .success(let dictionary):
print(dictionary)
}
}
By using Result types, you have a nice consistent pattern where you can check for .failure or .success throughout your code.
That having been said, I’d suggest a variety of other refinements:
I wouldn’t make this view controller subclass from another view controller, ViewController. It should subclass UIViewController.
(Technically you can re-subclass your own custom view controller subclasses, but it’s exceptionally uncommon. Frankly, when you’ve got so much in your view controller subclass that you need to have subclasses of subclasses, it may be code smell indicating that you’ve got too much in your view controller.)
I’d give this view controller a class name that unambiguously indicates the type of object, e.g. CurrencyExchangeViewController, not just CurrencyExchange. This habit will pay dividends in the future, when you start breaking these big view controllers into something more manageable.
You have the list of accepted currencies in four different places:
In your storyboard for segOutputCurrency
In your storyboard for segInputCurrency
In your btnConvert routine
In your assignOutput routine
This makes your code brittle, making it easy to make mistakes if you change the order of the currencies, add/remove currencies, etc. It would be better to have a list of currencies in one place, programmatically update your UISegmentedControl outlets in viewDidLoad and then have your routines all refer back to a single array of which currencies are permitted.
You should avoid using the ! forced unwrapping operator. For example, if the network request failed and you then reference result.value!, your app will crash. You want to gracefully handle errors that happen outside of your control.
If you’re going to format currencies, remember that in addition to currency symbols, you should consider that not all locales use . for decimal place (e.g. your European users may use ,). For that reason, we would generally use NumberFormatter for converting the calculated number back to a string.
Below, I’ve just used NumberFormatter for the output, but you really should use it when interpreting the user’s input too. But I will leave that to the reader.
There’s a more subtle point when dealing with currencies, above and beyond the currency symbol, namely how many decimal places the result should display. (E.g. when dealing with Japanese yen, you generally don’t have decimal places, whereas euros and US dollars and would have two decimal places.)
You can write your own conversion routine if you want, but I might associate the chosen currency codes with Locale identifiers, that way you can leverage the symbol and the number of fractional digits appropriate for each currency. And I’d format the string representations of numbers using NumberFormatters.
The convention for outlet names is usually some functional name followed by the type of control. E.g. you might have inputTextField or currencyTextField and outputLabel or convertedLabel. Likewise, I might rename the #IBAction to be didTapConvertButton(_:)
I’d personally excise the use of SwiftyJSON, which, despite the name, feels unswifty to me. I’d use JSONDecoder.
Pulling that all together, you might end up with something like:
// CurrencyViewController.swift
import UIKit
import Alamofire
// types used by this view controller
struct Currency {
let code: String // standard three character code
let localeIdentifier: String // a `Locale` identifier string used to determine how to format the results
}
enum CurrencyExchangeError: Error {
case currencyNotSupplied
case valueNotSupplied
case currencyNotFound
case webServiceError(String)
case unknownNetworkError(Data?, HTTPURLResponse?)
}
struct ExchangeRateResponse: Codable {
let error: String?
let base: String?
let rates: [String: Double]?
}
class CurrencyExchangeViewController: UIViewController {
// outlets
#IBOutlet weak var inputTextField: UITextField!
#IBOutlet weak var inputCurrencySegmentedControl: UISegmentedControl!
#IBOutlet weak var outputCurrencySegmentedControl: UISegmentedControl!
#IBOutlet weak var resultLabel: UILabel!
// private properties
private let currencies = [
Currency(code: "EUR", localeIdentifier: "fr_FR"),
Currency(code: "JPY", localeIdentifier: "jp_JP"),
Currency(code: "CNY", localeIdentifier: "ch_CH"),
Currency(code: "USD", localeIdentifier: "en_US")
]
override func viewDidLoad() {
super.viewDidLoad()
navigationController?.isNavigationBarHidden = true
updateCurrencyControls()
}
#IBAction func didTapConvertButton(_ sender: Any) {
let inputIndex = inputCurrencySegmentedControl.selectedSegmentIndex
let outputIndex = outputCurrencySegmentedControl.selectedSegmentIndex
guard inputIndex >= 0, outputIndex >= 0 else {
resultLabel.text = errorMessage(for: CurrencyExchangeError.currencyNotSupplied)
return
}
guard let text = inputTextField.text, let value = Double(text) else {
resultLabel.text = errorMessage(for: CurrencyExchangeError.valueNotSupplied)
return
}
performConversion(from: inputIndex, to: outputIndex, of: value) { result in
switch result {
case .failure(let error):
self.resultLabel.text = self.errorMessage(for: error)
case .success(let string):
self.resultLabel.text = string
}
}
}
func updateCurrencyControls() {
outputCurrencySegmentedControl.removeAllSegments()
inputCurrencySegmentedControl.removeAllSegments()
enumerateCurrencies { index, code in
outputCurrencySegmentedControl.insertSegment(withTitle: code, at: index, animated: false)
inputCurrencySegmentedControl.insertSegment(withTitle: code, at: index, animated: false)
}
}
}
// these might better belong in a presenter or view model rather than the view controller
private extension CurrencyExchangeViewController {
func enumerateCurrencies(block: (Int, String) -> Void) {
for (index, currency) in currencies.enumerated() {
block(index, currency.code)
}
}
func errorMessage(for error: Error) -> String {
switch error {
case CurrencyExchangeError.currencyNotFound:
return NSLocalizedString("No exchange rate found for those currencies.", comment: "Error")
case CurrencyExchangeError.unknownNetworkError:
return NSLocalizedString("Unknown error occurred.", comment: "Error")
case CurrencyExchangeError.currencyNotSupplied:
return NSLocalizedString("You must indicate the desired currencies.", comment: "Error")
case CurrencyExchangeError.valueNotSupplied:
return NSLocalizedString("No value to convert has been supplied.", comment: "Error")
case CurrencyExchangeError.webServiceError(let message):
return NSLocalizedString(message, comment: "Error")
case let error as NSError where error.domain == NSURLErrorDomain:
return NSLocalizedString("There was a network error.", comment: "Error")
case is DecodingError:
return NSLocalizedString("There was a problem parsing the server response.", comment: "Error")
default:
return error.localizedDescription
}
}
func performConversion(from fromIndex: Int, to toIndex: Int, of value: Double, completion: #escaping (Result<String?>) -> Void) {
let originalCurrency = currencies[fromIndex]
let outputCurrency = currencies[toIndex]
fetchExchangeRates(for: originalCurrency.code) { result in
switch result {
case .failure(let error):
completion(.failure(error))
case .success(let exchangeRates):
guard let exchangeRate = exchangeRates.rates?[outputCurrency.code] else {
completion(.failure(CurrencyExchangeError.currencyNotFound))
return
}
let outputValue = value * exchangeRate
let locale = Locale(identifier: outputCurrency.localeIdentifier)
let string = formatter(for: locale).string(for: outputValue)
completion(.success(string))
}
}
/// Currency formatter for specified locale.
///
/// Note, this formats number using the current locale (e.g. still uses
/// your local grouping and decimal separator), but gets the appropriate
/// properties for the target locale's currency, namely:
///
/// - the currency symbol, and
/// - the number of decimal places.
///
/// - Parameter locale: The `Locale` from which we'll use to get the currency-specific properties.
/// - Returns: A `NumberFormatter` that melds the current device's number formatting and
/// the specified locale's currency formatting.
func formatter(for locale: Locale) -> NumberFormatter {
let currencyFormatter = NumberFormatter()
currencyFormatter.numberStyle = .currency
currencyFormatter.locale = locale
let formatter = NumberFormatter()
formatter.numberStyle = .currency
formatter.currencyCode = currencyFormatter.currencyCode
formatter.currencySymbol = currencyFormatter.currencySymbol
formatter.internationalCurrencySymbol = currencyFormatter.internationalCurrencySymbol
formatter.maximumFractionDigits = currencyFormatter.maximumFractionDigits
formatter.minimumFractionDigits = currencyFormatter.minimumFractionDigits
return formatter
}
}
}
// this might better belong in a network service rather than in the view controller
private extension CurrencyExchangeViewController {
func fetchExchangeRates(for inputCurrencyCode: String, completion: #escaping (Result<ExchangeRateResponse>) -> Void) {
Alamofire.request("https://api.exchangeratesapi.io/latest?base=\(inputCurrencyCode)").response { response in
guard response.error == nil, let data = response.data else {
completion(.failure(response.error ?? CurrencyExchangeError.unknownNetworkError(response.data, response.response)))
return
}
do {
let exchangeRates = try JSONDecoder().decode(ExchangeRateResponse.self, from: data)
if let error = exchangeRates.error {
completion(.failure(CurrencyExchangeError.webServiceError(error)))
} else {
completion(.success(exchangeRates))
}
} catch {
completion(.failure(error))
}
}
}
}
As indicated in the comments above, I’d probably move some of that stuff in the extensions into different objects, but I suspect even the above changes are a bit much to take in at one time, so I’ve stopped my refactoring there.
I made a request to server with Alamofire and get data from it with completion handler, but I want to use some value outside of it, is it possible to do that?
let retur = Json().login(userName: param1, password: param2) { (json) in
print(json)
let jsonDic = JSON(json)
for item in jsonDic["result"].arrayValue {
let token = item["ubus_rpc_session"].stringValue
}
print(jsonDic["result"][1]["ubus_rpc_session"].stringValue)
}
If you want to access result outside the Alamofire then follow below code.
override func viewDidLoad()
{
super.viewDidLoad()
let retur = Json().login(userName: param1, password: param2) { (json) in
print(json)
let jsonDic = JSON(json)
for item in jsonDic["result"].arrayValue
{
let token = item["ubus_rpc_session"].stringValue
self.myFunction(str: token)
}
print(jsonDic["result"][1]["ubus_rpc_session"].stringValue)
}
}
func myFunction(str: String)
{
//Here it will print token value.
print("Token value ====%#",str)
}
Solution 2:
Here I'll use NSUserDefault, to know more about it you can search about it, there are multiple examples are available on google.
Now here, we will use NSUserDefault.
//Use below code in alamofire block to save token value.
let defaults = UserDefaults.standard
defaults.set(token, forKey: "tokenValue")
//Use below code to print anywhere.
let defaults = UserDefaults.standard
if let myToken = defaults.value(forKey: "tokenValue") as? String
{
print("defaults savedToken: \(myToken)")
}
Hope it works for you.
For more details you can refer this answer https://stackoverflow.com/a/41899777/5886755
As I mention in comment that you want to access your token everywhere in app you can do it this way:
Create a separate class for that like
import UIKit
class UserSettings: NSObject {
class func setAuthToken(authToken: String?) {
let defaults = NSUserDefaults.standardUserDefaults()
defaults.setObject(authToken, forKey: "authToken");
}
class func getAuthToken() -> String? {
let defaults = NSUserDefaults.standardUserDefaults()
return defaults.stringForKey("authToken")
}
}
Now you can set token this way:
let token = item["ubus_rpc_session"].stringValue
UserSettings.setAuthToken(token)
and you get token anywhere in app this way:
if let token = UserSettings.getAuthToken() {
//do your stuff here with token
}
I have initialized the credentials provider per this AWS Developer Guide. I'm not sure if it worked, and how to check. I can't seem to find any documentation on how to use Cognito with Swift. I'm running it as a unit test, and the test passes and the line print("identityId", identityId) outputs:
identityId <AWSTask: 0x17d5fde0; completed = NO; cancelled = NO; faulted = NO;>
However, during debug the property identityProvider.identityId is nil.
Here are my files:
// MyAuth.swift
import Foundation
import AWSCognito
class MyAuth {
func getUnauthCognitoId()->Bool {
let identityProvider = MyIdentityProvider()
let credentialsProvider = AWSCognitoCredentialsProvider(regionType: AWSRegionType.USEast1, identityProvider: identityProvider, unauthRoleArn: Constants.ARNUnauth.value, authRoleArn: Constants.ARNAuth.value)
let defaultServiceConfiguration = AWSServiceConfiguration(region: .USEast1, credentialsProvider: credentialsProvider)
AWSServiceManager.defaultServiceManager().defaultServiceConfiguration = defaultServiceConfiguration
if let identityId = identityProvider.getIdentityId() {
print("identityId", identityId)
return true
} else {
return false
}
}
}
And
// MyIdentityProvider.swift
import Foundation
import AWSCognito
class MyIdentityProvider: AWSAbstractCognitoIdentityProvider {
var _token: String!
var _logins: [ NSObject : AnyObject ]!
// Header stuff you may not need but I use for auth with my server
/*let acceptHeader = "application/vnd.exampleapp-api+json;version=1;"
let authHeader = "Token token="
let userDefaults = NSUserDefaults.standardUserDefaults()
let authToken = self.userDefaults.valueForKey("authentication_token") as String*/
// End point that my server gives amazon identityId and tokens to authorized users
let url = "https://api.myapp.com/api/amazon_id/"
func authenticatedWithProvider()->Bool {
if let logins = _logins {
return logins["ProviderName"] == nil
}
else {
return false
}
}
override var token: String {
get {
return _token
}
}
override var logins: [ NSObject : AnyObject ]! {
get {
return _logins
}
set {
_logins = newValue
}
}
override func getIdentityId() -> AWSTask! {
if self.identityId != nil {
return AWSTask(result: self.identityId)
}
else if(!self.authenticatedWithProvider()) {
return super.getIdentityId()
}
else{
return AWSTask(result: nil).continueWithBlock({ (task) -> AnyObject! in
if self.identityId == nil {
return self.refresh()
}
return AWSTask(result: self.identityId)
})
}
}
override func refresh() -> AWSTask! {
let task = AWSTaskCompletionSource()
if(!self.authenticatedWithProvider()) {
return super.getIdentityId()
}
else {
// TODO: Authenticate with developer
return task.task
}
/*let request = AFHTTPRequestOperationManager()
request.requestSerializer.setValue(self.acceptHeader, forHTTPHeaderField: "ACCEPT")
request.requestSerializer.setValue(self.authHeader+authToken, forHTTPHeaderField: "AUTHORIZATION")
request.GET(self.url, parameters: nil, success: { (request: AFHTTPRequestOperation!, response: AnyObject!) -> Void in
// The following 3 lines are required as referenced here: http://stackoverflow.com/a/26741208/535363
var tmp = NSMutableDictionary()
tmp.setObject("temp", forKey: "ExampleApp")
self.logins = tmp
// Get the properties from my server response
let properties: NSDictionary = response.objectForKey("properties") as NSDictionary
let amazonId = properties.objectForKey("amazon_identity") as String
let amazonToken = properties.objectForKey("token") as String
// Set the identityId and token for the ExampleAppIdentityProvider
self.identityId = amazonId
self._token = amazonToken
task.setResult(response)
}, failure: { (request: AFHTTPRequestOperation!, error: NSError!) -> Void in
task.setError(error)
})*/
return task.task
}
}
And
import XCTest
#testable import My
class MyTests: XCTestCase {
override func setUp() {
super.setUp()
// Put setup code here. This method is called before the invocation of each test method in the class.
}
override func tearDown() {
// Put teardown code here. This method is called after the invocation of each test method in the class.
super.tearDown()
}
func testExample() {
// This is an example of a functional test case.
// Use XCTAssert and related functions to verify your tests produce the correct results.
}
func testPerformanceExample() {
// This is an example of a performance test case.
self.measureBlock {
// Put the code you want to measure the time of here.
}
}
func testGetUnauthCognitoId() {
let myAuth = MyAuth()
XCTAssertTrue(myAuth.getUnauthCognitoId())
}
}
It turns out that if you create a default service configuration within the application:didFinishLaunchingWithOptions: application delegate method in your app delegate file as described here:
let credentialsProvider = AWSCognitoCredentialsProvider(
regionType: AWSRegionType.USEast1, identityPoolId: cognitoIdentityPoolId)
let defaultServiceConfiguration = AWSServiceConfiguration(
region: AWSRegionType.USEast1, credentialsProvider: credentialsProvider)
AWSServiceManager.defaultServiceManager().defaultServiceConfiguration = defaultServiceConfiguration
The SDK will use an unauthenticated identity whenever you try to use any of the AWS services, and you don't necessarily need to create a cognitoIdentity object.
getIdentityId returns an AWSTask. Since AWSTask is essentially BFTask with a different name, you can get the identityId using the continueWithBlock syntax shown on the BFTask page. Something like:
credentialProvider.getIdentityId().continueWithBlock {
(task: AWSTask!) -> AWSTask in
if task.error() {
// failed to retrieve identityId.
} else {
print("identityId", task.result())
}