Search code examples
swiftnsnotificationcenterpublishersubscribercombine

How to reference self in a Subscriber in Combine in Swift


Just getting to grips with Combine and have come across an issue where I need to call a method in my class when it receives a value from a Publisher, in this case a Notification from NotificationCenter...

Here's the subscription...

let subscribe = NotificationCenter.default.publisher(for: Notification.Name(rawValue: "LocalTalkNotificationReceivedData"), object: nil)
    .map( {
        ($0.object as! Data)
    } )
    .sink(receiveValue: {
        self.interpretIncoming(data: $0)
    })

What I get told by the compiler is Use of unresolved identifier 'self'. It's late, I'm kinda tired, anyone got an idea? Xcode 11 beta 5 on Catalina beta 5 btw...

Complete file:

import Foundation
import Combine
import SwiftUI

public class MessageData: ObservableObject, Identifiable {
public var peerData: [Peer] = [Peer]()
public var messageData: [Message] = [Message]()
public var objectWillChange = PassthroughSubject<Void, Never>()
let subscribe = NotificationCenter.default.publisher(for: Notification.Name(rawValue: "LocalTalkNotificationReceivedData"), object: nil)
    .map( {
        ($0.object as! Data)
    } )
    .sink(receiveValue: {
        self.interpretIncoming(data: $0)
    })

init() {
    self.setupDummyData()
}

private func setupDummyData() {
    self.peerData = self.load("peerData.json")
    self.messageData = self.load("messageData.json")
}

func addMessage(message: String, sender: MessageSource, name: String) {
    let newMessage = Message(id: Date().hashValue,
                             content: message,
                             source: sender,
                             correspondent: name)
    self.messageData.append(newMessage)
    self.objectWillChange.send()
}

func load<T: Decodable>(_ filename: String, as type: T.Type = T.self) -> T {
    let data: Data

    guard let file = Bundle.main.url(forResource: filename, withExtension: nil)
        else {
            fatalError("Couldn't find \(filename) in main bundle.")
    }

    do {
        data = try Data(contentsOf: file)
    } catch {
        fatalError("Couldn't load \(filename) from main bundle:\n\(error)")
    }

    do {
        let decoder = JSONDecoder()
        return try decoder.decode(T.self, from: data)
    } catch {
        fatalError("Couldn't parse \(filename) as \(T.self):\n\(error)")
    }
}

func interpretIncoming(data sent: Data) {
    do {
        let receivedTransmission = try NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(sent) as! [String: Any]
        self.addMessage(message: receivedTransmission["messagePayload"] as! String,
                        sender: .them,
                        name: receivedTransmission["messageSender"] as! String)
    } catch {
        print("FAILED DECODING THING")
    }
}

}


Solution

  • Everything in your question is a red herring except the word self. You are saying:

    class MessageData: ObservableObject, Identifiable {
        let subscribe = ... self ...
    }
    

    You cannot mention self in the initializer of a property declaration, because self is what we are in the process of initializing; there is no self yet.

    A simple solution here might be to change let subscribe to lazy var subscribe, but then you would have to ask for the value of subscribe in your actual code in order to get the initialization to happen. However, there are many other possible approaches.