Xcode (starting from version 11, maybe) is buuuuuuuuuuuggy. It has big features, but fails miserably on small or tiny functionalities.
You can try right-click the file and select "View As->Source Code". If this does not work either, then remove the file and re-add it back (at least this worked for me once or twice).
Post
Replies
Boosts
Views
Activity
I believe you have to create localizable.strings resource file in FR locale. The String.init and runtime do not do the magic translation work.
Unfortunately, contentsOfDirectory(at:) suffers the same problem as the enumerator.
But anyway, I devised a workaround that leads to a little more memory footprint.
var fileList = [URL]()
for case let fileURL as URL in it {
fileList.append(fileURL)
}
// Sort by length so that the shortest comes first
fileList.sort { $0.relativePath.count < $1.relativePath.count }
// The shortest path without last component is the root path.
let packageRoot = fileList[0].url.deletingLastPathComponent().path
It seems my conclusion about a single channel cannot handle multiple reading operations is not correct.
Here is a much improved sample to demonstrate the final solution:
import Cocoa
let OneMebi = 1024 * 1024
class IoJobState {
let name: String
let signature: UInt8
let label: NSTextField!
var total: UInt64 = 0
var eof = false
var offset: off_t = 0
var length = 0
var buffer = Data()
var done = false
var dispatchDone = false
var dispatchData: DispatchData?
var dispatchError: Int32 = 0
init(_ name: String, label: NSTextField, signature: UInt8) {
self.name = name
self.label = label
self.signature = signature
}
}
class DispatchIoVC: NSViewController {
@IBOutlet weak var label: NSTextField!
@IBOutlet weak var label1: NSTextField!
@IBOutlet weak var label2: NSTextField!
override func viewDidLoad() {
super.viewDidLoad()
}
let opQ = OperationQueue()
/// Should not be used!
let globalDispatchQ = DispatchQueue.global()
/// Note: the .concurrent attribute is a must, otherwise the created queue is in serial mode.
let dispatchQ = DispatchQueue(label: "test", qos: .utility, attributes: .concurrent)
@IBAction func test_click(_ sender: Any) {
let filePath = ("~/tmp/bigfile" as NSString).expandingTildeInPath
label.stringValue = filePath
var fh: Int32 = -1
let signatures: [UInt8] = [0x11, 0xef]
repeat {
fh = open(filePath, O_RDONLY)
if fh >= 0 { break }
fh = open(filePath, O_WRONLY | O_CREAT)
if (fh < 0) {
perror("Opening")
label1.stringValue = "Unable to create file"
return
}
let a = [UInt8](repeating: signatures[0], count: OneMebi)
a.withUnsafeBytes {
for _ in 0..<100 {
write(fh, $0.baseAddress, a.count)
}
}
let a2 = [UInt8](repeating: signatures[1], count: OneMebi)
a2.withUnsafeBytes {
for _ in 0..<90 {
write(fh, $0.baseAddress, a.count)
}
write(fh, $0.baseAddress, 13)
}
close(fh)
filePath.utf8CString.withUnsafeBytes {
if 0 != chmod($0.baseAddress, 0o644) {
perror("chmod")
}
}
} while true
let fmt = ByteCountFormatter()
fmt.countStyle = .binary
let size = lseek(fh, 0, SEEK_END)
print("size:", fmt.string(fromByteCount: size))
lseek(fh, 0, SEEK_SET)
let jobs = [IoJobState("job1", label: label1, signature: signatures[0]),
IoJobState("job2", label: label2, signature: signatures[1])]
jobs[0].offset = 0
jobs[0].length = Int(size) / 2
jobs[1].offset = off_t(jobs[0].length)
jobs[1].length = .max
let channel = DispatchIO(type: .random, fileDescriptor: fh, queue: dispatchQ) { error in
if error == 0 {
print("Good cleanup")
} else {
print("Bad cleanup \(error)")
}
close(fh)
if unlink(filePath) == 0 {
print("File successfully deleted")
} else {
perror("unlink")
}
}
jobs.forEach { job in
channel.read(offset: job.offset, length: job.length, queue: self.dispatchQ) { done, data, error in
job.dispatchDone = done
job.dispatchData = data
job.dispatchError = error
job.eof = job.dispatchDone && job.dispatchError == 0 && (job.dispatchData == nil || job.dispatchData!.isEmpty)
if let data = job.dispatchData { job.total += UInt64(data.count) }
OperationQueue.main.addOperation {
upateUI(with: job)
}
if let data {
job.buffer.reserveCapacity(data.count)
job.buffer.withUnsafeMutableBytes {
_ = data.copyBytes(to: $0)
}
Thread.sleep(forTimeInterval: TimeInterval.random(in: 0.01..<0.05))
if !job.buffer.allSatisfy({ $0 == job.signature }) {
print("\(job.name): bad reading \(job.total)")
}
}
}
}
func upateUI(with job: IoJobState) {
if job.dispatchDone && !job.done {
job.done = true
print("\(job.name) done")
}
if let data = job.dispatchData {
let byteString = fmt.string(fromByteCount: Int64(data.count))
let totalString = fmt.string(fromByteCount: Int64(job.total))
job.label.stringValue = "\(job.name): got \(byteString) bytes, total \(totalString) \(job.total)"
}
if job.dispatchError != 0 {
print("\(job.name): got error code:", job.dispatchError)
perror("")
}
}
}
}
Here is my experiment with DispatchIO, hope it may be helpful for those who are looking for a quick start.
Note that - The code still uses one channel (DispatchIO object) for reading. The result is that most of the cases 2nd part finishes and reaches EOF and thus causes the first part not having chance to execute cleanup handler.
import Cocoa
class IoJobState {
let name: String!
let label: NSTextField!
var total: UInt64 = 0
var eof = false
var offset: off_t = 0
var length = 0
var buffer = Data()
var done = false
var data: DispatchData?
var error: Int32 = 0
init(_ name: String, label: NSTextField) {
self.name = name
self.label = label
}
}
class DispatchIoVC: NSViewController {
@IBOutlet weak var label1: NSTextField!
@IBOutlet weak var label2: NSTextField!
override func viewDidLoad() {
super.viewDidLoad()
}
let opQ = OperationQueue()
/// Should not be used!
let globalDispatchQ = DispatchQueue.global()
/// Note: the .concurrent attribute is a must, otherwise the created queue is in serial mode.
let dispatchQ = DispatchQueue(label: "test", qos: .utility, attributes: .concurrent)
@IBAction func test_click(_ sender: Any) {
let filePath = ("~/tmp/bigfile" as NSString).expandingTildeInPath
print(filePath)
let fh = open(filePath, O_RDONLY)
if fh < 0 {
let proc = Process()
proc.executableURL = URL(fileURLWithPath: "/usr/bin/truncate")
proc.arguments = ["-s", "5G", filePath]
do {
try proc.run()
label1.stringValue = "Created \(filePath) of size 5GB, try again"
} catch { print(error) }
return
}
let fmt = ByteCountFormatter()
fmt.countStyle = .binary
let size = lseek(fh, 0, SEEK_END)
print("size:", fmt.string(fromByteCount: size))
lseek(fh, 0, SEEK_SET)
let jobs = [IoJobState("job1", label: label1), IoJobState("job2", label: label2)]
// TODO: A single channel cannot be used to issue multiple read operation?
let d = DispatchIO(type: .random, fileDescriptor: fh, queue: dispatchQ) { error in
if error == 0 {
print("All good")
} else {
print("Got error code:", error)
}
if jobs[0].eof && jobs[1].eof {
print("Closing file")
close(fh)
try? FileManager.default.removeItem(atPath: filePath)
}
}
opQ.addOperation {
jobs[0].offset = 0
jobs[0].length = Int(size) / 2
jobs[1].offset = off_t(jobs[0].length)
jobs[1].length = .max
jobs.forEach { job in
d.read(offset: job.offset, length: job.length, queue: self.dispatchQ) { done, data, error in
job.done = done
job.data = data
job.error = error
job.eof = job.done && job.error == 0 && (job.data == nil || job.data!.isEmpty)
if let data = job.data { job.total += UInt64(data.count) }
OperationQueue.main.addOperation {
upateUI(job: job)
}
if !job.eof, let data {
job.buffer.reserveCapacity(data.count)
job.buffer.withUnsafeMutableBytes {
_ = data.copyBytes(to: $0)
}
}
}
}
}
func upateUI(job: IoJobState ) {
if job.eof {
print("\(job.name!) done")
}
if let data = job.data {
let byteString = fmt.string(fromByteCount: Int64(data.count))
let totalString = fmt.string(fromByteCount: Int64(job.total))
job.label.stringValue = "\(job.name!): got \(byteString) bytes, total \(totalString)"
}
if job.error != 0 {
print("\(job.name!): got error code:", job.error)
perror("")
}
}
}
}
You just need multiple channels.
This drives away the cloud in my mind. Based on what you describe, I believe a channel (wrapped as a DispathIO) is efficient and cheap to construct, which was what I worried about.
BTW, I have latest Xcode 15.1 and Sonoma 14.2.1.
Yeah, I solved it myself.
I removed this file from project and re-added it back. Now it works fine.
Okay, I found what I need. It's NSFileWrapper and specifically, I want matchesContents(of:) method.
That can be done quite easily. But what I want is a more 'strict' way.
I'd like to know if Cocoa/Appket provide some low-level mechnisms to measure if a long string fit in a constrained rectangle when it's displayed in a graphics context (here, an NSTextField). When it's not fit entirely, I can abbreviate some path components to make the path string shorter.
Finally, I solved my own question. It seems I have to set yPlacement to .fill.
In IB designer, I am not even able to resize an added image view in first cell. The width and height is fixed at 32; if I try to modify the value, it automatically reverts back to 32.
It seems GridView is performing some kind of invisible magic, which I don't have any knowledge of.
I found out why, but it's not expected:
imageView.frame = NSMakeRect(0, 0, 80, 80)
let row = gridView.addRow(with: [imageView, label])
print(gridView.column(at: 0).width, row.height)
// output
// 80.0 1.1754943508222875e-38
Why the initial height is 1.175 for a newly added row (note I already resized image view to 80x80)?
Even later on I explicitly set row.height = 80, but it does not work; the image view is squeezed.
It's a stupid question. I found out why.
I set this up several years ago and I forgot the exact steps. Actually I have to define a custom path in Xcode Preferences->Locations->Custom Paths.
Well, it's partially true.
Playground:
print("class:")
let m1 = Mirror(reflecting: Some.self)
for child in m1.children { print(child.label ?? "", child.value) }
print("instance:")
let m2 = Mirror(reflecting: Some())
for child in m2.children { print(child.label ?? "", child.value) }
print("\n-=|E.O.F|=-")
class Some {
static let inst = Some()
private var n = 1234
var f = 1.234
}
Output:
class:
instance:
n 1234
f 1.234
Mirror can't mirror the static variable.