Problem about the usage of `withLatestFrom` - rx-swift

I am a newbie to RxSwift. I ran into a problem
The requirement is like this,
If there are values return from stream A, B and C then I emit an event on stream X (boolean)
Whenever there is true value returns from streamX, I want to take a take a snapshot of stream A, B and C and do something
Due to class design reason, we need to separate the action 1 and 2 into two Rx blocks
below is the simplified version of my code
class parent {
let streamA: BehaviorRelay<String?> = BehaviorRelay(value: nil)
let streamB: BehaviorRelay<String?> = BehaviorRelay(value: nil)
let streamC: BehaviorRelay<String?> = BehaviorRelay(value: nil)
let moduleVisibility: BehaviorRelay<Bool?> = BehaviorRelay(value: nil)
BehaviorRelay.combineLatest(streamA.filterNil(), streamB.filterNil(), streamB.filterNil()).subscribe { [weak self ] _, _, _ in
guard let self = self else { return }
self.moduleVisibility.accept(true)
}.disposed(by: bag)
}
class childClass {
moduleVisibility.filterNil().filter({ $0 == true}).withLatestFrom(Observable.combineLatest(streamA.filterNil(), streamB.filterNil(), streamC.filterNil())).observeOn(MainScheduler.instance).subscribe(onNext: { valueA, valueB, valueC in
// This line does not get called
print("\(valueA) \(valueB) \(valueC)")
}).disposed(by: bag)
}
// then I called
streamA.accept("A")
streamB.accept("B")
streamC.accept("C")
// but the block # childClass does not get fired
I check there is event from the moduleVisibility stream. but it says that there is not value from withLatestFrom block.
Would like to know if my block setup is not correct.
Cause I think A B C are causing the event of X
while there is event from X, there must be values from A B C
or I need to observe on main.thread? any help will be appreciated :pray

formatted code
import RxSwift
import RxCocoa
func ignoreNil<A>(x: A?) -> Observable<A> {
return x.map { Observable.just($0) } ?? Observable.empty()
}
class Parent {
let bag = DisposeBag()
let streamA: BehaviorRelay<String?> = BehaviorRelay(value: nil)
let streamB: BehaviorRelay<String?> = BehaviorRelay(value: nil)
let streamC: BehaviorRelay<String?> = BehaviorRelay(value: nil)
let moduleVisibility: BehaviorRelay<Bool?> = BehaviorRelay(value: nil)
init() {
BehaviorRelay
.combineLatest(
streamA.flatMap(ignoreNil),
streamB.flatMap(ignoreNil),
streamC.flatMap(ignoreNil)
).subscribe { [weak self ] _, _, _ in
guard let self = self else { return }
print("moduleVisibility ")
self.moduleVisibility.accept(true)
}.disposed(by: bag)
}
}
class Child: Parent {
override init() {
super.init()
moduleVisibility
.flatMap(ignoreNil)
.filter({ $0 == true})
.withLatestFrom(
Observable.combineLatest(
streamA,
streamB,
streamC
)
)
.subscribe(onNext: { valueA, valueB, valueC in
// This line does not get called
print("\(valueA) \(valueB) \(valueC)")
}).disposed(by: bag)
}
}
let child = Child()
child.streamA.accept("A")
child.streamB.accept("B")
child.streamC.accept("C")
/*
moduleVisibility
Optional("A") Optional("B") nil
*/

