Here is pseudocode for what I want to achieve:
let a = AClass()
let go = NSMutableArray()
go.add(a)
.
.
.
class AClass{
.
.
.
fileprivate func removeMyselfFromCollection() { //Called from within the class
//TODO: How to remove myself from the collection I am in.
}
}
It could be any collection, array or dictionary etc. Something similar to removeFromSuperview()
Update: Responding to a common question...
Question: Why I need this?
Answer: Scenario is a bit complex but bringing it down to most basic details. I am uploading files (audio, video, sound) to remote storage. Once files are uploaded, I have a mechanism (listeners) to know when my files are there, I don't need local objects to tell me the status. I add local objects to a global collection to keep them alive till the files are uploading (one object representing each file). Each object takes care of each file being uploaded. I wish that each of my objects removes itself silently from the global collection once upload is finished (reason explained above).
Not unless you:
a. Implement this method yourself.
b. Pass the collection in as a parameter.
c. Have the object's method forward the remove(self) message to the collection.
Given that you would have to just forward the message to the collection anyway, and you'd have to know the collection at the time you called the method, there's no value in such a method. It adds complexity without adding any value.
Another problem is that objects can be members of more than one collection. What should happen in that case? Should it be removed from all the collections it belongs to? The first one to which it was added? The last? And how would you track that?
All in all, this seems like a all-pain-no-gain proposition.
You can do this by typing your own custom insertion method into the extension of NSMutableArray, and in this method, you can assign the array to the object variable.
class SomeClass {
var containedBy: NSMutableArray?
var value: String
init(value: String) {
self.value = value
}
func removeFromCollection() -> Void {
guard let containedBy = self.containedBy else { return }
containedBy.remove(self)
}
}
extension NSMutableArray{
func addRemovable(value: SomeClass) {
value.containedBy = self
self.add(value)
}
}
Related
So I've been stuck on this problem for a while, and can't find questions addressing my particular problem online.
I am trying to set the value in description, which is defined as a lazy computed property and utilizes a self-executing closure.
To get the book's description, I make an API call, passing in another handler to the API completion handler so that I can set the book's description inside the lazy computed property.
I know my below code is wrong, since I get the error:
Cannot convert value of type '()' to specified type 'String'
class Book : NSObject {
func getInfo(for name: String, handler: #escaping (_ string: String) -> String) {
let task = URLSession.shared.dataTask(with: "foo_book.com" + name) { (data, response, error) in
guard let data = data else {return}
descriptionStr = String(data: data, encoding: .utf8) ?? "No description found"
handler(descriptionStr)
}
}
lazy var description: String = {
getInfo(for: self.name) { str in
return str
}
}()
}
How can I set the value of description?
I've tried two methods. Using a while loop to wait for a boolean: inelegant and defeats the purpose of async. Using a temp variable inside description - doesn't work because getInfo returns before the API call can finish.
In case you wonder my use case: I want to display books as individual views in a table view, but I don't want to make api calls for each book when I open the tableview. Thus, I want to lazily make the API call. Since the descriptions should be invariant, I'm choosing to make it a lazy computed property since it will only be computed once.
Edit: For those who are wondering, my solution was as the comments mentioned below. My approach wasn't correct - instead of trying to asynchronously set a property, I made a method and fetched the description in the view controller.
Already the explanation in comments are enough for what's going wrong, I will just add on the solution to your use case.
I want to display books as individual views in a table view, but I
don't want to make api calls for each book when I open the tableview.
Thus, I want to lazily make the API call.
First of all, does making lazy here make sense. Whenever in future you will call description, you are keeping a reference for URLSession and you will do it for all the books. Looks like you will easily create a memory leak.
Second, task.resume() is required in getInfo method.
Third, your model(Book) should not make the request. Why? think, I have given one reason above. Async does mean parallel, all these network calls are in the queue, If you have many models too many networks calls in the event loop.
You can shift network call responsibility to service may be BookService and then have a method like this BookService.getInfo(_ by: name). You Book model should be a dumb class.
class Book {
let description: String
init(desc: String) {
self.description = desc
}
}
Now your controller/Interactor would take care of calling the service to get info. Do the lazy call here.
class BookTableViewController: ViewController {
init(bookService: BookService, book: [String]) {
}
# you can call when you want to show this book
func loadBook(_ name: String) -> Book {
BookService.getInfo(name).map { Book(desc: str) }
}
func tableView(UITableView, didSelectRowAt: IndexPath) {
let bookName = ....
# This is lazy loading
let book = loadBook(bookName)
showThisBook()
}
}
Here, you can do the lazy call for loadBook. Hope this helps.
I've been running into this every now and then I'm always questioning myself whether I'm using RxSwift (or reactive means altogether) the wrong way.
The challenge is converting value types to something representable on the UI.
Usually on the lower level I'm storing data with simple types but I need something more stateful on the UI level. Just to give you an example, consider I have list of following types:
struct Person {
let firstName: String
let lastName: String
}
On the UI, however, I'm binding view models created from these items into a UITableView instance. I could achieve this by simply mapping from one type to another:
let displayedPersons = listOfPersons.map { PersonViewModel($0) }
This would make all items to be recreated on each update which I'm trying to avoid. I'm using MVVM and would like to keep the view model instances due to their transient state. Reloading table view on each update would also mess up animations.
I'm thinking if a custom binding could help here, binding one observable to another with cached mapping. Another solution that I've ended up doing is simply looping the observable so that when mapping, I get the previous value which I'll use as a cache.
Effectively I would need to map only the new items and keep the existing ones. Any ideas how would I achieve this?
In my very biased opinion, MVVM is good only for very complex UI where elements need to update dynamically and independently from each other. For all other cases I use my own library https://github.com/maxvol/RaspSwift (NB: it is not only for UI, but for UI as well). The core idea stems from MVI, and boils down to having a new snapshot of the state on every mutating event. So in your case the state would contain a collection of cached PersonViewModel, which will be partially updated upon receiving mutating events. The whole thing would be bound to UITableView via RxDataSources library.
One simple solution that I'm currently using is simply to map and recycle the old items if they exist.
I've created an extension to make it work with sequences. In memoryLookup you will receive the previous values and can reuse any item from the previous round.
public extension ObservableType where E: Sequence {
public func mapWithMemory<R>(memoryLookup: #escaping (Self.E.Element, [R]) throws -> R?, transform: #escaping (Self.E.Element) throws -> R) -> RxSwift.Observable<[R]> {
return self.scan([]) { (acc, elements) -> [R] in
let mapped = try elements.map { e in
return try memoryLookup(e, acc) ?? transform(e)
}
return mapped
}
}
}
And here's an example usage where an array of Ints is mapped to array of Strings.
func testMapWithMemory() {
var creationCounts = [Int: Int]()
let items = Observable.from([[1, 2],[2, 3],[2, 3]])
let mapped = items.mapWithMemory(memoryLookup: { (item, previousItems) -> String? in
return previousItems.first { $0 == "\(item)" }
}) { (item) -> String in
creationCounts[item, default: 0] += 1
return "\(item)"
}
let xs = try! mapped.toBlocking().toArray().last!
XCTAssertEqual(xs, ["2", "3"])
XCTAssertEqual(creationCounts, [
1: 1,
2: 1,
3: 1
])
}
Use at your own risk. And feel free to improve and share.
Also note that this is only useful if you need to avoid creating new items. In my case I'm using classes and binding UI elements to these items, so I don't want to recreate those.
I have a struct like,
struct LoginPage {
static let usernameField = Element("#username")
}
and I have a class like,
class LoginView {
func enterCredentails () {
LoginPage.usernameField.waitForExist()
}
}
and the Element api looks like
class Element {
init(...) {
...
}
func waitForExist () {
// print("caller property name")
}
}
...here inside waitForExist() I want to get that property name(usernameField) who triggered this method. so that I can print the error and success message dynamically inside waitForExist() based on the property name.
is this possible? is there any workaround for this?
NOTE: I have tried using Mirror to get all the properties(so that I can store and retrieve the key name based on Element), but it is not returning the static properties (Referred). So I am looking for the alternative solution.
Thanks in advance for helping me out!
I'd recommend the simple approach:
func waitForExist(propertyName: String [or possibly make it an enum]) {
// do what needs to be done
}
I'm assuming your properties call this in their didSet or something; just have each one pass the appropriate value to the waitForExist function.
As for the literal answer to your question, you could probably examine the stack using functions like backtrace(), but ehhhhhh... trust me, you don't really want to do that.
Background:
I designed a TableViewDataSource class that provides an implementation for UITableViewDataSource and UITableViewDelegate. You instantiate TableViewSection objects, which are passed to the TableViewDataSource which are used to configure cells, section headers, handle selection, row insertion, etc.
The TableViewSection object has a property called dataSource: [AnyObject]?, which, when set, is used to calculate the number of rows in the section, and provide an object for the cell configuration block:
// get the section, dequeue a cell for that section, retrieve the item from the dataSource
// ...
tableSection.cellConfigurationBlock?(cell: AnyObject, item: AnyObject?, indexPath: NSIndexPath)
return cell
What I'd like to do is assign a reference to an array from my viewModel to my tableSection.dataSource, having my viewModel update the array, in turn updating the table view. In Swift, you cannot pass an array by reference. The workaround seems to be to use an NSMutableArray, but with that comes a loss of type safety, and greater cognitive load while translating objects back and forth from Swift to Foundation.
Working Example:
let kCellIdentifier = "SomeCellIdentifier"
class MyViewController: UITableViewController {
// Property declarations
#IBOutlet var tableDataSource: TableViewDataSource!
var viewModel: MyViewControllerViewModel = MyViewControllerViewModel()
override func viewDidLoad() {
super.viewDidLoad()
self.setupTableView()
self.refresh()
}
func setupTableView() {
var tableSection = TableViewSection(cellIdentifier: kCellIdentifier)
tableSection.dataSource = self.viewModel.collection
// tableSection configuration
// ...
self.tableDataSource.addSection(tableSection)
}
func refresh() {
self.viewModel
.refresh()
.subscribeNext({ result in
self.tableView.reloadData()
}, error: { error in
self.logger.error(error.localizedDescription)
})
}
}
The refresh() method on the viewModel hits my API service, updates it's collection property on response, and provides the result on the next event of an RACSignal (RACSignal is a class provided by Reactive Cocoa and really, besides the point).
I've found one workaround, which involves reassigning the data source each time a single update is made, or after a batch update.
func refresh() {
self.viewModel
.refresh()
.subscribeNext({ result in
self.updateDataSource()
self.tableView.reloadData()
}, error: { error in
self.logger.error(error.localizedDescription)
})
}
func updateDataSource() {
self.tableDataSource.tableSectionForIndex(0)?.dataSource = viewModel.collection
}
This approach works, but only temporarily as a workaround. As a TableViewDataSource grows and becomes more complex, this method becomes increasingly more complex with imperative, procedural code, the opposite of what I set out to achieve when writing the class.
Question
Is there any workaround to stick to native Swift Array's to achieve the equivalent of passing a Foundation NSArray or NSMutableArray by reference?
Bonus Question
Can someone provide me with some class/struct design tips to accomplish the desired goal in pure Swift?
The simple solution is to wrap the array in a class. The class instance is passed by reference so the problem is effectively solved: a change to the array through any reference to the class instance affects the array as seen through every reference to that class instance.
The class in question can be extremely lightweight - basically, it just serves as a thin wrapper that carries the array along with it, and a client accesses the array directly through the class instance - or, just the opposite, you can design the class to manage the array, i.e. the class deliberately presents an array-like API that shields clients from the underlying implementation. Either approach might be appropriate; I've certainly done both.
Here's an example of the first kind of situation. My model object is an array belonging to a UIDocument subclass. My view controller is a UITableViewController. The user is going to view, add, and edit model entities in the table. Thus, the UITableViewController needs access to the UIDocument's array (which happens to be called people).
In Objective-C, my UITableViewController simply held a reference to the array, self.people, which was an NSMutableArray. This was just a pointer, so changes to self.people were also changes to the UIDocument's people - they are one and the same object.
In Swift, my UITableViewController holds a reference to the UIDocument object, self.doc. The array, which is now a Swift array, is "inside" it, so I can refer to it as self.doc.people. However, that's too much rewriting! Instead, I've created a calculated variable property self.people which acts as a gateway to self.doc.people:
var doc : PeopleDocument!
var people : [Person] { // front end for the document's model object
get {
return self.doc.people
}
set (val) {
self.doc.people = val
}
}
Hey presto, problem solved. Whenever I say something like self.people.append(newPerson), I'm passed right through to the UIDocument's model object people and I'm actually appending to that. The code thus looks and works just like it did in Objective-C, with no fuss at all.
In Swift is it possible to retrieve / remove an element in a Dictionary using it's index, not a key value? In this example I'm attempting to create a "to do" list where I can eventually access all the tasks assigned to an individual. But I would also like to remove an element (task) by index.
In the code below how can I remove the second task "wash dishes" by index not using a key value. I was hoping to call this func with something like: taskMgr.removeTaskByIndex(1)
Any additional explanation or advice is helpful as I'm just learning Swift.
import Foundation
class TaskManager{
var taskDictArray = Dictionary<String, String>()
init(){
taskDictArray = [:]
}
func addTask(task: String, person: String){
taskDictArray[task] = person
}
}
var taskMgr = TaskManager()
taskMgr.addTask("take out trash", person: "emma")
taskMgr.addTask("wash dishes", person: "jack")
taskMgr.addTask("clean floor", person: "cleo")
//taskMgr.removeTaskByIndex(1)
//var a = taskMgr.getTaskByIndex(1)
Dictionaries do not maintain their object order and the only way to get a value is using its key. You'll probably want to associate whatever list of tasks you have the index for with its corresponding key and use that manipulate the dictionary. This is the only way. You probably consider restructuring your application logic if it's impossible.
You'll need to add a function such as
func removeTask(task: String) {
taskDictArray[task] = nil
}
So find a way to get the actual task string with which you are associating it.