UserDefaults not working on Linux - swift

UserDefaults does not appear to function in an XCTest when running on Linux. I'm running the below test but userDefaults.string(forKey: "key") returns nil on Linux when on macOS it returns the correct value and passes.
I'm using Docker to execute the tests on Linux. My Dockerfile is as follows:
FROM swiftdocker/swift
WORKDIR /package
COPY . ./
RUN swift package resolve
RUN swift package clean
RUN swift test --parallel
The test file:
import XCTest
class UserDefaultsTests: XCTestCase {
func testUserDefaults() {
let userDefaults = UserDefaults.standard
userDefaults.set("value", forKey: "key")
_ = userDefaults.synchronize()
XCTAssertEqual(userDefaults.string(forKey: "key"), "value") //nil is not equal to "value"
}
static var allTests = [
("testUserDefaults", testUserDefaults)
]
}
UserDefaults appears to be implemented in Swift Core Libs here, which is available on Linux, so I'm not sure why it wouldn't work.

Related

Spawning a process in an app built with UIKit for macOS (Catalyst)

I'm building an application that shares most of the code between macOS and iOS versions (targeting macOS 11 and iOS 14). UIKit for Mac seems like a natural choice to help with this. Unfortunately, one of the libraries uses the Process type under the hood. Building it produces "Cannot find type Process in scope" error when a dependency on it is added and when targeting macOS. I'm fine with excluding this library for iOS, but I still need to link with it on macOS while keeping the ability to use UIKit on all platforms.
I've selected this library to be linked only for macOS in Xcode, but this has no effect and the same build error persists. Also, I'm getting this error without adding a single import SwiftLSPClient statement in the app, so I don't think conditional imports would help in this case.
What would be the best way to resolve this issue within the constraints listed above?
I created a LSPCatalyst class in my Mac Catalyst app to replace the MacOS LanguageServerProcessHost. To make that work, I replaced the process property with a processProxy that accesses the process instance in a MacOS bundle using the FoundationApp protocol as explained below.
Following #Adam's suggestion, I created a MacOS bundle to proxy for the process instance. You follow the same idea as he pointed to for AppKit access from Catalyst apps, but you just need Foundation to get access to Process. I called the bundle FoundationGlue and put everything in a FoundationGlue folder in my Xcode project. The bundle needs an Info.plist that identifies the principal class as "FoundationGlue.MacApp", and the MacApp.swift looks like:
import Foundation
class MacApp: NSObject, FoundationApp {
var process: Process!
var terminationObserver: NSObjectProtocol!
func initProcess(_ launchPath: String!, _ arguments: [String]?, _ environment: [String : String]?) {
process = Process()
process.launchPath = launchPath
process.arguments = arguments
process.environment = environment
}
func setTerminationCompletion(_ completion: (()->Void)!) {
let terminationCompletion = {
NotificationCenter.default.removeObserver(self.terminationObserver!)
completion?()
}
terminationObserver =
NotificationCenter.default.addObserver(
forName: Process.didTerminateNotification,
object: process,
queue: nil) { notification -> Void in
terminationCompletion()
}
}
func setupProcessPipes(_ stdin: Pipe!, _ stdout: Pipe!, _ stderr: Pipe!) {
process.standardInput = stdin
process.standardOutput = stdout
process.standardError = stderr
}
func launchProcess() {
process.launch()
print("Launched process \(process.processIdentifier)")
}
func terminateProcess() {
process.terminate()
}
func isRunningProcess() -> Bool {
return process.isRunning
}
}
The corresponding header I called FoundationApp.h looks like:
#import <Foundation/Foundation.h>
#protocol FoundationApp <NSObject>
typedef void (^terminationCompletion) ();
- (void)initProcess: (NSString *) launchPath :(NSArray<NSString *> *) arguments :(NSDictionary<NSString *, NSString *> *) environment;
- (void)setTerminationCompletion: (terminationCompletion) completion;
- (void)setupProcessPipes: (NSPipe *) stdin :(NSPipe *) stdout :(NSPipe *) stderr;
- (void)launchProcess;
- (void)terminateProcess;
- (bool)isRunningProcess;
#end
And the FoundationAppGlue-Bridging-Header.h just contains:
#import "FoundationApp.h"
Once you have the bundle built for MacOS, add it as a framework to your Mac Catalyst project. I created a Catalyst.swift in that project for access to the FoundationGlue bundle functionality::
import Foundation
#available(macCatalyst 13, *)
struct Catalyst {
/// Catalyst.foundation gives access to the Foundation functionality identified in FoundationApp.h and implemented in FoundationGlue/MacApp.swift
static var foundation: FoundationApp! {
let url = Bundle.main.builtInPlugInsURL?.appendingPathComponent("FoundationGlue.bundle")
let bundle = Bundle(path: url!.path)!
bundle.load()
let cls = bundle.principalClass as! NSObject.Type
return cls.init() as? FoundationApp
}
}
Then, you use it from your app like:
let foundationApp = Catalyst.foundation!
foundationApp.initProcess("/bin/sh", ["-c", "echo 1\nsleep 1\necho 2\nsleep 1\necho 3\nsleep 1\necho 4\nsleep 1\nexit\n"], nil)
foundationApp.setTerminationCompletion({print("terminated")})
foundationApp.launchProcess()
This is a messy solution but I know it works: Add a “Mac bundle” to your Catalyst app and import the MacOS-only framework with that.
Here’s a guide to creating and loading a Mac bundle: https://medium.com/better-programming/how-to-access-the-appkit-api-from-mac-catalyst-apps-2184527020b5
Once you have the bundle, you can add Mac-only libraries and frameworks to it. You’ll have to bridge data and method calls between the bundle and your iOS app, but it’s manageable.

