How to remove weak reference from object? in Swift - swift

I have a class which is named ChatDataSource. The ChatDataSource has a ChatManager class like lazy var. If a user will not do any action ChatManager will not to init. ChatManager has some blocks which are calling some functions from ChatDataSource. I made in closers weak references for ChatDataSource. I noticed that ChatDataSource object never deinit from memory because the ChatManager keeps him weak link. The problem also lies in the fact that when I open a new conversation and init a new ChatDataSource, the old ChatDataSource is in memory. ChatManager also lives in other controllers. How can I remove a weak refence from ChatManager?
A snippet of my code
class ChatDataSource: ChatDataSourceProtocol {
fileprivate lazy var chatManager: ChatManager = {
let chatManager = ChatManager()
chatManager.onMessageChanged = { [weak self] (message) in
guard let sSelf = self else { return }
sSelf.delegate?.chatDataSourceDidUpdate(sSelf)
}
chatManager.changeUIMessageByID = { [weak self] (id) in
guard let sSelf = self else { return }
guard let uiID = sSelf.messageDataDictionary[id] else { return }
let message = sSelf.messages[uiID]
message.status = .success
sSelf.delegate?.chatDataSourceDidUpdate(sSelf)
}
return chatManager
}()
}
I tried to make like. But it doesn't work.
chatManager.didLeaveConversation = { [weak self] in
self = nil
}

Related

Swift - Pass the CoreDataStack or just Context?

I'm trying to figure out Core Data. I've been following some different tutorials and they all do things a bit differently.
I have a CoreDataStack and it's initialized in SceneDelegate
lazy var coreDataStack = CoreDataStack(modelName: "model")
I believe I then use dependency injection? to set a corresponding property in the viewControllers
guard let tabController = window?.rootViewController as? UITabBarController,
let viewController = navigationController.topViewController as? ViewController else {
fatalError("Application storyboard mis-configuration. Application is mis-configured")
}
viewController.coreDataStack = coreDataStack
viewController.context = coreDataStack.ManagedObjectContext
My questions is should I pass the entire coreDataStack object to the next view? Or just the context?
Initially I was passing the entire coreDataStack, Everything seemed to work just fine. But I wasn't sure if that was correct since most tutorials seem to only reference the context. (But even then, most tutorials are vastly different, even when they are made by the same author.)
import UIKit
import CoreData
class CoreDataStack {
private let modelName: String
init(modelName: String) {
self.modelName = modelName
setupNotificationHandling()
}
lazy var managedContext: NSManagedObjectContext = {
return self.storeContainer.viewContext
}()
private lazy var storeContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name: self.modelName)
container.loadPersistentStores { (storeDescription, error) in
if let error = error as NSError? {
print("Unresolved error \(error), \(error.userInfo)")
}
}
return container
}()
// MARK: - Notification Handling
func saveForDidEnterBackground() {
saveContext()
}
#objc func saveChanges(_ notification: Notification) {
saveContext()
}
// MARK: - Helper Methods
private func setupNotificationHandling() {
let notificationCenter = NotificationCenter.default
notificationCenter.addObserver(self,
selector: #selector(saveChanges(_:)),
name: UIApplication.willTerminateNotification,
object: nil)
}
// MARK: -
private func saveContext() {
guard managedContext.hasChanges else { return }
do {
try managedContext.save()
} catch {
print("Unable to Save Managed Object Context")
print("\(error), \(error.localizedDescription)")
}
}
}

RxSwift - rx.tap not working

