Swift 4.2.1 requesting JSON with Xcode 10.1 - swift4.2

My code:
let cgpurl = URL(string: "https://api.coingecko.com/api/v3/ping")!
let task = URLSession.shared.dataTask(with: cgpurl) { (Data, URLResponse, Error) in
if let data = Data, let string = String(data: data, encoding: .utf8) {
let CGPing = string } ; resume() }
The problem is with the 2nd use of "cgpurl". I've tried changing case to no effect. The error I'm getting is, "Cannot use instance member 'cgpurl' within property initializer; property initializers run before 'self' is available". Ok... but I can't even replace cgpurl with the actual link? Then I get the error message "Ambiguous reference to member 'dataTask(with:completionHandler:)'" I realize this release of swift was supposed to be "small" & just to "fix errors" but I've not been able to find any current documentation on this release. I'm using swift 4.2.1 with Xcode 10.1

This code was taken directly from a teaching manual for Swift 4.2
No, it wasn't. The code you have was never right, in Swift 4.2 or any other version of Swift. You have blindly copied and pasted perhaps, without looking at the overall context.
The problem is that the code, as you have it, is sitting "loose" at the top of your view controller or other class declaration, perhaps something along these lines:
class MyViewController : UIViewController {
let cgpurl = // ...
let task = // ...
}
That's wrong. The most basic rule of Swift programming is that executable code can exist only in a function. For example:
class MyViewController : UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let cgpurl = // ...
let task = // ...
}
}
That may not solve all your issues, but at least you'll get past the most basic mistake you're making and the "Cannot use instance member" compile error will go away.

Related

error 'value of type Realm.Configuration' has no member path', swift 2.2

I have a red alert error 'value of type 'Realm.Configuration 'has no member 'path'.
I have not yet run the project as I'm building it, copying files and settings exactly from another project which has realm running fine.
Any ideas?
The error is on the line
print(Realm.Configuration.defaultConfiguration.path!)
it is on my MapViewController (a long swift file with a lot of code so I'm not sure which part to copy here. The top of the file has 'import RealmSwift', which is in my pod file.
the code block is
override func viewDidLoad() {
super.viewDidLoad()
title = "Map"
locationManager.delegate = self
if CLLocationManager.authorizationStatus() == .NotDetermined {
locationManager.requestWhenInUseAuthorization()
} else {
locationManager.startUpdatingLocation()
}
print(Realm.Configuration.defaultConfiguration.path!)
populateMap()
}
The Realm.Configuration.path property was changed from a String? to a fileURL: NSURL? property in Realm 0.99.0. It's likely that you're using a more recent version of Realm than that, which removed the path property.
So change path to fileURL.
Try to use
Realm.Configuration.defaultConfiguration.fileURL!

Call to swift method from JavaScript hangs xcode and application

I am writing an iOS App (using xcode 7.3 and swift 2.2) using JavascriptCode framework. Calling javascript methods from swift works perfect, but when I call the swift method from javascript, xcode simply shows a "loading" type of symbol and nothing happens. I need to "force quit" xcode to get out of this state.
I have followed https://www.raywenderlich.com/124075/javascriptcore-tutorial and http://nshipster.com/javascriptcore/ and I am trying pretty simple calls.
Has anyone faced this kind of issue?
My swift code is as follows:
#objc protocol WindowJSExports : JSExport {
var name: String { get set }
func getName() -> String
static func createWindowWithName(name: String) -> WindowJS
}
#objc class WindowJS : NSObject, WindowJSExports {
dynamic var name: String
init(name: String) {
self.name = name
}
class func createWindowWithName(name: String) -> WindowJS {
return WindowJS(name: name)
}
func getName() -> String {
NSLog("getName called from JS context")
return "\(name)"
}
}
I am initializing the context as follows:
runContext = JSContext()
runContext.name = "test_Context"
windowToJs = WindowJS(name: "test")
runContext.setObject(windowToJs.self, forKeyedSubscript: "WindowJS")
If I replace the last two lines in above code with below code without instantiating it, the code simply fails to load.
runContext.setObject(WindowJS.self, forKeyedSubscript: "WindowJS")
And the javascript code is as simple as
function check() {
return WindowJS.getName()
}
I do see the breakpoint being hit in the JS function check and when the WindowJS.getName gets called, xcode simply becomes unresponsive.
The setTimeout could be solved by adding following piece of code to my swift function.
let setTimeout: #convention(block) (JSValue, Int) -> () =
{ callback, timeout in
let timeVal = Int64(timeout)
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, timeVal), dispatch_get_main_queue(), { callback.callWithArguments(nil)})
}
To expose this native code to the JS context, I also added following.
runContext.setObject(unsafeBitCast(setTimeout, AnyObject.self), forKeyedSubscript: "setTimeout")
Things then worked fine.
You're creating a deadlock since you are calling from Swift to JavaScript back to Swift. I'm not sure exactly why it is a deadlock but I had a similar issue with WKWebView on Mac recently.
You need to decouple this and make the communication asynchronous. This obviously means you cannot simply return a value from your JS function in this case.
To decouple, you can break the deadlock by deferring the work the JavaScript function needs to do out of the current runloop iteration using setTimeout:
function myFunction() {
setTimeout(function() {
// The actual work is done here.
// Call the Swift part here.
}, 0);
}
The whole native ↔︎ JavaScript communication is very, very tricky. Avoid it if you can. There's a project called XWebView that may be able to help you as it tries to ease bridging between the two worlds.

