Measure strong/weak ARC references impact - Impact on execution time [closed] - ios

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 5 years ago.
Improve this question
I was wondering if (and how much) strong/weak references management have an impact on code execution, especially when freeing objects to which many classes might have a weak reference. At first I mistaken this for ARC, but it's not.
There's a similar question on the same topic, however they don't investigate performance impact or try to pull a number out of it.
Let me be clear: I'm not, in any way, suggesting that ARC or strong/weak might have a bad impact on performance, or say "don't use this". I LOVE this. I'm just curious about how efficient it is, and how to size it.
I've put together this piece of code to get an idea of the performance impact of strong/weak references in execution time.
import Foundation
class Experiment1Class {
weak var aClass: Experiment1Class?
}
class Experiment2Class {
var aClass: Experiment2Class?
}
var persistentClass: Experiment1Class? = Experiment1Class()
var nonWeakPersistentClass: Experiment2Class? = Experiment2Class()
var classHolder = [Experiment1Class]()
var nonWeakClassholder = [Experiment2Class]()
for _ in 1...1000 {
let aNewClass = Experiment1Class()
aNewClass.aClass = persistentClass
classHolder.append(aNewClass)
let someNewClass = Experiment2Class()
someNewClass.aClass = nonWeakPersistentClass
nonWeakClassholder.append(someNewClass)
}
let date = Date()
persistentClass = nil
let date2 = Date()
let someDate = Date()
nonWeakPersistentClass = nil
let someDate2 = Date()
let timeExperiment1 = date2.timeIntervalSince(date)
let timeExperiment2 = someDate2.timeIntervalSince(someDate)
print("Time: \(timeExperiment1)")
print("Time: \(timeExperiment2)")
This piece of code only measure the amount of time it takes to free an object and to set to nil all its references.
IF you execute it in Playground (Xcode 8.3.1) you will see a ratio of 10:1 but Playground execution is much slower than real execution, so I also suggest to execute the above code with "Release" build configuration.
If you execute in Release I suggest you set the iteration count to "1000000" at least, the way I did it:
insert the above code into file test.swift
from terminal, run swiftc test.swift
execute ./test
Being this kind of test, I believe the absolute results makes no to little sense, and I believe this has no impact on 99% of the usual apps...
My results so far shows, on Release configuration executed on my Mac:
Time: 3.99351119995117e-06
Time: 0.0
However the same execution, in Release, on my iPhone 7Plus:
Time: 1.4960765838623e-05
Time: 1.01327896118164e-06
Clearly this test shows there should be little to no concern for real impact on typical apps.
Here are my questions:
Can you think of any other way to measure strong/weak references impact on execution time? (I wonder what strategies the system put in place to improve on this)
What other metrics could be important? (like multi threading optimisation or thread locking)
How can I improve this?
EDIT 1
I found this LinkedList test to be very interesting for a few reasons, consider the following code:
//: Playground - noun: a place where people can play
import Foundation
var n = 0
class LinkedList: CustomStringConvertible {
var count = n
weak var previous: LinkedList?
var next: LinkedList?
deinit {
// print("Muorte \(count)")
}
init() {
// print("Crea \(count)")
n += 1
}
var description: String {
get {
return "Node \(count)"
}
}
func recurseDesc() -> String {
return(description + " > " + (next?.recurseDesc() ?? "FIN"))
}
}
func test() {
var controlArray = [LinkedList]()
var measureArray = [LinkedList]()
var currentNode: LinkedList? = LinkedList()
controlArray.append(currentNode!)
measureArray.append(currentNode!)
var startingNode = currentNode
for _ in 1...31000 {
let newNode = LinkedList()
currentNode?.next = newNode
newNode.previous = currentNode!
currentNode = newNode
controlArray.append(newNode)
measureArray.append(newNode)
}
controlArray.removeAll()
measureArray.removeAll()
print("test!")
let date = Date()
currentNode = nil
let date2 = Date()
let someDate = Date()
startingNode = nil
let someDate2 = Date()
let timeExperiment1 = date2.timeIntervalSince(date)
let timeExperiment2 = someDate2.timeIntervalSince(someDate)
print("Time: \(timeExperiment1)")
print("Time: \(timeExperiment2)")
}
test()
I found the following (running in Release configuration):
I couldn't be able to run more than ~32000 iterations on my phone, it's crashing of EXC_BAD_ACCESS during deinit (yes, during DEINIT... isn't this weird)
Timing for ~32000 iteration is 0 vs 0.06 seconds, which is huge!
I think this test is very CPU intensive because nodes are freed one after the other (if you see the code, only the next one is strong, the previous is weak)... so once the first one is freed, the others fall one after the other, but not altogether.

There is really no such thing as automatic memory management. Memory is managed by retain and release regardless of whether you use ARC. The difference is who writes the code, you or the compiler. There is manual memory management code that you write, and there is manual memory management code that ARC writes. In theory, ARC inserts into your code exactly the same retain and release commands that you would have inserted if you had done this correctly. Therefore the difference in performance should be epsilon-tiny.

Related

Realm delay at end of write function

I've built a sports game in Xcode/Swift using Realm as a base. It's running and working, but realm transactions are getting slower and slower over time, to the point where it's significantly slower than i'd hope. This is particularly evident in this example.
Each week there are a number of matches, with each player having a GameStat object created for every match (about 2,000 new GameStats created each week).
class GameStat: Object {
#objc dynamic var iden = UUID().uuidString
#objc dynamic var playerIden = "" // Links to iden of a Player Object
#objc dynamic var statOne = 0
#objc dynamic var statTwo = 0
#objc dynamic var statThree = 0
etc...
// Primary Key
override static func primaryKey() -> String? {
return "iden"
}
}
To prevent multiple realm writes throughout each game, I create GameStats outside of Realm (store in thisGameGameStats array) and then batch write these GameStats to realm once the game is complete. It is this batch write to Realm which is very slow.
try! realm.write {
for gmStat in thisGameGameStats {
realm.add(gmStat)
}
}
I've measured time taken and I always see the 'for' loop complete after 0.05 seconds. In the first couple of loops, there is no delay for the realm.write function to complete, however after a couple of seasons (c70k records in database) it takes a further 0.50 seconds for the realm.write function to complete.
Is this to be expected, or is there an opportunity to optimise here?
EDIT: By removing the primaryKey, the write becomes significantly quicker and doesn't seem to slow down as the database grows. I'm assuming there is therefore some form of sort by primaryKey that happens on commit.
This isn't exactly an answer but may provide some validatation and illustrate an interesting point.
TL;DR
Writing chunks of objects over time does not have a significant impact on write times - you should not be seeing a significant increase in write times based on the existing volume of data in this use case.
The long version with setup and testing:
When a write has completed, realm will generate a notification so we're using that notification to 'time' the writes.
We start with a class var to store the start time of each write and a notification to track when it's done
var startTime = Date()
var gameStatNotificationToken: NotificationToken?
then we add an observer to realm that will execute when the write has completed that determines the elapsed time between the start time and now
func addObserver {
if let realm = gGetRealm() { //this just gets a realm
self.gameStatNotificationToken = realm.observe { notification, realm in
let elapsed = Date().timeIntervalSince(self.startTime)
print("\(elapsed)")
}
}
}
Then the code to actually write the data
func writeLotsOfData() {
autoreleasepool {
var gameStatArray = [GameStat]()
for index in 0..<10000 {
let stat = GameStat()
stat.playerIden = "\(index)"
gameStatArray.append(stat)
}
if let realm = gGetRealm() {
self.startTime = Date()
try! realm.write {
realm.add(gameStatArray)
}
}
}
}
and then a loop that calls the above code 10 times; writing 10k per loop, 100k total objects.
func looper() {
for i in 0..<10 {
self.writeLotsOfData()
}
}
So the end result shows no increase in writing over time
0.19487500190734863
0.1404169797897339
0.14565002918243408
0.15493690967559814
0.14426898956298828
0.15933406352996826
0.15379595756530762
0.16700804233551025
0.1598280668258667
0.15421009063720703
EDIT
Here's the GameStat object I am using - it's identical to the one in the question
class GameStat: Object {
#objc dynamic var iden = UUID().uuidString
#objc dynamic var playerIden = "" // Links to iden of a Player Object
#objc dynamic var statOne = 0
#objc dynamic var statTwo = 0
#objc dynamic var statThree = 0
// Primary Key
override static func primaryKey() -> String? {
return "iden"
}
}

How to generate random number without repeating in Swift

I'm extremely new to Swift and programming in general so please be patient with me while I'm trying to get the hang of this.
I've been following a beginners course in Swift from Treehouse and managed to develop a simple app that generates random quotes. So far so good. Now before moving on to more advanced lessons I figured I'd try to update the existing app just to make sure I get some solid practice before moving on.
So here's my question: I've managed to generate a random number through the GameKit framework, but the problem is that sometimes quotes appear consecutively. How can I avoid this from happening?
Here's my code:
import GameKit
struct FactProvider {
let facts = [
"Ants stretch when they wake up in the morning.",
"Ostriches can run faster than horses.",
"Olympic gold medals are actually made mostly of silver.",
"You are born with 300 bones; by the time you are an adult you will have 206.",
"It takes about 8 minutes for light from the Sun to reach Earth.",
"Some bamboo plants can grow almost a meter in just one day.",
"The state of Florida is bigger than England.",
"Some penguins can leap 2-3 meters out of the water.",
"On average, it takes 66 days to form a new habit.",
"Mammoths still walked the Earth when the Great Pyramid was being built."
]
func randomFact() -> String {
let randomNumber = GKRandomSource.sharedRandom().nextInt(upperBound: facts.count)
return facts[randomNumber]
}
}
You can store last random number or last fact in a variable and check it in your randomFact function. Like this:
var lastRandomNumber = -1
func randomFact() -> String {
let randomNumber = GKRandomSource.sharedRandom().nextInt(upperBound: facts.count)
if randomNumber == lastRandomNumber {
return randomFact()
} else {
lastRandomNumber = randomNumber
return facts[randomNumber]
}
}

Displaying strings in iOS randomly without repeating them

I'm making a quiz app. The app uses a .json file as the 'database' of questions and answers. This .json file looks as follows...
{
"id" : "1",
"question": "Earth is a:",
"answers": [
"Planet",
"Meteor",
"Star",
"Asteroid"
],
"difficulty": "1"
}
...and just keeps going for over 500 questions.
I display the questions randomly by using the following code:
let randomNumber = Int(arc4random_uniform(UInt32(allEntries.count)))
LoadQuestion(randomNumber)
This work fine, but because it selects the questions randomly I'm finding that questions are repeating too regularly (even though I have over 500 questions!).
I've researched this extensively and think perhaps I've read too much as I'm now quite confused. I've read about 'seeding', about saving an index of questions asked, and trying to use NSUserDefault.
In short, how can I modify my code to achieve one of the following outcomes instead:
To not repeat any questions at all, but to stop asking questions when all questions have been asked once;
Similar to 1 above, but have the number of questions being asked set by the user rather than asking all questions in the database;
To not repeat any questions at all until all questions have been asked first; or,
To not repeat any questions that have previously been answered correctly, but those that were answered incorrectly may be repeated.
Below is what I think are the relevant bits of code:
LoadAllQuestionsAndAnswers()
let randomNumber = Int(arc4random_uniform(UInt32(allEntries.count)))
LoadQuestion(randomNumber)
func LoadAllQuestionsAndAnswers()
{
let path = NSBundle.mainBundle().pathForResource("content", ofType: "json")
let jsonData : NSData = NSData(contentsOfFile: path!)!
allEntries = (try! NSJSONSerialization.JSONObjectWithData(jsonData, options: NSJSONReadingOptions.MutableContainers)) as! NSArray
//println(allEntries)
}
func LoadQuestion(index : Int)
{
let entry : NSDictionary = allEntries.objectAtIndex(index) as! NSDictionary
let question : NSString = entry.objectForKey("question") as! NSString
let arr : NSMutableArray = entry.objectForKey("answers") as! NSMutableArray
//println(question)
//println(arr)
labelQuestion.text = question as String
let indices : [Int] = [0,1,2,3]
//let newSequence = shuffle(indices)
let newSequence = indices.shuffle()
var i : Int = 0
for(i = 0; i < newSequence.count; i++)
{
let index = newSequence[i]
if(index == 0)
{
// we need to store the correct answer index
currentCorrectAnswerIndex = i
}
let answer = arr.objectAtIndex(index) as! NSString
switch(i)
{
case 0:
buttonA.setTitle(answer as String, forState: UIControlState.Normal)
break;
case 1:
buttonB.setTitle(answer as String, forState: UIControlState.Normal)
break;
case 2:
buttonC.setTitle(answer as String, forState: UIControlState.Normal)
break;
case 3:
buttonD.setTitle(answer as String, forState: UIControlState.Normal)
break;
default:
break;
}
}
buttonNext.hidden = true
// we will need to reset the buttons to reenable them
ResetAnswerButtons()
}
#IBAction func PressedButtonNext(sender: UIButton) {
print("button Next pressed")
let randomNumber = Int(arc4random_uniform(UInt32(allEntries.count)))
LoadQuestion(randomNumber)
}
If I understand your requirements correctly:
You want to persist the randomly generated numbers between View Controller instances and application launches
you never want to repeat any question
A quick way to achieve this is to use NSUserDefaults to keep track of the order and value of (pseudo)random numbers from your PRNG. This way, you can instantiate the array of avaiable questions on instantaition by replaying previous dice rolls, removing those questions from the pool alongside in the right order.
This also lets you handle the PRNG as a black box, not caring about seeding and replay.
Also, make sure to remove any chosen questions from the current pool for any dice roll.
Side note: You are not following naming conventions - functions are to be starting lower case, for example.
For your own good and readability, please follow prevalent conventions, at least when sharing your code with others. Better yet, all the time.
Edit:
To be more verbose:
NSUserDefaults can save small amounts of data for you in an easy way. If you 'just want to remember an array of numbers', it's the go to place.
suppose your data is helt in an instance variable like so:
var questions : [Question] = // load the questions - carefully, see below.
you can easily remove the question your PSNG (pseudo-random number generator) selects like so:
let randomNumber = Int(arc4random_uniform(UInt32(questions.count)))
questions.removeAtIndex(randomNumber)
Thus the next dice roll (e.g. invocation of your PSNG) is guaranteed not to select the question already asked because it is no longer in the pool of avaiable questions it selects from.
This approach does need you to save all your random numbers in the right order to 'replay' what has happened before when you are instantiating a new instance (for example, because the app has been reopened). That's where NSUserDefaults' setObject:forKey: and objectForKey: come into play when loading the questions initially and 'remembering' every new dice roll.
I hope, this covers what you might didn't understand before.

