PromiseKit 3.0: chaining with loops - swift2

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 {
}
}

Related

is anyone able to restrict the type of the objects dropped on the mac in SwiftUI 3?

as per the documentation, it should be pretty straightforward. example for a List: https://developer.apple.com/documentation/swiftui/list/ondrop(of:istargeted:perform:)-75hvy#
the UTType should be the parameter restricting what a SwiftUI object can receive. in my case i want to accept only Apps. the UTType is .applicationBundle: https://developer.apple.com/documentation/uniformtypeidentifiers/uttype/3551459-applicationbundle
but it doesn't work. the SwiftUI object never changes status and never accepts the drop. the closure is never run. whether on Lists, H/VStacks, Buttons, whatever. the pdf type don't seem to work either, as well as many others. the only type that i'm able to use if fileURL, which is mainly like no restriction.
i'm not sure if i'm doing something wrong or if SwiftUI is half working for the mac.
here's the code:
List(appsToIgnore, id: \.self, selection: $selection) {
Text($0)
}
.onDrop(of: [.applicationBundle, .application], isTargeted: isTargeted) { providers in
print("hehe")
return true
}
replacing or just adding .fileURL in the UTType array makes the drop work but without any type restriction.
i've also tried to use .onInsert on a ForEach instead (https://developer.apple.com/documentation/swiftui/foreach/oninsert(of:perform:)-2whxl#), and to go through a proper DropDelegate (https://developer.apple.com/documentation/swiftui/dropdelegate#) but keep getting the same results. it would seem the SwiftUI drop for macOS is not yet working, but i can't find any official information about this. in the docs it is written macOS 11.0+ so i would expect it to work?
any info appreciated! thanks.
You need to validate manually, using DropDelegate of what kind of file is dragged over.
Here is a simplified demo of possible approach. Tested with Xcode 13 / macOS 11.6
let delegate = MyDelegate()
...
List(appsToIgnore, id: \.self, selection: $selection) {
Text($0)
}
.onDrop(of: [.fileURL], delegate: delegate) // << accept file URLs
and verification part like
class MyDelegate: DropDelegate {
func validateDrop(info: DropInfo) -> Bool {
// find provider with file URL
guard info.hasItemsConforming(to: [.fileURL]) else { return false }
guard let provider = info.itemProviders(for: [.fileURL]).first else { return false }
var result = false
if provider.canLoadObject(ofClass: String.self) {
let group = DispatchGroup()
group.enter() // << make decoding sync
// decode URL from item provider
_ = provider.loadObject(ofClass: String.self) { value, _ in
defer { group.leave() }
guard let fileURL = value, let url = URL(string: fileURL) else { return }
// verify type of content by URL
let flag = try? url.resourceValues(forKeys: [.contentTypeKey]).contentType == .applicationBundle
result = flag ?? false
}
// wait a bit for verification result
_ = group.wait(timeout: .now() + 0.5)
}
return result
}
func performDrop(info: DropInfo) -> Bool {
// handling code is here
return true
}
}

Convert PublishRelay to BehaviorRelay of optional Element

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()
}

Mapping of each emit -- SwitchMap guaranteeing atleast 1 emit / ConcatMap hybrid?

