I have the following code in RxSwift 4.0-based project:
private var _myRelay = PublishRelay<MyData>()
var myRelay: Observable<MyData> {
return _myRelay.asObservable()
}
Now I need to keep the last value (if any) in _myRelay, so I decided to convert it to BehaviorRelay:
private var _myRelay = BehaviorRelay<MyData?>(value: nil)
I want to keep public interface intact
var myRelay: Observable<MyData>
I mean I do not want to convert it to
var myRelay: Observable<MyData?>
The idea is not to "publish" initial value == nil to subscribers of myRelay, and start publishing only after some data appears. How can I do it? I am completely beginner in Rx, but I am sure there should be some elegant solution.
The simplest solution is to filter out the nils. If you were using 5.0 then you would do that with compactMap but since you said 4.0 that means you will need a filter and map:
var myRelay: Observable<MyData> {
return _myRelay.filter { $0 != nil }.map { $0! }.asObservable()
}
but you might actually be better off using a ReplaySubject instead of a Relay.
private let _myRelay = ReplaySubject<MyData>.create(bufferSize: 1)
var myRelay: Observable<MyData> {
return _myRelay.asObservable()
}
That way, you don't have to deal with nils at all. (Also note, _myRelay should be a let not a var.)
Doing the above will also allow you to emit a completed event when the observable goes out of scope (a relay doesn't allow that.)
private let _myRelay = ReplaySubject<MyData>.create(bufferSize: 1)
var myRelay: Observable<MyData> {
return _myRelay.asObservable()
}
deinit {
_myRelay.onCompleted()
}
Related
This is an example of our use case:
We have a selectedIndex and a list of items.
class FoosViewModel {
let selectedIndex = Variable<Int>(0)
let items: [Foo] = ... // assume that this is initialized properly
}
In reality, we often care about which item is selected instead of the index of the selected item. So we'll have code like this:
selectedIndex.asObservable().subscribe(onNext: { [weak self] index in
guard let self = self else { return }
let selectedItem = items[index]
// Do sth with `selectedItem` here
}
Notice that the value of selectedItem is always driven by selectedIndex. Therefore, we change the code to the following:
class FoosViewModel {
let selectedIndex = Variable<Int>(0)
let selectedItem = Variable<Int>(items[0])
let items: [Foo] = ... // assume that this is initialized properly
init() {
selectedIndex.asObservable().subscribe(onNext: { [weak self] index in
guard let self = self else { return }
self.selectedItem = items[index]
}
}
}
This seems to be a common enough use case. Do we have an existing operator in Rx that can map a Variable to another? Is there sth like this:
class FoosViewModel {
let selectedIndex = Variable<Int>(0)
let selectedItem = selectedIndex.map{ items[$0] }
let items: [Foo] = ... // assume that this is initialized properly
}
What you have done is created two bits of state that are dependent on each other. It would be better to just have one source of truth and a derivative which means that one should be implemented differently than the other. Assuming that selectedIndex is the source of truth, then I would expect to see:
class FoosViewModel {
let selectedIndex = Variable<Int>(0)
let selectedItem: Observable<Foo?>
let items: [Foo]
init(items: [Foo]) {
selectedItem = selectedIndex.asObservable().map { index in
index < items.count ? items[$0] : nil
}
self.items = items
}
}
Unlike in your attempt, there is no temptation for a user of this class to try to assign a new value to selectedItem (in fact, the code won't even compile if you try.) As a side benefit, there is no need to do the "weak self dance" either since the map doesn't refer to self at all. All of this works because you made items a let rather than a var (good for you!)
If you wanted to be able to add/remove items then things get a bit more complex...
class MutableFooViewModel {
let selectedIndex = Variable<Int>(0)
let selectedItem: Observable<Foo?>
let items = Variable<[Foo]>([])
init(items: [Foo]) {
items.value = items
let _items = self.items // this is done to avoid reference to `self` in the below.
selectedItem = Observable.combineLatest(
_items.asObservable(),
selectedIndex.asObservable()
) { items, index in
index < items.count ? items[index] : nil
}
}
}
The idea here is that Subjects (Variable is a kind of subject) should not be the first thing you think of when making an Observable that depends on some other observable. In this respect, they are only good for creating the initial observable. (RxCocoa is full of them.)
Oh and by the way, Variable had been deprecated.
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, ¶m)
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, ¶m)
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
}
}
I'm using promisekit 3.0 to help chain alamofire callbacks in a clean way. The objective is to start with a network call, with a promise to return an array of urls.
Then, I'm looking to execute network calls on as many of those urls as needed to find the next link i'm looking for. As soon as this link is found, I can pass it to the next step.
This part is where I'm stuck.
I can pick an arbitrary index in the array that I know has what I want, but I can't figure out the looping to keep it going until the right information is returned.
I tried learning from this obj-c example, but i couldn't get it working in swift.
https://stackoverflow.com/a/30693077/1079379
He's a more tangible example of what i've done.
Network.sharedInstance.makeFirstPromise(.GET, url: NSURL(string: fullSourceLink)! )
.then { (idArray) -> Promise<AnyObject> in
let ids = idArray as! [String]
//how do i do that in swift? (from the example SO answer)
//PMKPromise *p = [PMKPromise promiseWithValue: nil]; // create empty promise
//only thing i could do was feed it the first value
var p:Promise<AnyObject> = Network.sharedInstance.makePromiseRequestHostLink(.POST, id: ids[0])
//var to hold my eventual promise value, doesn't really work unless i set it to something first
var goodValue:Promise<AnyObject>
for item in ids {
//use continue to offset the promise from before the loop started
continue
//hard part
p = p.then{ returnValue -> Promise<AnyObject> in
//need a way to check if what i get is what i wanted then we can break the loop and move on
if returnValue = "whatIwant" {
goodvalue = returnValue
break
//or else we try again with the next on the list
}else {
return Network.sharedInstance.makeLoopingPromise(.POST, id: item)
}
}
}
return goodValue
}.then { (finalLink) -> Void in
//do stuck with finalLink
}
Can someone show me how to structure this properly, please?
Is nesting promises like that anti-pattern to avoid? In that case, what is the best approach.
I have finally figured this out with a combination of your post and the link you posted. It works, but I'll be glad if anyone has input on a proper solution.
func download(arrayOfObjects: [Object]) -> Promise<AnyObject> {
// This stopped the compiler from complaining
var promise : Promise<AnyObject> = Promise<AnyObject>("emptyPromise")
for object in arrayOfObjects {
promise = promise.then { _ in
return Promise { fulfill, reject in
Service.getData(stuff: object.stuff completion: { success, data in
if success {
print("Got the data")
}
fulfill(successful)
})
}
}
}
return promise
}
The only thing I'm not doing is showing in this example is retaining the received data, but I'm assuming you can do that with the results array you have now.
The key to figuring out my particular issue was using the "when" function. It keeps going until all the calls you inputted are finished. The map makes it easier to look at (and think about in my head)
}.then { (idArray) -> Void in
when(idArray.map({Network.sharedInstance.makePromiseRequest(.POST, params: ["thing":$0])})).then{ link -> Promise<String> in
return Promise { fulfill, reject in
let stringLink:[String] = link as! [String]
for entry in stringLink {
if entry != "" {
fulfill(entry)
break
}
}
}
}.then {
}
}
Currently I'm working on my new App written with Swift 2.0. Today I faced two strange errors in Xcode beta 5. I'd love if someone with a previous beta version of Xcode can confirm if I'm right or not. I also could misunderstand something, so I'll appreciate any feedback.
Here is some example code that made me struggle a while:
// Frist bug
protocol SomeProtocol {
var someArray: [String] { get set } // #1 bug
}
extension SomeProtocol {
func someFunction(someString: String) {
self.someArray.append(someString) // #1: Immutable value of type '[String]' only has mutating members named 'append'
}
}
// Second bug
protocol SomeInterfaceProtocol {
var someBool: Bool { get set } // #2 bug
}
class SomeClass: SomeInterfaceProtocol {
var someBool: Bool = false
func returnInterface() -> SomeInterfaceProtocol {
return self
}
}
let someInstance = SomeClass()
// can't set the value
someInstance.returnInterface().someBool = true // #2: Cannot assign to property: function call returns immutable value
The first error can be solved if you add the modifier mutating before the extension func declaration like this:
mutating func someFunction(someString: String) {
I suspect that's a change in the language.
The other one puzzles me as well. At least, here's a work-around:
var c = someInstance.returnInterface()
c.someBool = true
I think the second one isn't a bug as well for the same reason that you can't modify an item in a dictionary directly, or that you can't change elem in for elem in array { ... }.
Something has to be saved to be able to change it. Because you're returning the protocol type, the compiler can't know whether it's a struct or a class, whereas if it's a struct the operation of changing it would have no effect because it's not persisted in any way and structs aren't passed by reference. That's why Thomas' workaround works. Maybe it'll work too if returnInterface returned a class instance, instead of the protocol type.
EDIT: Just tried it out: Indeed it works either if you return SomeClass instead of SomeInterfaceProtocol or if you change the protocol to a class protocol, as it can't be a struct
protocol SomeInterfaceProtocol : class {
var someBool: Bool { get set }
}
class SomeClass: SomeInterfaceProtocol {
var someBool: Bool = false
func returnInterface() -> SomeInterfaceProtocol {
return self
}
}
or
protocol SomeInterfaceProtocol {
var someBool: Bool { get set }
}
class SomeClass: SomeInterfaceProtocol {
var someBool: Bool = false
func returnInterface() -> SomeClass {
return self
}
}
both work
I'm trying to have variables in swift that are critical app-wide user settings so they must be persisted to disk after every change. There is a small amount of these variables and I'm content with the first read happening from disk after the app starts.
I have code that looks similar to this:
var _myEnumMember:MyEnum?
var myEnumMember:MyEnum {
get {
if let value = _myEnumMember { // in memory
return value
}
var c:Cache = Cache()
var storedValue:MyEnum? = c.get("SomeStorageKey");
if let value = storedValue { // exists on disk
self.myEnumMember = value // call setter to persist
return self.myEnumMember // call getter again with value set
}
self.myEnumMember = .DefaultValue // assign via setter
return self.rankingDuration // call getter after `set`
}
set (newValue){
self._myEnumMember = newValue // assign to memory
var c:Cache = Cache()
c.put("SomeStorageKey", data: ser) // store in disk
}
I have about 5-6 properties that need to do this - I don't want to repeat myself over and over - is there any way to DRY this code up so I won't have to repeat this logic in several places?
(Note: Asking here and not CR.SE because I'd like answers to explain how to DRY getters/setters in these situations rather than receive critique on a particular piece of code)
I was working on something similar recently - this may be your best bet. I used this as a nested struct, but it doesn't strictly need to be nested.
First, define a LocalCache type that will handle the persistence of your properties:
struct LocalCache {
// set up keys as constants
// these could live in your class instead
static let EnumKey = "EnumKey"
static let IntKey = "IntKey"
static let StringKey = "StringKey"
// use a single cache variable, hopefully?
var cache = Cache()
// in-memory values go in a Dictionary
var localValues: [String: Any] = [:]
// fetch from local storage or from disk
// note that the default value also sets the return type
mutating func fetch<T>(key: String, defaultValue: T) -> T {
if localValues[key] == nil {
localValues[key] = cache.get(key) ?? defaultValue
}
return localValues[key]! as T
}
// save in both local storage and to disk
mutating func store(key: String, _ value: Any) {
localValues[key] = value
cache.put(key, data: value)
}
}
Then add a LocalCache instance to your class, and you can have much simpler getter/setters.
class Test {
// instance of the local cache
var localCache = LocalCache()
var enumPropery: MyEnum {
get { return localCache.fetch(LocalCache.EnumKey, defaultValue: MyEnum.DefaultValue) }
set { localCache.store(LocalCache.EnumKey, newValue) }
}
var intProperty: Int {
get { return localCache.fetch(LocalCache.IntKey, defaultValue: 0) }
set { localCache.store(LocalCache.IntKey, newValue) }
}
var stringProperty: String {
get { return localCache.fetch(LocalCache.StringKey, defaultValue: "---") }
set { localCache.store(LocalCache.StringKey, newValue) }
}
}
If you're using swift in an iOS or OS X context then NSUserDefaults are ABSOLUTELY the right way to do this.
https://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Classes/NSUserDefaults_Class/index.html
http://www.codingexplorer.com/nsuserdefaults-a-swift-introduction/