Realm Threading Confusion

So I'm working on setting up a background queue that does all realm writes on its own thread. I've run into some strange issues I can't figure out.
Issue #1
I'm not sure if this is related (see post: Xcode debug issues with realm) but I do have an apparent mismatch with my lldbg output as to whether a certain field:
messages element
My DataTypes
OTTOSession
class OTTOSession : Object {
dynamic var messages : MessageList?
dynamic var recordingStart : Double = NSDate().timeIntervalSince1970
func addLocationMessage(msg : dmParsedMessage) -> LocationMessage {
let dmsg : dmLocationMessage = msg as! dmLocationMessage
let locMsg = LocationMessage(locMsg: dmsg)
self.messages!.locationMessages.append(locMsg)
return locMsg;
}
}
MessageList
public class MessageList : Object {
dynamic var date : NSDate = NSDate();
dynamic var test : String = "HI";
let locationMessages = List<LocationMessage>()
let ahrsMessages = List<AHRSMessage>()
// let statusMessages = List<StatusMessageRLM>()
let logMessages = List<LogMessage>()
}
Realm Interactions
In my code I create my new OTTOSession in a code block on my realmQueue
internal var realmQueue = dispatch_queue_create("DataRecorder.realmQueue",
DISPATCH_QUEUE_SERIAL)
All realm calls are done on this realmQueue thread
dispatch_async(realmQueue) {
self.session = OTTOSession()
}
I've also tried different variants such as:
dispatch_async(realmQueue) {
self.session = OTTOSession()
// Directly making a message list
self.session!.messages = MessageList()
//Making a separate message list var
self.messages = MessageList()
self.session!.messages = self.messages
}
The reason I've played around with the MessageList is that I cant tell from the debugger whether the .messages variable is set or not
Recording
Once I signal to my processes I want to start recording I then actually make the write calls into Realm (which I'm not 100% sure i'm doing correctly)
dispatch_async(realmQueue){
// Update some of the data
self.session!.recordingStart = NSDate().timeIntervalSince1970
// Then start writing the objects
try! Realm().write {
// I've tried different variants of:
let session = self.session!
try! Realm().add(self.session!)
// Or
try! Realm().add(self.session!)
// or
let session = self.session!
session.messages = MessageList()
session.messages!.ahrsMessages
try! Realm().add(self.session!)
try! self.session!.messages = Realm().create(MessageList)
try! Realm().add(self.session!.messages!)
print ("Done")
}
}
Basically I've tried various combinations of trying to get the objects into realm.
Question: When adding an object with a one-to-one relationship do I have to add both objects to Realm or will just adding the parent object cause the related object to also be added to realm
Adding Data
Where things start to go awry is when I start adding data to my objects.
Inside my OTTOSession Object I have the following function:
func addLocationMessage(msg : dmParsedMessage) -> LocationMessage {
let dmsg : dmLocationMessage = msg as! dmLocationMessage
let locMsg = LocationMessage(locMsg: dmsg)
// THIS LINE IS CAUSING A 'REALM ACCESSED FROM INCORRECT THREAD ERROR
self.messages!.locationMessages.append(locMsg)
return locMsg;
}
I'm getting my access error on this line:
self.messages!.locationMessages.append(locMsg)
Now the function call itself is wrapped in the following block:
dispatch_async(realmQueue) {
try! Realm().write {
self.session?.addLocationMessage(msg)
}
}
So as far as I can tell by looking at the debugger - and by looking at the code - everything should be running inside the right thread.
My queue is SERIAL so things should be happening one after another. The only thing I can't figure out is when I break at this point the debugger does show that messages is nil but I cant trust that because:
Question
So my question is two fold
1) Is my code for adding an object into the RealmDB correct. i.e. do I need to make two separate Realm().add calls for both the OTTOSession and the MessageList or can I get away with a single call
2) Is there anything that pops out to explain why I'm getting a thread violation here - should doing all my realm writing calls on a single thread be enough ?
1) No, you don't need to make two separate calls to Realm.add(). When you add an object to a Realm all related objects are persisted as well.
2) Your thread violation very likely originates from the fact that dispatch queues make no guarantee over the thread on which they are executed on (beside the main queue). So that means your Realm queue is executed on different threads. You will need to make sure to retrieve your session object from a Realm opened on this thread. You might want to use primary keys for that purpose and share those between queues / threads.