im breaking my mind around how to do this in RX.
The actual usecase is mapping of LowerLevelEvent(val userId: String) to HigherLevelEvent(val user: User), where the User is provided by observable, so it can emit n times, so example output
LowerLevelEvent1(abc) -> HigherLevelEvent1(userAbc(nameVariation1)
LowerLevelEvent2(abc) -> HigherLevelEvent2(userAbc(nameVariation1)
LowerLevelEvent3(abc) -> HigherLevelEvent3(userAbc(nameVariation1)
LowerLevelEvent4(abc) -> HigherLevelEvent4(userAbc(nameVariation1)
HigherLevelEvent4(userAbc(nameVariation2)
HigherLevelEvent4(userAbc(nameVariation3)
So my naive solution was to use combineLatest. So while userId is not changed user observable is subscribed, i.e. not resubscribed when new lowerLevelEmits & its userId is not changed
val _lowerLevelEventObservable: Observable<LowerLevelEvent> = lowerLevelEventObservable
.replayingShare()
val _higherLevelEventObservable: Observable<HigherLevelEvent> = Observables
.combineLatest(
_lowerLevelEventObservable,
_lowerLevelEventObservable
.map { it.userId }
.distinctUntilChanged()
.switchMap { userRepository.findByIdObservable(it)
) { lowerLevelEvent, user -> createHigherLevelInstance... }
However this has glitch issues, since both sources in combineLatest originate from same observable.
Then I thought about
lowerLevelObservable.
.switchMap { lowerLevelEvent ->
userRepository.findByIdObservable(lowerLevelEvent.userId)
.map { user -> createHigherLevelInstance... }
}
This however can break if lowerLevelObservable emits fast, and since user observable can take some time, given lowerLevelX event can be skipped, which I cannot have. Also it resubscribes user observable each emit, which is wasteful since it wont change most likely
So, maybe concatMap? That has issue of that the user observable doesnt complete, so concatMap wouldnt work.
Anyone have a clue?
Thanks a lot
// Clarification:
basically its mapping of A variants (A1, A2..) to A' variants (A1', A2'..) while attaching a queried object to it, where the query is observable so it might reemit after the mapping was made, so AX' needs to be reemited with new query result. But the query is cold and doesnt complete
So example A1(1) -> A1'(user1), A2(1) -> A2'(user1), A3(1) -> A3'(user1) -- now somebody changes user1 somewhere else in the app, so next emit is A3'(user1')
Based on the comments you have made, the below would work in RxSwift. I have no idea how to translate it to RxJava. Honestly though, I think there is a fundamental misuse of Rx here. Good luck.
How it works: If it's allowed to subscribe it will, otherwise it will add the event to a buffer for later use. It is allowed to subscribe if it currently isn't subscribed to an inner event, or if the inner Observable it's currently subscribed to has emitted an element.
WARNING: It doesn't handle completions properly as it stands. I'll leave that to you as an exercise.
func example(lowerLevelEventObservable: Observable<LowerLevelEvent>, userRepository: UserRepository) {
let higherLevelEventObservable = lowerLevelEventObservable
.flatMapAtLeastOnce { event in // RxSwift's switchLatest I think.
Observable.combineLatest(
Observable.just(event),
userRepository.findByIdObservable(event.userId),
resultSelector: { (lowLevelEvent: $0, user: $1) }
)
}
.map { createHigherLevelInstance($0.lowLevelEvent, $0.user) }
// use higherLevelEventObservable
}
extension ObservableType {
func flatMapAtLeastOnce<U>(from fn: #escaping (E) -> Observable<U>) -> Observable<U> {
return Observable.create { observer in
let disposables = CompositeDisposable()
var nexts: [E] = []
var disposeKey: CompositeDisposable.DisposeKey?
var isAllowedToSubscribe = true
let lock = NSRecursiveLock()
func nextSubscription() {
isAllowedToSubscribe = true
if !nexts.isEmpty {
let e = nexts[0]
nexts.remove(at: 0)
subscribeToInner(e)
}
}
func subscribeToInner(_ element: E) {
isAllowedToSubscribe = false
if let key = disposeKey {
disposables.remove(for: key)
}
let disposable = fn(element).subscribe { innerEvent in
lock.lock(); defer { lock.unlock() }
switch innerEvent {
case .next:
observer.on(innerEvent)
nextSubscription()
case .error:
observer.on(innerEvent)
case .completed:
nextSubscription()
}
}
disposeKey = disposables.insert(disposable)
}
let disposable = self.subscribe { event in
lock.lock(); defer { lock.unlock() }
switch event {
case let .next(element):
if isAllowedToSubscribe == true {
subscribeToInner(element)
}
else {
nexts.append(element)
}
case let .error(error):
observer.onError(error)
case .completed:
observer.onCompleted()
}
}
_ = disposables.insert(disposable)
return disposables
}
}
}

I'm trying to delete a record out of Core Data in xCode 8/Swift 3 & latest core data syntax

I'm trying to delete an entire record out of coreData. I've retrieved the data and placed it in an array for manipulation (I have another function that lets the user edit the data using this method and it works fine) But I can't figure out how to just delete the record. [.remove(at: index)] doesn't work and neither does the code below. I can set all the fields to empty but that's not what I want, I want the record gone completely.
I went through the solutions given for similar problems but to no avail
#IBAction func Delete(_ sender: UIButton) { // The delete function
let request = NSFetchRequest<NSFetchRequestResult>(entityName: "DestinationsOne")
let context = appDelagate.persistentContainer.viewContext
var destArray = [DestinationsOne]() // The data array
do {
try destArray = context.fetch(request) as! [DestinationsOne]} //Fetching the data and placing it in the array
catch{
//error message
}
for index in (0..<destArray.count - 1){ //Go through the records
if destArray[index].destID == IDTitle!{ //Picks the record to edit
let object = destArray[index]
context.delete(object
}
appDelagate.saveContext()
}
I figured this one out. I'm posting the solution in case anyone else has the same question
func deleteRecords() -> Void { //The function to delete the record
let moc = getContext()
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "DestinationsOne")
let result = try? moc.fetch(fetchRequest)
let resultdata = result as! [DestinationsOne] // result as entity
for object in resultdata { // Go through the fetched result
if object.destID == self.IDTitle{ // If there is a match
moc.delete(object) // delete the object
}
}
do {
try moc.save() // Save the delete action
} catch let error as NSError {
print("Could not save \(error), \(error.userInfo)")
} catch {
}
}
func getContext () -> NSManagedObjectContext {
let appDelegate = UIApplication.shared.delegate as! AppDelegate
return appDelegate.persistentContainer.viewContext
}
deleteRecords() // Call the function
Why not applying a predicate to search this particular record. It's much more efficient than looping through a huge list.
func deleteRecords() { //The function to delete the record
let moc = getContext()
let fetchRequest = NSFetchRequest<DestinationsOne>(entityName: "DestinationsOne")
let predicate = NSPredicate(format: "destID == %#", self.IDTitle)
fetchRequest.predicate = predicate
do {
let resultdata = try moc.fetch(fetchRequest) // no type cast needed
if let objectToDelete = resultdata.first {
moc.delete(objectToDelete) // delete the object
try moc.save() // Save the delete action
}
} catch {
print("Could not save error: ", error)
}
}
Here are some issues with your code:
viewContext should be treated as readonly - you should use performBackgroundTask for all changes to core-data
You are fetching ALL of the entities and then then going through each one to find the one you want to delete. It is a lot faster to have core-data only fetch the one you want. You can do this by setting a predicate to the fetch request.
Instead of displaying your records by doing a fetch and using the array as a model, it is better to use a NSFetchedResultsController to do the fetch and manage the results. The fetchedResultsController will keep the data in sync when objects are changed, inserted or deleted. It also has delegate methods that will inform you when there are changes so you can update your view.
remove appDelagate.saveContext from your project. Apple's template code is wrong. You should never be writing to the viewContext so you should never have a reason to save it.
where is IDTitle being set? are you sure it is not nil?
(minor) for index in (0..<destArray.count - 1){ can be replaced with for (index, element) in destArray.enumerated() { which is clearer to read.

Implementing RxSwift with TRON

I am trying to start using RxSwift, therefore I tried to create a function that does a request and I tried to implement the rxResult() function that comes with TRON, the HTTP library I use. But the documentation on this is not very detailed. Can anyone point me in the right direction on what I am doing wrong? This is the function I have written:
static func readAllWithRx() {
let token = UserDefaults.standard.value(forKey: Constants.kTokenUserDefaultsKey) as! String
let url = URL(string: "api/url")!
let request: APIRequest<AssessmentResponse, MyAppError> = APIHelper.tron.request(url.absoluteString)
_ = request.rxResult().subscribe(onNext: { AssessmentResponse in
print("RX AssessmentResponse \(AssessmentResponse)")
}, onError: { Error in
}, onCompleted: {
}, onDisposed: {
})
}
Finally I try to call this request within my Controller using:
let read = Assessments.readAllWithRx()
There’re 2 things at the beginning:
let read = Assessments.readAllWithRx() assumes the function returns something synchronously
Implementation of readAllWithRx you posted doesn’t return anything.
I’ve never used TRON, but as far as I can see, its rxResult() returns an Observable<T> where T is a type of the response. In this case, to get asynchronously AssesmentResponse, you need to subscribe to the observable (as you already did by the way).
Here's an example - an updated implementation of your readAllWithRx (won’t compiled probably, writing code in notepad):
static func readAllWithRx() -> Observable<AssessmentResponse> {
let token = UserDefaults.standard.value(forKey: Constants.kTokenUserDefaultsKey) as! String
let url = URL(string: "api/url")!
let request: APIRequest<AssessmentResponse, MyAppError> = APIHelper.tron.request(url.absoluteString)
return request.rxResult()
}
then, if you need to get AssessmentResponse somewhere in the code:
Assessments.readAllWithRx().subscribe(onNext: { result in
// your response here
print(result)
})

Resources