I have sub class of OutputStream. In that I have two init() methods one have prefix word convenience.
Here is my code :
class FileOutputStream : OutputStream
{
fileprivate let filepath:URL
fileprivate let channel:DispatchIO!
convenience init?(filename:String) {
let pathURL = FileManager.default.urls(for: FileManager.SearchPathDirectory.documentDirectory, in:.userDomainMask).first!.appendingPathComponent(filename)
self.init(filepath:pathURL)
}
init?(filepath f:URL) {
self.filepath = f
//if let path = f.path,
if let cpath = (f.path).cString(using: String.Encoding.utf8) {
let outputflag:Int32 = O_CREAT | O_WRONLY // create, write-only
let mode:mode_t = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH // permissions: u+rw, g+r, o+r
self.channel = DispatchIO(type: DispatchIO.StreamType.stream, path:cpath, oflag:outputflag,mode: mode, queue: DispatchQueue.global(qos: DispatchQoS.QoSClass.background)) { (errcode:Int32) -> Void in
if errcode != 0 {
print("FileOutputStream: error creating io channel")
}
}
}
else {
self.channel = nil
return nil
}
}
func write(_ string: String) {
if let dataString = string.data(using: String.Encoding.utf8) {
dataString.withUnsafeBytes {(bytes: UnsafePointer<UInt8>) -> Void in
var data = DispatchData.empty
data.append(bytes, count: dataString.count)
self.channel.write(offset: 0, data: data, queue: DispatchQueue.global(qos:.background), ioHandler: { (complete: Bool, data: DispatchData?, errorCode: Int32) in
//handle progress reporting here
if errorCode != 0 {
print("FileOutputStream: error writing data to channel")
}
})
}
}
}
deinit {
self.channel.close(flags: DispatchIO.CloseFlags.stop)
}
}
I am getting error
'self' used before super.init call
I have tried by writing
super.init()
in both method, at start of methods
at end of methods
only in second method
only in first method.
But still getting error. If anyone know, please help me.
Use super.init(url: f, append: false/true) at the end of your init?(filepath f:URL) method .
Your intializer must call designated intializer of super class..Call any of these init methods that is available in OutputStream class...
public init(toMemory: ())
public init(toBuffer buffer: UnsafeMutablePointer<UInt8>, capacity: Int)
#available(iOS 4.0, *)
public init?(url: URL, append shouldAppend: Bool)
Related
hi i want to test CNContacts Store since this is my first time doing test, i don't have any idea how to conduct a unit test. This the code i want to test.
private func fetchContacts() {
var contacts: [Contact] = []
let keys: [CNKeyDescriptor] = [CNContactFormatter.descriptorForRequiredKeys(for: .fullName),
CNContactPhoneNumbersKey as CNKeyDescriptor]
let request = CNContactFetchRequest(keysToFetch: keys)
do {
try contactStore.enumerateContacts(with: request) {
(contact, stop) in
let name: String = CNContactFormatter.string(from: contact, style: .fullName) ?? contact.nickname
contacts.append(contentsOf: contact.phoneNumbers.compactMap({ phoneNumber in
let phoneNumberString: String = phoneNumber.value.stringValue
return .init(name: name, phoneNumber: phoneNumberString)
}))
}
allContacts = contacts
isContactsFetched = true
filterContacts()
}
catch {
print("unable to fetch contacts")
}
}
I'm using sourcery to generate mock from CNContactStore this is the enumerated mock i generate using sorcery
//MARK: - enumerateContacts
var enumerateContactsWithUsingBlockThrowableError: Error?
var enumerateContactsWithUsingBlockCallsCount = 0
var enumerateContactsWithUsingBlockCalled: Bool {
return enumerateContactsWithUsingBlockCallsCount > 0
}
var enumerateContactsWithUsingBlockReceivedArguments: (fetchRequest: CNContactFetchRequest, block: (CNContact, UnsafeMutablePointer<ObjCBool>) -> Void)?
var enumerateContactsWithUsingBlockReceivedInvocations: [(fetchRequest: CNContactFetchRequest, block: (CNContact, UnsafeMutablePointer<ObjCBool>) -> Void)] = []
var enumerateContactsWithUsingBlockClosure: ((CNContactFetchRequest, #escaping (CNContact, UnsafeMutablePointer<ObjCBool>) -> Void) throws -> Void)?
func enumerateContacts(with fetchRequest: CNContactFetchRequest, usingBlock block: #escaping (CNContact, UnsafeMutablePointer<ObjCBool>) -> Void) throws {
if let error = enumerateContactsWithUsingBlockThrowableError {
throw error
}
enumerateContactsWithUsingBlockCallsCount += 1
enumerateContactsWithUsingBlockReceivedArguments = (fetchRequest: fetchRequest, block: block)
enumerateContactsWithUsingBlockReceivedInvocations.append((fetchRequest: fetchRequest, block: block))
try enumerateContactsWithUsingBlockClosure?(fetchRequest, block)
}
what i did so far for unit test is this
it("should fetch contacts") {
let contact = CNContact()
let stop = UnsafeMutablePointer<ObjCBool>.allocate(capacity: 1)
stop[0] = true
// When
viewModel.onViewDidAppear()
// Then
mockContactStore.enumerateContactsWithUsingBlockClosure = { (_, args) in
args(contact, stop)
expect(mockContactStore.enumerateContactsWithUsingBlockCallsCount).to(equal(1))
}
}
Please help
if you want to test this ->
let request = CNContactFetchRequest(keysToFetch: keys)
you can do like this ->
protocol CNContactFetchRequestProtocol {
}
extension CNContactFetchRequest: CNContactFetchRequestProtocol {
}
let request: CNContactFetchRequestProtocol = CNContactFetchRequest(keysToFetch: keys)
and finally you can create mock
class MockContact: CNContactFetchRequestProtocol {
}
and then you can tests like this:
let request: CNContactFetchRequestProtocol = MockContact()
I am writing a Hacker News iOS application using SwiftUI/Combine. They have an API call for getting the ids of top posts and then you are supposed to request each story by itself. For this I have created storyIds(:) -> AnyPublisher<[Int], Error> and story(for:) -> AnyPublisher<Post, Error> for those calls.
Now I want to combine them into one function, getStories() which first download the identifiers and then goes through them fetching the stories one by one. I suppose you can use MergeMany or something else in the API for achieving this but I am not sure
The last thing I need is a function that combine these into stories() -> AnyPublisher<[Post], Error>. I found another question doing almost that. What I miss however, is a way to report progress of the task. I would like to update a counter for each fetched story to show the user how much is left of the download. How can I do this?
struct Agent {
struct Response<T> {
let value: T
let response: URLResponse
}
func run<T: Decodable>(_ request: URLRequest, _ decoder: JSONDecoder = JSONDecoder()) -> AnyPublisher<Response<T>, Error> {
return URLSession.shared
.dataTaskPublisher(for: request)
.tryMap { result -> Response<T> in
let value = try decoder.decode(T.self, from: result.data)
return Response(value: value, response: result.response)
}
.receive(on: DispatchQueue.main)
.eraseToAnyPublisher()
}
}
enum HackerNewsAPI {
static let agent = Agent()
static let base = URL(string: "https://hacker-news.firebaseio.com/v0/")!
}
extension HackerNewsAPI {
static func storyIds() -> AnyPublisher<[Int], Error> {
let request = URLRequest(url: base.appendingPathComponent("topstories.json"))
return agent.run(request)
.print()
.map(\.value)
.eraseToAnyPublisher()
}
}
extension HackerNewsAPI {
static func story(for id: Int) -> AnyPublisher<Post, Error> {
let request = URLRequest(url: base.appendingPathComponent("item/\(id).json"))
return agent.run(request)
.map(\.value)
.eraseToAnyPublisher()
}
}
extension HackerNewsAPI {
static func stories() -> AnyPublisher<[Post], Error> {
HackerNewsAPI.storyIds()
.flatMap { storyIDs -> AnyPublisher<[Post], Error> in
let stories = storyIDs.map { story(for: $0) }
return Publishers.MergeMany(stories)
.collect()
.eraseToAnyPublisher()
}
.eraseToAnyPublisher()
}
}
I expect something like the below code would work.
You end up with a count publisher that emits the number of articles that have been downloaded so far and a array publisher that emits the entire array of Posts after they have all been downloaded.
func getStories() -> (count: AnyPublisher<Int, Error>, stories: AnyPublisher<[Post], Error>) {
let posts = HackerNewsAPI.storyIds()
.map { $0.map { HackerNewsAPI.story(for: $0) } }
.flatMap { Publishers.Sequence(sequence: $0) }
.flatMap { $0 }
.share()
let count = posts
.scan(0) { count, _ in
return count + 1
}
let array = posts
.reduce([Post]()) { current, post in
current + [post]
}
return (count.eraseToAnyPublisher(), array.eraseToAnyPublisher())
}
I'd like to read the time value of a timecode track. There is an
excellent documentation from Apple (see Technical Note 2310)
but it's written in Objective C.
I have translated the core logic to Swift 3. It works exactly as the
ObjC version, which means that a CMSampleBuffer from a timecode
track is read and converted to a CMBlockBuffer. It fails when I
create the data pointer CMBlockBufferGetDataPointer (in the
timecodeFrame() func), which means, that the raw data is always
giving me 0 frames. So it boils down to the question, how do I
handle the raw data correctly?
import Foundation
import AVFoundation
import CoreMedia
let movie = URL(fileURLWithPath: "videoWithTimecodeTrack.mov")
let asset = AVAsset(url: movie)
asset.loadValuesAsynchronously(forKeys: ["tracks"]) {
var error: NSError?
guard asset.statusOfValue(forKey: "tracks", error: &error) == AVKeyValueStatus.loaded
else { if let error = error { return print(error) } }
readStartTimecode(asset: asset)
}
func readStartTimecode(ofAsset asset: AVAsset) {
let timecodeTracks = asset.tracks(withMediaType: AVMediaTypeTimecode)
guard let timecodeTrack = timecodeTracks.first,
let assetReader = try? AVAssetReader(asset: asset) else { return }
let readerOutput = AVAssetReaderTrackOutput(track: timecodeTrack, outputSettings: nil)
assetReader.add(readerOutput)
guard assetReader.startReading() else { return }
while let sampleBuffer = readerOutput.copyNextSampleBuffer() {
if let frame = timecodeFrame(sampleBuffer: sampleBuffer) {
print("timecodeFrame: \(frame)")
}
}
}
func timecodeFrame(sampleBuffer: CMSampleBuffer) -> UInt32? {
guard let blockBuffer = CMSampleBufferGetDataBuffer(sampleBuffer),
let formatDescription = CMSampleBufferGetFormatDescription(sampleBuffer)
else { return nil }
var rawData: UnsafeMutablePointer<Int8>? = nil
var length: Int = 0
var totalLength: Int = 0
let status = CMBlockBufferGetDataPointer(blockBuffer, 0, &length, &totalLength, &rawData)
guard status == kCMBlockBufferNoErr,
let frameRead = rawData?.pointee
else { return nil }
let type = CMFormatDescriptionGetMediaSubType(formatDescription)
if type == kCMTimeCodeFormatType_TimeCode32 {
let frame = UInt32(frameRead)
let bigFrame = CFSwapInt32BigToHost(frame)
print("kCMTimeCodeFormatType_TimeCode32: \(bigFrame)")
}
if type == kCMTimeCodeFormatType_TimeCode64 {
print("kCMTimeCodeFormatType_TimeCode64")
// todo
}
return nil
}
Edit: the Objective C version of the data pointer retrieval looks like this:
size_t length = 0;
size_t totalLength = 0;
char *rawData = NULL;
CMBlockBufferGetDataPointer(blockBuffer, 0, &length, &totalLength, &rawData);
if (status == kCMBlockBufferNoErr) {
int32_t *frameNumberRead = (int32_t *)rawData;
(int)Endian32_Swap(*frameNumberRead)]
}
The solution is to not convert the Int8 data like UInt32(rawData.pointee) but to access the UnsafeMutablePointer<Int8> pointer's memory as a different type (temporarily). This would look like this:
if let frames = rawData?.withMemoryRebound(to: UInt32.self, capacity: 1, { CFSwapInt32BigToHost($0.pointee) }) {
return frames
}
The full function would look like this:
func timecodeFrame(sampleBuffer: CMSampleBuffer) -> UInt32? {
guard let blockBuffer = CMSampleBufferGetDataBuffer(sampleBuffer),
let formatDescription = CMSampleBufferGetFormatDescription(sampleBuffer)
else { return nil }
var rawData: UnsafeMutablePointer<Int8>? = nil
var length: Int = 0
var totalLength: Int = 0
let status = CMBlockBufferGetDataPointer(blockBuffer, 0, &length, &totalLength, &rawData)
guard status == kCMBlockBufferNoErr else { return nil }
let type = CMFormatDescriptionGetMediaSubType(formatDescription)
if type == kCMTimeCodeFormatType_TimeCode32 {
if let frames = rawData?.withMemoryRebound(to: UInt32.self, capacity: 1, { CFSwapInt32BigToHost($0.pointee) }) {
return frames
}
}
if type == kCMTimeCodeFormatType_TimeCode64 {
if let frames = rawData?.withMemoryRebound(to: UInt64.self, capacity: 1, { CFSwapInt64BigToHost($0.pointee) }) {
return UInt32(frames)
}
}
return nil
}
I hope this is useful to others who want to read the start timecode of a video's timecode track.
I have a function that has defined a closure like this:
func synchronizeData(completion externalCompletion: RequestsCompletionHandler?) {
let closure = {
(operationCompletion:#escaping ()->Void) in
assert(Thread.isMainThread, "must be the main thread")
/*
Internal (non-optional) completion handler
*/
let internalCompletion: RequestsCompletionHandler = {
(success, newData, decommissionRequired, errors) -> Void in
/*
Synchronization is finished. Firstly, call either the external completion handler or the delegate
*/
if let externalCompletion = externalCompletion {
externalCompletion(success, newData, decommissionRequired, errors)
}
else {
self.delegate?.synchroniationInteractor(self, didSynchronizeDataWithStatus: success, dataIsNew: newData, decommissionRequired: decommissionRequired, error: errors.last)
}
/*
Now call the closure operation's completion handler
*/
operationCompletion()
}
/*
The synchronization itself
*/
guard let _ = self.keychain.retrieveActivationIdentifiers() else {
internalCompletion(false, false, false, [NSError(domain: "", code: 0, userInfo: ["reason" : "unable to retrieve credentials"])])
return
}
var errors :[NSError] = []
var numberOfRequests = 0
var completedRequests = 0
var decommissionRequired: Bool?
/*
Synchronization results handler. Regardless of success for fail, increase completed requests counter and append any errors to array, if final request call main completion block
*/
let handleCompletedRequests = {
(error: NSError?) -> Void in
// assert(NSThread.isMainThread(), "must be the main thread")
if let error = error {
errors.append(error)
}
completedRequests += 1
if(completedRequests >= numberOfRequests) {
internalCompletion(errors.count == 0, true, decommissionRequired, errors)
/*
Decrement operations counter
*/
self.manageBusy(retain: false)
}
}
/*
Increment operations counter
*/
self.manageBusy(retain: true)
/*
Do the actual synchronization.
Fetch the Patient Data first
*/
self.fetchPatientDataInternal {
(success, newData, error) -> Void in
numberOfRequests = 6
//Fetch Patient Schedule
self.fetchPatientScheduleInternal {
(success, newData, error) -> Void in
handleCompletedRequests(error)
}
//Fetch Patient Thresholds
self.fetchPatientThresholdInternal {
(success, newData, error) -> Void in
handleCompletedRequests(error)
}
//Fetch Patient Device Settings
self.fetchPatientDeviceSettingsInternal {
(success, newData, decommissionReq, error) -> Void in
decommissionRequired = decommissionReq
handleCompletedRequests(error)
}
// Device Checkin
self.deviceCheckInInternal {
(success, newData, error) -> Void in
handleCompletedRequests(error)
}
// Upload Vitals
self.uploadPendingVitalsInternal {
(success, newData, error) -> Void in
handleCompletedRequests(error)
}
//Upload Health sessions
self.uploadPendingHealthSessionsInternal {
(success, newData, error) -> Void in
handleCompletedRequests(error)
}
}
}
let operation = CIAsyncronousOperation.init(closure: closure as! (()->Void)-> Void)
operation.name = "Data Synchronization"
isolationQueue.addOperation(operation)
}
When we invoke this line in the above function i.e
let operation = CIAsyncronousOperation.init(closure: closure as! (()->Void)-> Void)
The application crashes with the following info:
0x00000001003b083c CIAppliance`partial apply forwarder for CIAppliance.SynchronizationInteractor.(synchronizeData (completion : Swift.Optional<(Swift.Bool, Swift.Optional, Swift.Optional, Swift.Array<__ObjC.NSError>) -> ()>) -> ()).(closure #1) at SynchronizationInteractor.swift
The CIAsyncronousOperation init is defined as follows:
init(closure aClosure: #escaping (()->Void)-> Void)
{
closure = aClosure
}
I am unable to find out the reason for the crash. Is it casting issue or due to the new Swift 3 syntax change?
If you are having to force cast the function then there is a good chance you have got the function signature incorrect. If CIAsyncronousOperation is defined as follows it will compile. Both the function and the function that is parameter aClosure need to be set as #escaping
class CIAsyncronousOperation {
init(closure aClosure: #escaping (#escaping ()->Void)->Void)
{
closure = aClosure
}
var closure : (#escaping ()->Void)-> Void;
}
I have a FileHelper class where I've implemented 3 methods whose job is to write a Dictionary contents to a file. Those methods are:
func storeDictionary(_ dictionary: Dictionary<String, String>, inFile fileName: String, atDirectory directory: String) -> Bool {
let ext = "txt"
let filePath = createFile(fileName, withExtension: ext, atDirectory: directory)
/**** //If I use this method, file is created and dictionary is saved
guard (dictionary as NSDictionary).write(to: filePath!, atomically: true) else {
return false
}
*/
guard NSKeyedArchiver.archiveRootObject(dictionary, toFile: (filePath?.absoluteString)!) else {
return false
}
return true
}
func createFile(_ file: String, withExtension ext: String, atDirectory directory: String) -> URL? {
let directoryPath = createDirectory(directory)
let filePath = directoryPath?.appendingPathComponent(file).appendingPathExtension(ext)
if !FileManager.default.fileExists(atPath: (filePath?.absoluteString)!) {
let success = FileManager.default.createFile(atPath: (filePath?.absoluteString)!, contents: nil, attributes: nil)
print("\(success)") //** here is the issue I investigated. Always prints false.
}
return filePath
}
func createDirectory(_ directory: String) -> URL? {
let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
let directoryPath = documentsDirectory.appendingPathComponent(directory)
do {
try FileManager.default.createDirectory(at: directoryPath, withIntermediateDirectories: true, attributes: nil)
} catch let error as NSError {
fatalError("Error creating directory: \(error.localizedDescription)")
}
return directoryPath
}
When I call FileHelper().storeDictionary(aValidDictionary, inFile: "abc", atDirectory: "XYZ") to write the dictionary, it fails with this procedure. But if I use
guard (dictionary as NSDictionary).write(to: filePath!, atomically: true) else {
return false
}
it works.
What's wrong with NSKeyedArchiver.archiveRootObject(_:toFile:) method??
And why FileManager.default.createFile(atPath: (filePath?.absoluteString)!, contents: nil, attributes: nil) always returns false?
First of all filePath?.absoluteString returns the entire – even percent escaped – string including the file:// scheme and the method expects a path without the scheme (filePath?.path - the naming is a bit confusing ;-) ).
I recommend to save a [String:String] dictionary as property list file. It's not necessary to create the file explicitly.
I changed the signatures of the methods slightly in the Swift-3-way. Further there is no need to use any optional type.
func store(dictionary: Dictionary<String, String>, in fileName: String, at directory: String) -> Bool {
let fileExtension = "plist"
let directoryURL = create(directory:directory)
do {
let data = try PropertyListSerialization.data(fromPropertyList: dictionary, format: .xml, options: 0)
try data.write(to: directoryURL.appendingPathComponent(fileName).appendingPathExtension(fileExtension))
return true
} catch {
print(error)
return false
}
}
func create(directory: String) -> URL {
let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
let directoryURL = documentsDirectory.appendingPathComponent(directory)
do {
try FileManager.default.createDirectory(at: directoryURL, withIntermediateDirectories: true, attributes: nil)
} catch let error as NSError {
fatalError("Error creating directory: \(error.localizedDescription)")
}
return directoryURL
}
PS: Instead of returning a Bool you could make the store method can throw and handle the error in the calling method:
func store(dictionary: Dictionary<String, String>, in fileName: String, at directory: String) throws {
let fileExtension = "plist"
let directoryURL = create(directory:directory)
let data = try PropertyListSerialization.data(fromPropertyList: dictionary, format: .xml, options: 0)
try data.write(to: directoryURL.appendingPathComponent(fileName).appendingPathExtension(fileExtension))
}
Here's a swift 5 extension that should save any Dictionary where the Key and Value are Codable
extension Dictionary where Key: Codable, Value: Codable {
static func load(fromFileName fileName: String, using fileManager: FileManager = .default) -> [Key: Value]? {
let fileURL = Self.getDocumentsURL(on: .cachesDirectory, withName: fileName, using: fileManager)
guard let data = fileManager.contents(atPath: fileURL.path) else { return nil }
do {
return try JSONDecoder().decode([Key: Value].self, from: data)
} catch(let error) {
print(error)
return nil
}
}
func saveToDisk(on directory: FileManager.SearchPathDirectory,
withName name: String,
using fileManager: FileManager = .default) throws {
let fileURL = Self.getDocumentsURL(on: .cachesDirectory, withName: name, using: fileManager)
let data = try JSONEncoder().encode(self)
try data.write(to: fileURL)
}
private static func getDocumentsURL(on directory: FileManager.SearchPathDirectory,
withName name: String,
using fileManager: FileManager) -> URL {
let folderURLs = fileManager.urls(for: .cachesDirectory, in: .userDomainMask)
let fileURL = folderURLs[0].appendingPathComponent(name)
return fileURL
}
}
Usage:
let myDict = [MyKey: MyValue].load(from: diskDirectory, andFileName: diskFileName) // load
try myDict.saveToDisk(on: diskDirectory, withName: diskFileName) // save