NSCache() is not working properly

I think I am tired of NSCache(). Could not understand what's the problem behind this. Trying to save an array of [AnyObject] to NSCahce(), which I have done using this following line of code.
NSCache().setObject(data, forKey: "News")
And tried to get it back using this way.
if let news = NSCache()("News") as? [AnyObject]
{
}
else
{
// I am always here :)
}
So I was thinking what's the problem with this. After searching a bit in Google I could see that setting totalCostLimit and countLimit will help you solve this problem. So I have set it like this.
NSCache().totalCostLimit = 50000
NSCache().countLimit = 50000
After setting this also, it was not working. So I thought of running this code in main thread, which I have done like this.
dispatch_async(dispatch_get_main_queue()) {
NSCache().setObject(data, forKey: "News")
}
Still it returned nil. Now last but not the least I have created one global instance of NSCache() and called all these operations using that instance. Well, doing like that also didn't give the expected result. It always gave me nil.
What's happening here? I know that NSCache() can store AnyObject values. I am saving lot of images in the same project without any problem, when I am trying to save this it returns nil.
Well this AnyObject contains some custom classes. Is that can be a problem? If yes, how will I save it locally without using CoreData or NSUserdefaults.
How I created an instance globally and accessed these. Created one instance of NSCache in the AppDelegate.swift file but outside of AppDelegate class
let mainCache = NSCache()
#NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
func applicationDidFinishLaunching(aNotification: NSNotification)
{
mainCache.totalCostLimit = 50000
mainCache.countLimit = 50000
}
}
Later I have used it like this.
mainCache.setObject(data, forKey: "News")
And getting the data back like this.
if let news = mainCache.objectForKey("News") as? [AnyObject]
{
}
else
{
// Always here :)
}
When you write NSCache() you are creating a new NSCache instance. You're doing this on just about every line.
What you need to do is create one instance, let myCache = NSCache(), and then reuse it: myCache.setObject(data, forKey: "News").

What is the correct way to use backgroundCompletionHandler in Alamofire?