I have view controller. Inside it I have view
lazy var statusView: StatusView = {
var statusView = StatusView()
return statusView
}()
Inside statusView I have button
lazy var backButton: UIButton = {
var button = UIButton(type: .system)
button.titleLabel?.font = UIFont().regularFontOfSize(size: 20)
return button
}()
In controller I have
override func viewDidLoad() {
super.viewDidLoad()
setupRx()
}
func setupRx() {
_ = statusView.backButton.rx.tap.subscribe { [weak self] in
guard let strongSelf = self else { return }
print("hello")
}
}
But when I tap to button, nothing get printed to console.
What am I doing wrong ?
In general nothing wrong, but there's a minor hidden trick.
You use
backButton.rx.tap.subscribe { [weak self] in
But you need to use
backButton.rx.tap.subscribe { [weak self] _ in ...
Did you notice underscore in the second version? The second version calls method
public func subscribe(_ on: #escaping (Event<E>) -> Void)
of ObservableType. In this case on closure to deliver an event is provided, but we just ignore incoming parameter of this closure using underscore
It looks like the subscription is going out of scope as soon as setupRx returns. If you add a DisposeBag to the view controller and add the subscription to the dispose bag, does that solve the problem? Something like this:
func setupRx() {
statusView.backButton.rx.tap
.subscribe { [weak self] in
guard let strongSelf = self else { return }
print("hello")
}
}
.addDisposableTo(self.disposeBag)
}
Hope that helps.

RxSwift Driver calling twice on first time

I have a CoreLocation manager that should handle all CLLocationManager by offering observable properties through RxSwift (and its Extensions and DelegateProxies). LocationRepository looks like this:
class LocationRepository {
static let sharedInstance = LocationRepository()
var locationManager: CLLocationManager = CLLocationManager()
private (set) var supportsRequiredLocationServices: Driver<Bool>
private (set) var location: Driver<CLLocationCoordinate2D>
private (set) var authorized: Driver<Bool>
private init() {
locationManager.distanceFilter = kCLDistanceFilterNone
locationManager.desiredAccuracy = kCLLocationAccuracyBestForNavigation
supportsRequiredLocationServices = Observable.deferred {
let support = CLLocationManager.locationServicesEnabled() && CLLocationManager.significantLocationChangeMonitoringAvailable() && CLLocationManager.isMonitoringAvailable(for:CLCircularRegion.self)
return Observable.just(support)
}
.asDriver(onErrorJustReturn: false)
authorized = Observable.deferred { [weak locationManager] in
let status = CLLocationManager.authorizationStatus()
guard let locationManager = locationManager else {
return Observable.just(status)
}
return locationManager.rx.didChangeAuthorizationStatus.startWith(status)
}
.asDriver(onErrorJustReturn: CLAuthorizationStatus.notDetermined)
.map {
switch $0 {
case .authorizedAlways:
return true
default:
return false
}
}
location = locationManager.rx.didUpdateLocations.asDriver(onErrorJustReturn: []).flatMap {
return $0.last.map(Driver.just) ?? Driver.empty()
}
.map { $0.coordinate }
}
func requestLocationPermission() {
locationManager.requestAlwaysAuthorization()
}
}
My presenter then listens to changes on the repository properties. LocatorPresenter looks like this:
class LocatorPresenter: LocatorPresenterProtocol {
weak var view: LocatorViewProtocol?
var repository: LocationRepository?
let disposeBag = DisposeBag()
func handleLocationAccessPermission() {
guard repository != nil, view != nil else {
return
}
repository?.authorized.drive(onNext: {[weak self] (authorized) in
if !authorized {
print("not authorized")
if let sourceView = self?.view! as? UIViewController, let authorizationView = R.storyboard.locator.locationAccessRequestView() {
sourceView.navigationController?.present(authorizationView, animated: true)
}
} else {
print("authorized")
}
}).addDisposableTo(disposeBag)
}
}
It does work, but I'm getting the Driver calling twice for the first time I try to get the authorization status, so the access request view gets presented twice. What am I missing here?
Regards!
From startWith documentation:
StartWith
emit a specified sequence of items before beginning to emit the items from the source Observable
I have not tried it, but probably if you remove startWith(status) you won't receive the status twice.
It seems you are receiving the next sequence from the observable:
---------------------------------unauthorized----authorized----->
So with the line:
startWith(status) // status is unauthorized
you finally get this one:
-------unauthorized---------unauthorized----authorized----->

Create member variable with closure in its constructor

I'm having the following code in my class:
// MARK: - Lifecycle
init() {
authenticationContext = AuthenticationContext()
synchronizationContext = SynchronizationContext()
employeesCoordinator = EmployeesCoordinator()
serverErrorObserver =
NotificationObserver(notification: serverErrorNotification,
block: handleServerError) // <- Error
}
// MARK: - Listeners
private let serverErrorObserver: NotificationObserver!
private lazy var handleServerError: NSError -> () = {
[unowned self] (error) in
// Currently means that the token is expired, so remove stored instance
self.handleAuthorizationDidExpired()
}
It looks legit, but I'm getting the following complier error:
Use of 'self' in property access 'handleServerError' before all stored
properties are initialized
If it would help, this is the source behind NotificationObserver:
class ValueWrapper<T> {
let value: T
init(_ value: T) { self.value = value }
}
// Notification
struct Notification<A> {
let name: String
}
// Global Functions
func publish<A>(note: Notification<A>, value: A) {
let userInfo = ["value": ValueWrapper(value)]
NSNotificationCenter.defaultCenter().postNotificationName(note.name, object: nil, userInfo: userInfo)
}
//
class NotificationObserver {
let observer: NSObjectProtocol
init<A>(notification: Notification<A>, block aBlock: A -> ()) {
observer = NSNotificationCenter.defaultCenter().addObserverForName(notification.name, object: nil, queue: nil) { note in
let wrapper = note.userInfo?["value"] as? ValueWrapper<A>
if let value = wrapper?.value {
aBlock(value)
} else {
assert(false, "Couldn't understand user info")
}
}
}
deinit {
NSNotificationCenter.defaultCenter().removeObserver(observer)
}
}
// Global variables
let serverErrorNotification: Notification<NSError> = Notification(name: "ServerErrorNotification")
let synchronizationDidCompleteNotification: Notification<Int> = Notification(name: "SynchronizationDidCompleteNotification")
let authorizationDidCompleteNotification: Notification<Authorization> = Notification(name: "SynchronizationDidCompleteNotification")
You cannot call self until you have properly initialized the object using super.init()
if you have un-initialized let variables they should initialize before super.init() call.
so doing so you cannot call to self - closure calls to self
so you have to change let to var, then call super.init() before assigning closure
private let serverErrorObserver: NotificationObserver!
to
private var serverErrorObserver: NotificationObserver!
eg.
init() {
super.init()
authenticationContext = AuthenticationContext()
synchronizationContext = SynchronizationContext()
employeesCoordinator = EmployeesCoordinator()
serverErrorObserver =
NotificationObserver(notification: serverErrorNotification,
block: handleServerError) // <- Error
}
The problem is that you are accessing self in the init() in the following line:
self.handleAuthorizationDidExpired()
You can't do it until all of the stored properties are initialised. And the only property, which is not initialised yet in your case, is serverErrorObserver.
In order to fix it easily, you can mark this property in the following way:
private(set) var serverErrorObserver: NotificationObserver?
By marking it optional you tell the compiler that this property doesn't need to be initialised when the object is created.

Swift. Why singleton lost data?

Why my data is lost in singleton? Please take a look:
class YtDataManager {
static var shared_instance = YtDataManager()
let apiKey = /*...*/
let /*...*/
var channelData = [NSObject:AnyObject]()
var videosArray = [[NSObject:AnyObject]]()
var playlistId: String { return self.channelData["playlistId"] as! String}
var urlStringForRequestChannelDetails: String { return String("https://www.googleapis.com/youtube/v3/channels?part=contentDetails,snippet&forUsername=\(self./*ChannelName*/)&key=\(self.apiKey)") }
var urlStringForRequestChannelVideos: String { return String("https://www.googleapis.com/youtube/v3/playlistItems?part=snippet&playlistId=\(self.playlistId)&key=\(self.apiKey)") }
func fn1() { /*...*/ }
func fn2() { /*...*/ }
// class definition continue...
Look, from one function(fn1) I'm writing data to channelData:
//...
self.channelData["title"] = snippetDict["title"]
self.channelData["playlistId"] = ((firstItemDict["contentDetails"] as! [NSObject:AnyObject])["relatedPlaylists"] as! [NSObject:AnyObject])["uploads"]
//...
and so on... From second function(fn2), I'm reading data:
//...
let targetURL = NSURL(fileURLWithPath: self.urlStringForRequestChannelVideos)
//...
Hence urlStringForRequestChannelVideos is computational property it uses playlistId (look code above).
Here I was surprised about emptiness of channedData from second function(I saw it in Debug mode, also I printed it's count to stdout outside of function). Why????
class YtFeedViewController: UIViewController {
#IBOutlet weak var menuButton:UIBarButtonItem!
override func viewDidLoad() {
super.viewDidLoad()
if self.revealViewController() != nil {
menuButton.target = self.revealViewController()
menuButton.action = "revealToggle:"
self.view.addGestureRecognizer(self.revealViewController().panGestureRecognizer())
YtDataManager.shared_instance.fn1()
print(YtDataManager.shared_instance.channelData.count) //0
YtDataManager.shared_instance.fn2() //errorness
}
self.navigationItem.title = "YouTube feed"
// Do any additional setup after loading the view.
}