我已经在使用这个Swift代码已经有一段时间了,并且没有发现问题。代码 下面应该为NSOutlineView提供文件目录作为数据源。 GUI非常简单 只是一个带有NSOutlineView和OutlineViewController实例的Object的窗口。 当我启动应用程序时,它会显示根条目,当我展开根条目时,它会在短时间内显示子项。然后应用程序崩溃,文件中出现错误" main.swift"在线" NSApplicationMain(C_ARGC,C_ARGV) - > " EXC_BAD_ACCESS(代码= EXC_I386_GPFLT)" ?
如果添加了一些println()来证明目录结构 - 这似乎没问题。
快速代码:
import Cocoa
import Foundation
class FileSystemItem {
let propertyKeys = [NSURLLocalizedNameKey, NSURLEffectiveIconKey, NSURLIsPackageKey, NSURLIsDirectoryKey,NSURLTypeIdentifierKey]
let fileURL: NSURL
var name: String! {
let resourceValues = fileURL.resourceValuesForKeys([NSURLNameKey], error: nil)
return resourceValues[NSURLNameKey] as? NSString
}
var localizedName: String! {
let resourceValues = fileURL.resourceValuesForKeys([NSURLLocalizedNameKey], error: nil)
return resourceValues[NSURLLocalizedNameKey] as? NSString
}
var icon: NSImage! {
let resourceValues = fileURL.resourceValuesForKeys([NSURLEffectiveIconKey], error: nil)
return resourceValues[NSURLEffectiveIconKey] as? NSImage
}
var dateOfCreation: NSDate! {
let resourceValues = self.fileURL.resourceValuesForKeys([NSURLCreationDateKey], error: nil)
return resourceValues[NSURLCreationDateKey] as? NSDate
}
var dateOfLastModification: NSDate! {
let resourceValues = fileURL.resourceValuesForKeys([NSURLContentModificationDateKey], error: nil)
return resourceValues[NSURLContentModificationDateKey] as? NSDate
}
var typeIdentifier: String! {
let resourceValues = fileURL.resourceValuesForKeys([NSURLTypeIdentifierKey], error: nil)
return resourceValues[NSURLTypeIdentifierKey] as? NSString
}
var isDirectory: String! {
let resourceValues = fileURL.resourceValuesForKeys([NSURLIsDirectoryKey], error: nil)
return resourceValues[NSURLIsDirectoryKey] as? NSString
}
var children: [FileSystemItem] {
var childs: [FileSystemItem] = []
var isDirectory: ObjCBool = ObjCBool(1)
let fileManager = NSFileManager.defaultManager()
var checkValidation = NSFileManager.defaultManager()
if (checkValidation.fileExistsAtPath(fileURL.relativePath)) {
if let itemURLs = fileManager.contentsOfDirectoryAtURL(fileURL, includingPropertiesForKeys:propertyKeys, options:.SkipsHiddenFiles, error:nil) {
for fsItemURL in itemURLs as [NSURL] {
if (fileManager.fileExistsAtPath(fsItemURL.relativePath, isDirectory: &isDirectory))
{
if(isDirectory == true) {
let checkItem = FileSystemItem(fileURL: fsItemURL)
childs.append(checkItem)
}
}
}
}
}
return childs
}
init (fileURL: NSURL) {
self.fileURL = fileURL
}
func hasChildren() -> Bool {
return self.children.count > 0
}
}
class OutlineViewController : NSObject, NSOutlineViewDataSource {
let rootFolder : String = "/"
let rootfsItem : FileSystemItem
let fsItemURL : NSURL
let propertyKeys = [NSURLLocalizedNameKey, NSURLEffectiveIconKey, NSURLIsPackageKey, NSURLIsDirectoryKey,NSURLTypeIdentifierKey]
init() {
self.fsItemURL = NSURL.fileURLWithPath(rootFolder)
self.rootfsItem = FileSystemItem(fileURL: fsItemURL)
for fsItem in rootfsItem.children as [FileSystemItem] {
for fsSubItem in fsItem.children as [FileSystemItem] {
println("\(fsItem.name) - \(fsSubItem.name)")
}
}
}
func outlineView(outlineView: NSOutlineView!, numberOfChildrenOfItem item: AnyObject!) -> Int {
if let theItem: AnyObject = item {
let tmpfsItem: FileSystemItem = item as FileSystemItem
return tmpfsItem.children.count
}
return 1
}
func outlineView(outlineView: NSOutlineView!, isItemExpandable item: AnyObject!) -> Bool {
if let theItem: AnyObject = item {
let tmpfsItem: FileSystemItem = item as FileSystemItem
return tmpfsItem.hasChildren()
}
return false
}
func outlineView(outlineView: NSOutlineView!, child index: Int, ofItem item: AnyObject!) -> AnyObject! {
if let theItem: AnyObject = item {
let tmpfsItem: FileSystemItem = item as FileSystemItem
return tmpfsItem.children[index]
}
return rootfsItem
}
func outlineView(outlineView: NSOutlineView!, objectValueForTableColumn tableColumn: NSTableColumn!, byItem item: AnyObject!) -> AnyObject! {
if let theItem: AnyObject = item {
let tmpfsItem: FileSystemItem = item as FileSystemItem
return tmpfsItem.localizedName
}
return "-empty-"
}
}
class AppDelegate: NSObject, NSApplicationDelegate {
@IBOutlet var window: NSWindow
func applicationDidFinishLaunching(aNotification: NSNotification?) {
// Insert code here to initialize your application
}
func applicationWillTerminate(aNotification: NSNotification?) {
// Insert code here to tear down your application
}
}
任何提示?
答案 0 :(得分:4)
我在NSOutlineView上有一个与EXC_BAD_ACCESS类似的问题 - 带有NSOutlineViewDataSource。扩展节点时的相同行为,显示数据然后发生崩溃。仪器中的一些分析显示在某处创建了一个Zombie对象,然后Outline视图尝试访问它。
我认为这是一个错误 - 但我设法通过改变所有Swift' Strings'来解决这个问题。到了NSStrings'。如果您正在使用它们,则可能必须对所有Swift类型执行此操作。
为了确保一切都是NSString,我必须在类中声明常量,例如:
var empty_string : NSString = ""
因为任何时候我喂它一个Swift字符串都会崩溃。哦,希望这将在未来得到解决!
答案 1 :(得分:4)
所以,只是为了澄清发生了什么。 NSOutlineView不保留为其“模型”提供的对象;总是希望客户保留它们。对于ARC代码,这不能很好地工作,因为如果向NSOutlineView方法返回一个新实例,该对象将不会被任何东西保留,并且很快就会被释放。然后随后的outlineView委托方法触摸这些对象将导致崩溃。解决方法是将对象保留在自己的数组中。
请注意,从objectValueForTableColumn返回的对象由NSControl的objectValue保留。
回到Swift:正如Thomas所说,对象必须是objc对象,因为它们被桥接到objc类。 Swift字符串隐式桥接到临时NSString。由于上述问题,这会导致崩溃,因为没有任何东西会保留NSString实例。这就是维护一系列NSStrings“解决”这个问题的原因。
解决方案是NSOutlineView可以选择保留给它的项目。请考虑通过bugreporter.apple.com
记录错误请求谢谢, corbin(我在NSOutlineView上工作)
答案 2 :(得分:2)
似乎
outlineView(outlineView: NSOutlineView!, objectValueForTableColumn tableColumn: NSTableColumn!, byItem item: AnyObject!) -> AnyObject!
需要返回符合obj-c协议的对象。所以你可以返回
@objc class MyClass {
...
}
(或NSString等)。但不是String
或Array
等原生的Swift内容。
答案 3 :(得分:0)
我相信这里遇到的一个问题是,每次访问子属性时,“children”数组都会被替换。
我认为这会导致NSOutlineView中的一些弱引用在查询DataSource以获取信息时中断。
如果你缓存“孩子”并访问缓存来计算“numberOfChildren”和“getChildForIndex”,你会看到一个改进。
答案 4 :(得分:0)
在Swift 3.0中,我使用了以下代码,它编译并运行没有问题。由于我正试图将TreeTest翻译成斯威夫特,所以它远非完整,而是朝着正确的方向迈出了一步。
import Cocoa
import Foundation
class FileSystemItem: NSObject {
let propertyKeys: [URLResourceKey] = [.localizedNameKey, .effectiveIconKey, .isDirectoryKey, .typeIdentifierKey]
var fileURL: URL
var name: String! {
let resourceValues = try! fileURL.resourceValues(forKeys: [.nameKey])
return resourceValues.name
}
var localizedName: String! {
let resourceValues = try! fileURL.resourceValues(forKeys: [.localizedNameKey])
return resourceValues.localizedName
}
var icon: NSImage! {
let resourceValues = try! fileURL.resourceValues(forKeys: [.effectiveIconKey])
return resourceValues.effectiveIcon as? NSImage
}
var dateOfCreation: Date! {
let resourceValues = try! fileURL.resourceValues(forKeys: [.creationDateKey])
return resourceValues.creationDate
}
var dateOfLastModification: Date! {
let resourceValues = try! fileURL.resourceValues(forKeys: [.contentModificationDateKey])
return resourceValues.contentAccessDate
}
var typeIdentifier: String! {
let resourceValues = try! fileURL.resourceValues(forKeys: [.typeIdentifierKey])
return resourceValues.typeIdentifier
}
var isDirectory: Bool! {
let resourceValues = try! fileURL.resourceValues(forKeys: [.isDirectoryKey])
return resourceValues.isDirectory
}
init(url: Foundation.URL) {
self.fileURL = url
}
var children: [FileSystemItem] {
var childs: [FileSystemItem] = []
let fileManager = FileManager.default
// show no hidden Files (if you want this, comment out next line)
// let options = FileManager.DirectoryEnumerationOptions.skipsHiddenFiles
var directoryURL = ObjCBool(false)
let validURL = fileManager.fileExists(atPath: fileURL.relativePath, isDirectory: &directoryURL)
if (validURL && directoryURL.boolValue) {
// contents of directory
do {
let childURLs = try
fileManager.contentsOfDirectory(at: fileURL, includingPropertiesForKeys: propertyKeys, options: [])
for childURL in childURLs {
let child = FileSystemItem(url: childURL)
childs.append(child)
}
}
catch {
print("Unexpected error occured: \(error).")
}
}
return childs
}
func hasChildren() -> Bool {
return self.children.count > 0
}
}
class OutLineViewController: NSViewController, NSOutlineViewDelegate, NSOutlineViewDataSource {
@IBOutlet weak var outlineView: NSOutlineView!
@IBOutlet weak var pathController: NSPathControl!
var fileSystemItemURL: URL!
let propertyKeys: [URLResourceKey] = [.localizedNameKey, .effectiveIconKey, .isDirectoryKey, .typeIdentifierKey]
var rootfileSystemItem: FileSystemItem!
var rootURL: URL!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
let userDirectoryURL = URL(fileURLWithPath: NSHomeDirectory())
// directory "Pictures" is set as root
let rootURL = userDirectoryURL.appendingPathComponent("Pictures", isDirectory: true)
self.pathController.url = rootURL
self.rootfileSystemItem = FileSystemItem(url: rootURL)
for fileSystemItem in rootfileSystemItem.children as [FileSystemItem] {
for subItem in fileSystemItem.children as [FileSystemItem] {
print("\(fileSystemItem.name) - \(subItem.name)")
}
}
//FileSystemItem.rootItemWithPath(self.pathControl.URL.path)
//self.searchForFilesInDirectory(picturesPath)
}
override var representedObject: Any? {
didSet {
// Update the view, if already loaded.
}
}
@IBAction func pathControllerAction(_ sender: NSPathControl) {
print("controller clicked")
}
// MARK: - outline data source methods
func outlineView(_ outlineView: NSOutlineView, numberOfChildrenOfItem item: Any?) -> Int {
if let fileSystemItem = item as? FileSystemItem {
return fileSystemItem.children.count
}
return 1
}
func outlineView(_ outlineView: NSOutlineView, isItemExpandable item: Any) -> Bool {
if let fileSystemItem = item as? FileSystemItem {
return fileSystemItem.hasChildren()
}
return false
}
func outlineView(_ outlineView: NSOutlineView, child index: Int, ofItem item: Any?) -> Any {
if let fileSystemItem = item as? FileSystemItem {
return fileSystemItem.children[index]
}
return rootfileSystemItem
}
func outlineView(_ outlineView: NSOutlineView, objectValueFor tableColumn: NSTableColumn?, byItem item: Any?) -> Any? {
if let fileSystemItem = item as? FileSystemItem {
switch tableColumn?.identifier {
case "tree"?:
return fileSystemItem.localizedName
case "coordinate"?:
return " empty "
default:
break
}
}
return " -empty- "
}
// MARK: - outline view delegate methods
func outlineView(_ outlineView: NSOutlineView, shouldEdit tableColumn: NSTableColumn?, item: Any) -> Bool {
return false
}
}
通过新编辑,大纲视图现在显示所有文件和目录。您可以在FileSystemItem类的子部分中影响外观。