I'm not clear on how to use this properly but had seen other people doing this type of thing:
func application(application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: () -> Void) {
manager.sharedInstance.backgroundCompletionHandler = completionHandler
}
In our similar implementation, at this point completionHandler is partial apply forwarder for reabstraction thunk helper...
Where manager is (despite being a singleton) essentially:
let configuration = NSURLSessionConfiguration.backgroundSessionConfigurationWithIdentifier("com.ourcompany.app")
let manager = Alamofire.Manager(configuration: configuration)
However this causes the following warning to be printed in the console:
Warning: Application delegate received call to -application:handleEventsForBackgroundURLSession:completionHandler: but the completion handler was never called.
I set a breakpoint here and at this point the message is already visible in the console and backgroundCompletionHandler is nil.
We're building against the iOS 9 SDK with Xcode 7.0 and currently using Alamofire 2.0.2
I originally thought this was introduced when we merged our Swift 2.0 branch but I'm also seeing the message with an earlier commit using Xcode 6.4 against the iOS 8 SDK.
Update 1
To address #cnoon's suggestions:
The identifier matches - the configuration and manager are set inside a singleton so there's no way for it to be wrong.
When adding and printing inside of didSet on backgroundCompletionHandler in the Manager class, the message is logged before the warning.
When printing inside of the closure set to sessionDidFinishEventsForBackgroundURLSession on the delegate inside the Manager class, the message is printed after the warning.
When overriding sessionDidFinishEventsForBackgroundURLSession and printing inside of it before calling backgroundCompletionHandler, the message is printed after the warning.
As for verifying I have my Xcode project set up correctly for background sessions, I'm not sure how to do that and couldn't find any documentation on how to do so.
I should note that when trying to upload some screenshots from my phone I was initially unable to reproduce this issue in order to try these suggestions.
It was only after trying to share some photos that I was able to reproduce this again. I'm not sure or the correlation (if any) but it may be related to the photos taking longer to upload.
Update 2
The UIBackgroundModes are set exactly as #Nick described, and calling completionHandler() directly inside of application:handleEventsForBackgroundURLSession:completionHandler: does not display the warning.
Update 3
So, it appears I overlooked an small but important detail. There's a wrapper around Alamofire.Manager that doesn't expose it directly. The relevant part of its implementation looks like this:
private var manager: Manager
public var backgroundCompletionHandler: (() -> Void)? {
get {
return manager.backgroundCompletionHandler
}
set {
manager.backgroundCompletionHandler = backgroundCompletionHandler
}
}
and setting the completion handler in the AppDelegate executes that code path.
func application(application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: () -> Void) {
myManager.sharedInstance.backgroundCompletionHandler = completionHandler
}
I've confirmed that the following change to expose the instance of Alamofire.Manager and access it directly does not produce the warning:
public var manager: Manager
// public var backgroundCompletionHandler: (() -> Void)? {
// get {
// return manager.backgroundCompletionHandler
// }
// set {
// manager.backgroundCompletionHandler = backgroundCompletionHandler
// }
// }
and in the AppDelegate:
func application(application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: () -> Void) {
myManager.sharedInstance.manager.backgroundCompletionHandler = completionHandler
}
Based on this it appears that using a computed property to proxy the completion handler is the cause of the issue.
I'd really prefer not to expose this property and would love to know of any workarounds or alternatives.
It appears as though everything you are doing is correct. I have an example app that does exactly what you've described that works correctly and does not throw the warning you are seeing. I'm guessing you still have some small error somewhere. Here are a few ideas to try out:
Verify the identifier matches the identifier of your background session
Add a didSet log statement on the backgroundSessionHandler in the Manager class temporarily to verify it is getting set
Add a log statement into the sessionDidFinishEventsForBackgroundURLSession to verify it is getting called as expected
Override the sessionDidFinishEventsForBackgroundURLSession on the delegate and manually call the backgroundSessionHandler
Verify you have your Xcode project set up correctly for background sessions
Update 2
Your computed property is wrong. Instead it needs to set the backgroundCompletionHandler to newValue. Otherwise you are never setting it to the new value correctly. See this thread for more info.

Can I receive a callback whenever an NSPasteboard is written to?

