Implementing RxSwift with TRON - rx-swift

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

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

PromiseKit 3.0: chaining with loops

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

Swift - closure expression syntax

i'm working with Alamofire library, i've noticed that they use this syntax
func download(method: Alamofire.Method, URLString: URLStringConvertible, headers: [String : String]? = default, #destination: Alamofire.Request.DownloadFileDestination) -> Alamofire.Request
that takes 4 parameters as input but if you go to the documentation to call the method they use the following
Alamofire.download(.GET, "http://httpbin.org/stream/100") { temporaryURL, response in
let fileManager = NSFileManager.defaultManager()
if let directoryURL = fileManager.URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask)[0] as? NSURL {
let pathComponent = response.suggestedFilename
return directoryURL.URLByAppendingPathComponent(pathComponent!)
}
return temporaryURL}
that takes only 2 parameters (method: and URLString:) i think that the param headers is optional because provide the default statement.
I don't understand how Destination is handled.
Could you please explain me how the closure is handled?
Why the curl braces is open AFTER the method call and not inside the call after the URLString param?
I really appreciate any help you can provide
Marco
It's the trailing closure technique
If a method accepts a closure as last param
class Foo {
func doSomething(number: Int, word: String, completion: () -> ()) {
}
}
You can call it following the classic way:
Foo().doSomething(1, word: "hello", completion: { () -> () in
// your code here
})
Or using the trailing closure technique:
Foo().doSomething(1, word: "hello") { () -> () in
// your code here
}
The result is the same, it just a more elegant (IMHO) syntax.

Swift URL Response is nil

I have created a custom DataManager class. Inside it I want to fetch data in a method and return an NSData object to convert to JSON afterwards.
I have tried to get the data using the completionHandler but no luck:
class func fetchData() -> NSData? {
var session = NSURLSession.sharedSession(),
result = NSData?()
let DataURL : NSURL = NSURL(string: "http://...file.json")!
let sessionTask = session.dataTaskWithURL(DataURL, completionHandler: { (data: NSData!, response: NSURLResponse!, error: NSError!) -> Void in
result = data
})
sessionTask.resume()
return result
}
The dataTask runs asynchronously. That means that the completion handler closure will not be called by the time you return from fetchData. Thus, result will not have been set yet.
Because of this, you should not try to retrieve data synchronously from an asynchronous method. Instead, you should employ an asynchronous completion handler pattern yourself:
class func fetchData(completion: #escaping (Data?, Error?) -> Void) {
let session = URLSession.shared
let url = URL(string: "http://...file.json")!
let task = session.dataTask(with: url) { data, response, error in
completion(data, error)
}
task.resume()
}
And you'd call it like so:
MyClass.fetchData { data, error in
guard let data = data, error == nil else {
print(error ?? "Unknown error")
return
}
// use `data` here; remember to dispatch UI and model updates to the main queue
}
// but do not try to use `data` here
...
FYI, for the original pre-Swift 3 syntax, see previous revision of this answer.

Swift: Error updating UI component when using a delegate

I'm trying to update a progress bar with the progress of loading a load of values into CoreData. However, whenever I try to call an update on my progressView component, I get a fatal error stating that "unexpectedly found nil while unwrapping an Optional value".
The interesting thing is that this happens even if I put 'self.progressView.progress = 0.5' in the delegate method of my program - indicating that it's the progressView component it can't find rather than an issue with the value. A quick check with println also confirms the value does exist and so isn't nil. Note that if I put the 'self.progressView.progress = 0.5' statement under a function connected directly to a button, it works fine so it must be some sort of issue with the command being called from the delegate.
Can anyone work out what I'm doing wrong here? Thanks for your help.
Delegate method:
class ViewControllerUpdate: UIViewController, NSURLSessionDelegate, NSURLSessionDownloadDelegate, saveUpdate {
[....]
func updateStatus(status: String, progress: Float?) {
if let percentProgress = progress? {
self.progressView.progress = 0.5
}
//println(progress) - NOTE THIS IS CORRECTLY POPULATED WITH THE APPROPRIATE VALUE
}
Calling class:
protocol saveUpdate {
func updateStatus(status:String, progress:Float?)
}
class sqlPullSave {
let classtoUpdate: saveUpdate = ViewControllerUpdate()
func saveTSVtoSQL(fromFile: NSURL) -> Int {
//Load up the information into a Dictionary (tsv)
//let tsvURL = NSURL(fileURLWithPath: NSBundle.mainBundle().pathForResource(fromFileName, ofType: fromFileExtension)!)
let tsvURL: NSURL = fromFile
let tab = NSCharacterSet(charactersInString: "\t")
let tsv = CSV(contentsOfURL: tsvURL, separator: tab)
//let defResult: AnyObject = tsv.rows[0]["Name"]!
//let tryagain:String = AnyObjecttoString(tsv.rows[1]["Name"]!)
//load the data into the SQLite database...
dispatch_async(dispatch_get_main_queue()) {
for a in 0..<tsv.rows.count {
self.SQLsaveLine(self.AnyObjecttoString(tsv.rows[a]["Name"]!),
name_l: "",
desc: self.AnyObjecttoString(tsv.rows[a]["1"]!),
jobTitle: self.AnyObjecttoString(tsv.rows[a]["2"]!),
extn: self.AnyObjecttoString(tsv.rows[a]["3"]!)
// update status
var percentComplete: Float = (Float(a) / Float(tsv.rows.count))
self.classtoUpdate.self.updateStatus("SQLload", progress: percentComplete)
}
}
return 0
}

Resources