Start WKExtendedRuntimeSession (WKExtendedRuntimeObject was dealloced while running) - ios

I have a problem when I try to start WKExtendedRuntimeSession
-[WKExtendedRuntimeSession dealloc]:285: WKExtendedRuntimeObject was dealloced while running. Invalidating the session 46EB2DE0-311C-41D0-93BE-46FE744B685A
class SessionCoordinator: NSObject, WKExtendedRuntimeSessionDelegate {
let runtimeSession = WKExtendedRuntimeSession();
override init() {
super.init()
runtimeSession.delegate = self;
runtimeSession.start();
}
...
}

I ran across the same issue. Here is my solution.
Create a simple singleton class to manage WKExtendedRuntimeSession
import Foundation
import SwiftUI
class ExtendedRunTime: NSObject, WKExtendedRuntimeSessionDelegate {
static let shared = ExtendedRunTime()
let session: WKExtendedRuntimeSession
override init() {
// Create the session object.
session = WKExtendedRuntimeSession()
super.init()
// Assign the delegate.
session.delegate = self
}
func extendedRuntimeSession(_ extendedRuntimeSession: WKExtendedRuntimeSession, didInvalidateWith reason: WKExtendedRuntimeSessionInvalidationReason, error: Error?) {
print("didInvalidateWithReason: \(reason)")
}
func extendedRuntimeSessionDidStart(_ extendedRuntimeSession: WKExtendedRuntimeSession) {
print("extendedRuntimeSessionDidStart")
}
func extendedRuntimeSessionWillExpire(_ extendedRuntimeSession: WKExtendedRuntimeSession) {
print("extendedRuntimeSessionWillExpire")
}
func start() {
session.start()
}
func stop() {
session.invalidate()
}
}
Init the singleton. You can do this in .appear() or in your WKApplicationDelegate applicationDidBecomeActive()
_ = ExtendedRunTime.shared
Start the session where it is needed
ExtendedRunTime.shared.start()
Make sure to stop it when finished
ExtendedRunTime.shared.stop()

Related

Protocol-Delegate pattern not notifying View Controller

