如何通过按下 Swift 中的 UIAlertController 警报中的按钮,使用 NSXMLParser 和提要地址上的变量来解析 RSS 提要?
How to parse a RSS feed using NSXMLParser with variables on the feed address by pressing a button in a UIAlertController Alert in Swift?
我一直在尝试在我的浏览器应用程序中集成 Pinboard 书签视图(通过解析 RSS Feed 并将其显示在 UITableView
中),但我遇到了一些问题。可以看我之前的提问 and 。我正在尝试使用我在警报中从用户那里收集的变量来获取用户的秘密 RSS 提要令牌。但是,当我 运行 应用程序时,它会导致崩溃并出现错误 "EXC_BAD_INSTRUCTION",并显示以下控制台输出:
fatal error: unexpectedly found nil while unwrapping an Optional value
我检查了所有内容,但找不到 nil
。
这是我正在使用的代码:
class SettingsTableViewController: UITableViewController, MFMailComposeViewControllerDelegate, NSXMLParserDelegate {
var _pinboardUsername: String?
var _pinboardAPIToken: String?
var parser: NSXMLParser = NSXMLParser()
var bookmarks: [Bookmark] = []
var pinboardSecret: String = String()
var eName: String = String()
...
@IBAction func pinboardUserDetailsRequestAlert(sender: AnyObject) {
//Create the AlertController
var pinboardUsernameField :UITextField?
var pinboardAPITokenField :UITextField?
let pinboardUserDetailsSheetController: UIAlertController = UIAlertController(title: "Pinboard Details", message: "Please enter your Pinboard Username and API Token to access your bookmarks", preferredStyle: .Alert)
//Add a text field
pinboardUserDetailsSheetController.addTextFieldWithConfigurationHandler({(usernameField: UITextField!) in
usernameField.placeholder = "Username"
usernameField.text = self._pinboardUsername
var parent = self.presentingViewController as! ViewController
pinboardUsernameField = usernameField
})
pinboardUserDetailsSheetController.addTextFieldWithConfigurationHandler({(apiTokenField: UITextField!) in
apiTokenField.placeholder = "API Token"
apiTokenField.text = self._pinboardAPIToken
var parent = self.presentingViewController as! ViewController
pinboardAPITokenField = apiTokenField
})
pinboardUserDetailsSheetController.addAction(UIAlertAction(title: "Cancel", style: .Cancel, handler: nil))
pinboardUserDetailsSheetController.addAction(UIAlertAction(title: "Done", style: .Default, handler: { (action) -> Void in
// Now do whatever you want with inputTextField (remember to unwrap the optional)
var valueUser = pinboardUsernameField?.text
NSUserDefaults.standardUserDefaults().setValue(valueUser, forKey: AppDefaultKeys.PinboardUsername.rawValue)
var valueAPI = pinboardAPITokenField?.text
NSUserDefaults.standardUserDefaults().setValue(valueAPI, forKey: AppDefaultKeys.PinboardAPIToken.rawValue)
let url:NSURL = NSURL(string: "https://api.pinboard.in/v1/user/secret/?auth_token=\(valueAPI)")!
self.parser = NSXMLParser(contentsOfURL: url)!
self.parser.delegate = self
self.parser.parse()
func parser(parser: NSXMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [NSObject : AnyObject]) {
self.eName = elementName
if elementName == "result" {
self.pinboardSecret = String()
NSUserDefaults.standardUserDefaults().setValue(self.pinboardSecret, forKey: AppDefaultKeys.PinboardSecret.rawValue)
}
}
}))
self.presentViewController(pinboardUserDetailsSheetController, animated: true, completion: nil)
}
错误在解析器函数的 let url: NSURL = ...
行。
我做错了什么?
let url:NSURL = NSURL(string: "https://api.pinboard.in/v1/user/secret/?auth_token=\(valueAPI)")!
NSURL()
是一个 可失败的初始化程序 ,这意味着它可能会在初始化时失败(格式错误 URL,服务器没有响应,...)。
通过使用 !
强制解包,您声明您绝对确定获得有效对象,但您没有。这会导致致命错误。
问题是 valueAPI
是可选的。例如,如果 valueAPI
是 foo
,您的 URL 字符串将如下所示:
https://api.pinboard.in/v1/user/secret/?auth_token=Optional("foo")
我建议先构建您的 URL 字符串,看看它,您就会明白我的意思。
最重要的是,URL 中的 Optional("...")
引用无效,因此使用该字符串实例化 URL 将失败。并使用 !
解包可选的 NSURL
将如您概述的那样崩溃。
在 URL 字符串中使用之前,您必须先解包 valueAPI
可选。
let url:NSURL = NSURL(string: "https://api.pinboard.in/v1/user/secret/?auth_token=\(valueAPI!)")!
或者您可以使 valueAPI
成为一个隐式展开的可选值。
坦率地说,如果 任何 机会 valueAPI
可能是 nil
,我宁愿使用可选绑定(即 if let
子句), 这样您以后就可以优雅地检测到这些问题了。
关于完全独立的 XML 解析问题,有几个问题:
我建议通过 NSURLSession
异步检索响应。使用 NSXMLParser
和 contentsOfURL
同步执行请求,这会导致糟糕的用户体验,并且 iOS 看门狗进程可能会终止您的应用程序。
而是使用 NSURLSession
方法 dataTaskWithURL
,然后将结果 NSData
与 NSXMLParser
对象一起使用。
如果这样做,您还可以将 NSData
响应对象转换为字符串并检查它。通常如果存在 API 问题,响应将包含一些错误性质的描述(有时是文本,有时是 HTML,有时是 XML;取决于该 Web 服务的方式被设计)。如果您没有得到预期的 XML 响应,您真的需要查看原始响应以确定问题所在。
您的 NSXMLParserDelegate
方法 didStartElement
定义在 内部 addAction
块中。它必须是 class 本身的方法,而不是埋在这个闭包中。
您的 didStartElement
正在保存秘密。但是你还没有秘密,所以没有必要试图保存它。
我没有看到其他 NSXMLParserDelegate
方法(至少 foundCharacters
、didEndElement
和 parseErrorOccurred
)。你有其他地方定义的吗?参见
我一直在尝试在我的浏览器应用程序中集成 Pinboard 书签视图(通过解析 RSS Feed 并将其显示在 UITableView
中),但我遇到了一些问题。可以看我之前的提问
fatal error: unexpectedly found nil while unwrapping an Optional value
我检查了所有内容,但找不到 nil
。
这是我正在使用的代码:
class SettingsTableViewController: UITableViewController, MFMailComposeViewControllerDelegate, NSXMLParserDelegate {
var _pinboardUsername: String?
var _pinboardAPIToken: String?
var parser: NSXMLParser = NSXMLParser()
var bookmarks: [Bookmark] = []
var pinboardSecret: String = String()
var eName: String = String()
...
@IBAction func pinboardUserDetailsRequestAlert(sender: AnyObject) {
//Create the AlertController
var pinboardUsernameField :UITextField?
var pinboardAPITokenField :UITextField?
let pinboardUserDetailsSheetController: UIAlertController = UIAlertController(title: "Pinboard Details", message: "Please enter your Pinboard Username and API Token to access your bookmarks", preferredStyle: .Alert)
//Add a text field
pinboardUserDetailsSheetController.addTextFieldWithConfigurationHandler({(usernameField: UITextField!) in
usernameField.placeholder = "Username"
usernameField.text = self._pinboardUsername
var parent = self.presentingViewController as! ViewController
pinboardUsernameField = usernameField
})
pinboardUserDetailsSheetController.addTextFieldWithConfigurationHandler({(apiTokenField: UITextField!) in
apiTokenField.placeholder = "API Token"
apiTokenField.text = self._pinboardAPIToken
var parent = self.presentingViewController as! ViewController
pinboardAPITokenField = apiTokenField
})
pinboardUserDetailsSheetController.addAction(UIAlertAction(title: "Cancel", style: .Cancel, handler: nil))
pinboardUserDetailsSheetController.addAction(UIAlertAction(title: "Done", style: .Default, handler: { (action) -> Void in
// Now do whatever you want with inputTextField (remember to unwrap the optional)
var valueUser = pinboardUsernameField?.text
NSUserDefaults.standardUserDefaults().setValue(valueUser, forKey: AppDefaultKeys.PinboardUsername.rawValue)
var valueAPI = pinboardAPITokenField?.text
NSUserDefaults.standardUserDefaults().setValue(valueAPI, forKey: AppDefaultKeys.PinboardAPIToken.rawValue)
let url:NSURL = NSURL(string: "https://api.pinboard.in/v1/user/secret/?auth_token=\(valueAPI)")!
self.parser = NSXMLParser(contentsOfURL: url)!
self.parser.delegate = self
self.parser.parse()
func parser(parser: NSXMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [NSObject : AnyObject]) {
self.eName = elementName
if elementName == "result" {
self.pinboardSecret = String()
NSUserDefaults.standardUserDefaults().setValue(self.pinboardSecret, forKey: AppDefaultKeys.PinboardSecret.rawValue)
}
}
}))
self.presentViewController(pinboardUserDetailsSheetController, animated: true, completion: nil)
}
错误在解析器函数的 let url: NSURL = ...
行。
我做错了什么?
let url:NSURL = NSURL(string: "https://api.pinboard.in/v1/user/secret/?auth_token=\(valueAPI)")!
NSURL()
是一个 可失败的初始化程序 ,这意味着它可能会在初始化时失败(格式错误 URL,服务器没有响应,...)。
通过使用 !
强制解包,您声明您绝对确定获得有效对象,但您没有。这会导致致命错误。
问题是 valueAPI
是可选的。例如,如果 valueAPI
是 foo
,您的 URL 字符串将如下所示:
https://api.pinboard.in/v1/user/secret/?auth_token=Optional("foo")
我建议先构建您的 URL 字符串,看看它,您就会明白我的意思。
最重要的是,URL 中的 Optional("...")
引用无效,因此使用该字符串实例化 URL 将失败。并使用 !
解包可选的 NSURL
将如您概述的那样崩溃。
在 URL 字符串中使用之前,您必须先解包 valueAPI
可选。
let url:NSURL = NSURL(string: "https://api.pinboard.in/v1/user/secret/?auth_token=\(valueAPI!)")!
或者您可以使 valueAPI
成为一个隐式展开的可选值。
坦率地说,如果 任何 机会 valueAPI
可能是 nil
,我宁愿使用可选绑定(即 if let
子句), 这样您以后就可以优雅地检测到这些问题了。
关于完全独立的 XML 解析问题,有几个问题:
我建议通过
NSURLSession
异步检索响应。使用NSXMLParser
和contentsOfURL
同步执行请求,这会导致糟糕的用户体验,并且 iOS 看门狗进程可能会终止您的应用程序。而是使用
NSURLSession
方法dataTaskWithURL
,然后将结果NSData
与NSXMLParser
对象一起使用。如果这样做,您还可以将
NSData
响应对象转换为字符串并检查它。通常如果存在 API 问题,响应将包含一些错误性质的描述(有时是文本,有时是 HTML,有时是 XML;取决于该 Web 服务的方式被设计)。如果您没有得到预期的 XML 响应,您真的需要查看原始响应以确定问题所在。您的
NSXMLParserDelegate
方法didStartElement
定义在 内部addAction
块中。它必须是 class 本身的方法,而不是埋在这个闭包中。您的
didStartElement
正在保存秘密。但是你还没有秘密,所以没有必要试图保存它。我没有看到其他
NSXMLParserDelegate
方法(至少foundCharacters
、didEndElement
和parseErrorOccurred
)。你有其他地方定义的吗?参见