Notice in the code below how the functions I created mirror the text descriptions almost exactly. That is what you should be striving for. No need for any classes here, just a couple of functions (these functions are your view models.) Notice how they are very reusable...
// If there are values return from stream A, B and C then I emit an event on stream X (boolean)
func allEmitedValue<A, B, C>(_ a: Observable<A?>, _ b: Observable<B?>, _ c: Observable<C?>) -> Observable<Bool> {
Observable.combineLatest(a, b, c) { allContainValue($0, $1, $2) }
}
// Whenever there is true value returns from streamX, I want to take a take a snapshot of stream A, B and C and do something
func snapshot<A, B, C>(_ a: Observable<A?>, _ b: Observable<B?>, _ c: Observable<C?>) -> Observable<(A, B, C)> {
Observable.combineLatest(a, b, c)
.compactMap{ allContainValue($0.0, $0.1, $0.2) ? ($0.0!, $0.1!, $0.2!) : nil }
}
func allContainValue<A, B, C>(_ a: A?, _ b: B?, _ c: C?) -> Bool {
a != nil && b != nil && c != nil
}
If the architecture requires that you put these in classes with relays then:
class parent {
let streamA: BehaviorRelay<String?> = BehaviorRelay(value: nil)
let streamB: BehaviorRelay<String?> = BehaviorRelay(value: nil)
let streamC: BehaviorRelay<String?> = BehaviorRelay(value: nil)
let moduleVisibility: Observable<Bool>
init() {
moduleVisibility = allEmitedValue(streamA.asObservable(), streamB.asObservable(), streamC.asObservable())
}
}
class childClass: parent {
let bag = DisposeBag()
override init() {
super.init()
snapshot(streamA.asObservable(), streamB.asObservable(), streamC.asObservable())
.observe(on: MainScheduler.instance)
.subscribe(onNext: { valueA, valueB, valueC in
// now it gets called.
print("\(valueA) \(valueB) \(valueC)")
})
.disposed(by: bag)
}
}
But note:
Avoid the use of the subject types [Including Relays]. Rx is effectively a functional programming paradigm. Using subjects [or Relays] means we are now managing state, which is potentially mutating. Dealing with both mutating state and asynchronous programming at the same time is very hard to get right. Furthermore, many of the operators (extension methods) have been carefully written to ensure that correct and consistent lifetime of subscriptions and sequences is maintained; when you introduce subjects, you can break this. -- Intro to Rx
Specifically in this case, that observe(on:) is woefully inadequate in ensuring that this code handles threads correctly.

Related

Swift 2 to swift 3 conversion Midi Input

