问题描述
我正在使用自定义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
分析器中看到,这个int
导致了内存泄漏。
这是什么问题?
编辑:更多信息
正如指出的那样,泄漏可能在其他任何地方,我也确实在仪器检查器中看到了closure
的提取数据方法。所以可能是个问题。
属性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_type:"trending")
} else if section_type == "most_viewed" {
requestType = .filteredShows(offset: 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)
}
}
}
}
}
}
解决方法
问题可能出在您使用init
方法的方式上。在Swift中,Objective-C中使用的模式不再适用,这意味着您不应为self
分配任何内容。例如,如果某些初始化在Objective-C中看起来像这样:
- (instancetype)init {
self = [super init];
if (self) {
// Initializing code
}
return self;
}
在Swift中,完全相同的初始化看起来像:
init() {
// Initialize all members defined by this subclass
super.init()
// Perform other initialization routines (e.g. call self.configure())
}
请注意,对self
和return self
语句的赋值已由一个简单的super.init()
调用替换。还要注意的另一件重要事情是,子类中的所有成员必须在调用super之前初始化,而其余初始化逻辑必须在调用super之后,而Objective-C中根本不存在这些规则。这些是Swift强制执行的规则,以确保初始化程序尽可能安全,如果尝试使用不同于此标准模式的某些东西,可能会有很多奇怪的副作用。
现在,回到问题所在,我没有适当的解释,为什么除了分配给self
之外,您的代码还会导致内存泄漏是一种不确定的行为。
一种更好的替代方法是以静态方法执行反序列化:
extension Video: Codable {
static func create(from dictionary: [String: Any]) throws -> Video {
return 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
}
}
更新:
由于上面使用静态方法的代码片段就可以正常工作,因此更好的解决方案是将JSON反序列化委托给对象到专门的函数/类:
func decodeJSON<T: Decodable>(_ dictionary: [String: Any]) throws -> T {
return try JSONDecoder().decode(T.self,from: JSONSerialization.data(withJSONObject: dictionary))
}
让Swift类型推断机制确定T
是什么类型:
let json = [String: Any]()
let video: Video = try? decodeJSON(json)
更新2
乍一看,其余代码看起来还不错。那里没有明显的保留周期。我建议尝试对每个封闭使用[weak self]
并检查泄漏是否仍然存在(通常是养成虚弱自我的好习惯,除非您特别需要牢记此自我)。如果这不能消除内存泄漏,则可能来自其他地方。请记住,没有正确使用Swift的内存管理工具会导致非常奇怪的问题。这可能来自完全不相关的代码。
例如,我曾经让应用程序播放随机音频,结果发现它是视图控制器的泄漏实例,该实例正在将委托注册到其他服务(以委托为强引用),从而创建了一个强大的参考周期。整个音频管理已正确实施(音频需要以一定的时间间隔播放),并且很难追踪。因此,在花了几个小时调试视图控制器和音频服务之间的交互之后,我终于发现了泄漏发生的地方,它实在令人沮丧,以至于与产生意外行为的那段代码完全无关。
所以我想说的是,在这段特定的代码部分(尽管对我来说看起来还不错)尝试了几件事之后,请尝试检查并清理其余的代码,因为问题出在其他地方的可能性很大。