【发布时间】:2020-12-21 07:03:09
【问题描述】:
我正在使用自定义init 方法将JSON 数据解析为Video struct。
extension Video: Codable {
init(dictionary: [String: Any]) throws {
self = try JSONDecoder().decode(Video.self, from: JSONSerialization.data(withJSONObject: dictionary))
}
private enum CodingKeys: String, CodingKey {
case duration, nsfw, genres, nextVideo, title, video_thumbnail_9x16, onexone_img, video_script, feature_img, sh_heading, tags, alt_content, video_thumbnail_16x9, pub_date, slug, aspect_ratio, _id, interactive, show, cast_crew, srt, sw_more
}
}
但我可以在仪器Leaks profiler 中看到这个int 导致内存泄漏。
这里有什么问题?
编辑:更多信息
正如有人指出的那样,泄漏可能在其他任何地方,我确实在仪器检查器中看到了带有closure 的获取数据方法。所以这可能是个问题。
这里是网络调用过程和代码中Video对象的创建。
homeVideosDatasource 属性完成了从 API 获取数据的全部工作。
callHomeVideosAPI 是从 viewDidLoad 调用的。 callHomeVideosAPI 首先获取了一个配置 json,它告诉在主屏幕中加载哪些部分。这些部分包含Video 对象(与其他一些对象一起,它们也会导致泄漏)。
var homeVideosDatasource = HomeVideosDatasource()
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(updateFirebaseToken), name: Notification.Name(Constants.fcmToken), object: nil)
notificationUpdate()
updateFirebaseToken()
activityIndicatorView.type = .ballPulse
activityIndicatorView.color = UIColor(hexString: Constants.kPinkColor)
self.refreshControl.tintColor = .white
self.refreshControl.addTarget(self, action: #selector(callAPIs), for: .valueChanged)
collectionView.addSubview(refreshControl)
callHomeVideosAPI()
setupViews()
NotificationCenter.default.post(name: Notification.Name.init(rawValue: Constants.homeLoadedNotification), object: nil)
if let appDelegate = UIApplication.shared.delegate as? AppDelegate {
// Tell Appdelegate that app is loaded
appDelegate.isHomeLoaded = true
appDelegate.showVideoDetailFromNotification()
}
}
private func callHomeVideosAPI() {
DispatchQueue.global(qos: .background).async {
self.homeVideosDatasource.fetchDataForHomeVideos { [weak self] (completed, error) in
guard let self = self else { return }
if let error = error {
DispatchQueue.main.async {
self.view.showMessageTicker(message: error)
}
return
}
if completed {
// remove sections with no data
let homeVideosSectionsWithDataOnly = self.homeVideosDatasource.datasource.filter { (homeVideosSection) -> Bool in
if homeVideosSection.datasource.count != 0 {
return true
} else {
return false
}
}
self.homeVideosDatasource.datasource = homeVideosSectionsWithDataOnly
self.stopAnimating()
self.refreshControl.endRefreshing()
self.state = .success
} else {
self.state = .error
}
}
}
}
现在HomeVideosDatasource:
class HomeVideosDatasource {
private var provider = MoyaProvider<ScoopWhoop>(plugins: [CompleteUrlLoggerPlugin()])
private var scoopWhoopProvider = MoyaProvider<ScoopWhoop>(plugins: [CompleteUrlLoggerPlugin()])
var datasource = [HomeVideosSection]()
private var dataCount = 0
func fetchDataForHomeVideos(_ closure: @escaping (Bool, String?) -> Void) {
if !(NetworkState().isInternetAvailable) {
closure(false, Constants.noInternetConnectionString)
return
}
DispatchQueue.global(qos: .background).async {
self.scoopWhoopProvider.request(.home) { [weak self] result in
guard let self = self else { return }
switch result {
case .success(let response):
do {
if var responseDict = try response.mapJSON() as? Dictionary<String, Any> {
if let data = responseDict["data"] as? [Dictionary<String, Any>] {
self.datasource.removeAll()
self.dataCount = data.count
for (index, dataDict) in data.enumerated() {
let homeVideosSection = HomeVideosSection(dataDict)
self.datasource.append(homeVideosSection)
homeVideosSection.fetchDataForSection(index) { [weak self] (success) in
guard let self = self else { return }
self.datasource[index] = homeVideosSection
let dataNotSetSections = self.datasource.filter { (homeVideosSectionObject) -> Bool in
!homeVideosSectionObject.isDataSet
}
if dataNotSetSections.count == 0 {
DispatchQueue.main.async {
closure(true, nil)
}
} else {
DispatchQueue.main.async {
closure(false, nil)
}
}
} else {
DispatchQueue.main.async {
closure(false, nil)
}
}
}
}
} else {
DispatchQueue.main.async {
closure(false, nil)
}
}
} else {
DispatchQueue.main.async {
closure(false, nil)
}
}
} catch (let error) {
DispatchQueue.main.async {
closure(false, error.localizedDescription)
}
}
case .failure(let error):
DispatchQueue.main.async {
closure(false, error.localizedDescription)
}
}
}
}
}
}
还有HomeVideosSection:
class HomeVideosSection {
var section_type: String!
var value: Value!
var showDetail: ShowDetail?
var isDataSet = false
var datasource = [Any]()
private var provider = MoyaProvider<ScoopWhoop>(plugins: [CompleteUrlLoggerPlugin()])
convenience init(_ dictionary: Dictionary<String, Any>) {
self.init()
section_type = dictionary["section_type"] as? String
do {
value = try JSONDecoder().decode(Value.self, from: JSONSerialization.data(withJSONObject: dictionary["value"] as? [String: Any] as Any))
} catch (let error) {
print("Error setting section \(section_type ?? "") value : \(error.localizedDescription)")
}
}
fileprivate func fetchDataForSection(_ index: Int, closure: @escaping (Bool) -> Void) {
print("fetching detail for section : \(section_type ?? "")")
// fetch details for section types
if !(NetworkState().isInternetAvailable) {
closure(false)
return
}
var requestType: ScoopWhoop?
if section_type == "app_exclusive" {
requestType = .appExclusiveVideos(offset: nil)
} else if section_type == "recently_added" {
requestType = .videos(offset: nil)
} else if section_type == "shows" {
requestType = .shows(offset: nil)
} else if section_type == "anchors" {
requestType = .actors(offset: nil)
} else if section_type == "more_shows" {
requestType = .filteredShows(offset: nil, filter_slug: value.slug)
} else if section_type == "sw_shows_video" {
requestType = .filteredShows(offset: nil, filter_slug: value.slug, filter_type:"show_sw_more")
} else if section_type == "sw_videos" {
requestType = .filteredShows(offset: nil, filter_slug: nil, filter_type:"sw_more")
} else if section_type == "sw_shows" {
requestType = .scoopwhoopShows(offset: nil)
} else if section_type == "trending" {
requestType = .filteredShows(offset: nil, filter_slug: nil, filter_type:"trending")
} else if section_type == "most_viewed" {
requestType = .filteredShows(offset: nil, filter_slug: nil, filter_type:"most_viewed")
} else {
// unhandled section_type
self.isDataSet = true
print("Data set for section : \(self.section_type ?? "")")
closure(true)
}
DispatchQueue.global(qos: .background).async {
if let requestType = requestType {
self.provider.request(requestType) { [weak self] result in
guard let self = self else { return }
switch result {
case .success(let response):
do {
if let responseDict = try response.mapJSON() as? Dictionary<String, Any> {
if let data = responseDict["data"] as? [Dictionary<String, Any>] {
if let showDetails = responseDict["show_details"] as? [String : Any] {
do {
let dataObject = try ShowDetail(dictionary: showDetails)
self.showDetail = dataObject
} catch (let error) {
print("HomeVideoSection ShowDetail object error " + error.localizedDescription)
}
}
for dataDict in data {
let dataDict = dataDict
if self.section_type == "shows" || self.section_type == "sw_shows" {
do {
let show = try Show(dictionary: dataDict)
self.datasource.append(show)
} catch (let error) {
print("HomeVideoSection Show object error " + error.localizedDescription)
}
} else if self.section_type == "anchors" {
do {
let actor = try Anchor(dictionary: dataDict)
self.datasource.append(actor)
} catch (let error) {
print("HomeVideoSection Anchor object error " + error.localizedDescription)
}
} else {
do {
let video = try Video(dictionary: dataDict)
self.datasource.append(video)
} catch (let error) {
print("HomeVideoSection Video object error " + error.localizedDescription)
}
}
}
if self.section_type != "anchors" && self.datasource.count != 0 {
self.datasource.append(ViewMore(title: "View All"))
}
self.isDataSet = true
print("Data set for section : \(self.section_type ?? "")")
closure(true)
} else {
print("Data set for section dict map error: \(self.section_type ?? "")")
closure(false)
}
}
} catch {
print("Data set for section JSON map error: \(self.section_type ?? "")")
closure(false)
}
case .failure:
print("Data set for section failure: \(self.section_type ?? "")")
closure(false)
}
}
}
}
}
}
【问题讨论】:
标签: ios swift memory-leaks instruments