Xcode instrument time profiler total time estimated wrong when using third party pods libraries - ios

I am using Xcode instrument time profiler to measure the performance of my app. At the same time, I am also using CFAbsoluteTimeGetCurrent function in my code to print the time I used. However, I have found that sometimes the execution time of a function measured by these two methods are the same, but sometimes there is a big difference.
Here is an example
import UIKit
import ZippyJSON
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewDidAppear(_ animated: Bool) {
let encoder = JSONEncoder()
var students = [Student]()
var scores = [Int]()
for _ in 0...100 {
scores.append(99984)
}
for _ in 0...100 {
students.append(Student(name: "StudentName", age: 99, scores: scores))
}
var dataList = [Data]()
for _ in 0...100 {
let data = try! encoder.encode(students)
dataList.append(data)
}
testJson(dataList: dataList)
}
}
func testJson(dataList: [Data]) {
let t1 = CFAbsoluteTimeGetCurrent()
let decoder = JSONDecoder()
var results = [[Student]]()
for data in dataList {
let r1 = CFAbsoluteTimeGetCurrent()
let result = try! decoder.decode([Student].self, from: data)
let r2 = CFAbsoluteTimeGetCurrent()
results.append(result)
print("single time \(r2 - r1)")
}
let t2 = CFAbsoluteTimeGetCurrent()
print("total time \(t2 - t1)")
}
At this time, I am using Apple's JSONDecoder to decode the data created by me. The printing of the program is
total time 1.428820013999939
And it corresponds to what Time Profiler produce
However, if I change to use a third party library ZippyJSONDecoder to decode the data
func testJson(dataList: [Data]) {
let t1 = CFAbsoluteTimeGetCurrent()
let decoder = ZippyJSONDecoder()
var results = [[Student]]()
for data in dataList {
let r1 = CFAbsoluteTimeGetCurrent()
let result = try! decoder.decode([Student].self, from: data)
let r2 = CFAbsoluteTimeGetCurrent()
results.append(result)
print("single time \(r2 - r1)")
}
let t2 = CFAbsoluteTimeGetCurrent()
print("total time \(t2 - t1)")
}
The printing of the program is
total time 3.199104905128479
But the Time Profile produces this result
It is only 325ms, totally different from what produces by the measurement of CFAbsoluteTimeGetCurrent
I am wondering why this problem occured? Which result is the correct one?

Related

Laggy WCSession sendMessageData

