How can I fail during an init? - swift

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
}
}

Related

why diskConfig.cachePathBlock should be reset to nil here?

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()
}
}

Swift. How to get value of variable outside the function

I am working on robotics project and trying to get position of tracking object as a value of the yTrack variable (see the code). I can print yTrack from the func handleVisionRequestUpdate, but I need access to yTrack outside this function in order to use it with other function. getCoord () as an example. Please help!
import AVFoundation
import Vision
import UIKit
import Foundation
class ViewController: UIViewController, AVCaptureVideoDataOutputSampleBufferDelegate {
var protocolString: String?
var inputStream: InputStream?
var outputStream: OutputStream?
var dataAsString: String?
var yTrack: Double?
#IBOutlet private weak var cameraView: UIView?
#IBOutlet private weak var highlightView: UIView? {
didSet {
self.highlightView?.layer.borderColor = UIColor.red.cgColor
self.highlightView?.layer.borderWidth = 4
self.highlightView?.backgroundColor = .clear
}
}
private let visionSequenceHandler = VNSequenceRequestHandler()
private lazy var cameraLayer: AVCaptureVideoPreviewLayer = AVCaptureVideoPreviewLayer(session: self.captureSession)
private lazy var captureSession: AVCaptureSession = {
let session = AVCaptureSession()
session.sessionPreset = AVCaptureSession.Preset.photo
guard
let backCamera = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .back),
let input = try? AVCaptureDeviceInput(device: backCamera)
else { return session }
session.addInput(input)
return session
}()
override func viewDidLoad() {
super.viewDidLoad()
self.highlightView?.frame = .zero
self.cameraView?.layer.addSublayer(self.cameraLayer)
let videoOutput = AVCaptureVideoDataOutput()
videoOutput.setSampleBufferDelegate(self, queue: DispatchQueue(label: "MyQueue"))
self.captureSession.addOutput(videoOutput)
self.captureSession.startRunning()
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
self.cameraLayer.frame = self.cameraView?.bounds ?? .zero
}
public var lastObservation: VNDetectedObjectObservation?
func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {
guard
let pixelBuffer: CVPixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer),
let lastObservation = self.lastObservation
else { return }
let request = VNTrackObjectRequest(detectedObjectObservation: lastObservation, completionHandler: self.handleVisionRequestUpdate)
request.trackingLevel = .fast
do {
try self.visionSequenceHandler.perform([request], on: pixelBuffer)
} catch {
print("Throws: \(error)")
}
}
public func handleVisionRequestUpdate(_ request: VNRequest, error: Error?) {
test {(yTrack) in
DispatchQueue.main.async {
guard let newObservation = request.results?.first as? VNDetectedObjectObservation else { return }
self.lastObservation = newObservation
guard newObservation.confidence >= 0.3 else {
self.highlightView?.frame = .zero
return
}
self.transformedRect = newObservation.boundingBox
self.transformedRect!.origin.y = 1 - self.transformedRect!.origin.y
let convertedRect = self.cameraLayer.layerRectConverted(fromMetadataOutputRect: self.transformedRect!)
self.highlightView?.frame = convertedRect
}
}
let yTrack = Double(self.transformedRect?.origin.y ?? 0.5)
// HERE IT WORKS, yTrack IS PRINTING, BUT I NEED IT OUTSIDE THIS FUNCTION
print(yTrack as Any)
}
public func test (returnCompletion: #escaping (AnyObject) -> () ){
DispatchQueue.global(qos: .background).async {
self.yTrack = Double(self.transformedRect?.origin.y ?? 0.5)
returnCompletion(self.yTrack as AnyObject)
}
}
public func getCoord () {
//HERE IT DOESN'T WORK. NOTHING IS PRINTING FROM HERE.
print(yTrack)
}
The problem is with your handleVisionRequestUpdate function.
First of all #Azat was right in comments: when you declare let yTrack = Double(self.transformedRect?.origin.y ?? 0.5) you create new variable in scope of this function which has no connection with var yTrack from ViewController's scope.
So when you print yTrack in this function you print function's internal variable which will be destroyed after function returns. To be able to use yTrack outside of a function you need to assign new value to ViewController's youTrack and then you'll be able to use it in any function you want
yTrack = Double(self.transformedRect?.origin.y ?? 0.5)
The second problem is with DispatchQueue.main.async. The code inside this block will be performed in most cases after this block
let yTrack = Double(self.transformedRect?.origin.y ?? 0.5)
print(yTrack as Any)
that's because using this block you tell compiler "create separate «queue» for this block of code and asynchronously perform it when you'll be able to do it" so this line self.transformedRect = newObservation.boundingBox will be done in most cases after this line let yTrack = Double(self.transformedRect?.origin.y ?? 0.5) and you'll have previous transformedRect inside this line which is nil if I understand correctly. So remove DispatchQueue.main.async from this function or move self.yTrack = Double(self.transformedRect?.origin.y ?? 0.5) into it.
Check out the code below. I added a lot of comments but I didn't test it out. But it should fix things. There were a couple confusing things going on. Specifically with the "test" function. Also, you may want to practice using self.myVar or self.myFunc() so that you start to get a better understanding of which variables are local and which are properties on the view controller:
public func handleVisionRequestUpdate(_ request: VNRequest, error: Error?) {
// test {(yTrack) in /// remove this since we removed it below
DispatchQueue.main.async {
guard let newObservation = request.results?.first as? VNDetectedObjectObservation else { return }
self.lastObservation = newObservation
guard newObservation.confidence >= 0.3 else {
self.highlightView?.frame = .zero
return
}
self.transformedRect = newObservation.boundingBox
self.transformedRect!.origin.y = 1 - self.transformedRect!.origin.y
let convertedRect = self.cameraLayer.layerRectConverted(fromMetadataOutputRect: self.transformedRect!)
self.highlightView?.frame = convertedRect
//
// Move these into the Dispatch closure
// let yTrack = Double(self.transformedRect?.origin.y ?? 0.5) // delete this one
yTrack = Double(self.transformedRect?.origin.y ?? 0.5) // replace it with this one
print(yTrack as Any)
}
}
//
// Remove this. I'm not sure what it does, but its making things more complex
//
// public func test (returnCompletion: #escaping (AnyObject) -> () ){
// DispatchQueue.global(qos: .background).async {
// self.yTrack = Double(self.transformedRect?.origin.y ?? 0.5)
// returnCompletion(self.yTrack as AnyObject)
// }
// }