Application crashes on device - works perfectly on Simulator

Recently, I have been working on an application. The goal of the application is to show you your last 10 photos taken in the Today view of the Notification Center, among other things. The application works perfectly in the iOS Simulator, but as soon as I put it on a real device for testing, it fails. It returns the error:
fatal error: Unexpected nil while unwrapping an Optional value
Typically this is very easy to fix since XCode highlights the code that returned nil and gives you the opportunity to fix it. In this case, none of my code gets highlighted. Instead, it highlights a line from Thread 1 (Is this the correct term? Thread 1?), as seen below:
Also note that above the highlited line is the line
; function signature specialization <Arg[0] = Exploded, Arg[1] = Exploded of Swift.(_fatalErrorMessage (Swift.StaticString, Swift.StaticString, Swift.StaticString, Swift.UInt) -> ()).(closure #2)
I included this line in the picture because of the "fatalErrorMessage" part of it. I suspect this could clue me in on the error, but I have no idea what this means. I'm not yet to the point of understanding that.
Also, after some thought, I placed a breakpoint at the viewDidLoad() function in the hopes of tracking any nil values, but the code appears to never even get to this point. It seems as if none of my code is being run.
Has anyone had problems like this/understands what that error code (If that's what it is) means? I'm pretty desperate now, hence me being here.
Thanks,
CodeIt
EDIT:
I placed a println line inside the viewDidLoad function to double check if it was being run. The println function runs and outputs correctly, so I think I may just have messed up my breakpoint somehow. Anyway - the code runs, but it still doesn't highlight any of my code causing the nil value.
EDIT 2:
As requested, I have inserted parts of my code. Please keep in mind that this code is a "first draft", if you will, and I have not yet gotten around to cleaning it up. I'm just trying to get it to work:
#IBOutlet weak var currentPosLabel: UILabel!
var currentImgPos = 0
#IBOutlet weak var imageView: UIImageView!
var images: NSMutableArray!
var totalImageCountNeeded: Int!
func fetchPhotos() {
images = NSMutableArray()
totalImageCountNeeded = 10
self.fetchPhotoAtIndexFromEnd(0)
}
func fetchPhotoAtIndexFromEnd(index: Int) {
let imgManager = PHImageManager.defaultManager()
var requestOptions = PHImageRequestOptions()
requestOptions.synchronous = true
var fetchOptions = PHFetchOptions()
fetchOptions.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)]
if let fetchResult = PHAsset.fetchAssetsWithMediaType(PHAssetMediaType.Image, options: fetchOptions) {
if fetchResult.count > 0 {
imgManager.requestImageForAsset(fetchResult.objectAtIndex(fetchResult.count - 1 - index) as? PHAsset, targetSize: view.frame.size, contentMode: PHImageContentMode.AspectFill, options: requestOptions, resultHandler: { (image, _) in
self.images.addObject(image)
if index + 1 < fetchResult.count && self.images.count < self.totalImageCountNeeded {
self.fetchPhotoAtIndexFromEnd(index + 1)
} else {
println("Completed array: \(self.images)")
}
})
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewDidAppear(animated: Bool) {
// NOTE: I am calling the fetchPhotos() function here since earlier in my debugging I thought the problem may be that I was calling it too early in the viewDidLoad function. It an incorrect theory, but it was worth a try.
fetchPhotos()
if images.count > 0 {
imageView.image = images[1] as? UIImage
}
}
I removed some parts of the code that I know have no reasons to cause my error, such as #IBActions, didReceiveMemoryWarning(), etc..
Even it's old question. I found my issue by deleting derived data from xcode.
Xcode -> window->Projects then select and delete your project derived data.
I don't think it is possible to determine exactly where the problem is without seeing your code.
Somewhere in your code you may have forced downcast a variable as!. If you change this to:
if let variable = optionalVariable as? SomeClass {
//Insert your code
}
Then this should fix your problem. Read this to learn more about casting in swift:
https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/TypeCasting.html
You should run the same device on simulator and real device.As an example if you run on 5s on real device then try the same device on simulator.By running simulator, it will definitely show the error.My error was the not connecting the #IBActions. Hope that this tip will help you.
I received the same error when trying to set a non-optional value to the value of an implicitly-unwrapped optional that was nil.
Ex. someGlobalFunction() returns an ImplicitlyUnwrappedOptional with a nil value then you try to set that to a regular value.
func someGlobalFunction() -> String! { return nil }
class MyClass {
let aVariable: String
init() {
aVariable = someGlobalFunction()
}
}

Resources