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) let parserData = ParserData(url: feed.url, data: data)
FeedParser.parse(parserData) { (parsedFeed, error) in Task { @MainActor in
guard let account = feed.account, let parsedFeed = parsedFeed, error == nil else {
completion()
self.delegate?.localAccountRefresher(self, requestCompletedFor: feed)
return
}
Task { @MainActor in
do { do {
let articleChanges = try await account.update(feed: feed, with: parsedFeed) let parsedFeed = try await FeedParser.parse(parserData)
if let httpResponse = response as? HTTPURLResponse { guard let account = feed.account, let parsedFeed else {
feed.conditionalGetInfo = HTTPConditionalGetInfo(urlResponse: httpResponse)
}
feed.contentHash = dataHash
self.delegate?.localAccountRefresher(self, requestCompletedFor: feed)
self.delegate?.localAccountRefresher(self, articleChanges: articleChanges) {
completion()
}
} catch {
completion() completion()
self.delegate?.localAccountRefresher(self, requestCompletedFor: feed) 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 { func downloadSession(_ downloadSession: DownloadSession, shouldContinueAfterReceivingData data: Data, representedObject: AnyObject) -> Bool {
let feed = representedObject as! Feed 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) { @MainActor public static func download(_ url: URL,_ completion: @escaping @Sendable (_ parsedFeed: ParsedFeed?) -> Void) {
downloadUsingCache(url) { (data, response, error) in Task {
guard let data = data else {
guard let downloadData = try? await downloadUsingCache(url) else {
completion(nil)
return
}
guard let data = downloadData.data else {
completion(nil) completion(nil)
return return
} }
let parserData = ParserData(url: url.absoluteString, data: data) 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) completion(parsedFeed)
} }
} }

View File

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

View File

@ -12,12 +12,8 @@ import ParserObjC
// FeedParser handles RSS, Atom, JSON Feed, and RSS-in-JSON. // FeedParser handles RSS, Atom, JSON Feed, and RSS-in-JSON.
// You dont need to know the type of feed. // You dont need to know the type of feed.
public typealias FeedParserCallback = (_ parsedFeed: ParsedFeed?, _ error: Error?) -> Void
public struct FeedParser { public struct FeedParser {
private static let parseQueue = DispatchQueue(label: "FeedParser parse queue")
public static func canParse(_ parserData: ParserData) -> Bool { public static func canParse(_ parserData: ParserData) -> Bool {
let type = feedType(parserData) 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 { 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  case .rssInJSON:
// but its probably a good idea to use a background queue if return try RSSInJSONParser.parse(parserData)
// you might be doing a lot of parsing. (Such as in a feed reader.)
do { case .rss:
let type = feedType(parserData) return RSSParser.parse(parserData)
switch type { case .atom:
return AtomParser.parse(parserData)
case .jsonFeed: case .unknown, .notAFeed:
return try JSONFeedParser.parse(parserData) return nil
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)
}
}
} }
} }
} }