Property declared in my class isn't recognized when attempting to use it inside a function?

I've checked for the misspelling of the property, that's definitely not the case. I'm trying to use the property mySong that I declared in my class inside the parseSongs() function.
That function isn't inside the class but it's in the same file. And the target membership of that class is set to the project name as are the other files as well.
I'm very confused why the compiler isn't recognizing the name of my property in the parseSongs()?
I can declare the property outside of the class but I should be able to use it even if it's declared inside the class.
import UIKit
class SongsTableViewController: UITableViewController {
//A property that is an array of type 'Song'
var mySong = [Song]()
private let cache = NSCache()
private func fetchMyData(){
let myUrl = NSURL(string: "http://itunes.apple.com/search?term=beatles&country=us")!
let mySession = NSURLSession.sharedSession()
//The work to be queued initiates
let myTask = mySession.dataTaskWithURL(myUrl){
//This closure right here is the Completion Handler
data, response, error in
if error != nil{
//Handle error
}else{
let myHttpResponse = response as! NSHTTPURLResponse
switch myHttpResponse.statusCode {
case 200..<300:
print("OK")
print("data: \(data)")
default: print("request failed: \(myHttpResponse.statusCode)")
}
}
}
myTask.resume()
}
}
func parseJson(myData data: NSData){
do{
let json: AnyObject? = try NSJSONSerialization.JSONObjectWithData(data, options: [])
if let unwrappedJson: AnyObject = json{
parseSongs(unwrappedJson)
}
}catch{
}
}
func parseSongs(json1: AnyObject){
mySong = []
//Optional Binding
if let array = json1["results"] as? [[String:AnyObject]]{
//For-In loop
for songDictionary in array{
if let title = songDictionary["trackName"] as? NSString{
if let artist = songDictionary["artistName"] as? NSString{
if let albumName = songDictionary ["collectionName"] as? NSString{
if let artWorkUrl = songDictionary["artWorkUrl100"] as? NSString {
let song = Song(artist: (artist as String), title: (title as String), albumName: (albumName as String), artWorkUrl: (artWorkUrl as String))
mySong.append(song)
}
}
}
}
}
dispatch_async(dispatch_get_main_queue()){
self.tableView.reloadData()
}
}
}
To use the property outside which declared inside a class you have to follow this
SongsTableViewController().theProperty
If you declare it outside class then you can access it in function of outside class

Swift objects array to plist file

