How to add sessionDelegate option on SocketManager's config Swift - swift

I'm tryng to connect to a self signed SSL URL.
But when I add sessionDelegate as option it's not working.
import Foundation
import SocketIO
import UIKit
class SocketM: NSObject, URLSessionDelegate {
static var manager = SocketManager(socketURL: URL(string:"https://localhost:8000")!, config: [.log(true), .secure(true), .selfSigned(true), .sessionDelegate(self)])
func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
let protectionSpace = challenge.protectionSpace
guard protectionSpace.authenticationMethod ==
NSURLAuthenticationMethodServerTrust,
protectionSpace.host.contains(Services_Routes.host) else {
completionHandler(.performDefaultHandling, nil)
return
}
guard let serverTrust = protectionSpace.serverTrust else {
completionHandler(.performDefaultHandling, nil)
return
}
let credential = URLCredential(trust: serverTrust)
completionHandler(.useCredential, credential)
}
It returns me
Type 'Any' has no member 'sessionDelegate'
When I try :
SocketIOClientOption.sessionDelegate(self)
Type '(SocketM) -> () -> SocketM' does not conform to protocol 'URLSessionDelegate'
Can someone explain me the problem?
Thanks !

You are creating static variable and passing delegate as "self", you can't use self before initialising object.
If you don't need static object of manager then you can write code as
class SocketM: NSObject, URLSessionDelegate {
var manager: SocketManager?
override init() {
super.init()
manager = SocketManager(socketURL: URL(string:"https://localhost:8000")!, config: [.log(true), .reconnects(true), .selfSigned(true), .sessionDelegate(self)])
}
}
And If you want static manager the
class SocketM: NSObject, URLSessionDelegate {
static var manager = SocketManager(socketURL: URL(string:"https://localhost:8000")!, config: [.log(true), .reconnects(true), .selfSigned(true)])
override init() {
super.init()
SocketM.manager.config.insert(.sessionDelegate(self))
}
}

Related

How to bridge a Swift Promise to React Native

I'm integrating an iOS native SDK into React-Native. There's a function called SDK.getCardData which I want to use from RN. My first attempt was to call resolve and reject inside the closure:
import Foundation
import SDK
#objc(SwiftComponentManager)
class SwiftComponentManager: NSObject {
#objc
func getCardData(_ resolve: RCTPromiseResolveBlock, rejecter reject: RCTPromiseRejectBlock) -> Void {
let cardId: String = "test"
let secret: String = "test"
SDK.getCardData(cardId, secret: secret) { (cardData, error) in
if (error != nil) {
reject(String(format: "Card data request failed: %#", error!.localizedDescription))
} else {
let pan = cardData!.pan
let cvv = cardData!.cvv
resolve(String(format: "Card data fetched successfully, pan: %#, cvv: %#", pan, cvv))
}
}
}
#objc func testMethod() -> Void {
print("This Does appear")
}
}
Unfortunately this throws the following error: escaping closure captures non-escaping parameter 'resolve'.
Second attempt:
import Foundation
import SDK
import Promises
#objc(SwiftComponentManager)
class SwiftComponentManager: NSObject {
#objc
func getCardData(_ resolve: RCTPromiseResolveBlock, rejecter reject: RCTPromiseRejectBlock) -> Promise<CardData?> {
let cardId: String = "test"
let secret: String = "test"
let promise = Promise<CardData?>()
SDK.getCardData(cardId, secret: secret) { (cardData, error) in
if (error != nil) {
promise.reject(error)
} else {
let pan = cardData!.pan
let cvv = cardData!.cvv
promise.resolve(cardData)
}
}
return promise
}
#objc func testMethod() -> Void {
print("This Does appear")
}
}
How to call the resolve & reject properly? It is important for the function to return Void, more here.
Found the answer, Just had to add #escaping to the arguments:
#objc func fling(_ options: NSDictionary, resolver resolve: #escaping RCTPromiseResolveBlock, rejecter reject: #escaping RCTPromiseRejectBlock) -> Void {
...

Passing Data from Delegate Swift Class to EnvironmentObject in SwiftUI Struct