My Model saves data to Firestore. Once that data is saved, I'd like it to alert my ViewController so that a function can be called. However, nothing is being passed to my ViewController.
This is my Model:
protocol ProtocolModel {
func wasDataSavedSuccessfully(dataSavedSuccessfully:Bool)
}
class Model {
var delegate:ProtocolModel?
func createUserAddedRecipe(
docId:String,
completion: #escaping (Recipe?) -> Void) {
let db = Firestore.firestore()
do {
try db.collection("userFavourites").document(currentUserId).collection("userRecipes").document(docId).setData(from: recipe) { (error) in
print("Data Saved Successfully") // THIS OUTPUTS TO THE CONSOLE
// Notify delegate that data was saved to Firestore
self.delegate?.wasDataSavedSuccessfully(dataSavedSuccessfully: true)
}
}
catch {
print("Error \(error)")
}
}
}
The print("Data Saved Successfully") outputs to the console, but the delegate method right below it doesn't get called.
And this is my ViewController:
class ViewController: UIViewController {
private var model = Model()
override func viewDidLoad() {
super.viewDidLoad()
model.delegate = self
}
}
extension ViewController: ProtocolModel {
func wasDataSavedSuccessfully(dataSavedSuccessfully: Bool) {
if dataSavedSuccessfully == true {
print("Result is true.")
}
else {
print("Result is false.")
}
print("Protocol-Delegate Pattern Works")
}
}
Is there something I'm missing from this pattern? I haven't been able to notice anything different in the articles I've reviewed.
So I test your code and simulate something like that
import UIKit
protocol ProtocolModel {
func wasDataSavedSuccessfully(dataSavedSuccessfully:Bool)
}
class Model {
var delegate:ProtocolModel?
// I use this timer for simulate that firebase store data every 3 seconds for example
var timer: Timer?
func createUserAddedRecipe(
docId:String) {
timer = Timer.scheduledTimer(withTimeInterval: 3, repeats: true, block: { _ in
self.delegate?.wasDataSavedSuccessfully(dataSavedSuccessfully: true)
})
}
}
class NavigationController: UINavigationController {
var model = Model()
override func viewDidLoad() {
super.viewDidLoad()
model.delegate = self
// Call this method to register for network notification
model.createUserAddedRecipe(docId: "exampleId")
}
}
extension NavigationController: ProtocolModel {
func wasDataSavedSuccessfully(dataSavedSuccessfully: Bool) {
print(#function)
}
}
so you can see the result as image below, my delegate update controller that conform to that protocol.

NSObject Delegate Methods are not being called

I'm building a NFC reading app. I want all my NFC implementation to be inside a separate class called NFCReader, so I can call it every time the user taps the readNFC button in my MainVC. Here is my current MainVC code:
class MainVC: ViewController {
#IBOutlet weak var readNFCButton: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
addTargetToReadNFCButton()
}
private func addTargetToReadNFCButton() {
readNFCButton.addTarget(self, action: #selector(readNFCButtonTouched), for: .touchUpInside)
}
#objc func readNFCButtonTouched() {
NFCReader().beginNFCReaderSession()
}
}
Here is my NFCReader class code:
class NFCReader: NSObject {
var session: NFCReaderSession?
func beginNFCReaderSession() {
session = NFCNDEFReaderSession(delegate: self, queue: nil, invalidateAfterFirstRead: true)
session!.alertMessage = "Hold your device near your tag for scanning"
session!.begin()
}
}
extension NFCReader: NFCNDEFReaderSessionDelegate {
func readerSessionDidBecomeActive(_ session: NFCNDEFReaderSession) {
print("readerSessionDidBecomeActive")
}
func readerSession(_ session: NFCNDEFReaderSession, didDetectNDEFs messages: [NFCNDEFMessage]) {
print("didDetectTags")
}
func readerSession(_ session: NFCNDEFReaderSession, didInvalidateWithError error: Error) {
print(error.localizedDescription)
}
}
The problem is that NFCReader's delegate functions are never being called. If I put the code from my NFCReader class into my MainVC class and call it directly, delegate functions are being called every time I scan my NFC tags. This code works:
class MainVC: ViewController {
#IBOutlet weak var readNFCButton: UIButton!
var session: NFCReaderSession?
override func viewDidLoad() {
super.viewDidLoad()
addTargetToReadNFCButton()
}
private func addTargetToReadNFCButton() {
readNFCButton.addTarget(self, action: #selector(readNFCButtonTouched), for: .touchUpInside)
}
#objc func readNFCButtonTouched() {
beginNFCReaderSession()
}
func beginNFCReaderSession() {
session = NFCNDEFReaderSession(delegate: self, queue: nil, invalidateAfterFirstRead: true)
session!.alertMessage = "Hold your device near your tag for scanning"
session!.begin()
}
}
extension MainVC: NFCNDEFReaderSessionDelegate {
func readerSessionDidBecomeActive(_ session: NFCNDEFReaderSession) {
print("readerSessionDidBecomeActive")
}
func readerSession(_ session: NFCNDEFReaderSession, didDetectNDEFs messages: [NFCNDEFMessage]) {
print("didDetectTags")
}
func readerSession(_ session: NFCNDEFReaderSession, didInvalidateWithError error: Error) {
print(error.localizedDescription)
}
}
Is there any way to make delegate functions work from my NFCReader class, not from MainVC? What's the mistake in my separate implementation?
Any help is appreciated!
EDITED:
I solved it by adding NFCReader property to main MainVC class:
let nfcReader = NFCReader()
And calling it like this:
#objc func readNFCButtonTouched() {
nfcReader.beginNFCReaderSession()
}
Thank you for your help!
Your NFCReader is being initialized, and then almost immediately deinitialized because you didn’t store any references to it (which is necessary to keep the object alive).
You can confirm this by putting a breakpoint in the deinit of your NFCReader class