I am polling the apple watch for Core Motion at a frequency of 0.01. The purpose of the application is to see movement in real-time. To capture data as quickly as possible, I leverage the didReceiveMessageData/ sendMessageData functions. On the iPhone, I have a simple function that reads the data:
func session(_ session: WCSession, didReceiveMessageData messageData: Data) {
let records : [Double] = try! NSKeyedUnarchiver.unarchivedObject(ofClasses: [NSArray.self], from: messageData) as! [Double]
}
And on an Apple Watch 6, I have a simple function that sends the data. However, sending suffers from a sporadic yet significant delay.
class MyController: WKInterfaceController, WCSessionDelegate {
private let motion = CMMotionManager()
private let motionQueue = OperationQueue()
private let messagingQueue = OperationQueue()
private let healthStore = HKHealthStore()
private var stack : QuaternionStack = QuaternionStack()
override init() {
super.init()
if WCSession.isSupported() {
let session = WCSession.default
session.delegate = self
if session.activationState == .notActivated { session.activate() }
}
// Serial queue for sample handling and calculations.
messagingQueue.qualityOfService = .userInteractive
messagingQueue.maxConcurrentOperationCount = 1
motionQueue.qualityOfService = .userInteractive
motionQueue.maxConcurrentOperationCount = 1
startGettingData();
}
func startGettingData() {
// If we have already started the workout, then do nothing.
if (workoutSession != nil) { return }
if !motion.isDeviceMotionAvailable { return }
let workoutConfiguration = HKWorkoutConfiguration()
workoutConfiguration.activityType = .functionalStrengthTraining
workoutConfiguration.locationType = .indoor
do {
workoutSession = try HKWorkoutSession(healthStore: healthStore, configuration: workoutConfiguration)
} catch { fatalError("Unable to create the workout session!") }
// Start the workout session and device motion updates.
workoutSession!.startActivity(with: Date())
motion.deviceMotionUpdateInterval = 0.01
motion.startDeviceMotionUpdates(using: .xArbitraryZVertical, to: motionQueue) { [self] (deviceMotion: CMDeviceMotion?, _ : Error?) in
guard let motion = deviceMotion else { return }
let attitude = motion.attitude.quaternion
stack.push(Quaternion(x: attitude.x, y: attitude.y, z: attitude.z, w: attitude.w))
guard let quaternions = stack.pop() else { return }
messagingQueue.cancelAllOperations()
let blockOperation = BlockOperation()
blockOperation.addExecutionBlock({ [unowned blockOperation] in
if blockOperation.isCancelled { return }
self.sendDataToPhone(quaternions: quaternions)
})
messagingQueue.addOperation(blockOperation)
}
}
private func sendDataToPhone(quaternions: [Quaternion]) {
if WCSession.default.isReachable {
var capturedQuaternions : [Double] = [Double]()
for quat in quaternions { capturedQuaternions.append(contentsOf: [quat.x, quat.y, quat.z, quat.w]) }
WCSession.default.sendMessageData(try! NSKeyedArchiver.archivedData(withRootObject: capturedQuaternions, requiringSecureCoding: false), replyHandler: nil, errorHandler: nil);
}
}
}
I've implemented a stack as follows:
struct QuaternionStack {
private let max = 2;
private var array: [Quaternion] = []
mutating func push(_ element: Quaternion) {
array.append(element)
if array.count > max { array.removeFirst() }
}
mutating func pop() -> [Quaternion]? {
if (array.count < max) { return nil }
var results : [Quaternion] = [Quaternion]()
for _ in 0 ..< max { results.append(array.popLast()!)}
results.reverse()
array.removeAll()
return results
}
}
If I set QuaternionStack.max to a big number, like 10, I see no obvious throttling on the iPhone when receiving data. This is because I send more data but less often. However, decreasing the number degrades the performance. As an example, imagine I send every 2 incoming packets ( QuaternionStack.max = 2 ). Sometimes, a few seconds pass between when the packets are received. When this happens, the iWatch seems to send them very quickly in an effort to catch up. Another example of this issue is when listening to music on paired Apple Airpods or receiving an incoming call. The WCSession sendMessageData from the watch becomes very inconsistent.
What must I do to increase the throughput of the WCSession sendMessageData ? The application I am writing requires very fast ( 100hz ) and continuous motion updates.

How to show json data in line chart using swift

