Import Swift class created dynamically - ios

I am experimenting with a piece of code and I need some assistance. I am creating a file that is a swift file that contains a class and a variable. I am able to successfully create and read the file. Now, is it possible for my to use this swift file and access its variable (v, in this case)?
func writeF() {
let file = "Sample.swift"
let text = "import Foundation \n" +
"public class Sample { \n" +
" let v: Int = 0 \n" +
"}"
if let dir = NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.DocumentDirectory, NSSearchPathDomainMask.AllDomainsMask, true).first {
let path = NSURL(fileURLWithPath: dir).URLByAppendingPathComponent(file)
//writing
do {
try text.writeToURL(path, atomically: false, encoding: NSUTF8StringEncoding)
}
catch {print("error writing file")}
//reading
do {
let text2 = try NSString(contentsOfURL: path, encoding: NSUTF8StringEncoding)
print(text2)
}
catch {
print("error reading file")
}
}
}

You can't add code on runtime. When your code is compiled, there are no *.swift files left, that can be read by humans. After compiling, your code is basically 0 and 1 only.

As FelixSFD said in their answer, you cannot dynamically build a Swift file and compile it at runtime on the device, at least not in the normal Sandboxed environment. If you have a Jailbroken device, you can build and install the Swift open source runtime and compile programs on-the-fly that way.
As an alternative, you could look into the JavaScriptCore framework to build and run dynamic JavaScript code, and bridge it into your app.
Here's a quick example of passing an object to JavaScript and returning the same object with a class mapping:
import JavaScriptCore
let js = "function test(input) { return input }"
class TestClass: NSObject {
var name: String
init(name: String) {
self.name = name
}
}
let context = JSContext()
context.evaluateScript(js)
let testFunc = context.objectForKeyedSubscript("test")
let result = testFunc.callWithArguments([TestClass(name: "test")])
result.toDictionary()
let testObj = result.toObjectOfClass(TestClass.self) as? TestClass
testObj?.name // "test"

Related

Swift ZIPFoundation extract string in memory is not working

I am using ZipFoundation in Swift from https://github.com/weichsel/ZIPFoundation
My requirement is unzip the file contents in memory and directly convert into String.
let archive = Archive(url: fileUrl, accessMode: .read, preferredEncoding: .utf8)
do{
try archive?.extract(entry, consumer: {data in
print(data.count)
})
}catch{}
The archive object is always null its not reading zip file. Also what is the entry object to pass to extract method?
ZIP Foundation archives support subscripting. This allows you to obtain an Entry by subscripting into an Archive via archive["path/to/file.txt"].
To get access to the contents of the obtained file, you use the closure-based version of extract as follows:
guard let archiveURL = Bundle.main.url(forResource: "archive", withExtension: "zip"),
let archive = Archive(url: archiveURL, accessMode: .read),
let entry = archive["test/data.random"]
else { return }
_ = try? archive.extract(entry) { data in
print(data.count)
}

Update Plist data without erasing old data