Add a generic delegate to a base class in Swift

Ideally, I want to create a BaseViewController class that takes in a protocol type (of a delegate) and have a weak variable as the delegate. Something like this:
class BaseViewController<Delegate: AnyObject> {
weak var delegate: Delegate?
init(delegate: Delegate) {
self.delegate = delegate
super.init(...)
}
}
And then inherit from a view controller like so:
protocol MyDelegate: AnyObject {
func funcA()
func funcB()
}
class SomeViewController: BaseViewController<MyDelegate> {
func doSomething() {
delegate?.funcA()
}
}
This doesn't work as the compiler complains:
'BaseViewController' requires that 'MyDelegate' be a class type
How can I work this around to achieve what I need?
Thanks in advance :)
Thats because in swift protocols doesn't confirm to them selves, you can't use "MyProtocol" as concrete type confirming to protocol "MyDelegate"
What you can rather do is
protocol MyDelegate: AnyObject {
func funcA()
func funcB()
}
class BaseViewController<Delegate: MyDelegate> {
weak var delegate: Delegate?
init(delegate: Delegate) {
self.delegate = delegate
super.init(...)
//keeping OPs code as is
}
}
class SomeOtherDelegateClass: MyDelegate {
func funcA() {
//some code here
}
func funcB() {
//some code here
}
}
class SomeViewController: BaseViewController<SomeOtherDelegateClass> {
func doSomething() {
self.delegate?.funcA()
}
}
EDIT 1:
As OP mentioned in comment, he is trying to introduce a generic property in BaseViewController that will simply hold a weak reference to any instance whose class is decided/declared by Child classes of BaseViewController using generics, I am simplifying the above answer a bit
Try this
protocol MyDelegate {
func funcA()
func funcB()
}
class BaseViewController<Delegate> where Delegate: AnyObject {
weak var delegate: Delegate?
init(delegate: Delegate) {
self.delegate = delegate
super.init(...)
//keeping OPs code as is
}
}
class SomeOtherDelegateClass: MyDelegate {
func funcA() {
//some code here
}
func funcB() {
//some code here
}
}
class SomeViewController: BaseViewController<SomeOtherDelegateClass> {
func doSomething() {
self.delegate?.funcA()
}
}
protocol MyDelegate2 {
func funcABCD()
}
class SomeOtherDelegateClass2: MyDelegate2 {
func funcABCD() {
//some code here
}
}
class SomeViewController2: BaseViewController<SomeOtherDelegateClass2> {
func doSomething() {
self.delegate?.funcABCD()
}
}
TBH, I really dont see much of benefit of this design! Probably you need to revisit the code structure and see if you can come up with better code structure :)
You should set your delegate as a constraint for the generic type T in BaseViewController:
protocol MyDelegate: AnyObject {
func funcA()
func funcB()
}
class Delegated1: MyDelegate {
func funcA() { print("A1") }
func funcB() {}
}
class Delegated2: MyDelegate {
func funcA() { print("A2") }
func funcB() {}
}
class BaseViewController<T: MyDelegate>: UIViewController {
var delegate: T?
func doSomething() {
delegate?.funcA()
}
}
class SomeViewController1: BaseViewController<Delegated1> {}
class SomeViewController2: BaseViewController<Delegated2> {}
class TestClass {
let viewController1: SomeViewController1 = {
let viewController = SomeViewController1(nibName: nil, bundle: nil)
viewController.delegate = .init()
return viewController
}()
let viewController2: SomeViewController2 = {
let viewController = SomeViewController2(nibName: nil, bundle: nil)
viewController.delegate = .init()
return viewController
}()
// prints:
// A1
// A2
func myFunc() {
viewController1.doSomething()
viewController2.doSomething()
}
}

Check if ARSession is running (ARKit)