swift framework: code changed and the built framework not update?

Environment: mac OS, Catalina, I tried on iOS simulator
AudioKit framework's structure is like this
I added the following code , rebuild the xcode target, and the change is not updated in the new framework
// see the right channel data
public class Table: NSObject, MutableCollection, Codable {
public
convenience init?(rhs file: AVAudioFile) {
let size = Int(file.length)
self.init(count: size)
guard let data = file.toFloatChannelData() else { return nil }
for i in 0 ..< size {
self[i] = data[1][i]
}
}
}
I have tried Product -> Clear Build folder , both on target AudioKit and Cookbook
I have tried Product -> Clear Build folder , and put alt, on both
I have tried to quit Xcode , and reboot MBP
Still not work.
How to update the swift framework.

Serverside Swift and Vapor on Ubuntu

I am trying to write an API based on Project2 from Server Side Swift by Paul Hudson
https://www.hackingwithswift.com/store/server-side-swift
I am running:
Swift version 5.0 (swift-5.0-RELEASE)
Target: x86_64-unknown-linux-gnu
and
Vapor Toolbox: 3.1.10
on Ubuntu 18.04.3 LTS (GNU/Linux 4.15.0-66-generic x86_64)
The idea is to use Fluent and SQLite to maintain (CRUD) property records.
The relevant part of configure.swift is:
let directoryConfig = DirectoryConfig.detect()
services.register(directoryConfig)
try services.register(FluentSQLiteProvider())
var databaseConfig = DatabasesConfig()
let db = try SQLiteDatabase(storage: .file(path: "\(directoryConfig.workDir)properties.db"))
databaseConfig.add(database: db, as: .sqlite)
services.register(databaseConfig)
var migrationConfig = MigrationConfig()
migrationConfig.add(model: Property.self, database: .sqlite)
services.register(migrationConfig)
In Routes.swift I have this:
router.post(Property.self, at: "properties", "create") { req, property -> Future<Property> in
return property.save(on: req)
}
router.get("properties", "list") { req -> Future<[Property]> in
return Property.query(on: req).all()
}
And Property.swift is this:
// Property.swift
//
// Created by Peter Matthews on 07/12/2019.
//
import Fluent
import FluentSQLite
import Foundation
import Vapor
enum TenureType: String, Codable {
case freehold
case cross_lease
case unit_title
// etc...
}
enum PropertyType: String, Codable {
case commercial
case office
case industrial
case retail
// etc...
}
struct Property: Content, SQLiteUUIDModel, Migration {
var id: UUID?
var tenureType: TenureType? // var tenureType: String?
var propertyType: PropertyType? // var propertyType: String?
// etc...
}
When tenureType and propertyType are defined as String? everything works fine, but I think there is a strong case for using enums to specify the possible values for tenureType and propertyType.
Terminal output:
pm#PHQ:~/PropertyHQ$ vapor run
Running PropertyHQ ...
[ INFO ] Migrating 'sqlite' database (/home/pm/PropertyHQ/.build/checkouts/fluent/Sources/Fluent/Migration/MigrationConfig.swift:69)
[ INFO ] Migrations complete (/home/pm/PropertyHQ/.build/checkouts/fluent/Sources/Fluent/Migration/MigrationConfig.swift:73)
Running default command: .build/debug/Run serve
The problem is that when I try to use enums for tenureType and propertyType, the app builds just fine but when I run the vapor server I get this error.
Terminal output:
pm#PHQ:~/PropertyHQ$ vapor run
Running PropertyHQ ...
[ INFO ] Migrating 'sqlite' database (/home/pm/PropertyHQ/.build/checkouts/fluent/Sources/Fluent/Migration/MigrationConfig.swift:69)
[ INFO ] Preparing migration 'Property' (/home/pm/PropertyHQ/.build/checkouts/fluent/Sources/Fluent/Migration/Migrations.swift:111)
⚠️ [DecodingError.dataCorrupted: Cannot initialize TenureType from invalid String value 1]
Error: Run failed.
pm#PHQ:~/PropertyHQ$
When I specify tenureType and propertyType as optional (which I think they should be) the error value is 1 - when they are not optional the error value is 0.
I should add that this error only occurs when I run the server for the first time ie: when it creates the (empty) database. If I run it for the first time with tenureType and propertyType defined as String? it runs OK and then I can change them to enums and it continues to work OK.
You need to conform your enum to SQLiteEnumType and ReflectionDecodable. You also need to supply string values for each case. Using your TenureType as an example, try:
enum TenureType: String, Codable, SQLiteEnumType, ReflectionDecodable {
static func reflectDecoded() throws -> (TenureType, TenureType) {
return ( .freehold, .cross_lease )
}
case freehold = "freehold"
case cross_lease = "cross_lease"
case unit_title = "unit_title"
// etc...
}