I've read Apple's Pasteboard Programming Guide, but it doesn't answer a particular question I have.
I'm trying to write a Cocoa application (for OS X, not iOS) that will keep track of everything that is written to the general pasteboard (so, whenever any application copies and pastes, but not, say, drags-and-drops, which also makes use of NSPasteboard). I could (almost) accomplish this by basically polling the general pasteboard on a background thread constantly, and checking changeCount. Of course, doing this would make me feel very dirty on the inside.
My question is, is there a way to ask the Pasteboard server to notify me through some sort of callback any time a change is made to the general pasteboard? I couldn't find anything in the NSPasteboard class reference, but I'm hoping it lurks somewhere else.
Another way I could imagine accomplishing this is if there was a way to swap out the general pasteboard implementation with a subclass of NSPasteboard that I could define myself to issue a callback. Maybe something like this is possible?
I would greatly prefer if this were possible with public, App Store-legal APIs, but if using a private API is necessary, I'll take that too.
Thanks!
Unfortunately the only available method is by polling (booo!). There are no notifications and there's nothing to observe for changed pasteboard contents. Check out Apple's ClipboardViewer sample code to see how they deal with inspecting the clipboard. Add a (hopefully not overzealous) timer to keep checking for differences and you've got a basic (if clunky) solution that should be App-Store-Friendly.
File an enhancement request at bugreporter.apple.com to request notifications or some other callback. Unfortunately it wouldn't help you until the next major OS release at the earliest but for now it's polling until we all ask them to give us something better.
There was once a post on a mailing list where the decision against a notification api was described. I can't find it right now though. The bottom line was that probably too many applications would register for that api even though they really wouldn't need to. If you then copy something the whole system goes through the new clipboard content like crazy, creating lots of work for the computer. So i don't think they'll change that behavior anytime soon. The whole NSPasteboard API is internally built around using the changeCount, too. So even your custom subclass of NSPasteboard would still have to keep polling.
If you really want to check if the pasteboard changed, just keep observing the changeCount very half second. Comparing integers is really fast so there's really no performance issue here.
Based on answer provided by Joshua I came up with similar implementation but in swift, here is the link to its gist: PasteboardWatcher.swift
Code snippet from same:
class PasteboardWatcher : NSObject {
// assigning a pasteboard object
private let pasteboard = NSPasteboard.generalPasteboard()
// to keep track of count of objects currently copied
// also helps in determining if a new object is copied
private var changeCount : Int
// used to perform polling to identify if url with desired kind is copied
private var timer: NSTimer?
// the delegate which will be notified when desired link is copied
weak var delegate: PasteboardWatcherDelegate?
// the kinds of files for which if url is copied the delegate is notified
private let fileKinds : [String]
/// initializer which should be used to initialize object of this class
/// - Parameter fileKinds: an array containing the desired file kinds
init(fileKinds: [String]) {
// assigning current pasteboard changeCount so that it can be compared later to identify changes
changeCount = pasteboard.changeCount
// assigning passed desired file kinds to respective instance variable
self.fileKinds = fileKinds
super.init()
}
/// starts polling to identify if url with desired kind is copied
/// - Note: uses an NSTimer for polling
func startPolling () {
// setup and start of timer
timer = NSTimer.scheduledTimerWithTimeInterval(2, target: self, selector: Selector("checkForChangesInPasteboard"), userInfo: nil, repeats: true)
}
/// method invoked continuously by timer
/// - Note: To keep this method as private I referred this answer at stackoverflow - [Swift - NSTimer does not invoke a private func as selector](http://stackoverflow.com/a/30947182/217586)
#objc private func checkForChangesInPasteboard() {
// check if there is any new item copied
// also check if kind of copied item is string
if let copiedString = pasteboard.stringForType(NSPasteboardTypeString) where pasteboard.changeCount != changeCount {
// obtain url from copied link if its path extension is one of the desired extensions
if let fileUrl = NSURL(string: copiedString) where self.fileKinds.contains(fileUrl.pathExtension!){
// invoke appropriate method on delegate
self.delegate?.newlyCopiedUrlObtained(copiedUrl: fileUrl)
}
// assign new change count to instance variable for later comparison
changeCount = pasteboard.changeCount
}
}
}
Note: in the shared code I am trying to identify if user has copied a
file url or not, the provided code can easily be modified for other general
purposes.
For those who need simplified version of code snippet that gets the job done in Swift 5.7,
it just works (base on #Devarshi code):
func watch(using closure: #escaping (_ copiedString: String) -> Void) {
let pasteboard = NSPasteboard.general
var changeCount = NSPasteboard.general.changeCount
Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { _ in
guard let copiedString = pasteboard.string(forType: .string),
pasteboard.changeCount != changeCount else { return }
defer {
changeCount = pasteboard.changeCount
}
closure(copiedString)
}
}
how to use is as below:
watch {
print("detected : \($0)")
}
then if you attempt copy any text in your pasteboard, it will watch and print out to the console like below..
detected : your copied message in pasteboard
detected : your copied message in pasteboard
in case, full code sample for how to use it for example in SwiftUI:
import SwiftUI
#main
struct TestApp: App {
var body: some Scene {
WindowGroup {
ContentView()
.onAppear {
watch {
print("detect : \($0)")
}
}
}
}
func watch(using closure: #escaping (_ copiedString: String) -> Void) {
let pasteboard = NSPasteboard.general
var changeCount = NSPasteboard.general.changeCount
Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { _ in
guard let copiedString = pasteboard.string(forType: .string),
pasteboard.changeCount != changeCount else { return }
defer {
changeCount = pasteboard.changeCount
}
closure(copiedString)
}
}
}
It's not necessary to poll. Pasteboard would generally only be changed by the current view is inactive or does not have focus. Pasteboard has a counter that is incremented when contents change. When window regains focus (windowDidBecomeKey), check if changeCount has changed then process accordingly.
This does not capture every change, but lets your application respond if the Pasteboard is different when it becomes active.
In Swift...
var pasteboardChangeCount = NSPasteboard.general().changeCount
func windowDidBecomeKey(_ notification: Notification)
{ Swift.print("windowDidBecomeKey")
if pasteboardChangeCount != NSPasteboard.general().changeCount
{ viewController.checkPasteboard()
pasteboardChangeCount = NSPasteboard.general().changeCount
}
}
I have a solution for more strict case: detecting when your content in NSPasteboard was replaced by something else.
If you create a class that conforms to NSPasteboardWriting and pass it to -writeObjects: along with the actual content, NSPasteboard will retain this object until its content is replaced. If there are no other strong references to this object, it get deallocated.
Deallocation of this object is the moment when new NSPasteboard got new content.

Resources