How can I make a class property array be a heterogeneous array of a generic type? - arrays

Here's what I want. I'm writing a very simple event dispatcher (click that link to see my code). It was working fine when I only had the listen() and the fire() method. This is how you could use it:
struct UserHasBirthday: Event {
let name: String
init(name: String) {
self.name = name
}
}
let events = TestDispatcher()
events.listen {
(event: UserHasBirthday) in
print("Happy birthday \(event.name)!")
}
events.fire( UserHasBirthday(name: "John Doe") )
That's all well and good, but now I wanted to add the feature that you could push events to a queue and then later fire them all at once. That's why I added the push and flush methods.
Now the problem is that in the flush() method I need to be able to downcast the generic Event type to the specific event type that was given. Otherwise the fire() method doesn't work.
So I thought, maybe I could save the type information in the same array as the event itself. As you can see I tried to do that with a tuple. Unfortunately it doesn't work like that.
I think that if I could find a way to make the variable pushedEvents accept a generic type like so: var pushedEvents = Array<E: Event>() then it could work. But the only way I know to do that is to assign that generic to the whole class like so: class TestDispatcher<E: Event> { }, but then every instance of that class can only be used for one specific type of event and I definitely don't want that.
Does anybody know some kind of way to make this work?

This guy on reddit gave me the solution by using a so-called type-erasure pattern (I didn't know about that pattern).
I edited his code to meet my needs more and this is what I have now:
public protocol Event {}
public protocol ErasedListener {
func matches(eventType: Event.Type) -> Bool
func dispatchIfMatches(event: Event)
}
public struct Listener<T: Event>: ErasedListener {
let dispatch: T -> Void
public func matches(eventType: Event.Type) -> Bool {
return matches(String(eventType))
}
func matches(eventType: String) -> Bool {
return eventType == String(T.self)
}
public func dispatchIfMatches(event: Event) {
if matches(String(event.dynamicType)) {
dispatch(event as! T)
}
}
}
public protocol Dispatcher {
func listen<E: Event>(listener: E -> Void)
func fire(event: Event)
func queue<E: Event>(event: E)
func flushQueueOf<E: Event>(eventType: E.Type)
func flushQueue()
func forgetListenersFor<E: Event>(event: E.Type)
func emptyQueueOf<E: Event>(eventType: E.Type)
func emptyQueue()
}
public class MyDispatcher: Dispatcher {
var listeners = [ErasedListener]()
var queuedEvents = [Event]()
public init() {}
public func listen<E: Event>(listener: E -> Void) {
let concreteListener = Listener(dispatch: listener)
listeners.append(concreteListener as ErasedListener)
}
public func fire(event: Event) {
for listener in listeners {
listener.dispatchIfMatches(event)
}
}
public func queue<E: Event>(event: E) {
queuedEvents.append(event)
}
public func flushQueue() {
for event in queuedEvents {
fire(event)
}
emptyQueue()
}
public func emptyQueue() {
queuedEvents = []
}
public func flushQueueOf<E: Event>(eventType: E.Type) {
for event in queuedEvents where String(event.dynamicType) == String(eventType) {
fire(event)
}
emptyQueueOf(eventType)
}
public func forgetListenersFor<E: Event>(eventType: E.Type) {
listeners = listeners.filter { !$0.matches(eventType) }
}
public func emptyQueueOf<E: Event>(eventType: E.Type) {
queuedEvents = queuedEvents.filter { String($0.dynamicType) != String(eventType) }
}
}
Example usage
struct UserDied: Event {
var name: String
}
class UserWasBorn: Event {
let year: Int
init(year: Int) {
self.year = year
}
}
// you can use both classes and structs as events as you can see
let daveDied = UserDied(name: "Dave")
let bartWasBorn = UserWasBorn(year: 2000)
var events = MyDispatcher()
events.listen {
(event: UserDied) in
print(event.name)
}
events.listen {
(event: UserWasBorn) in
print(event.year)
}
events.queue(daveDied)
events.queue(UserWasBorn(year: 1990))
events.queue(UserWasBorn(year: 2013))
events.queue(UserDied(name: "Evert"))
// nothing is fired yet, do whatever you need to do first
events.flushQueue()
/*
This prints:
Dave
1990
2013
Evert
*/
// You could also have flushed just one type of event, like so:
events.flushQueueOf(UserDied)
// This would've printed Dave and Evert,
// but not the year-numbers of the other events

The problem is that Swift doesn't allow type conversion to metatypes.
One workaround is to include all types that conform to Event (at least those you will use in your Dispatcher) in a switch case in the flush() function of your TestDispatcher class. It isn't as versatile as the functionality I believe you're looking for, and as you've shown with your own answer, type erasure is the way to here. I'll leave my original answer intact however, as it explains why your original approach of attempting to cast to metatypes didn't work.
public protocol Event {}
public enum Listener<E: Event> {
public typealias T = E -> ()
}
public protocol Dispatcher {
func listen<E: Event>(listener: Listener<E>.T)
func fire<E: Event>(event: E)
func push<E: Event>(event: E)
func flush()
}
//
public class TestDispatcher: Dispatcher {
var listeners = [String:[Any]]()
var pushedEvents = [Event]()
public init() {}
public func listen<E: Event>(listener: Listener<E>.T) {
var listeners = self.listeners[String(E.self)] ?? []
listeners += [listener] as [Any]
self.listeners[String(E.self)] = listeners
}
public func fire<E: Event>(event: E) {
listeners[String(E.self)]?.forEach {
let f = $0 as! Listener<E>.T
f(event)
}
}
public func push<E: Event>(event: E) {
pushedEvents = pushedEvents + [event]
}
/* Include a switch case over all types conforming to Event ... */
public func flush() {
for event in pushedEvents {
switch event {
case let ev as UserHasBirthday: fire(ev)
case let ev as UserWonTheLottery: fire(ev)
case _: print("Unknown event type.")
}
}
}
}
Example usage:
struct UserHasBirthday: Event {
let name: String
init(name: String) {
self.name = name
}
}
struct UserWonTheLottery: Event {
let name: String
let amount: Int
init(name: String, amount: Int) {
self.name = name
self.amount = amount
}
}
let events = TestDispatcher()
events.listen {
(event: UserHasBirthday) in
print("Happy birthday \(event.name)!")
}
events.listen {
(event: UserWonTheLottery) in
print("Congratulations \(event.name) for winning \(event.amount)!")
}
events.push(UserHasBirthday(name: "John Doe"))
events.push(UserHasBirthday(name: "Jane Doe"))
events.push(UserWonTheLottery(name: "Jane Doe", amount: 42000))
events.flush()
/* Happy birthday John Doe!
Happy birthday Jane Doe!
Congratulations Jane Doe for winning 42000! */

Related

How do i get array of dictionary's 2nd value from JSON response, in Swift

This is my JSON response in postman:
{
"result": {
"reviewcat": [
{
"id": 5
},
{
"id": 6
},
{
"id": 7
}
],
"reviewDet": {
"review_details": [
{
"review_point": "2.0"
},
{
"review_point": "4.5"
},
{
"review_point": "3",
}
],
for above response i have created model like below
public class BuyerReviewModel {
public var result : BuyserReviewResult?
}
public class BuyserReviewResult {
public var reviewcat : Array<Reviewcat>?
public var reviewDet : ReviewDet?
}
public class Reviewcat {
public var review_name : String?
public var id : Int?
}
public class ReviewDet {
public var review_details : Array<Review_details>?
}
public class Review_details {
public var review_cat_id : Int?
public var review_point : Float?
}
code: with this code i am getting only first review review_point val 2.5...how do i get 2nd review_point value 4.5 how?, could anybody guide me
class PostReviewVC: UIViewController {
#IBOutlet var rateImageViews: [UIImageView]!// 5 images array
#IBOutlet var rateButtons: [UIButton]!// 5 buttons array
var rateFatched: Float = 0.0
private var testData = ReviewModel(dictionary: NSDictionary()){
didSet{
titleLbl.text = byuReviewData?.result?.enquiry?.comments
idLbl.text = byuReviewData?.result?.enquiry?.ref_no
userNameLbl.text = "\(byuReviewData?.result?.reviewDet?.get_to_user?.first_name ?? "") \(byuReviewData?.result?.reviewDet?.get_to_user?.last_name ?? "")"
if let data = testData?.result?.reviewDet{
for buyReviewData in data.review_details ?? []{
rateFatched = buyReviewData.review_point ?? 0.0
if rateFatched == NSString("2.5").floatValue{
for button in rateButtons {
if button.tag >= 2 {
allImages[button.tag].image = (#imageLiteral(resourceName: "staremp"))
}
else {
allImages[button.tag].image = (#imageLiteral(resourceName: "star"))
}
}
}
}
}
}
}
}
Since the main issue here seems to be how to get the correct data to display here is solution that focus on that but ignores any UI related stuff since I consider it off-topic.
I did some changes to the structure but nothing major, changed from class to struct, removed some optionals that didn't make sense and changed from var to let but feel free to change it back since it isn't really important to the answer.
public struct BuyserReviewResult {
public let reviewcat : [Reviewcat]
public let reviewDet : ReviewDet?
}
public struct Reviewcat {
public let review_name : String
public let id : Int
}
public struct ReviewDet {
public let review_details : [Review_details]
}
public struct Review_details {
public let review_cat_id : Int
public let review_point : Float
}
Now you can simply get each category name and the points for that category by doing
for category in result.reviewcat {
let points = result.reviewDet?.review_details
.first(where: { $0.review_cat_id == category.id })?.review_point ?? 0.0
print(category.review_name, points)
}
Note that also ignored the top level type here since it is no longer needed once you have parsed the data.
Yes the review_point is of type String and not Float, as suggested by #burnsi
After getting the string value you can always parse it to Float

append object if not exist swift

Im new to swift language and I'm trying to find a solution for my problem.
I have created a class called Book
I am tring to create Book. A book has name (can’t be changed after creation), purchaseid (can’t be changed after creation), and market price (can be changed)
and also a class Shelf. A shelf has name (that can’t be changed after creation) and an array of books (that can be changed by calling add and delete methods and is unique on book purchaseId - ie. doesn’t have duplicate purchaseIds). Shelf also has a method for computing the average price of books in the shelf.
I am trying to find a solution on how to check an array before adding to it and taking books purchaseId and deleting a book from the array.
here is my code:
class Book{
let name: String
let purchaseID: Int
let marketPrice: Double
init(name: String, purchaseID: Int, marketPrice: Double) {
self.name = name
self.purchaseID = purchaseID
self.marketPrice = marketPrice
}
func bookPrinter() -> String {
return "Name: \(self.name) Price: \(marketPrice)"
}
}
class Shelf{
private let shelfName: String
private var arrayOfBooks = [Book]()
init(shelfName: String) {
self.shelfName = shelfName
}
func add(book : Book ){
}
func delete(book : Book){
}
func printer() {
for b in self.arrayOfBooks{
print(b.bookPrinter())
}
}
}
For add(book:) function, if the book's purchaseID is unique, you can use contains(where:) function to know if the Book already exists in the Array:
func add(book: Book){
if !arrayOfBooks.contains(where: { $0.purchaseID == book.purchaseID }) {
arrayOfBooks.append(book)
}
}
or add an extension to Book that conforms to Equatable protocol:
extension Book: Equatable {
static func == (lhs: Book, rhs: Book) -> Bool {
return lhs.purchaseID == rhs.purchaseID
}
}
and simplify your add(book:) function:
func add(book: Book){
if !arrayOfBooks.contains(book) {
arrayOfBooks.append(book)
}
}
For delete(book:) function you can use removeAll(where:) function of Array:
func delete(book: Book){
arrayOfBooks.removeAll(where: { $0.purchaseID == book.purchaseID })
}
However, as #LeoDabus said in the comments, if book ordering doesn't mater you should probably use a Set. It will be faster on some aspects.
An implementation may be like this:
extension Book: Hashable {
static func == (lhs: Book, rhs: Book) -> Bool {
return lhs.purchaseID == rhs.purchaseID
}
func hash(into hasher: inout Hasher) {
hasher.combine(purchaseID)
}
}
class Shelf {
private let shelfName: String
private var setOfBooks = Set<Book>()
init(shelfName: String) {
self.shelfName = shelfName
}
func add(book: Book){
setOfBooks.insert(book)
}
func delete(book: Book){
setOfBooks.remove(book)
}
}

Return function data into an Array

I have found away to search and return data of current USB's plugged into the system.
I can print this by using print(usbDelegate()) Return:
device added: Cruzer Fit
device added: USB-C Digital AV Multiport Adapter
device added: USB2.0 Hub
device added: USB3.0 Hub
What I would like to do is return these values into an array? Like this:
var usbDevices = [Cruzer Fit, USB-C Digital AV Multiport Adapter, USB2.0 Hub, USB3.0 Hub]
Here is the code that I used to scan for USB's:
import Cocoa
import IOKit
import IOKit.usb
import IOKit.usb.IOUSBLib
class ViewController: NSViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
#IBAction func usbSelect(_ sender: Any) {
print(usbDelegate())
}
override var representedObject: Any? {
didSet {
// Update the view, if already loaded.
}
}
}
public protocol USBWatcherDelegate: class {
/// Called on the main thread when a device is connected.
func deviceAdded(_ device: io_object_t)
/// Called on the main thread when a device is disconnected.
func deviceRemoved(_ device: io_object_t)
}
/// An object which observes USB devices added and removed from the system.
/// Abstracts away most of the ugliness of IOKit APIs.
public class USBWatcher {
private weak var delegate: USBWatcherDelegate?
private let notificationPort = IONotificationPortCreate(kIOMasterPortDefault)
private var addedIterator: io_iterator_t = 0
private var removedIterator: io_iterator_t = 0
public init(delegate: USBWatcherDelegate) {
self.delegate = delegate
func handleNotification(instance: UnsafeMutableRawPointer?, _ iterator: io_iterator_t) {
let watcher = Unmanaged<USBWatcher>.fromOpaque(instance!).takeUnretainedValue()
let handler: ((io_iterator_t) -> Void)?
switch iterator {
case watcher.addedIterator: handler = watcher.delegate?.deviceAdded
case watcher.removedIterator: handler = watcher.delegate?.deviceRemoved
default: assertionFailure("received unexpected IOIterator"); return
}
while case let device = IOIteratorNext(iterator), device != IO_OBJECT_NULL {
handler?(device)
IOObjectRelease(device)
}
}
let query = IOServiceMatching(kIOUSBDeviceClassName)
let opaqueSelf = Unmanaged.passUnretained(self).toOpaque()
// Watch for connected devices.
IOServiceAddMatchingNotification(
notificationPort, kIOMatchedNotification, query,
handleNotification, opaqueSelf, &addedIterator)
handleNotification(instance: opaqueSelf, addedIterator)
// Watch for disconnected devices.
IOServiceAddMatchingNotification(
notificationPort, kIOTerminatedNotification, query,
handleNotification, opaqueSelf, &removedIterator)
handleNotification(instance: opaqueSelf, removedIterator)
// Add the notification to the main run loop to receive future updates.
CFRunLoopAddSource(
CFRunLoopGetMain(),
IONotificationPortGetRunLoopSource(notificationPort).takeUnretainedValue(),
.commonModes)
}
deinit {
IOObjectRelease(addedIterator)
IOObjectRelease(removedIterator)
IONotificationPortDestroy(notificationPort)
}
}
extension io_object_t {
/// - Returns: The device's name.
func name() -> String? {
let buf = UnsafeMutablePointer<io_name_t>.allocate(capacity: 1)
defer { buf.deallocate() }
return buf.withMemoryRebound(to: CChar.self, capacity: MemoryLayout<io_name_t>.size) {
if IORegistryEntryGetName(self, $0) == KERN_SUCCESS {
return String(cString: $0)
}
return nil
}
}
}
class usbDelegate: USBWatcherDelegate {
private var usbWatcher: USBWatcher!
init() {
usbWatcher = USBWatcher(delegate: self)
}
func deviceAdded(_ device: io_object_t) {
print("device added: \(device.name() ?? "<unknown>")")
}
func deviceRemoved(_ device: io_object_t) {
print("device removed: \(device.name() ?? "<unknown>")")
}
}
var example = usbDelegate()
I have been looking for a while now and haven't seen anything to achieve this. Help would greatly be appreciated!
You can return Array from this function
func deviceAdded(_ device: io_object_t)-> [String] {
print("device added: \(device.name() ?? "<unknown>")")
return [device.name()]
}

Error while using index method of Array class

I have created a custom class MyArray for practice and learning. I have created a few methods inside that class and an internal array. I am facing an error while using the index(of: ) method on the internal array defined inside the class. Code of my class is as below:
public class MyArray<Element> {
private var factorialNumber: Double = 0
private var arr: [Element] = []
internal var instance: MyArray?
private init() {}
public func removeElement(_ item: Element) -> [Element] {
var found = true
while found {
if arr.contains(item) { // ERROR: Missing argument label '"where:" in call
let index = arr.index(of: item) // Error: cannot invoke index with an argument list of type (of: Element)
arr.remove(at: index!)
} else {
found = false
}
}
return arr
}
public func removeFirstOccurance(of value: Element) -> [Element] {
let index : Int = arr.index(of: value)
arr.remove(at: index!) // ERROR: Cannot force wrap value of non optional type Int
return arr
}
}

Array of funcs in a Swift class instance

How can I get an array of funcs prefixed with a certain name from a Swift class instance without hardcoding those funcs' names?
e.g.
class Dialog {
init() {
}
func stepOne(session: Session) {
...
}
func stepTwo(session: Session) {
...
}
func stepThree(session: Session) {
...
}
func someOtherFunc() {
...
}
func steps() -> [(Session) -> Void] {
// should return [stepOne, stepTwo, stepThree];
}
}
The functions have to come from somewhere. They don't necessarily have to be defined in Dialog; they can be local variables inside func steps(). For instance:
func steps() -> [(Session) -> Void] {
var allSteps: [(Session) -> Void] = []
for i in 0..<3 {
let currentStep: (Session) -> Void = { session in
print("Executing step \(i)")
}
allSteps.append(currentStep)
}
return allSteps
}

Resources