I'm hoping someone may be able to help i'm using Xcode 8 and swift 3
I have a playground file Xcode 7 swift 2 that involves a Midi callback for Midi Input everything works fine in 7
I tried a conversion to 8 and it brought up errors regarding memory and a few name changes mostly of what i believe to be non serious i also redefined the infinite loop using PlaygroundSupport
However the error i cannot get over involves MyMIDIReadProc at
MIDIInputPortCreate(midiClient, "MidiTest_InPort", MyMIDIReadProc, nil, &inPort);
The error says
Cannot convert value of type '(pktList: UnsafePointer, readProcRefCon: UnsafeMutablePointer, srcConnRefCon: UnsafeMutablePointer) -> Void' to expected argument type 'MIDIReadProc' (aka '#convention(c) (UnsafePointer, Optional>, Optional>) -> ()')
My understanding is that it needs a #convention(c) wrapper of some description inserted. I think i'm on the right track because you can wrap a function but my knowledge of where to put it has run out. Again i was hoping some one might be able to advise
Thanks for reading
apologies for any bad language as i'm self taught
Here is the original Xcode 7 code
import Cocoa
import CoreMIDI
import XCPlayground
func getDisplayName(obj: MIDIObjectRef) -> String
{
var param: Unmanaged<CFString>?
var name: String = "Error";
let err: OSStatus = MIDIObjectGetStringProperty(obj, kMIDIPropertyDisplayName, &param)
if err == OSStatus(noErr)
{
name = param!.takeRetainedValue() as String
}
return name;
}
func MyMIDIReadProc(pktList: UnsafePointer<MIDIPacketList>,
readProcRefCon: UnsafeMutablePointer<Void>, srcConnRefCon: UnsafeMutablePointer<Void>) -> Void
{
let packetList:MIDIPacketList = pktList.memory;
let srcRef:MIDIEndpointRef = UnsafeMutablePointer<MIDIEndpointRef>(COpaquePointer(srcConnRefCon)).memory;
print("MIDI Received From Source: \(getDisplayName(srcRef))");
var packet:MIDIPacket = packetList.packet;
for _ in 1...packetList.numPackets
{
let bytes = Mirror(reflecting: packet.data).children;
var dumpStr = "";
// bytes mirror contains all the zero values in the ridiulous packet data tuple
// so use the packet length to iterate.
var i = packet.length;
for (_, attr) in bytes.enumerate()
{
dumpStr += String(format:"$%02X ", attr.value as! UInt8);
--i;
if (i <= 0)
{
break;
}
}
print(dumpStr)
packet = MIDIPacketNext(&packet).memory;
}
}
var midiClient: MIDIClientRef = 0;
var inPort:MIDIPortRef = 0;
var src:MIDIEndpointRef = MIDIGetSource(0);
MIDIClientCreate("MidiTestClient", nil, nil, &midiClient);
MIDIInputPortCreate(midiClient, "MidiTest_InPort", MyMIDIReadProc, nil, &inPort);
MIDIPortConnectSource(inPort, src, &src);
// Keep playground running
XCPlaygroundPage.currentPage.needsIndefiniteExecution = true;
And here is the Xcode 8 code converted
var str = "Hello, playground"
import Cocoa
import CoreMIDI
import XCPlayground
import PlaygroundSupport
func getDisplayName(obj: MIDIObjectRef) -> String
{
var param: Unmanaged<CFString>?
var name: String = "Error";
let err: OSStatus = MIDIObjectGetStringProperty(obj, kMIDIPropertyDisplayName, &param)
if err == OSStatus(noErr)
{
name = param!.takeRetainedValue() as String
}
return name;
}
func MyMIDIReadProc(pktList: UnsafePointer<MIDIPacketList>,
readProcRefCon: UnsafeMutablePointer<Void>, srcConnRefCon: UnsafeMutablePointer<Void>) -> Void
{
let packetList:MIDIPacketList = pktList.pointee;
let srcRef:MIDIEndpointRef = UnsafeMutablePointer<MIDIEndpointRef>(OpaquePointer(srcConnRefCon)).pointee;
print("MIDI Received From Source: \(getDisplayName(obj: srcRef))");
var packet:MIDIPacket = packetList.packet;
for _ in 1...packetList.numPackets
{
let bytes = Mirror(reflecting: packet.data).children;
var dumpStr = "";
var i = packet.length;
for (_, attr) in bytes.enumerated()
{
dumpStr += String(format:"$%02X ", attr.value as! UInt8);
i -= 1;
if (i <= 0)
{
break;
}
}
print(dumpStr)
packet = MIDIPacketNext(&packet).pointee;
}
}
var midiClient: MIDIClientRef = 0;
var inPort:MIDIPortRef = 0;
var src:MIDIEndpointRef = MIDIGetSource(0);
MIDIClientCreate("MidiTestClient", nil, nil, &midiClient);
MIDIInputPortCreate(midiClient, "MidiTest_InPort", MyMIDIReadProc, nil, &inPort);
MIDIPortConnectSource(inPort, src, &src);
PlaygroundPage.current.needsIndefiniteExecution = true
Pointer types are drastically changed in Swift 3. Many C-based APIs' signatures are changed accordingly.
Following those changes manually would be painful. You can make Swift work for you, with a little modification.
Try changing the function header:
func MyMIDIReadProc(pktList: UnsafePointer<MIDIPacketList>,
readProcRefCon: UnsafeMutablePointer<Void>, srcConnRefCon: UnsafeMutablePointer<Void>) -> Void
{
to a closure declaration:
let MyMIDIReadProc: MIDIReadProc = {pktList, readProcRefCon, srcConnRefCon in
Swift infers argument types perfectly in this style.
You may need to fix pointer type conversion:
let srcRef:MIDIEndpointRef = UnsafeMutablePointer<MIDIEndpointRef>(OpaquePointer(srcConnRefCon)).pointee;
to something like this:
//I'm not sure using `!` is safe here...
let srcRef: MIDIEndpointRef = UnsafeMutablePointer(srcConnRefCon!).pointee
(By the way, the equivalent part in your Xcode 7 code is a little bit redundant. You have no need to use intermediate COpaquePointer there.)
In Swift 3, pointers cannot be nil, and nullable pointers are represented with Optionals. You may need many other fixes to work with C-based APIs in Swift 3.
OOPer is pointing (ahem) you in the right direction. Here is a blog post on using Swift 3 Core MIDI along with a working github repo.
Assuming that you're working with CoreMIDI 1.3 or later, you may have more luck using MIDIInputPortCreateWithBlock instead of MIDIInputPortCreate.
This method takes a Swift block as a parameter instead of requiring an #convention(c) function reference, making it more amenable to use within methods belonging to Swift classes, e.g.:
public func midiReadBlock(ptr: UnsafePointer<MIDIPacketList>, _: UnsafeMutableRawPointer?) -> Void {
let list: MIDIPacketList = ptr.pointee
...
}
You may also find these two extensions useful.
This one (derived from here) allows you to iterate directly over a MIDIPacketList using for pkt in list:
extension MIDIPacketList: Sequence {
public func makeIterator() -> AnyIterator<MIDIPacket> {
var iterator: MIDIPacket?
var nextIndex: UInt32 = 0
return AnyIterator {
nextIndex += 1
if nextIndex > self.numPackets { return nil }
if iterator != nil {
iterator = withUnsafePointer(to: &iterator!) { MIDIPacketNext($0).pointee }
} else {
iterator = self.packet;
}
return iterator
}
}
}
and this one adds a method to a MIDIPacket to extract the contents as a [UInt8] instead of having to use the really broken tuple syntax:
extension MIDIPacket {
public var asArray: [UInt8] {
let mirror = Mirror(reflecting: self.data)
let length = Int(self.length)
var result = [UInt8]()
result.reserveCapacity(length)
for (n, child) in mirror.children.enumerated() {
if n == length {
break
}
result.append(child.value as! UInt8)
}
return result
}
}

Swift 3: how to write this for(;;) loop

Looks like Apple doesn't like C loops, but doesn't provide good approach over it (or I couldn't find it). I have such loop to go from some view to the root in UI hierarchy:
for var parentView = view; parentView != nil; parentView = parentView.parent {
...
}
How to write this in Swift 3 manner?
This would be a way to do it in Swift 3:
var parentView: View! = view
while parentView != nil {
// Do stuff
parentView = parentView.parent
}
If you want to group the loop progression stuff next to while and not at the end of block, you may use defer, like this:
var parentView: View! = view
while parentView != nil {
defer { parentView = parentView.parent }
// Do stuff
}
If you want to limit the scope of parentView, you can encapsulate everything in a do block:
do {
var parentView: View! = view
while parentView != nil {
defer { parentView = parentView.parent }
// Do stuff
}
}
But it's quite verbose so you could define a new generic function for similar loops, like this:
func kindaCStyleLoop<T>(first: T, obtainNext: T -> T?, action: T -> ()) {
var current: T! = first
repeat {
action(current)
current = obtainNext(current)
} while current != nil
}
kindaCStyleLoop(view, obtainNext: { $0.parent }) {
// Do stuff with $0
}
And a last one that relies on GeneratorType and SequenceType to enable using the for-in-loop syntax:
struct CStyleGenerator<T> : GeneratorType, SequenceType {
let getNext: T -> T?
var current: T!
init(first: T, getNext: T -> T?) {
self.getNext = getNext
self.current = first
}
mutating func next() -> T? {
defer {
if current != nil {
current = getNext(current)
}
}
return current
}
}
for parentView in CStyleGenerator(first: view, getNext: { $0.parent }) {
// Do stuff with parentView
}
Correct but too-late answer: there are built-in functions in Swift 3 which provide the solution:
public func sequence<T>(first: T, next: (T) -> T?) -> UnfoldSequence<T, (T?, Bool)>
public func sequence<T, State>(state: State, next: (inout State) -> T?) -> UnfoldSequence<T, State>
We can use them in this manner:
sequence(first: view, next: {
// do something with $0...
return $0.superview
})
For instance
for view in views where view.superview != nil {
}

iOS DynamoDB Object Mapper load does not return all the attributes

I'm running below function in my iPad app to get an Item by it's hash key (IdName).
This table only contains an Hashkey (No Range key available) but when I run this, It returns an result object which contains only the HashKey Value (The same which I pass). The other property (IdValue) is not there in the result. What am I doing wrong here?
func getCurrentFinalReportNumber()->Int
{
let dynamoDBObjectMapper = AWSDynamoDBObjectMapper.defaultDynamoDBObjectMapper()
var currentId :Int = -1
dynamoDBObjectMapper .load(DDBIDStoreTableRow.self, hashKey: "FinalReportNumber", rangeKey: nil) .continueWithExecutor(AWSExecutor.mainThreadExecutor(), withBlock: { (task:AWSTask!) -> AnyObject! in
if (task.error == nil) {
if (task.result != nil) {
let resultRow :DDBIDStoreTableRow = task.result as! DDBIDStoreTableRow
print(resultRow.IdValue)
currentId = Int(resultRow.IdValue!)
}
} else {
print("Error: \(task.error)")
let alert = SCLAlertView()
alert.addButton("Close", colorButtonBackground: Constants.InspectionTypes.colorClose) {}
alert.showCloseButton = false
alert.showError("", subTitle: (task.error?.description)!)
}
return nil
}).waitUntilFinished()
return currentId
}
class DDBIDStoreTableRow :AWSDynamoDBObjectModel ,AWSDynamoDBModeling {
var IdName:String? //HK
var IdValue:Int? = -1
class func dynamoDBTableName() -> String! {
return AWSIDStoreDynamoDBTableName
}
class func hashKeyAttribute() -> String! {
return "IdName"
}
//MARK: NSObjectProtocol hack
override func isEqual(object: AnyObject?) -> Bool {
return super.isEqual(object)
}
override func `self`() -> Self {
return self
}
}
Just found the mistake. Problem is in the data type of the mapping class. Earlier it was
var IdValue:Int? = -1
But once I change the Int to NSNumber as below. It started to work fine
var IdValue:NSNumber? = -1

swift function or closure with pre-filled parameter

I have a class that takes a completion handler. Can I pre-fill some the parameters? The completion handler is a function itself rather than a closure.
func completionHandler(value: Int, value2: Int)
{
print(value + value2)
}
func run() {
let handler = completionHandler
handler(9, value2: 7) //runs the handler
someinstance.handler = handler //someinstance will eventually run the handler
let handler2 = completionHandler(9) //is this possible?
someinstance2.handler = handler2 //is this possible?
someinstance3.handler = { a,b in return a+b } //also fine
}
You could, technically, define a handler for you completion handler, for cases when you want to make use of a default value for value or value2 in completionHandler.
func completionHandler(value: Int, _ value2: Int) {
print(value + value2)
}
func defaultValueHandler(defaultValue: Int? = nil, defaultValue2: Int? = nil) -> ((Int) -> ()) {
if let defaultValue2 = defaultValue2 {
return { value in completionHandler(value, defaultValue2) }
}
else if let defaultValue = defaultValue {
return { value in completionHandler(defaultValue, value) }
}
return { _ in print("Invalid use: supply a single non-nil default value.") }
}
var handler = defaultValueHandler(9) // default value for 'value'
handler(5) // calls foo(9, 5), prints 14
handler = defaultValueHandler(nil, defaultValue2: 11) // default value for 'value2'
handler(5) // calls foo(5, 11), prints 16
The use of this is, for your case, probably limited in practice. The handler closure instance above will be of type (Int) -> () as compared to e.g. completionHandler function which is of type (Int, Int) -> (). So if someinstance.handler (in your example) expects the latter, you'll be in trouble.
Now, you could modify defaultValueHandler(..) to return closures of type (Int, Int) -> (), but then you'll need to supply calls to the resulting handling closure with two arguments, where one argument will be ignored in favour of the default value you supplied when assigning a closure to the handler. Again, this technically possibly, but will most likely just confuse whomever codes, and lives near the subject of "troll coding" (e.g. not-so-appriciated 1st april insertion into the code of a colleague, "handler(2,2) == 4 // false ?!?"). Anyway:
func completionHandler(value: Int, _ value2: Int) {
print(value + value2)
}
func defaultValueHandler(defaultValue: Int? = nil, defaultValue2: Int? = nil) -> ((Int, Int) -> ()) {
if let defaultValue2 = defaultValue2 {
if defaultValue == nil {
return { value, _ in completionHandler(value, defaultValue2) }
}
}
else if let defaultValue = defaultValue {
return { _, value2 in completionHandler(defaultValue, value2) }
}
return { _ in print("Invalid use: supply a single non-nil default value.") }
}
var handler = defaultValueHandler(9)
handler(0,5) // ignores first parameter '0', calls foo(9, 5), prints 14
handler = defaultValueHandler(nil, defaultValue2: 11)
handler(5,0) // ignores second parameter '0', calls foo(5, 11), prints 16

Swift 2 , CLGeocoder() and placemarks

I want to get the address of the GPS signal and I write that code: `
CLGeocoder().reverseGeocodeLocation(userLocation) { (placemarks, error) -> Void in
if error != nil {
print("Κάτι δεν πήγε καλά με το GPS!")
} else {
if let p = CLPlacemark(placemark: placemarks?[0] as! CLPlacemark) {
print(p)
}
}
The error is that: Downcast from 'CLPlacemark?' to 'CLPlacemark' only unwraps optionals; did you mean to use '!'? . Where do I have the error?
No need to initialize a CLPlacemark like this. This async method returns an array of CLPlacemarks and this array is optional. So you need to unwrap the array if it exists and return the first element of that array which is already a CLPlacemark.
if let validPlacemark = placemarks?[0]{
print(validPlacemark)
}
'[CLPlacemark]?' is not convertible to '[CLPlacemark]' -> swift2

Resources