why diskConfig.cachePathBlock should be reset to nil here? - swift

I'm watching the source code of Kingfisher. And i have an question in the following code (Source code):
why reset diskConfig.cachePathBlock to nil at the end of init method?
I can't figure it out.
public convenience init(
name: String,
cacheDirectoryURL: URL?,
diskCachePathClosure: DiskCachePathClosure? = nil) throws
{
if name.isEmpty {
fatalError("[Kingfisher] You should specify a name for the cache. A cache with empty name is not permitted.")
}
let totalMemory = ProcessInfo.processInfo.physicalMemory
let costLimit = totalMemory / 4
let memoryStorage = MemoryStorage.Backend<Image>(config:
.init(totalCostLimit: (costLimit > Int.max) ? Int.max : Int(costLimit)))
var diskConfig = DiskStorage.Config(
name: name,
sizeLimit: 0,
directory: cacheDirectoryURL
)
if let closure = diskCachePathClosure {
diskConfig.cachePathBlock = closure
}
let diskStorage = try DiskStorage.Backend<Data>(config: diskConfig)
diskConfig.cachePathBlock = nil
self.init(memoryStorage: memoryStorage, diskStorage: diskStorage)
}

Not setting the property to nil would keep a strong reference to the passed-in closure and potentially create a memory leak. Setting it to nil avoids this.
Assuming the nil assignment were not present, here's an example for a leak:
class CacheBuilder {
func createCache() -> ImageCache {
let cache = try! ImageCache(name: "Cache", cacheDirectoryURL: nil, diskCachePathClosure: self.path)
return cache
}
func path(_ url: URL, _ str: String) -> URL {
return url
}
deinit {
print("deinit") // is not called when Cache.init is done
}
}
class Cache {
let cache: ImageCache
init() {
let builder = CacheBuilder()
self.cache = builder.createCache()
}
}

Related

How can I fail during an init?