How do I pass incoming data from a method triggered by a delegate in a Swift class to an EnvironmentObject?
I am aware that for this to work my Swift class needs to be called/initialized from the SwiftUI struct (be a child of the parent SwiftUI struct). However, I initialise my Swift class in the ExtensionDelegate of the Apple Watch app. I would like to see the UI Text element change when the name is updated.
The following code runs on the Apple Watch:
class User: ObservableObject {
#Published var id: UUID?
#Published var name: String?
}
//SwiftUI struct
struct UI: View {
#EnvironmentObject var userEnv: User
var body: some View {
Text(userEnv.name)
}
}
// Swift class
class WatchConnectivityProvider: NSObject, WCSessionDelegate {
static let shared = WatchConnectivityProvider()
private let session: WCSession
init(session: WCSession = .default) {
self.session = session
super.init()
}
func activateSession() {
if WCSession.isSupported() {
session.delegate = self
session.activate()
}
}
//This func gets triggered when data is sent from the iPhone
func session(_ session: WCSession, didReceiveMessage message: [String : Any], replyHandler: #escaping ([String : Any]) -> Void) {
let list = message["list"]
let jsonDecoder = JSONDecoder()
if let data = try? jsonDecoder.decode(User.self, from: list as! Data) {
// !!! This is where I would like to update the EnvironmentObject userEnv !!!
// What is the best way to do this? Remember, this class has already been initialised within the ExtensionDelegate.
}
}
}
//ExtensionDelegate of Apple Watch app, initialising the WatchConnectivityProvider
class ExtensionDelegate: NSObject, WKExtensionDelegate {
func applicationDidFinishLaunching() {
// Perform any final initialization of your application.
WatchConnectivityProvider.shared.activateSession()
}
}
Dependency Injection
One of the solutions could be to store the reference to your #EnvironmentObject globally, eg. in some dependency container.
enum Dependencies {
struct Name: Equatable {
let rawValue: String
static let `default` = Name(rawValue: "__default__")
static func == (lhs: Name, rhs: Name) -> Bool { lhs.rawValue == rhs.rawValue }
}
final class Container {
private var dependencies: [(key: Dependencies.Name, value: Any)] = []
static let `default` = Container()
func register(_ dependency: Any, for key: Dependencies.Name = .default) {
dependencies.append((key: key, value: dependency))
}
func resolve<T>(_ key: Dependencies.Name = .default) -> T {
return (dependencies
.filter { (dependencyTuple) -> Bool in
return dependencyTuple.key == key
&& dependencyTuple.value is T
}
.first)?.value as! T
}
}
}
Then you create your object like this:
Dependencies.Container.default.register(User())
And you can access it from anywhere in your code:
let user: User = Dependencies.Container.default.resolve()
user.modify()
A more detailed explanation of Dependency Injection for Swift can be found here.
Singleton
Alternatively you can use standard Singleton pattern to make your User data available globally. A more detailed explanation can be found here.
Final thoughts
Clean Architecture for SwiftUI is a good example (in my opinion at least) of how to write an iOS app in the clean way. It's a little bit complicated, but you can pick up some parts.
Here bad Jumbo code:
class ExtensionDelegate: ObservableObject, NSObject, WCSessionDelegate, WKExtensionDelegate {
var session: WCSession?
#Published var model = Model
func session(_ session: WCSession, didReceiveMessage message: [String : Any], replyHandler: #escaping ([String : Any]) -> Void) {
print(#function)
var replyValues = Dictionary<String, Any>()
replyValues["status"] = "failed"
// 2442 Bytes
if let data = message["data"] as? Data {
// push that work back to the main thread
DispatchQueue.main.async {
self.model = try? JSONDecoder().decode(Model.self, from: data)
}
if let vm = vm {
replyValues["status"] = "ok"
replyValues["id"] = vm.id.uuidString
}
}
replyHandler(replyValues)
}
...

Trying to make Google SignIn helper but 'uiDelegate must either be a |UIViewController|

I'm trying to make Google social helper is NSObject outside of ViewController. I'm present SignIn using UIApplication extension in root ViewController, but I still have an error.
'uiDelegate must either be a |UIViewController| or implement the |signIn:presentViewController:| and |signIn:dismissViewController:| methods from |GIDSignInUIDelegate|.'
This my social helper object
import GoogleSignIn
class GidHelper: NSObject, GIDSignInUIDelegate, GIDSignInDelegate {
private let succesAuth: (String, String, String, String) -> ()
private let failedAuth: (Error) -> ()
init(succesAuth: #escaping (String, String, String, String) -> (), failedAuth: #escaping (Error) -> ()) {
self.succesAuth = succesAuth
self.failedAuth = failedAuth
super.init()
GIDSignIn.sharedInstance().uiDelegate = self
GIDSignIn.sharedInstance().delegate = self
}
func openGidAuthorization() {
GIDSignIn.sharedInstance().signIn()
}
func gidLogout() {
GIDSignIn.sharedInstance().signOut()
}
// Present a view that prompts the user to sign in with Google
private func signIn(signIn: GIDSignIn!,
presentViewController viewController: UIViewController!) {
UIApplication.topViewController()?.present(viewController, animated: true, completion: nil)
}
private func signIn(signIn: GIDSignIn!,
dismissViewController viewController: UIViewController!) {
UIApplication.topViewController()?.dismiss(animated: true, completion: nil)
}
func sign(_ signIn: GIDSignIn!, didSignInFor user: GIDGoogleUser!, withError error: Error!) {
if let error = error {
print("\(error.localizedDescription)")
self.failedAuth(error)
} else {
let userId = user.userID
let accessToken = user.authentication.accessToken
let userAvatarUrl = user.profile.imageURL(withDimension: 100)?.absoluteString
let email = user.profile.email
self.succesAuth(accessToken!, userId!, email!, userAvatarUrl!)
}
}
}
My UIApplication extension:
import Foundation
import UIKit
extension UIApplication {
class func topViewController(controller: UIViewController? =
UIApplication.shared.keyWindow?.rootViewController) -> UIViewController? {
if let navigationController = controller as? UINavigationController {
return topViewController(controller: navigationController.visibleViewController)
}
if let tabController = controller as? UITabBarController {
if let selected = tabController.selectedViewController {
return topViewController(controller: selected)
}
}
if let presented = controller?.presentedViewController {
return topViewController(controller: presented)
}
return controller
}
}
Your implementation of GidHelper class look very weird starting from the init and finishing with this extension:(. I recommend you to create a service (let it be your GoogleLoginService), make it a singleton and create also a NavigationService instead of this mess with extension. Here is some ideas how to implement this:
import GoogleSignIn
final class GoogleLoginService: NSObject {
typealias SignInResponse = (_ user: User?, _ error: Error?) -> ()
static let sharedInstance = GoogleLoginService()
private var presenter: UIViewController?
private var singInCompletion: SignInResponse?
//Call next function in appDelegate: didFinishLaunchingWithOptions
#discardableResult func registerInApplication(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
if let url = Bundle.main.url(forResource: "GoogleService-Info", withExtension: "plist"),
let data = try? Data(contentsOf: url) {
let dictionary = try? PropertyListSerialization.propertyList(from: data, options: [], format: nil) as? [String : AnyObject]
if let clientID = dictionary??["CLIENT_ID"] {
GIDSignIn.sharedInstance().clientID = clientID as? String
}
}
GIDSignIn.sharedInstance().delegate = self
GIDSignIn.sharedInstance().uiDelegate = self
return true
}
// Call this function in AppDelegate: open url
#discardableResult func handleURLIn(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any]) -> Bool {
return GIDSignIn.sharedInstance().handle(url, sourceApplication: options[UIApplication.OpenURLOptionsKey.sourceApplication] as? String, annotation: options[UIApplication.OpenURLOptionsKey.annotation])
}
// MARK: - UserManagement
func signIn(_ controller: UIViewController, completion: SignInResponse?) {
singInCompletion = completion
presenter = controller
GIDSignIn.sharedInstance().signIn()
}
func signOut() {
GIDSignIn.sharedInstance().signOut()
}
func isLoggedIn() -> Bool {
return GIDSignIn.sharedInstance().hasAuthInKeychain()
}
}
extension GoogleLoginService: GIDSignInDelegate {
// MARK: - GIDSignInDelegate
func sign(_ signIn: GIDSignIn!, didSignInFor user: GIDGoogleUser!, withError error: Error!) {
if let error = error {
self.singInCompletion?(nil, error)
return
}
guard let authentication = user.authentication else {
self.singInCompletion?(nil, error)
return
}
let googleUserObj = User(name: user.profile.name) // <-- You can get your user data
}
}
extension GoogleLoginService: GIDSignInUIDelegate {
// MARK: - GIDSignInUIDelegate
func sign(_ signIn: GIDSignIn!, present viewController: UIViewController!) {
presenter?.present(viewController, animated: true, completion: nil)
}
func sign(_ signIn: GIDSignIn!, dismiss viewController: UIViewController!) {
presenter = nil
viewController.dismiss(animated: true, completion: nil)
}
}
Now using isLoggedIn method you can save a result in lets say UserDefaults and using NavigationService check if user is logged in or not and go to a proper view controller (as example have a look on the following method of NavigavionService:
func presentCurrentUserUI() {
// next line is extension on UserDefaults which keep Bool value - result of logging procedure
if UserDefaults().isLoggedIn {
let homeViewController = UIStoryboard(name: StoryboardName.main, bundle: nil).instantiateInitialViewController()
self.window?.rootViewController = homeViewController
} else {
let loginViewController = UIStoryboard(name: StoryboardName.login, bundle: nil).instantiateInitialViewController()
self.window?.rootViewController = loginViewController
}
self.window?.makeKeyAndVisible()
}
Hope it will help you to refactor your code and avoid this error:) Good luck!

Mock third party classes (Firebase) in Swift

I'm trying to unit test a class of my own which is calling a method on a third party class:
FIRAuth.auth()?.signInAnonymously() { (user, error) in
//
}
I'm using protocol based dependency injection to achieve this:
protocol FIRAuthProtocol {
func signInAnonymously(completion: FIRAuthResultCallback?)
}
extension FIRAuth: FIRAuthProtocol {}
class MyClass {
private var firAuth: FIRAuthProtocol
init(firAuth: FIRAuthProtocol) {
self.firAuth = firAuth
}
func signIn() {
firAuth.signInAnonymously() { (user, error) in
//
}
}
}
class MockFIRAuth: FIRAuthProtocol {
var signInAnonymouslyCalled = false
func signInAnonymously(completion: FIRAuthResultCallback? = nil) {
signInAnonymouslyCalled = true
}
}
class MyClassSpec: QuickSpec {
override func spec() {
describe("MyClass") {
describe(".signIn()") {
it("should call signInAnonymously() on firAuth") {
let mockFIRAuth = MockFIRAuth()
let myClass = MyClass(firAuth: mockFIRAuth)
expect(mockFIRAuth.signInAnonymouslyCalled).to(beFalse())
myClass.signIn()
expect(mockFIRAuth.signInAnonymouslyCalled).to(beTrue())
}
}
}
}
}
So far so good!
Now, I'd like my mockFIRAuth to return an instance of FIRUser.
Here's my issue: I can't create an instance of FIRUser myself.
FYI: public typealias FIRAuthResultCallback = (FIRUser?, Error?) -> Swift.Void
If found this great article which explains how to make a method on a third party class return a protocol instead of a type. http://masilotti.com/testing-nsurlsession-input/
Maybe my situation is different than the article's, but here's my shot at this:
I've defined a FIRUserProtocol:
protocol FIRUserProtocol {
var uid: String { get }
}
extension FIRUser: FIRUserProtocol {}
I've updated my FIRAuthProtocol to call the completion handler with FIRUserProtocol instead of FIRUser:
protocol FIRAuthProtocol {
func signInAnonymously(completion: ((FIRUserProtocol?, Error?) -> Void)?)
}
I've updated my FIRAuth extension to support the modified protocol. My newly defined method calls the default implementation of signInAnonymously:
extension FIRAuth: FIRAuthProtocol {
func signInAnonymously(completion: ((FIRUserProtocol?, Error?) -> Void)? = nil) {
signInAnonymously(completion: completion)
}
}
Finally, I've updated MockFIRAuth to support the modified protocol:
class MockFIRAuth: FIRAuthProtocol {
var signInAnonymouslyCalled = false
func signInAnonymously(completion: ((FIRUserProtocol?, Error?) -> Void)? = nil) {
signInAnonymouslyCalled = true
}
}
Now, when I run my test everything comes to a crashing halt:
Thread 1: EXC_BAD_ACCESS (code=2, address=0x7fff586a2ff8)
Please advice!
Update
After renaming the completion argument label in my FIRAuthProtocol's method everything seems to work as expected:
protocol FIRAuthProtocol {
func signInAnonymously(completionWithProtocol: ((FIRUserProtocol?, Error?) -> Void)?)
}
extension FIRAuth: FIRAuthProtocol {
func signInAnonymously(completionWithProtocol: ((FIRUserProtocol?, Error?) -> Void)? = nil) {
signInAnonymously(completion: completionWithProtocol)
}
}
This solves my issue for now, but I'd still like to know why my first attempt was unsuccessful. Does this mean that the two methods with different parameter types in their closures can't be told apart, which was causing my app to crash?
I've finally found an elegant way to solve this.
protocol FIRAuthProtocol {
func signInAnonymously(completion: ((FIRUserProtocol?, Error?) -> Void)?)
}
extension FIRAuth: FIRAuthProtocol {
func signInAnonymously(completion: ((FIRUserProtocol?, Error?) -> Void)? = nil) {
let completion = completion as FIRAuthResultCallback?
signInAnonymously(completion: completion)
}
}
This way, there's no need to alter function names or argument labels.

Class as a delegate placeholder for a struct

I have a simple struct where I use NSURLSession to get the data from the web. I would like to extend it to provide progress of downloading. I cannot use struct as a NSURLSession delegate because it needs to be a NSObject so I have created simple class where I pass to NSURLSession delegate. This is short version of my code:
struct Resource {
private var progressDelegate: ProgressSessionDelegate?
private var session: NSURLSession!
init() {
self.progressDelegate = ProgressSessionDelegate()
let configuration = NSURLSessionConfiguration.defaultSessionConfiguration()
session = NSURLSession(configuration: configuration, delegate: self.progressDelegate!, delegateQueue: nil)
}
func loadAsynchronous(callback: A? -> ()) {
session.dataTaskWithURL(resourceURL) {
data, response, error in
let json = data.flatMap {
try? NSJSONSerialization.JSONObjectWithData($0, options: NSJSONReadingOptions())
}
callback(mycalback)
}.resume()
}
}
and thats my cass which should handle the delegate methods:
class ProgressSessionDelegate: NSObject, NSURLSessionDelegate, NSURLSessionDataDelegate {
var expectedContentLength = 0
var currentLength = 0
override init() {
super.init()
}
func URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask, didReceiveResponse response: NSURLResponse, completionHandler: (NSURLSessionResponseDisposition) -> Void) {
expectedContentLength = Int(response.expectedContentLength)
print("0: \(expectedContentLength)")
completionHandler(NSURLSessionResponseDisposition.Allow)
}
func URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask, didReceiveData data: NSData) {
self.currentLength += data.length
let percentageDownloaded = Float(self.currentLength) / Float(self.expectedContentLength)
print("1: \(percentageDownloaded)")
}
func URLSession(session: NSURLSession, task: NSURLSessionTask, didCompleteWithError error: NSError?) {
print("Downloaded")
}
}
The problem is that the delegate methods are not called.
When I move the code from Resource and ProgressSessionDelegate struct to my ViewController and set up ViewController as a NSURLSession delegate all is working fine. I think the problem is in a way how the class is stored is struct.
Any help would be most appreciated.