Fix concurrency warnings in Parser module.

This commit is contained in:
Brent Simmons 2024-04-16 22:03:20 -07:00
parent f449a443d8
commit e03ad03e60
4 changed files with 50 additions and 77 deletions

View File

@ -101,36 +101,37 @@ extension LocalAccountRefresher: DownloadSessionDelegate {
}
let parserData = ParserData(url: feed.url, data: data)
FeedParser.parse(parserData) { (parsedFeed, error) in
guard let account = feed.account, let parsedFeed = parsedFeed, error == nil else {
completion()
self.delegate?.localAccountRefresher(self, requestCompletedFor: feed)
return
}
Task { @MainActor in
Task { @MainActor in
do {
let articleChanges = try await account.update(feed: feed, with: parsedFeed)
do {
let parsedFeed = try await FeedParser.parse(parserData)
if let httpResponse = response as? HTTPURLResponse {
feed.conditionalGetInfo = HTTPConditionalGetInfo(urlResponse: httpResponse)
}
feed.contentHash = dataHash
self.delegate?.localAccountRefresher(self, requestCompletedFor: feed)
self.delegate?.localAccountRefresher(self, articleChanges: articleChanges) {
completion()
}
} catch {
guard let account = feed.account, let parsedFeed else {
completion()
self.delegate?.localAccountRefresher(self, requestCompletedFor: feed)
return
}
let articleChanges = try await account.update(feed: feed, with: parsedFeed)
if let httpResponse = response as? HTTPURLResponse {
feed.conditionalGetInfo = HTTPConditionalGetInfo(urlResponse: httpResponse)
}
feed.contentHash = dataHash
self.delegate?.localAccountRefresher(self, requestCompletedFor: feed)
self.delegate?.localAccountRefresher(self, articleChanges: articleChanges) {
completion()
}
} catch {
completion()
self.delegate?.localAccountRefresher(self, requestCompletedFor: feed)
}
}
}
func downloadSession(_ downloadSession: DownloadSession, shouldContinueAfterReceivingData data: Data, representedObject: AnyObject) -> Bool {
let feed = representedObject as! Feed

View File

@ -23,14 +23,25 @@ public struct InitialFeedDownloader {
@MainActor public static func download(_ url: URL,_ completion: @escaping @Sendable (_ parsedFeed: ParsedFeed?) -> Void) {
downloadUsingCache(url) { (data, response, error) in
guard let data = data else {
Task {
guard let downloadData = try? await downloadUsingCache(url) else {
completion(nil)
return
}
guard let data = downloadData.data else {
completion(nil)
return
}
let parserData = ParserData(url: url.absoluteString, data: data)
FeedParser.parse(parserData) { (parsedFeed, error) in
Task.detached {
guard let parsedFeed = try? await FeedParser.parse(parserData) else {
completion(nil)
return
}
completion(parsedFeed)
}
}

View File

@ -10,6 +10,7 @@
NS_ASSUME_NONNULL_BEGIN
__attribute__((swift_attr("@Sendable")))
@interface ParserData : NSObject
@property (nonatomic, readonly) NSString *url;

View File

@ -12,12 +12,8 @@ import ParserObjC
// FeedParser handles RSS, Atom, JSON Feed, and RSS-in-JSON.
// You dont need to know the type of feed.
public typealias FeedParserCallback = (_ parsedFeed: ParsedFeed?, _ error: Error?) -> Void
public struct FeedParser {
private static let parseQueue = DispatchQueue(label: "FeedParser parse queue")
public static func canParse(_ parserData: ParserData) -> Bool {
let type = feedType(parserData)
@ -30,62 +26,26 @@ public struct FeedParser {
}
}
public static func mightBeAbleToParseBasedOnPartialData(_ parserData: ParserData) -> Bool {
public static func parse(_ parserData: ParserData) async throws -> ParsedFeed? {
let type = feedType(parserData, isPartialData: true)
let type = feedType(parserData)
switch type {
case .jsonFeed, .rssInJSON, .rss, .atom, .unknown:
return true
default:
return false
}
}
public static func parse(_ parserData: ParserData) throws -> ParsedFeed? {
case .jsonFeed:
return try JSONFeedParser.parse(parserData)
// This is generally fast enough to call on the main thread 
// but its probably a good idea to use a background queue if
// you might be doing a lot of parsing. (Such as in a feed reader.)
case .rssInJSON:
return try RSSInJSONParser.parse(parserData)
do {
let type = feedType(parserData)
case .rss:
return RSSParser.parse(parserData)
switch type {
case .atom:
return AtomParser.parse(parserData)
case .jsonFeed:
return try JSONFeedParser.parse(parserData)
case .rssInJSON:
return try RSSInJSONParser.parse(parserData)
case .rss:
return RSSParser.parse(parserData)
case .atom:
return AtomParser.parse(parserData)
case .unknown, .notAFeed:
return nil
}
}
catch { throw error }
}
public static func parse(_ parserData: ParserData, _ completion: @escaping FeedParserCallback) {
parseQueue.async {
do {
let parsedFeed = try parse(parserData)
DispatchQueue.main.async {
completion(parsedFeed, nil)
}
}
catch {
DispatchQueue.main.async {
completion(nil, error)
}
}
case .unknown, .notAFeed:
return nil
}
}
}