I am trying to design an ios app to display json data in a line chart.
First of all, this is my json data.
{
TH_5min: [
{
Data: "2019-02-23T00:00:00",
Time: "11:00:00",
XTP_A: 10.5, //temperature 1
XHP_A: 11.5, //humidity 1
XTP_B: 33.5,
XHP_B: 44.6,
XTP_C: 88.9,
XHP_C: 66.6,
XTP_D: 77.9,
XHP_D: 99.6,
XTP_E: 87.87,
XHP_E: 66.66
},
{
Data: "2019-02-23T00:00:00",
Time: "11:05:00",
XTP_A: 55.2, //temperature 1
XHP_A: 44.3, //humidity 1
XTP_B: 66.6,
XHP_B: 77.87,
XTP_C: 87.77,
XHP_C: 87.87,
XTP_D: 8.87,
XHP_D: 78.78,
XTP_E: 87.78,
XHP_E: 87.87
}
]
}
This is my implementation of the swift code showing json data.
override func viewDidLoad() {
super.viewDidLoad()
apiip = APIip
getlatestTh_5min()
#objc func getlatestTh_5min(){
guard let th_5minUrl = URL(string: "http://" + apiip + "/api/Th_5min") else{
return
}
let request = URLRequest(url: th_5minUrl)
let task = URLSession.shared.dataTask(with: request, completionHandler: {(data,response,error) -> Void in
if let error = error {
print(error)
return
}
if let data = data {
self.th_5mins = self.pardrJsonData(data: data)
self.getchat()
}
})
task.resume()
//getchat()
}
func pardrJsonData(data: Data) -> [Th_5min]{
var th_5mins = [Th_5min]()
do {
let jsonResult = try JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.mutableContainers) as? NSDictionary
let jsonTh_5mins = jsonResult?["TH_5min"] as! [AnyObject]
print(jsonTh_5mins)
print(th_5mins.count)
for jsonTh_5min in jsonTh_5mins{
var th_5min = Th_5min()
th_5min.Data = jsonTh_5min["Data"] as! String
th_5min.Time = jsonTh_5min["Time"] as! String
th_5min.XTP_A = jsonTh_5min["XTP_A"] as! Double
th_5min.XHP_A = jsonTh_5min["XHP_A"] as! Double
print(th_5min)
th_5mins.append(th_5min)
//getchat()
} }catch{
print(error)
}
//getchat()
return th_5mins
}
This is how I draw the line chart, using swift code.
#objc func getchat(){
chartView = LineChartView()
chartView.frame = CGRect(x: 20, y: 80, width: self.view.bounds.width-20,height: self.view.bounds.height-100)
self.view.addSubview(chartView)
var dataEntries1 = [ChartDataEntry]()
for i in 0..<th_5mins.count {
chartView.xAxis.valueFormatter = IndexAxisValueFormatter(values: [th_5mins[i].Time])
let y = th_5mins[i].XTP_A
let entry = ChartDataEntry.init(x: Double(i), y: Double(y))
dataEntries1.append(entry)
}
let chartDataSet1 = LineChartDataSet(entries: dataEntries1, label: "temperature")
chartDataSet1.colors = [NSUIColor.red]
var dataEntries2 = [ChartDataEntry]()
for i in 0..<th_5mins.count {
chartView.xAxis.valueFormatter = IndexAxisValueFormatter(values: [th_5mins[i].Time])
let y = th_5mins[i].XHP_A
let entry = ChartDataEntry.init(x: Double(i), y: Double(y))
dataEntries2.append(entry)
}
let chartDataSet2 = LineChartDataSet(entries: dataEntries2, label: "humidity")
chartDataSet2.colors = [NSUIColor.black]
let chartData = LineChartData(dataSets: [chartDataSet1, chartDataSet2])
chartView.data = chartData
}
}
This is the result of my work.
enter image description here
Although the json data is successfully displayed, I don't know why it is loading for a long time, and I hope that the "time" in my json data can be displayed on the X axis above, marked with my temperature and humidity, and cannot be successful.
I also hope that my line chart view can be implemented as a layout.
"I don't know why it is loading for a long time". Do you mean that the graph does not load immediately upon opening the view? This is because the data is loading asynchronously from a remote source (correctly now, well done). It may well take a few seconds for your JSON to download over the web. That is ok. You can test the endpoint in a browser and see how long the response takes.
"I hope that the 'time' in my json data can be displayed on the X axis above". Yes. You can take the assignation of IndexAxisValueFormatter outside of the loop and you should pass all labels as values into the constructor. Try this code, replacing the equivalent loop:-
var labels: [String] = []
for i in 0..<th_5mins.count {
let y = th_5mins[i].XTP_A
let entry = ChartDataEntry.init(x: Double(i), y: Double(y))
dataEntries1.append(entry)
labels.append(th_5mins[i].Time)
}
chartView.xAxis.valueFormatter = IndexAxisValueFormatter(values: labels)
Note that the method you use for plotting against dates will result in an evenly spread graph, irrespective of your time gaps (e.g. if readings are 5 mins apart between the first two, but 5 years apart for the next two, they will still appear with even gaps between them.

mach_wait_until() Strange Behavior on iPad

I created a simple project to test out the functionality of mach_wait_until(). This code gives me an accurate printout of how precise the 1 second delay is. The console printout is virtually identical and extremely precise on both the iOS Simulator and on my iPad Air 2. However, on my iPad there is a HUGE delay, where the same 1 second delay takes about 100 seconds! And to add to the weirdness of it, the printout in the console says it only takes 1 second (with extremely low jitter and/or lag).
How can this be? Is there some timing conversion that I need to do for a physical iOS device when using mach_wait_until()?
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
playNoteTest()
}
var start = mach_absolute_time()
var end = mach_absolute_time()
func playNoteTest() {
let when = mach_absolute_time() + 1000000000
self.start = mach_absolute_time()
mach_wait_until(when)
self.end = mach_absolute_time()
let timeDelta = (self.end - self.start)
let newTimeDelta = Double(timeDelta) / 1000000000.0
print("Delta Time = \(newTimeDelta)")
playNoteTest()
}
}
mach_absolute_time units are CPU dependent. You need to multiply by a device-specific constant in order to get real-world units. It is discussed in this Tech Q&A from Apple.
Here is some playground code that demonstrates the idea:
import PlaygroundSupport
import Foundation
PlaygroundPage.current.needsIndefiniteExecution = true
class TimeBase {
static let NANOS_PER_USEC: UInt64 = 1000
static let NANOS_PER_MILLISEC: UInt64 = 1000 * NANOS_PER_USEC
static let NANOS_PER_SEC: UInt64 = 1000 * NANOS_PER_MILLISEC
static var timebaseInfo: mach_timebase_info! = {
var tb = mach_timebase_info(numer: 0, denom: 0)
let status = mach_timebase_info(&tb)
if status == KERN_SUCCESS {
return tb
} else {
return nil
}
}()
static func toNanos(abs:UInt64) -> UInt64 {
return (abs * UInt64(timebaseInfo.numer)) / UInt64(timebaseInfo.denom)
}
static func toAbs(nanos:UInt64) -> UInt64 {
return (nanos * UInt64(timebaseInfo.denom)) / UInt64(timebaseInfo.numer)
}
}
let duration = TimeBase.toAbs(nanos: 10 * TimeBase.NANOS_PER_SEC)
DispatchQueue.global(qos: .userInitiated).async {
print("Start")
let start = mach_absolute_time()
mach_wait_until(start+duration)
let stop = mach_absolute_time()
let elapsed = stop-start
let elapsedNanos = TimeBase.toNanos(abs: elapsed)
let elapsedSecs = elapsedNanos/TimeBase.NANOS_PER_SEC
print("Elapsed nanoseconds = \(elapsedNanos)")
print("Elapsed seconds = \(elapsedSecs)")
}