I am trying to create this class:
class CaptureVideoDataOutput: AVCaptureVideoDataOutput, AVCaptureVideoDataOutputSampleBufferDelegate {
private let imageCaptureModel: ImageCaptureModel
override init() {
super.init()
}
convenience init?(imageCaptureModel:ImageCaptureModel) {
self.imageCaptureModel = imageCaptureModel
let queueID = "\(Bundle.main.bundleIdentifier!).captureDeviceOutput"
let captureVideoDataOutputQueue = DispatchQueue(label: queueID,
attributes: DispatchQueue.Attributes.concurrent)
let captureVideoDataOutput = CaptureVideoDataOutput()
captureVideoDataOutput.videoSettings = videoSettingsParameter
captureVideoDataOutput.setSampleBufferDelegate(captureVideoDataOutput, queue: captureVideoDataOutputQueue)
guard
imageCaptureModel.captureSession.canAddOutput(captureVideoDataOutput) else {
return nil
}
self = captureVideoDataOutput
}
the idea is the user just using the convenience init and receive a nil if the whole thing fails on the guard or receive the object if it succeeds.
But this code is failing on the first line with
let' property 'imageCaptureModel' may not be initialized directly; use "self.init(...)" or "self = ..." instead
and on the last line with
Cannot assign to value: 'self' is immutable
Any ideas?
Create an additional (private) initializer for CaptureVideoDataOutput that takes ImageCaptureModel and call it in the convenience initializer instead of CaptureVideoDataOutput():
private init(_ model: ImageCaptureModel) {
self.imageCaptureModel = model
super.init()
}
convenience init?(imageCaptureModel:ImageCaptureModel) {
let queueID = "\(Bundle.main.bundleIdentifier!).captureDeviceOutput"
let captureVideoDataOutputQueue = DispatchQueue(label: queueID,
attributes: DispatchQueue.Attributes.concurrent)
self.init(imageCaptureModel)
videoSettings = videoSettingsParameter
setSampleBufferDelegate(captureVideoDataOutput, queue: captureVideoDataOutputQueue)
guard
imageCaptureModel.captureSession.canAddOutput(captureVideoDataOutput) else {
return nil
}
}
Note that also non-convenience initializers may return nil, so I'm not sure why you chose this setup. Consider this:
init?(model: ImageCaptureModel) {
self.imageCaptureModel = model
let queueID = "\(Bundle.main.bundleIdentifier!).captureDeviceOutput"
let captureVideoDataOutputQueue = DispatchQueue(label: queueID,
attributes: DispatchQueue.Attributes.concurrent)
super.init()
videoSettings = videoSettingsParameter
setSampleBufferDelegate(captureVideoDataOutput, queue: captureVideoDataOutputQueue)
guard
imageCaptureModel.captureSession.canAddOutput(captureVideoDataOutput) else {
return nil
}
}

Alamofire memory leaks Instruments

I am trying to clean my app from memory leaks and I have a few problems understanding this
Why Alamofire function Request.serializeResponseJSON is called 30 seconds after I've launched app: I did not touch anything or navigate anywhere, the screen was static.
Why does it leaks?
Why does my code leaks?
I get same leaks when the screen have loaded.
What I've tried so far:
Autoreleasepool;
Appending to and initialising arrays in every way that possible;
Changing all variable (class, func) to be optional/not
optional/weak;
Initialising classes in UIViewController;
Initialising classes in main thread;
Searching these problems in the internet.
I've found out, using Xcode memory tool, that it is somehow connected with _ContiguousArrayStorage, but I do not understand how and what is it actually.
I am out of any ideas what is wrong here. Any tips would be much appreciated.
Here is all related code:
My general API request
public func requestWithLocation(_ httpmethod: Alamofire.HTTPMethod, URL: String, parameters: [String: AnyObject]?, completionHandler: #escaping CompletionHandler) -> (){
var header: HTTPHeaders = [:]
var location: [String: Double] = [:]
let locationManager = CLLocationManager()
if (CLLocationManager.authorizationStatus() == .authorizedWhenInUse
|| CLLocationManager.authorizationStatus() == .authorizedAlways) && locationManager.location != nil {
location = [
"lon" : locationManager.location!.coordinate.longitude,
"lat" : locationManager.location!.coordinate.latitude
]
}
if User.sharedInstance.token != "" {
header["Authorization"] = User.sharedInstance.token
}
var parametersWithLocation = parameters ?? [:]
parametersWithLocation["location"] = location as AnyObject
Alamofire.request("\(serverAddress)/\(URL)", method: httpmethod, parameters: parametersWithLocation, encoding: JSONEncoding.default, headers: header).validate().responseJSON { response in
var data: JSON?
if response.result.value != nil {
data = JSON(response.result.value!)
}
if User.sharedInstance.token == "" {
User.sharedInstance.token = response.response?.allHeaderFields["Authorization"] as! String
} else {
if let header = response.response?.allHeaderFields["Authorization"] as? String {
if User.sharedInstance.token != header {
User.sharedInstance.token = header
}
}
}
completionHandler(data, response.result.error as NSError?)
}
}
My screen request
class func requestMainScreen(handler: #escaping ([ShortRestaurant], [ShortRestaurant], [ShortRestaurant]) -> ()) {
var dataForBestChoise: [ShortRestaurant] = []
var dataForTop: [ShortRestaurant] = []
var dataForNearest: [ShortRestaurant] = []
let group = DispatchGroup()
group.enter()
APIModel.sharedInstance.requestWithLocation(.post, URL: "restaurants/near", parameters: nil, completionHandler: {(data, error) in
guard let `data` = data else {
group.leave()
return
}
for JSON in data["restaurants"].arrayValue {
dataForNearest.append(ShortRestaurant.initFromJSON(JSON)) //here is leak
}
group.leave()
})
group.enter()
APIModel.sharedInstance.requestWithLocation(.post, URL: "restaurants/top", parameters: nil, completionHandler: {(data, error) in
guard let `data` = data else {
group.leave()
return
}
for JSON in data["restaurants"].arrayValue {
dataForTop.append(ShortRestaurant.initFromJSON(JSON))//here is leak
}
group.leave()
})
group.enter()
APIModel.sharedInstance.requestWithLocation(.post, URL: "restaurants/personal", parameters: nil, completionHandler: {(data, error) in
guard let `data` = data else {
group.leave()
return
}
for JSON in data["restaurants"].arrayValue {
dataForBestChoise.append(ShortRestaurant.initFromJSON(JSON)) //here is leak
}
group.leave()
})
group.notify(queue: DispatchQueue.main) {
handler(dataForBestChoise, dataForTop, dataForNearest)
}
}
My classes (I know this kind of initialisation is kinda wrong, but I'd changed to init(data: JSON) - did not help:
class func initFromJSON(_ data: JSON) -> ShortRestaurant {
let restaurant = ShortRestaurant()
restaurant.id = data["id"].stringValue
restaurant.name = data["name"].stringValue
restaurant.image = data["img"].stringValue
restaurant.description = data["shortDesc"].stringValue
restaurant.nameOfMetrostatin = data["address"]["metro"]["name"].stringValue
restaurant.mapType = data["mapType"].stringValue
restaurant.address = data["address"]["street"].stringValue
restaurant.longitude = data["address"]["location"][0].doubleValue
restaurant.latitude = data["address"]["location"][1].doubleValue
restaurant.phone = data["phone"].stringValue
restaurant.workTime = data["currentWork"].stringValue
restaurant.avarageBill = data["price"].stringValue
restaurant.peopleInfo = data["croud"].stringValue
restaurant.rating = data["rating"].stringValue
restaurant.ratingTrend = data["trend"].stringValue
restaurant.distance = data["distance"].doubleValue
restaurant.isFavourited = data["isFavourited"].bool ?? false
restaurant.specialOfferDescription = data["discounts"]["name"].string
restaurant.specialOfferName = data["discounts"]["type"].string
restaurant.alertText = data["label"]["name"].string
restaurant.alertIcon = data["label"]["type"].string
restaurant.alertBackground = data["label"]["color"].string
restaurant.avaliableDates = ReservationSchedule.initArrayFrom(data: data["availableDates"])
restaurant.avaliableTimes = data["scheduleRes"].arrayObject as? [String] ?? []
restaurant.doesHaveDiscount = data["discounts"]["id"].string != nil
restaurant.doesHaveEvent = data["events"]["id"].string != nil
restaurant.weeklyTop = data["weeklyTop"].bool ?? false
restaurant.monthlyTop = data["monthlyTop"].bool ?? false
restaurant.yearTop = data["yearTop"].bool ?? false
restaurant.isActive = data["isActive"].bool ?? true
return restaurant
}
Array of these leaks:
class ReservationSchedule {
var description: String
var data: String
var dayTitle: String
var fullTitle: String
init(data: JSON) {
self.data = data["value"].stringValue
self.dayTitle = data["day"].stringValue
self.description = data["label"].stringValue
self.fullTitle = data["title"].stringValue
}
class func initArrayFrom(data: JSON) -> [ReservationSchedule] {
var schedule: [ReservationSchedule] = []
for day in data.arrayValue {
schedule.append(ReservationSchedule.init(data: day)) //here is leak
}
return schedule
}
}
Have you tried setting URLCache time to request, This will remove the the cache of request and free's the memory

NSKeyedArchiver.archiveRootObject returns "false" if I want to overwrite data, why?

NSKeyedArchiver.archiveRootObject(<#rootObject: AnyObject#>, toFile: <#String#>)
Only returns true the first time. Every next time I call it, the method returns false.
I read some SO, some posts said that I can't rewrite data this way. However, I tried :
NSFileManager.defaultManager().removeItemAtPath(path, error: nil)
and it still didn't help.
What I did:
Checked all my model files for the NSCoding protocol
Checked all my required init(coder aDecoder: NSCoder) and func encodeWithCoder(aCoder: NSCoder)
I am missing something, since I have done this in my last app and it worked fla`
import Foundation
private let ON_DISK_DATA_DICTIONARY = "savedDataPathsOnDisk"
private let _WBMAccessDataOnDiskMShared = WBMAccessDataOnDiskM()
private var dataDirectories:NSArray! = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)
private var dataDirectoryURL:NSURL! = NSURL(fileURLWithPath: dataDirectories.objectAtIndex(0) as! String, isDirectory: true)
private var dataDirectoryPath:String! = dataDirectoryURL.path!
let FILE_FORMAT = ".archive"
class WBMAccessDataOnDiskM: NSObject
{
class var sharedData: WBMAccessDataOnDiskM
{
return _WBMAccessDataOnDiskMShared
}
private var dataAndPathDictionary = [String:String]()
func getDataAndPathDictionary() -> [String:String]
{
return self.dataAndPathDictionary
}
func addDataAndPathToDictionary(data:String ,path:String)
{
if !checkIfDataAllreadyExists(data)
{
let fullPath = createFullDataPath(path)
dataAndPathDictionary[data] = fullPath
NSUserDefaults.standardUserDefaults().setObject(dataAndPathDictionary, forKey: ON_DISK_DATA_DICTIONARY)
}
}
func checkIfDataIsAvailable(dataPathComponent:String) -> (Bool,String)
{
var paths = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)[0] as! String
var dataPath = paths.stringByAppendingPathComponent(dataPathComponent)
var checkValidation = NSFileManager.defaultManager()
println(dataPathComponent)
if (checkValidation.fileExistsAtPath(dataPath))
{
return (true,dataPath)
}
else
{
return (false,"")
}
}
func checkForDataOnDisk() -> Bool
{
let dataDict = NSUserDefaults.standardUserDefaults().objectForKey(ON_DISK_DATA_DICTIONARY) as? [String:String]
if dataDict == nil
{
return false
}
else
{
dataAndPathDictionary = dataDict!
return true
}
}
private func checkIfDataAllreadyExists(data:String) -> Bool
{
let keys = self.dataAndPathDictionary.keys.array
if contains(keys, data)
{
return true
}
return false
}
private func createFullDataPath(path:String) -> String
{
var fullPathURL = dataDirectoryURL.URLByAppendingPathComponent(path + FILE_FORMAT)
return fullPathURL.path!
}
func saveDataArray(data:[AnyObject], path:String)
{
NSFileManager.defaultManager().removeItemAtPath(path, error: nil)
if NSKeyedArchiver.archiveRootObject(data, toFile: path)
{
// SAVING
println(" Saving data ARRAY ")
}
else
{
println(" NOT saving data ARRAY ")
}
}
func saveDataObject(dataObject:AnyObject, path:String)
{
if NSKeyedArchiver.archiveRootObject(dataObject, toFile: path)
{
println(" Saving data OBJECT ")
}
else
{
println(" NOT saving data OBJECT ")
}
}
// dataFromDisk = NSKeyedUnarchiver.unarchiveObjectWithFile(pathForNews) as? [AnyObject]
func loadDataArray(path:String) -> [AnyObject]?
{
var dataArrayFromDisk: [AnyObject]?
dataArrayFromDisk = NSKeyedUnarchiver.unarchiveObjectWithFile(path) as? [AnyObject]
return dataArrayFromDisk
}
func loadDataObject(path:String) -> AnyObject?
{
var dataObjectFromDisk: AnyObject?
dataObjectFromDisk = NSKeyedUnarchiver.unarchiveObjectWithFile(path)
return dataObjectFromDisk
}
func getNewsDataLanguagePath() -> String
{
var currentOSLanguage = LOCALIZATION.currentOsLanguage
currentOSLanguage = currentOSLanguage.substringToIndex(2)
if currentOSLanguage == "de"
{
return ON_DISK_CONTENT_DE
}
else if currentOSLanguage == "en"
{
return ON_DISK_CONTENT_ENG
}
return ON_DISK_CONTENT_ENG
}
`
I am using Xcode 6.4 and Swift 1.2.
Any help & code correction is welcome.
Because of the code you put here does't contain the call of saveDataArray or saveDataObject so I judge that you have maintain the path of a archived object manually.This is where thing went wrong. The method of NSKeyedArchiver named archiveRootObject can automatically maintain the archiver file path.
In the Apple's doucumet
Archives an object graph rooted at a given object by encoding it into a data object then atomically writes the resulting data object to a file at a given path, and returns a Boolean value that indicates whether the operation was successful.
And there is another question in SO may help you.
I followed apple instructions in this good example: Persist Data
But I had the same problem you describe with my app for AppleTV. At the end I change .Documents directory for CacheDirectory and it's working well.
static let DocumentsDirectorio = NSFileManager().URLsForDirectory(.CachesDirectory, inDomains: .UserDomainMask).first!

Cannot assign value to stored property from within computed property

I created a struct in which I have a property '_photo' that is lazily assigned when the computed property 'photo' is called. I keep getting the error
Cannot assign to '_photo' in 'self'
Here is the code. I wrote the computed method in both Swift 1.1 (photo1) and swift 1.2 (photo2) syntax. Both give the same compile time error as noted above.
What changes are needed to fix that error?
import UIKit
public struct PhotoStruct {
var _photo:UIImage?
var urlString:String?
init(image:UIImage?, url:String?){
self._photo = image
self.urlString = url
}
init(url:String?){
self.urlString = url
}
var photo1:UIImage? {
if let theURL = self._photo {
return self._photo
}else{
if let urlString = self.urlString{
if let url = NSURL(string: urlString as String){
if let imageData :NSData = NSData(contentsOfURL: url){
if let image:UIImage = UIImage(data:imageData){
self._photo = image //** Cannot assign error is here**
}
}
}
}
return self._photo
}
}
var photo2:UIImage? {
if let theURL = self._photo {
return self._photo
}else{
if let urlString = self.urlString,
url = NSURL(string: urlString as String),
imageData :NSData = NSData(contentsOfURL: url),
image:UIImage = UIImage(data:imageData){
self._photo = image //** Cannot assign error is here**
}
return self._photo
}
}
}
As for struct, If you want to mutate the self property inside computed properties, you have to explicitly declare the getter as mutating get { .. }
public struct PhotoStruct {
var _photo:UIImage?
var urlString:String?
init(image:UIImage?, url:String?){
self._photo = image
self.urlString = url
}
init(url:String?){
self.urlString = url
}
var photo1:UIImage? {
mutating get {
// ^^^^^^^^^^^^
// you can set `self._photo = image` here
}
}
var photo2:UIImage? {
mutating get {
// ^^^^^^^^^^^^
// you can set `self._photo = image` here
}
}
}
Of course, the struct itself have to be mutable:
var pVar:PhotoStruct = PhotoStruct(image: nil, url: nil)
pVar.photo1 // no problem
let pLet:PhotoStruct = PhotoStruct(image: nil, url: nil)
pLet.photo1 // < [!] error: immutable value of type 'PhotoStruct' only has mutating members named 'photo1'
One caveat:
As far as I know, mutating get { } feature is undocumented on the language reference.
Structs are value types and they are immutable.
This means that you cannot set variable and mutate self.
If you need to mutate struct, you have to make mutating func
public struct PhotoStruct {
var _photo:UIImage?
var urlString:String?
mutating func loadPhoto() -> UIImage {
.. Code here
_photo = UIImage(data:imageData)
// Here you mutate a struct, but it's ok because we make method as mutating
}
}
In you example I would make a mutating method instead of property
mutating func photo () -> UIImage? {
if let photo = self._photo {
return photo
} else {
if let urlString = self.urlString,
url = NSURL(string: urlString),
imageData = NSData(contentsOfURL: url),
image = UIImage(data:imageData) {
self._photo = image
}
return self._photo
}
}
PhotoStruct is a struct and therefore a value type. For value types,
only methods explicitly marked as mutating can modify the properties
of self, so this is not possible within a computed property.
If you change PhotoStruct to be a class then your code compiles
without problems.

Swift 'NilLiteralConvertible' error: Optional class type

In StoryViewController.swift:
var story :Story?
if story != nil {...}// ERROR: Type UInt8 doer not conform to protocol 'NilLiteralConvertible'
In Story.swift:
class Story {
var title: String
var content: String
init(title: String, content: String) {
self.title = title
self.content = content
}
func description() -> String {
return title
}
class func loadStories(completion: ((Array<Story>?, NSErrorPointer) -> Void)!) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), {
var error: NSErrorPointer = nil
let path = NSBundle.mainBundle().bundlePath
let manager = NSFileManager.defaultManager()
var stories = [Story]()
if let contents = manager.contentsOfDirectoryAtPath(path, error: error) {
error = nil
for file in contents {
if file.hasSuffix(".grm") {
let filePath = path.stringByAppendingPathComponent(file as String)
let title = file.stringByDeletingPathExtension
let content = NSString(contentsOfFile: filePath, encoding: NSUTF8StringEncoding, error: error)
let story = Story(title: title, content: content)
if error != nil {
break
}
stories.append(story)
error = nil
}
}
}
stories.sort({ a, b in
a.title < b.title
})
dispatch_async(dispatch_get_main_queue(), {
if error != nil {
completion(nil, error)
} else {
completion(stories, nil)
}
})
});
}
}
Am I write it right? How can I fix the error:"Type UInt8 doer not conform to protocol 'NilLiteralConvertible'" in StoryViewController.swift? Thanks a lot!
more: I just want to check whether the story file is empty. And if it's not, show info in the view. The code above successfully ran before Swift beta3 released. I guess may be the new NilLiteralConvertible protocal cause this error.
/*************************************************/
I download this project from http://cdn5.raywenderlich.com/wp-content/uploads/2014/09/Grimm-Swift.zip as my exercise from a bbs. If you run this version without any modified you'll noticed the error above. Thanks the creator of this project.
/*************************************************/
use this
var story: Story?
story = Story (title: "Stack", content: "overflow")
if story != nil {
println("It's not nil!")
}
else {
println("It's nil!")
}
Replace Class with class.
If this is not the cause please post more code.