Socket IO iOS sdk confuses xCode

installed socket.io sdk using cocoapods:
pod 'Socket.IO-Client-Swift'
Once I import it
import SocketIO
I get this weird warning and whenever I run the app it crashes:
item is referred as the following:
func createChatBox(item: AnyObject) -> UIView {
message.text = (item["message"] as! String)
}
And called as the following:
createChatBox(item: messages[0] as AnyObject)
Where
var messages:[Dictionary<String, AnyObject>] = [Dictionary<String, AnyObject>]()
With declaration
messages = [["type": 1 as AnyObject, "message" : "Hello" as AnyObject]]
Everything works fine without import SocketIO, I don't really know what is the problem with SocketIO and my variables.
Also, accessing the data directly without my functions works fine, as the following:
print(messages[0]["message"] as! String)
Thanks in advance.

Why can't I remove the NSUserDefaults key in swift?

When I run the following test case, the testRemovePref method fails as it doesn't actually remove the value. I'm using Xcode 7.2. Why is this happening?
EDIT: This is only occurring when the tests belong to a Framework Library. The tests below seem to work fine when run in an Application. Odd.
import XCTest
class NSUserDefaultsTests: XCTestCase {
func testSetPref() {
let prefs = NSUserDefaults.standardUserDefaults()
prefs.setValue("value1", forKeyPath: "testKey")
let val : String? = prefs.stringForKey("testKey")
print("value=\(val)") // prints "value1"
XCTAssertEqual("value1", val)
}
func testRemovePref() {
let prefs = NSUserDefaults.standardUserDefaults()
prefs.removeObjectForKey("testKey")
let val : String? = prefs.stringForKey("testKey")
print("value=\(val)") // prints "value1"
XCTAssertNil(val)
}
}
This is only occurring when the tests belong to a Framework Library.
The tests below seem to work fine when run in an Application.
An application automatically synchronizes defaults when you send it to the background. This doesn't happen with tests. Try adding:
prefs.synchronize()