I'm trying to make a file downloader application using swift and cocoa. I am using plist for the download history. Reading the data works however, writing the data will erase the previous data and replace it for the new data.
Here is the code
let newdownloaditem = downloadList(root: [downloadListt(downloadURL: response.url!.absoluteString, fileName: response.suggestedFilename!)])
// This is a codeable method
let encoder = PropertyListEncoder()
encoder.outputFormat = .xml
let pListFilURL = uniqueDataDir()?.appendingPathComponent("downloads.plist")
do {
let data = try encoder.encode(newdownloaditem)
try data.write(to: pListFilURL!)
// Here is the problem
} catch {
print(error)
}
// Here is the codeable
public struct downloadList: Codable {
let root: [downloadListt]
}
public struct downloadListt: Codable {
let downloadURL: String
let fileName: String
}
Here is an image of what happens
The contents got erased
Thanks!
You are indeed replacing the previous data with your new one.
You need to retrieve the previous data.
Append your new data to it.
Save that combination
let newItem = downloadListt(downloadURL: response.url!.absoluteString,
fileName: response.suggestedFilename!)
var allItems: [downloadListt] = []
allItems.append(contentsOf: previousList.root)
allitems.append(newItem)
let newList = downloadList(root: allItems)
...
let data = try encoder.encode(newList)
try data.write(to: pListFilURL!)
Unrelated but recommended (it's convention):
You should starting naming your struct/classes with an uppercase: downloadList => DownloadList
I would avoid naming downloadListt, it's unredable it's hard at first look to make a difference between downloadListt and downloadList. Instead, name it maybe DownloadItem. More readable.

Cleaning malformed UTF-8 data

Background
With Swift, I'm trying to fetch HTML via URLSession rather than by loading it into a WKWebView first as I only need the HTML and none of the subresources. I'm running into a problem with certain pages that work when loaded into WKWebView but when loaded via URLSession (or even a simple NSString(contentsOf: url, encoding String.Encoding.utf8.rawValue)) the UTF-8 conversion fails.
How to reproduce
This fails (prints "nil"):
print(try? NSString(contentsOf: URL(string: "http://www.huffingtonpost.jp/techcrunch-japan/amazon-is-gobbling-whole-foods-for-a-reported-13-7-billion_b_17171132.html?utm_hp_ref=japan&ir=Japan")!, encoding: String.Encoding.utf8.rawValue))
But changing the URL to the site's homepage, it succeeds:
print(try? NSString(contentsOf: URL(string: "http://www.huffingtonpost.jp")!, encoding: String.Encoding.utf8.rawValue))
Question
How can I "clean" the data returned by a URL that contains malformed UTF-8? I'd like to either remove or replace any invalid sequences in the malformed UTF-8 so that the rest of it can be viewed. WKWebView is able to render the page just fine (and claims it's UTF-8 content as well), as you can see by visiting the URL: http://www.huffingtonpost.jp/techcrunch-japan/amazon-is-gobbling-whole-foods-for-a-reported-13-7-billion_b_17171132.html?utm_hp_ref=japan&ir=Japan
Here is an approach to create a String from (possibly) malformed
UTF-8 data:
Read the website contents into a Data object.
Append a 0 byte to make it a "C string"
Use String(cString:) for the conversion. This initializer replaces ill-formed UTF-8 code unit sequences with the Unicode replacement character ("\u{FFFD}").
Optionally: Remove all occurrences of the replacement character.
Example for the "cleaning" process:
var data = Data(bytes: [65, 66, 200, 67]) // malformed UTF-8
data.append(0)
let s = data.withUnsafeBytes { (p: UnsafePointer<CChar>) in String(cString: p) }
let clean = s.replacingOccurrences(of: "\u{FFFD}", with: "")
print(clean) // ABC
Swift 5:
var data = Data([65, 66, 200, 67]) // malformed UTF-8
data.append(0)
let s = data.withUnsafeBytes { p in
String(cString: p.bindMemory(to: CChar.self).baseAddress!)
}
let clean = s.replacingOccurrences(of: "\u{FFFD}", with: "")
print(clean) // ABC
Of course this can be defined as a custom init method:
extension String {
init(malformedUTF8 data: Data) {
var data = data
data.append(0)
self = data.withUnsafeBytes { (p: UnsafePointer<CChar>) in
String(cString: p).replacingOccurrences(of: "\u{FFFD}", with: "")
}
}
}
Swift 5:
extension String {
init(malformedUTF8 data: Data) {
var data = data
data.append(0)
self = data.withUnsafeBytes{ p in
String(cString: p.bindMemory(to: CChar.self).baseAddress!)
}.replacingOccurrences(of: "\u{FFFD}", with: "")
}
}
Usage:
let data = Data(bytes: [65, 66, 200, 67])
let s = String(malformedUTF8: data)
print(s) // ABC
The cleaning can be done more "directly" using transcode with
extension String {
init(malformedUTF8 data: Data) {
var utf16units = [UInt16]()
utf16units.reserveCapacity(data.count) // A rough estimate
_ = transcode(data.makeIterator(), from: UTF8.self, to: UTF16.self,
stoppingOnError: false) { code in
if code != 0xFFFD {
utf16units.append(code)
}
}
self = String(utf16CodeUnits: utf16units, count: utf16units.count)
}
}
This is essentially what String(cString:)
does, compare
CString.swift and
StringCreate.swift.
Yet another option is to use the UTF8 codecs decode() method
and ignore errors:
extension String {
init(malformedUTF8 data: Data) {
var str = ""
var iterator = data.makeIterator()
var utf8codec = UTF8()
var done = false
while !done {
switch utf8codec.decode(&iterator) {
case .emptyInput:
done = true
case let .scalarValue(val):
str.unicodeScalars.append(val)
case .error:
break // ignore errors
}
}
self = str
}
}