How to detect max dB Swift

I'm trying to detect dB on a iOS Device, however, I am new to AV audio foundation can't really get to figure it out. I have come across this post: iOS - Detect Blow into Mic and convert the results! (swift), but it is not working for me.
My current code is this:
import Foundation
import UIKit
import AVFoundation
import CoreAudio
class ViewController: UIViewController {
var recorder: AVAudioRecorder!
var levelTimer = NSTimer()
var lowPassResults: Double = 0.0
override func viewDidLoad() {
super.viewDidLoad()
//make an AudioSession, set it to PlayAndRecord and make it active
var audioSession:AVAudioSession = AVAudioSession.sharedInstance()
audioSession.setCategory(AVAudioSessionCategoryPlayAndRecord, withOptions: nil)
audioSession.setActive(true, error: nil)
//set up the URL for the audio file
var documents: AnyObject = NSSearchPathForDirectoriesInDomains( NSSearchPathDirectory.DocumentDirectory, NSSearchPathDomainMask.UserDomainMask, true)[0]
var str = documents.stringByAppendingPathComponent("recordTest.caf")
var url = NSURL.fileURLWithPath(str as String)
// make a dictionary to hold the recording settings so we can instantiate our AVAudioRecorder
var recordSettings: [NSObject : AnyObject] = [AVFormatIDKey:kAudioFormatAppleIMA4,
AVSampleRateKey:44100.0,
AVNumberOfChannelsKey:2,AVEncoderBitRateKey:12800,
AVLinearPCMBitDepthKey:16,
AVEncoderAudioQualityKey:AVAudioQuality.Max.rawValue
]
//declare a variable to store the returned error if we have a problem instantiating our AVAudioRecorder
var error: NSError?
//Instantiate an AVAudioRecorder
recorder = AVAudioRecorder(URL:url, settings: recordSettings, error: &error)
//If there's an error, print otherwise, run prepareToRecord and meteringEnabled to turn on metering (must be run in that order)
if let e = error {
print(e.localizedDescription)
} else {
recorder.prepareToRecord()
recorder.meteringEnabled = true
//start recording
recorder.record()
//instantiate a timer to be called with whatever frequency we want to grab metering values
self.levelTimer = NSTimer.scheduledTimerWithTimeInterval(0.02, target: self, selector: #selector(ViewController.levelTimerCallback), userInfo: nil, repeats: true)
}
}
//This selector/function is called every time our timer (levelTime) fires
func levelTimerCallback() {
//we have to update meters before we can get the metering values
recorder.updateMeters()
//print to the console if we are beyond a threshold value. Here I've used -7
if recorder.averagePowerForChannel(0) > -7 {
print("Dis be da level I'm hearin' you in dat mic ")
print(recorder.averagePowerForChannel(0))
print("Do the thing I want, mofo")
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
i was currently building my app about movie making,and learned something about how to metering sound level in dB.
the origin data of recorder.averagePowerForChannel is not really dB level of the sound,it's provide a level range which is -160 - 0,so we need some modification to make this data more reasonable
so i was finding some thing that makes this data(value) convert to dB level data.
(Sorry about forgot where i was found it!)
here is the code
/**
Format dBFS to dB
- author: RÅGE_Devil_Jåmeson
- date: (2016-07-13) 20:07:03
- parameter dBFSValue: raw value of averagePowerOfChannel
- returns: formatted value
*/
func dBFS_convertTo_dB (dBFSValue: Float) -> Float
{
var level:Float = 0.0
let peak_bottom:Float = -60.0 // dBFS -> -160..0 so it can be -80 or -60
if dBFSValue < peak_bottom
{
level = 0.0
}
else if dBFSValue >= 0.0
{
level = 1.0
}
else
{
let root:Float = 2.0
let minAmp:Float = powf(10.0, 0.05 * peak_bottom)
let inverseAmpRange:Float = 1.0 / (1.0 - minAmp)
let amp:Float = powf(10.0, 0.05 * dBFSValue)
let adjAmp:Float = (amp - minAmp) * inverseAmpRange
level = powf(adjAmp, 1.0 / root)
}
return level
}
i was noticed that you are recording whit 2 channels so it will be little different with my code;
wish could help you out or give you some ideas :D
LAST UPDATE
Change coding language to swift

Activity Indicator stops before asynchronous query ends

I have a ViewController that calls a class HKQueryWeight which runs a HealthKit Query (very, very slow btw) and saves the data to CoreData. If the user leaves the VC before the query is over, the app crashes.
fatal error: unexpectedly found nil while unwrapping an Optional value
(lldb)
Initially I thought I could patch this by adding an activityIndicator which starts animating in viewDidAppear and stops at the end of the last function in the VC. It works. However, due to, I believe, the asynchronous nature of healthKit Querys, the animation stops before the actual healthKit query completes.
Question: How can I create a solution where the animation stops only when the last healthKit Query has completed?
I am not sure if it is necessary to provide the code, but I have done so in case it is useful
ViewController:
class ViewController: UIViewController {
#IBOutlet var activityIndicator: UIActivityIndicatorView!
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
activityIndicator.startAnimating()
setupArrays ()
}
func setupArrays (){
println("setting up arrays")
if NSUserDefaults.standardUserDefaults().boolForKey("hrSwitch") == true {
var hkQueryHeartRate = HKQueryHeartRate()
hkQueryHeartRate.performHKQuery()
}
if NSUserDefaults.standardUserDefaults().boolForKey("weightSwitch") == true {
var hkQueryWeight = HKQueryWeight()
hkQueryWeight.performHKQuery()
}
self.activityIndicator.stopAnimating()
}
HKQuery
import Foundation
import CoreData
import HealthKit
class HKQueryWeight: HKQueryProtocol {
func performHKQuery() {
var appDel:AppDelegate = UIApplication.sharedApplication().delegate as AppDelegate
var context = (UIApplication.sharedApplication().delegate as AppDelegate).managedObjectContext!
let healthKitManager = HealthKitManager.sharedInstance
let calendar = NSCalendar.currentCalendar()
let interval = NSDateComponents()
interval.day = 1
// Set the anchor date to Monday at 3:00 a.m.
let anchorComponents =
calendar.components(.CalendarUnitDay | .CalendarUnitMonth |
.CalendarUnitYear | .CalendarUnitWeekday, fromDate: NSDate())
let offset = (7 + anchorComponents.weekday - 2) % 7
anchorComponents.day -= offset
anchorComponents.hour = 3
//let now = NSDate()
let anchorDate = calendar.dateFromComponents(anchorComponents)
let quantityType = HKObjectType.quantityTypeForIdentifier(HKQuantityTypeIdentifierBodyMass)
// Create the query
let query = HKStatisticsCollectionQuery(quantityType: quantityType,
quantitySamplePredicate: nil,
options: .DiscreteAverage,
anchorDate: anchorDate,
intervalComponents: interval)
// Set the results handler
query.initialResultsHandler = {
query, results, error in
if error != nil {
// Perform proper error handling here
println("*** An error occurred while calculating the statistics: \(error.localizedDescription) ***")
abort()
}
let endDate = NSDate()
let startDate =
calendar.dateByAddingUnit(.MonthCalendarUnit,
value: -6, toDate: endDate, options: nil)
// Plot the weekly step counts over the past 6 months
results.enumerateStatisticsFromDate(startDate, toDate: endDate) {
statistics, stop in
if let quantity = statistics.averageQuantity() {
let date = statistics.startDate
let weight = quantity.doubleValueForUnit(HKUnit.gramUnitWithMetricPrefix(.Kilo))
println("weight date: \(date)")
println("weight value: \(weight)")
var weightData = NSEntityDescription.insertNewObjectForEntityForName("HKWeight", inManagedObjectContext: context) as HKWeight
//Saving to CoreData
weightData.setValue(weight, forKey: "weight_data")
weightData.setValue(date, forKey: "weight_date")
context.save(nil)
}
}
}
healthKitManager.healthStore.executeQuery(query)
}
}
Right now, self.activityIndicator.stopAnimating() is being called immediately after the queries are called. Since the queries are asynchronous, they can still be being executed in the background for some time after they're called, so if you remove the activity indicator immediately after the queries are invoked, the queries probably won't be complete yet. If you want the activity indicator to stop animating after your queries are complete, you have to call for it to stop animating from within your asynchronous query block.
Since your queries are in a different class, you can post a notification to end the activity indicator's animation at the end of each query, then stop animating the UIActivityIndicatorView after the second query finishes and the second notification is received, ex:
var notificationCount:Int = 0
var totalQueries = 0
func setupArrays (){
println("setting up arrays")
notificationCount = 0
totalQueries = 0
NSNotificationCenter.defaultCenter().addObserver(self, selector: "removeActivityIndicator", name:"ActivityIndicatorNotification", object: nil)
if NSUserDefaults.standardUserDefaults().boolForKey("hrSwitch") == true {
totalQueries = totalQueries + 1
var hkQueryHeartRate = HKQueryHeartRate()
hkQueryHeartRate.performHKQuery()
}
if NSUserDefaults.standardUserDefaults().boolForKey("weightSwitch") == true {
totalQueries = totalQueries + 1
var hkQueryWeight = HKQueryWeight()
hkQueryWeight.performHKQuery()
}
if totalQueries == 0 {
self.activityIndicator.stopAnimating()
}
}
func removeActivityIndicator () {
notificationCount = notificationCount + 1
if notificationCount == totalQueries {
dispatch_async(dispatch_get_main_queue()) {
self.activityIndicator.stopAnimating()
NSNotificationCenter.defaultCenter().removeObserver(self, name:"ActivityIndicatorNotification", object:nil)
}
}
}
Then in HKQueryWeight:
func performHKQuery() {
// ...All the code before the query...
// Set the results handler
query.initialResultsHandler = {
query, results, error in
//...All the code currently within your query...
NSNotificationCenter.defaultCenter().postNotificationName("ActivityIndicatorNotification", object: nil) // <-- post notification to stop animating the activity indicator once the query's complete
}

Resources