I have an ARSCNView that can occasionally pause its session depending on the situation. Is there a way to check if its session is running?
Something like this:
class myARView: ARSCNView {
...
func foo() {
if(session.running) {
// do stuff
}
}
...
}
At this moment, it seems there isn't a way to check if the session is running, by ARSession object itself. However, by implementing ARSCNViewDelegate, you can get notified whenever your session is interrupted or the interruption ends.
One way to achieve your goal is to set a boolean and update it whenever you pause/resume the session, and check its value in your functions.
class ViewController: UIViewController, ARSCNViewDelegate {
var isSessionRunning: Bool
func foo() {
if self.isSessionRunning {
// do stuff
}
}
func pauseSession() {
self.sceneView.session.pause()
self.isSessionRunning = false
}
func runSession() {
let configuration = ARWorldTrackingConfiguration()
sceneView.session.run(configuration)
self.isSessionRunning = true
}
// ARSCNViewDelegate:
func sessionWasInterrupted(_ session: ARSession) {
self.isSessionRunning = false
}
func sessionInterruptionEnded(_ session: ARSession) {
self.isSessionRunning = true
}
}

Delegated functions not working outside of UIViewController

I'm using the Startscream Websocket framework. Everything works fine as long as I keep all of the code in a UIViewController as seen here. But as soon as a create a wrapper class for Startscream all of the delegated functions stop working. Also my local websocket server is not getting a connection.
How can I get the code working inside a wrapper class?
MyService.swift:
import Starscream
public class MyService: WebSocketDelegate {
var socket = WebSocket(url: URL(string: "ws://localhost:3900/websocket")!)
func connect() {
socket.delegate = self
socket.connect()
print("Connecting")
}
// MARK: Websocket Delegate Methods.
public func websocketDidConnect(socket: WebSocket) {
print("websocket is connected")
}
public func websocketDidDisconnect(socket: WebSocket, error: NSError?) {
if let e = error {
print("websocket is disconnected: \(e.localizedDescription)")
} else {
print("websocket disconnected")
}
}
public func websocketDidReceiveMessage(socket: WebSocket, text: String) {
print("Received text: \(text)")
}
public func websocketDidReceiveData(socket: WebSocket, data: Data) {
print("Received data: \(data.count)")
}
// MARK: Write Text Action
#IBAction func writeText(_ sender: UIBarButtonItem) {
socket.write(string: "hello there!")
}
// MARK: Disconnect Action
#IBAction func disconnect(_ sender: UIBarButtonItem) {
if socket.isConnected {
sender.title = "Connect"
socket.disconnect()
} else {
sender.title = "Disconnect"
socket.connect()
}
}
}
ViewController.swift:
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let service = MyService()
service.connect()
}
}
The reference to Service in ViewController was not stored anywhere so as soon as the function was run it was cleaned up. This is how I fixed it:
class ViewController: UIViewController {
var service = MyService()
override func viewDidLoad() {
super.viewDidLoad()
service.connect()
}
...
I'm not sure what is wrong with your code, it can be an endpoint issue, or WS is not allocated somehow. I have same code with Starscream working in Swift 3, here is the main part of my class.
class ConnectionManager {
private var savedSocket: WebSocket?
fileprivate var socket: WebSocket {
if let saved = savedSocket {
return saved
}
let wsURL = URL(string: UserDefaultsManager.wsURLString)!
savedSocket = WebSocket(url: wsURL)
return savedSocket!
}
func startSession() {
if (socket.isConnected) { return }
socket.headers = headers
socket.delegate = self
socket.connect()
}
func endSession() {
if (socket.isConnected) {
socket.disconnect()
}
}
}
extension ConnectionManager: WebSocketDelegate {
func websocketDidConnect(socket: WebSocket) {
}
func websocketDidDisconnect(socket: WebSocket, error: NSError?){
if let e = error {
log.error("websocket is disconnected with ERROR: \(e.localizedDescription)")
} else {
log.error("websocket disconnected")
}
}
func websocketDidReceiveMessage(socket: WebSocket, text: String){
}
func websocketDidReceiveData(socket: WebSocket, data: Data){
}
}
My code is not a best practise for sure, but it works without any problem. Try to use my version, maybe it will work for you.

Resources