I am trying to save my object's array to array.plist but I get the following error:
Thread 1: signal SIGABRT error
My object class looks like this:
class Note {
// MARK: Properties
var title: String
var photo: UIImage?
var text: String
// MARK: Initialization
init?(title: String, photo: UIImage?, text: String) {
// Initialize stored properties.
self.title = title
self.photo = photo
self.text = text
// Initialization should fail if there is no name or if the rating is negative.
if title.isEmpty{
return nil
}
}
func encodeWithCoder(aCoder: NSCoder!) {
aCoder.encodeObject(title, forKey:"title")
aCoder.encodeObject(text, forKey:"text")
aCoder.encodeObject(photo, forKey:"photo")
}
init (coder aDecoder: NSCoder!) {
self.title = aDecoder.decodeObjectForKey("title") as! String
self.text = aDecoder.decodeObjectForKey("text") as! String
self.photo = aDecoder.decodeObjectForKey("photo") as! UIImage
}
}
In the controller, I try to save the array with the Notes object like this:
notes = [Notes]()
notes.append(note)
let paths = NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.DocumentDirectory,NSSearchPathDomainMask.AllDomainsMask, true)
let path: AnyObject = paths[0]
let arrPath = path.stringByAppendingString("/array.plist")
NSKeyedArchiver.archiveRootObject(notes, toFile: arrPath)
Not all the properties in your class are not optional, yet when you retrieve them from the plist, you are unwrapping all of them. This might cause your code to crash.
For example, if the photo is nil and you saved the object, when you are retrieving it, you are unwrapping it self.photo = aDecoder.decodeObjectForKey("photo") as! UIImage, which will crash if you did not save anything there.
Try removing the unwrapping and check again for your crash. Even if this was not the cause of your crash, it will cause a crash at some point.
If this does not fix your problem, please paste the complete error log so it is a bit more clear what is happening.
For swift 5. You can save an array of custom classes to a .plist file that inherits from NSObject and NSSecureCoding.
If we create a custom class called Person:
import Foundation
class Person: NSObject, NSSecureCoding {
//Must conform to NSSecureCoding protocol
public class var supportsSecureCoding: Bool { return true } //set to 'true'
//just some generic things to describe a person
private var name:String!
private var gender:String!
private var height:Double!
//used to create a new instance of the class 'Person'
init(name:String, gender:String, height:Double) {
super.init()
self.name = name
self.gender = gender
self.height = height
}
//used for NSSecureCoding:
func encode(with coder: NSCoder) {
coder.encode(name, forKey: "name") //encodes the name to a key of 'name'
coder.encode(gender, forKey: "gender")
coder.encode(height, forKey: "height")
}
//used for NSSecureCoding:
required init?(coder: NSCoder) {
super.init()
self.name = (coder.decodeObject(forKey: "name") as! String)
self.gender = (coder.decodeObject(forKey: "gender") as! String)
self.height = (coder.decodeObject(forKey: "height") as! Double)
}
//created just to print the data from the class
public override var description: String { return String(format: "name=%#,gender=%#,height%f", name, gender, height) }
}
Now we can create functions to save and load from a .plist file in the ViewController class:
We need to gather data from the directory system of the device:
func documentsDirectory()->String {
let paths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)
let documentsDirectory = paths.first!
return documentsDirectory
}
func dataFilePath ()->String{
return self.documentsDirectory().appendingFormat("/your_file_name_here.plist")
}
function to save the array:
func saveData(_ people:[Person]) {
let archiver = NSKeyedArchiver(requiringSecureCoding: true)
archiver.encode(people, forKey: "your_file_name_here")
let data = archiver.encodedData
try! data.write(to: URL(fileURLWithPath: dataFilePath()))
}
function to load the array:
func loadData() -> [Person] {
let path = self.dataFilePath()
let defaultManager = FileManager()
var arr = [Person]()
if defaultManager.fileExists(atPath: path) {
let url = URL(fileURLWithPath: path)
let data = try! Data(contentsOf: url)
let unarchiver = try! NSKeyedUnarchiver(forReadingFrom: data)
//Ensure the unarchiver is required to use secure coding
unarchiver.requiresSecureCoding = true
//This is where it is important to specify classes that can be decoded:
unarchiver.setClass(Person.classForCoder(), forClassName: "parentModule.Person")
let allowedClasses =[NSArray.classForCoder(),Person.classForCoder()]
//Finally decode the object as an array of your custom class
arr = unarchiver.decodeObject(of: allowedClasses, forKey: "your_file_name_here") as! [Person]
unarchiver.finishDecoding()
}
return arr
}
In the ViewController class:
override func viewDidLoad() {
super.viewDidLoad()
let testPerson = Person(name: "Bill", gender: "Male", height: 65.5)
let people:[Person] = [testPerson]
//Save the array
saveData(people)
//Load and print the first index in the array
print(loadData()[0].description)
}
Output:
[name=Bill,gender=Male,height=65.5000000]

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.