Cannot find file in app extension's shared container

I want to write log file at my extension, and read it at my app.
For this purpose, I'm using shared groups (so both the app and the extension would be able to read from the same file)
I wrote the following code:
Extension:
let fileManager = NSFileManager.defaultManager()
let containerUrl = fileManager.containerURLForSecurityApplicationGroupIdentifier("group.MyCompany.MyProj")
let extensionLogDirectory = containerUrl?.path?.stringByAppendingString("AppExtensionLogs")
let logFileManager = DDLogFileManagerDefault(logsDirectory: extensionLogDirectory)
PacketTunnelProvider.fileLogger = DDFileLogger(logFileManager: logFileManager)
PacketTunnelProvider.fileLogger!.rollingFrequency = 60*60*12
PacketTunnelProvider.fileLogger!.logFileManager.maximumNumberOfLogFiles = 1
DDLog.addLogger(PacketTunnelProvider.fileLogger)
App (just to read the log file):
let fileManager = NSFileManager.defaultManager()
let containerUrl = fileManager.containerURLForSecurityApplicationGroupIdentifier("group.MyCompany.MyProj")
if let extensionLogDirectory = containerUrl?.path?.stringByAppendingString("AppExtensionLogs") {
do {
let directoryContents = try fileManager.contentsOfDirectoryAtPath(extensionLogDirectory)//always fails
for file in directoryContents {
let path = extensionLogDirectory.stringByAppendingString(file)
do {
let fileContents = try NSString(contentsOfFile: path, encoding: NSUTF8StringEncoding)
NSLog("file: \(fileContents)")
}
catch {/* error handling here */
}
}
}
catch {/* error handling here */
NSLog("nope!")
}
But, something now right - it's seems like contentsOfDirectoryAtPath always fails with "no such file" error
What's wrong in this code?
The problem is unrelated to app extensions or CocoaLumberjack.
stringByAppendingString just concatenates strings, so that the path
separator "/" is missing in the generated directory name.
There was a dedicated method stringByAppendingPathComponent, which however
has been deprecated in Objective-C and is no longer available in Swift.
You should operate on the URL by using URLByAppendingPathComponent
instead:
let extensionLogDirectory = containerUrl?.URLByAppendingPathComponent("AppExtensionLogs").path

How to write data to a file in the projects directory?

I've some irregular huge amount of data in a text file, and by some crucial logics I've made it just like the way I needs. Now on every time the app loads the whole process if could be performed will cost deficiency up to remarkable extent.
So what should I do in order to store this regular data for one permanent time, so then through that I should retrieve when and where I got need of it?
Data is an unmodifiable pure in text format.
Okay your question is very vague, but to give you a head start you can save data to a file and then read from it like this:
let file = "file.txt" //this is the file we will write to and read from it
let text = "some text" //your data/text
if let dir : NSString = NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.DocumentDirectory, NSSearchPathDomainMask.AllDomainsMask, true).first {
let path = dir.stringByAppendingPathComponent(file);
//writing
do {
try text.writeToFile(path, atomically: false, encoding: NSUTF8StringEncoding)
}
catch {
/* error handling here */
}
//reading
do {
let textOut = try NSString(contentsOfFile: path, encoding: NSUTF8StringEncoding)
//use textOut - your content
}
catch {
/* error handling here */
}
}

Resources