UIStoryboardScene deallocates its controller unexpectedly without changing retain count - ios

I am running unit tests against my view controllers. There are two view controllers in this suite, one of them has a segue to the other in the storyboard file.
In tests for both controllers, I create my test subject in same way: i get a storyboard with storyboardWithName:factory:bundle: (Typhoon for dependency injection uses the factory thing). Then, I use storyboard.instantiateViewControllerWithIdentifier: to get the VC.
In tests for my first view controller, all tests pass. Importantly, the view controller's deinit method is called ONLY when the vc itself goes out of scope in the test cases, or a new instance becomes the subject and the old one has its retain count reduced to 0.
however, in tests for the second VC, the tests fail in either of these cases (the VC goes out of scope or a new one becomes the subject) because for some reason there is a call being made to [UIStoryboardScene dealloc] which in turn calls deinit on the controller, even though its retain count should still be 1 (retained by the test case). ARC sends another release message to the VC when I set a new instance as the subject, and I get EXC_BAD_ACCESS (CODE=i386, GPFLT).
The [UIStoryboardScene dealloc] happens in a bunch of compiled code in XCTest framework (copied here), but I believe it's happening because the test case is ending, even though it shouldn't.
* thread #1: tid = 0x57cf91, 0x00000001089953a3 sbprod`Mode2ViewController.__deallocating_deinit(self=0x00007fd2bbdde5d0) + 19 at Mode2ViewController.swift:68, queue = 'com.apple.main-thread', stop reason = breakpoint 3.1
* frame #0: 0x00000001089953a3 sbprod`Mode2ViewController.__deallocating_deinit(self=0x00007fd2bbdde5d0) + 19 at Mode2ViewController.swift:68
frame #1: 0x0000000108995492 sbprod`#objc Mode2ViewController.__deallocating_deinit + 34 at Mode2ViewController.swift:0
frame #2: 0x000000010a1ff702 UIKit`-[UIStoryboardScene dealloc] + 36
frame #3: 0x000000010b04dafe libobjc.A.dylib`objc_object::sidetable_release(bool) + 232
frame #4: 0x000000010b04e0b8 libobjc.A.dylib`(anonymous namespace)::AutoreleasePoolPage::pop(void*) + 488
frame #5: 0x0000000115841f37 XCTest`__24-[XCTestCase invokeTest]_block_invoke_2 + 430
frame #6: 0x0000000115876613 XCTest`-[XCTestContext performInScope:] + 190
frame #7: 0x0000000115841d78 XCTest`-[XCTestCase invokeTest] + 169
frame #8: 0x00000001158423a2 XCTest`-[XCTestCase performTest:] + 459
frame #9: 0x000000011583fcf7 XCTest`-[XCTestSuite performTest:] + 396
frame #10: 0x000000011583fcf7 XCTest`-[XCTestSuite performTest:] + 396
frame #11: 0x000000011583fcf7 XCTest`-[XCTestSuite performTest:] + 396
frame #12: 0x000000011582cb10 XCTest`__25-[XCTestDriver _runSuite]_block_invoke + 51
frame #13: 0x000000011584db4c XCTest`-[XCTestObservationCenter _observeTestExecutionForBlock:] + 640
frame #14: 0x000000011582ca55 XCTest`-[XCTestDriver _runSuite] + 453
frame #15: 0x000000011582d7d1 XCTest`-[XCTestDriver _checkForTestManager] + 259
frame #16: 0x0000000115877a9a XCTest`_XCTestMain + 628
So:
Why is the UIStoryboardScene being created at all? I don't want a scene, just a VC, for my tests. It doesn't appear to happen in the previous test case. I think the only differences as far as the storyboard cares between the two VCs is that one of them has a segue into it, and the other out of it.
If the UIStoryboardScene is in fact existing in both cases, why is it deiniting my VC before it ought to?
My test cases are also not very different from one another in scope, however this test case passes some closures to places and I'm not 100% on how closures affect ARC.
EDIT: relevant code shown here.
In my test case, which is made using Quick:
override func spec() {
var subject: Mode2ViewController!
let presentDisplayString = "DesiredString"
describe("Mode2ViewController") {
describe("loading") {
describe("date and location") {
context("when location is available") {
beforeEach {
let system = MockSystemComponents.CreateWith(location: true, groups: nil)
let assembly = ApplicationAssembly().activateWithCollaboratingAssemblies([
system
])
//crash occurs on next line, before the 2nd test case, because the
//old subject has already been deallocated (by UIStoryboardScene)
//but reassigning this var, which had retains it, triggers a release
subject = assembly.mode2ViewController() as! Mode2ViewController
let _ = subject.view
}
it("records the location") {
expect(subject).notTo(beNil())
}
it("displays the location as a meaningful string") {
expect(subject.locationLabel.text).to(equal(presentDisplayString))
}
}
}
}
}
}
Note: the stack trace given above (first edit) is from a breakpoint I inserted in Mode2ViewController.deinit().

Turns out that the problem was that objective c treats things which begin with the keyword new differently with regards to their retain count. Typhoon can't handle methods that begin with new for this reason (see issue).
Solution: rename my typhoon method and the object being generated has the correct retain count.

Related

Realm - Why are notification blocks triggered when a write transaction begins?

I noticed today that Realm notification blocks are triggered when a write transaction begins. Intuitively I would have thought that ending a write transaction would trigger collection notifications with the changes that just happened, but I tracked down a crash in my code today resulting from a notification block being called when a write transaction begins.
Relevant stack frames before my notification handling is invoked:
frame #17: 0x0000000102535b98 Realm`RLMNotificationToken* RLMAddNotificationBlock<realm::Results>(this=0x00000001742974f0, changes=0x000000016fdf9de0, err=<unavailable>) block_pointer, bool)::'lambda'(realm::CollectionChangeSet const&, std::exception_ptr)::operator()(realm::CollectionChangeSet const&, std::exception_ptr) const + 608 at RLMCollection.mm:345
frame #18: 0x0000000102535700 Realm`realm::CollectionChangeCallback::Impl<RLMNotificationToken* RLMAddNotificationBlock<realm::Results>(objc_object*, realm::Results&, void (objc_object*, RLMCollectionChange*, NSError*) block_pointer, bool)::'lambda'(realm::CollectionChangeSet const&, std::exception_ptr)>::after(this=0x00000001742974e8, change=0x000000016fdf9de0) + 56 at collection_notifications.hpp:157
frame #19: 0x000000010248723c Realm`realm::CollectionChangeCallback::after(this=0x000000016fdf9dd0, c=0x000000016fdf9de0) + 64 at collection_notifications.hpp:122
frame #20: 0x0000000102487198 Realm`auto realm::_impl::CollectionNotifier::after_advance(this=0x000000016fdf9f90, lock=0x000000016fdf9ef0, callback=0x0000000103b62520)::$_9::operator()<std::__1::unique_lock<std::__1::mutex>, realm::_impl::CollectionNotifier::Callback>(std::__1::unique_lock<std::__1::mutex>&, realm::_impl::CollectionNotifier::Callback&) const + 156 at collection_notifier.cpp:326
frame #21: 0x0000000102479780 Realm`void realm::_impl::CollectionNotifier::for_each_callback<realm::_impl::CollectionNotifier::after_advance()::$_9>(this=0x00000001049d3e18, fn=0x000000016fdf9f90)::$_9&&) + 236 at collection_notifier.cpp:367
frame #22: 0x0000000102479688 Realm`realm::_impl::CollectionNotifier::after_advance(this=0x00000001049d3e18) + 28 at collection_notifier.cpp:315
frame #23: 0x000000010247ba3c Realm`realm::_impl::NotifierPackage::after_advance(this=0x000000016fdfa5e8) + 352 at collection_notifier.cpp:474
frame #24: 0x00000001026c8de4 Realm`void (anonymous namespace)::advance_with_notifications<realm::_impl::transaction::begin(context=0x0000000174221480, sg=0x0000000104020200, func=0x000000016fdfa540, notifiers=0x000000016fdfa5e8)::$_1>(realm::BindingContext*, realm::SharedGroup&, realm::_impl::transaction::begin(realm::SharedGroup&, realm::BindingContext*, realm::_impl::NotifierPackage&)::$_1&&, realm::_impl::NotifierPackage&) + 1152 at transact_log_handler.cpp:674
frame #25: 0x00000001026c8958 Realm`realm::_impl::transaction::begin(sg=0x0000000104020200, context=0x0000000174221480, notifiers=0x000000016fdfa5e8) + 56 at transact_log_handler.cpp:702
frame #26: 0x00000001024de620 Realm`realm::_impl::RealmCoordinator::promote_to_write(this=0x0000000103b0e108, realm=0x0000000103b0e498) + 328 at realm_coordinator.cpp:741
frame #27: 0x00000001026766b4 Realm`realm::Realm::begin_transaction(this=0x0000000103b0e498) + 552 at shared_realm.cpp:483
frame #28: 0x000000010262df3c Realm`::-[RLMRealm beginWriteTransaction](self=0x00000001740a9fc0, _cmd="beginWriteTransaction") + 48 at RLMRealm.mm:437
In a particular use case in my code, one of my notification callbacks creates a new set of RLMResults to display in a table and adds a notification block to it. Adding the notification block raises the expected exception in that case: Cannot create asynchronous query while in a write transaction.
That rule is easy to understand, I'm really just curious about why beginning a write transaction would trigger collection notifications, instead of waiting until after the transaction.
If a write was made on a different thread between when the Realm was last refreshed and when you begin a write transaction, beginning the write transaction will implicitly refresh the Realm first. If this results in anything changing, any applicable notifications will be sent immediately to notify you of the change.

NSEntityDescription.insertNewObject Function Causing Crash in Swift 3

I'm following this introduction tutorial to this tutorial, and am completely new to Core Data.
Here is the class that produces the error (however the error sends me to the first line of AppDelegate with a sigabrt. The line that begins "let newUser" seems to produce the error, because the error dissappears when I comment it out.
#IBAction func btnSave(){
let appDel:AppDelegate = (UIApplication.shared().delegate as! AppDelegate)
let context:NSManagedObjectContext = appDel.persistentContainer.viewContext
let newUser = NSEntityDescription.insertNewObject(forEntityName: "Users", into: context) as NSManagedObject
do {
try context.save()
} catch {}
print("Object Saved.")
}
This problem looks similar, but the answer is confusing to me and seems a bit overcomplicated
Since no one has posted about this error on the above Youtube video's comment section yet, I assume this is an error due to a change in Swift 3 (there was another error due to a change in Swift 2 which is documented in the comments).
Any help is greatly appreciated!
frame #0: 0x00000001067cbf06 libsystem_kernel.dylib`__pthread_kill + 10
frame #1: 0x00000001068ec4ec libsystem_pthread.dylib`pthread_kill + 90
frame #2: 0x000000010651e0b3 libsystem_c.dylib`abort + 129
frame #3: 0x00000001067ea43a libc++abi.dylib`abort_message + 266
frame #4: 0x000000010680ea9f libc++abi.dylib`default_terminate_handler() + 267
frame #5: 0x00000001055be59f libobjc.A.dylib`_objc_terminate() + 103
frame #6: 0x000000010680bc09 libc++abi.dylib`std::__terminate(void (*)()) + 8
frame #7: 0x000000010680b894 libc++abi.dylib`__cxa_rethrow + 99
frame #8: 0x00000001055be4b7 libobjc.A.dylib`objc_exception_rethrow + 40
frame #9: 0x00000001030f7bf1 CoreFoundation`CFRunLoopRunSpecific + 433
frame #10: 0x000000010850ea48 GraphicsServices`GSEventRunModal + 161
frame #11: 0x0000000103b30e8b UIKit`UIApplicationMain + 159
* frame #12: 0x0000000102b5dc6f CoreDataYoutube`main + 111 at AppDelegate.swift:5
frame #13: 0x00000001064726bd libdyld.dylib`start + 1
I'm not 100% sure what solved this problem for me... but I think it may have been as simple as my .xcdatamodeld file was not saving the entity and attribute data I entered, and unlike everything else I've encountered in Xcode thusfar, it was required for me to explicitly save (CMD+S) before leaving the .xcdatamodeld file or else the data was immediately erased if I clicked on ViewController or anywhere else from Project navigator.
I'm not sure if this is an error only in the beta version of Xcode 8.0 I'm using, but it seems like it's time to update to 8.1...
After updating to 8.1, the above solution no longer works...
Final update... I'm not sure why the problem reappeared, but I solved it the same way, by deleting the .xcdatamodeld and making a new one. I also ran into trouble, because I changed the name of it from Model.xcdatamodeld to CoreDataYoutube.xcdatamodeld. If you do this, just remember you have to update the line in AppDelegate: let container = NSPersistentContainer(name: "Model") to match.

NSManagedObject changes memory address?

I have a transient, transformable property set for my MO subclass [FeedItem], and in a category, I provide lazy loaded access:
- (id)images
{
if (!self.sImages) {
self.sImages = [[self.imageEntitiesClass alloc] initWithModel:self];
}
return self.sImages;
}
- (void)setImages:(id)images
{
self.sImages = images;
}
Now, within -[FeedItem.managedObjectContext performBlock:] I call -[FeedItem prefetchImages]. What that does, is performs the following call stack:
-[FeedItem prefetchImages]
-[FeedItemImages avatar]
-[FeedItem avatarURL]
- MULTI-THREAD ASSERTION
Within -[FeedItemImages avatar] method, I call self.model.avatarURL, but by checking the debugger, self.model.managedObjectContext is different from the encapsulating MOC, so it makes sense that the assertion is triggered .. but, why is the MOC different? I explicitly pass self in the -[FeedItemImages init], so they should be the same object?
To confirm this issue, I have disabled the caching, and returned a new object every time, and the app worked great:
- (id)images
{
#warning TODO - underlying object is changing randomly?
/** For some weird reason, when we cache image entities, then attempt to
* retrieve an image, we sometimes trigger a multithreading assertions
* breakpoint. Debugger shows the owner of the image entity is different
* from the model the image entity is referencing ¯\_(ツ)_/¯
*
* Possible solutions:
* The Bad:
* Current solution. Easy, but very ineffecient.
* The Ugly:
* Cache the image entities object privately, and expose a different
* property that reassigns self every time.
* The Good:
* Firgure out when the object mutates (awake from fetch, or some other
* callback of CoreData) and invalidate the object there.
*/
return [[self.imageEntitiesClass alloc] initWithModel:self];
}
This was working perfectly when we had the root MOC as main, and created children MOC on the fly to perform object mapping.
backtrace:
frame #0: [...] CoreData`+[NSManagedObjectContext __Multithreading_Violation_AllThatIsLeftToUsIsHonor__] + 4
frame #1: [...] CoreData`_sharedIMPL_pvfk_core + 221
* frame #2: [...] Telly`Telly.TLYUserImages.feedAction.getter : Telly.TLYImageEntity(self=0x00007f84ca5cf6c0) + 416 at TLYUserImages.swift:26
frame #3: [...] Telly`#objc Telly.TLYUserImages.feedAction.getter : Telly.TLYImageEntity + 34 at TLYUserImages.swift:0
frame #4: [...] Foundation`-[NSObject(NSKeyValueCoding) valueForKey:] + 251
frame #5: [...] Foundation`-[NSArray(NSKeyValueCoding) valueForKey:] + 437
frame #6: [...] Foundation`-[NSObject(NSKeyValueCoding) valueForKeyPath:] + 245
frame #7: [...] Foundation`-[NSArray(NSKeyValueCoding) valueForKeyPath:] + 435
frame #8: [...] Foundation`-[NSObject(NSKeyValueCoding) valueForKeyPath:] + 261
frame #9: [...] Foundation`-[NSArray(NSKeyValueCoding) valueForKeyPath:] + 435
frame #10: [...] Foundation`-[NSObject(NSKeyValueCoding) valueForKeyPath:] + 261
frame #11: [...] Foundation`-[NSArray(NSKeyValueCoding) valueForKeyPath:] + 435
frame #12: [...] Telly`Telly.TLYMappingMeta.prefetch (target=AnyObject at 0x000000011e8ac858, self=0x00007f84ca423040)(forTarget : Swift.AnyObject) -> () + 361 at TLYMappingMeta.swift:75
frame #13: [...] Telly`#objc Telly.TLYMappingMeta.prefetch (Telly.TLYMappingMeta)(forTarget : Swift.AnyObject) -> () + 54 at TLYMappingMeta.swift:0
frame #14: [...] Telly`-[TLYMapTask _tag:with:using:in:](self=0x00007f84cecd64f0, _cmd=0x000000010aa12ee9, items=0x00007f84ca6d12e0, feedId=0x00007f84ce81ddf0, mapMeta=0x00007f84ca423040, moc=0x00007f84c9c89500) + 179 at TLYMapTask.m:42
frame #15: [...] Telly`__39-[TLYMapTask _map:toMOC:sync:callback:]_block_invoke(.block_descriptor=<unavailable>) + 1920 at TLYMapTask.m:127
frame #16: [...] CoreData`developerSubmittedBlockToNSManagedObjectContextPerform + 201
frame #17: [...] libdispatch.dylib`_dispatch_client_callout + 8
frame #18: 0x00000001107a76a7 libdispatch.dylib`_dispatch_queue_drain + 2176
frame #19: 0x00000001107a6cc0 libdispatch.dylib`_dispatch_queue_invoke + 235
frame #20: 0x00000001107aa3b9 libdispatch.dylib`_dispatch_root_queue_drain + 1359
frame #21: 0x00000001107abb17 libdispatch.dylib`_dispatch_worker_thread3 + 111
frame #22: 0x0000000110b2d637 libsystem_pthread.dylib`_pthread_wqthread + 729
frame #23: 0x0000000110b2b40d libsystem_pthread.dylib`start_wqthread + 13
ImageEntities.swift
import Foundation
/** Each model object is composed of an imageEntities subclass that
* holds the image entities associated with that model.
*/
class TLYImageEntities: NSObject {
unowned let model: AnyObject
init(model: AnyObject) {
self.model = model
}
}
Example subclass of ImageEntities. Notice how self.user.avatarURL access the MO subclass property:
TLYUserImages:
import Foundation
class TLYUserImages: TLYImageEntities {
var user: TVUser {
return model as! TVUser
}
lazy var profileHeader: TLYImageEntity = TLYImageEntity(
listItem: self.user,
imageURL: self.user.avatarURL,
formatName: TLYImageFormatUserProfileHeader,
processor: TVImageProcessor.avatarProcessor()
)
...
}
TVUser+Aggregator, which provides the image entities class:
#implementation TVUser (Aggregator)
- (Class)imageEntitiesClass
{
return [TLYUserImages class];
}
...
#end
Because initWithModel:self is called on an instance of a managed object subclass, it seems logical that the address changes. self refers to the instance variable, so whenever you call this with a different variable, the content of the arguments of your cryptic method will also be different. Perhaps your clever method to just insert a new object is a little bit too clever.
Maybe you should dispense with this hardly readable and evidently buggy method and do something more intuitive and straight-forward, like a class method that takes a context and returns a new object inserted into that context.
I would concur with the time-honored quote of the previous poster, Marcus S. Zarra:
The mantra is the same. Keep the code simple. Do not be clever, do not be lazy. Do it right and it will be easy to maintain. Be clever or lazy and you are only punishing yourself and your users.
What does [[self.imageEntitiesClass alloc] initWithModel:self] do? Specifically was does -initWithModel: do? It is very strange to be calling an alloc init like that.
Looking into that method will help to determine what this interesting issue is.

Failed Trying to use Extensions - Swift

I've recently read The Swift Programming Language document, which introduced to me Extensions, and so I tried to implement this code:
extension SKTexture{
var size: CGSize {
return self.size()
}
}
Later in the same code, I try to access a property of the SKTexture:
someTexture.size.width
However, when I run the app, I get a EXC_BAD_ACCESS
I have also noticed that even if I don't try to access the width property via my new computed property, implementing someTexture.size().width instead of someTexture.size.width , I get this error. Could someone explain me what I'm doing wrong?
Short answer:
For a class derived from NSObject, a Swift property
which has the same name as an existing Objective-C method replaces that method.
Therefore in your case,
var size: CGSize {
return self.size()
}
calls itself recursively until the program aborts with a stack overflow
(well, that's what this site is for :).
If you choose a different name for the property, e.g.
var theSize: CGSize {
return self.size()
}
then everything works nicely.
Long answer:
SKTexture is a subclass of NSObject. Therefore all Swift properties are
"Objective-C compatible". As a consequence, the compiler generates a getter
method that can be called from Objective-C code. The getter method for the
size property is a -size method. So you have now two -size methods:
The original one from SKTexture and a second one defined in your Swift code.
If you do the same with your own Objective-C class defined in the same project
then you will get a linker warning:
instance method 'size' in category from /Users/.../main.o overrides
method from class in /Users/.../MyClass.o
If the Objective-C class is defined in a external framework (as in your case)
the linker does not notice the conflict.
Now return self.size() calls the generated Objective-C getter method, which in turn
calls the extension method. This leads to "infinite" recursion and ultimately
to a stack overflow.
This is confirmed by the stack backtrace which you can get with the lldb bt
command when the program has crashed:
* thread #1: tid = 0x3d2ef, 0x000000010fb15e01 libobjc.A.dylib`objc::DenseMapBase, unsigned long, true, objc::DenseMapInfo > >, DisguisedPtr, unsigned long, objc::DenseMapInfo >, true>::FindAndConstruct(DisguisedPtr const&) + 21, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=2, address=0x7fff51b9cfe8)
frame #0: 0x000000010fb15e01 libobjc.A.dylib`objc::DenseMapBase, unsigned long, true, objc::DenseMapInfo > >, DisguisedPtr, unsigned long, objc::DenseMapInfo >, true>::FindAndConstruct(DisguisedPtr const&) + 21
frame #1: 0x000000010fb13e14 libobjc.A.dylib`objc_object::sidetable_retain() + 94
* frame #2: 0x000000010d8674d9 cdtest2`ext.cdtest2.ObjectiveC.SKTexture.size.getter : C.CGSize(self=0x00007fcceea020f0) + 25 at AppDelegate.swift:19
frame #3: 0x000000010d867542 cdtest2`#objc ext.cdtest2.ObjectiveC.SKTexture.size.getter : C.CGSize + 34 at AppDelegate.swift:0
frame #4: 0x000000010d8674ed cdtest2`ext.cdtest2.ObjectiveC.SKTexture.size.getter : C.CGSize(self=0x00007fcceea020f0) + 45 at AppDelegate.swift:19
frame #5: 0x000000010d867542 cdtest2`#objc ext.cdtest2.ObjectiveC.SKTexture.size.getter : C.CGSize + 34 at AppDelegate.swift:0
frame #6: 0x000000010d8674ed cdtest2`ext.cdtest2.ObjectiveC.SKTexture.size.getter : C.CGSize(self=0x00007fcceea020f0) + 45 at AppDelegate.swift:19
frame #7: 0x000000010d867542 cdtest2`#objc ext.cdtest2.ObjectiveC.SKTexture.size.getter : C.CGSize + 34 at AppDelegate.swift:0
frame #8: 0x000000010d8674ed cdtest2`ext.cdtest2.ObjectiveC.SKTexture.size.getter : C.CGSize(self=0x00007fcceea020f0) + 45 at AppDelegate.swift:19
...
frame #149556: 0x000000010d8674ed cdtest2`ext.cdtest2.ObjectiveC.SKTexture.size.getter : C.CGSize(self=0x00007fcceea020f0) + 45 at AppDelegate.swift:19
frame #149557: 0x000000010d867542 cdtest2`#objc ext.cdtest2.ObjectiveC.SKTexture.size.getter : C.CGSize + 34 at AppDelegate.swift:0
frame #149558: 0x000000010d8694e0 cdtest2`cdtest2.AppDelegate.application (application=0x00007fccee8005a0, launchOptions=None, self=0x00007fccebc06410)(ObjectiveC.UIApplication, didFinishLaunchingWithOptions : Swift.Optional>) -> Swift.Bool + 112 at AppDelegate.swift:83
frame #149559: 0x000000010d8697b0 cdtest2`#objc cdtest2.AppDelegate.application (cdtest2.AppDelegate)(ObjectiveC.UIApplication, didFinishLaunchingWithOptions : Swift.Optional>) -> Swift.Bool + 560 at AppDelegate.swift:0
...
frame #149572: 0x000000010d86bcaa cdtest2`main + 42 at AppDelegate.swift:0
frame #149573: 0x00000001102f0145 libdyld.dylib`start + 1
This (hopefully) explains also why the problem occurs with both someTexture.size().width and someTexture.size.width:
In both cases, the custom extension method is called.

Why private(set) is not working in Swift?

From Apple docs:
“Each access-level modifier above optionally accepts a single
argument, which consists of the keyword set enclosed in parentheses
(for instance, private(set)). Use this form of an access-level
modifier when you want to specify an access level for the setter of a
variable or subscript that’s less than or equal to the access level of
the variable or subscript itself, as discussed in Getters and
Setters.”
Excerpt From: Apple Inc. “The Swift Programming Language.” iBooks.
https://itun.es/ru/jEUH0.l
An example which I try to test in Playground:
import UIKit
class A {
private(set) var name: String {
get { return "Hello, \(self.name)" }
set { self.name = "Unknown" }
}
init(_ name:String) {
self.name = name
}
}
let a = A("Andrew")
a.name = "Hello"
An error which I get in Console Output:
Playground execution failed: error: Execution was interrupted, reason: EXC_BAD_ACCESS (code=2, address=0x7fff5056eff8).
The process has been left at the point where it was interrupted, use "thread return -x" to return to the state before expression evaluation.
* thread #1: tid = 0xea721, 0x00000001104f308c libsystem_pthread.dylib`__mtx_droplock + 222, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=2, address=0x7fff5056eff8)
* frame #0: 0x00000001104f308c libsystem_pthread.dylib`__mtx_droplock + 222
frame #1: 0x00000001104f2f07 libsystem_pthread.dylib`pthread_mutex_unlock + 68
frame #2: 0x000000010ffbd2b5 libc++.1.dylib`std::__1::mutex::unlock() + 9
frame #3: 0x000000010f040b94 libswift_stdlib_core.dylib`swift_getGenericMetadata + 260
frame #4: 0x000000010ef28a24 libswift_stdlib_core.dylib`Swift.IndexingGenerator.init <A : Swift._Collection>(Swift.IndexingGenerator<A>.Type)(A) -> Swift.IndexingGenerator<A> + 164
frame #5: 0x000000010ef55f1a libswift_stdlib_core.dylib`protocol witness for Swift._Sequence_.generate <A : Swift._Sequence_>(#inout Swift._Sequence_.Self)() -> Swift._Sequence_.Self.GeneratorType in conformance Swift._ContiguousArrayBuffer : Swift._Sequence_ + 154
frame #6: 0x000000010ef284d5 libswift_stdlib_core.dylib`Swift._copyCollectionToNativeArrayBuffer <A : protocol<Swift._Collection, Swift._Sequence_>>(A) -> Swift._ContiguousArrayBuffer<A.GeneratorType.Element> + 1061
frame #7: 0x000000010ef40281 libswift_stdlib_core.dylib`Swift.Array.convertFromArrayLiteral <A>(Swift.Array<A>.Type)(Swift.Array<A>...) -> Swift.Array<A> + 641
frame #8: 0x000000010f1eaae4 PlaygroundLogger`Swift.UInt64.toBytes (Swift.UInt64)() -> Swift.Array<Swift.UInt8> + 292
frame #9: 0x000000010f1eb6a4 PlaygroundLogger`protocol witness for PlaygroundLogger.ToBytes.toBytes <A : PlaygroundLogger.ToBytes>(#inout PlaygroundLogger.ToBytes.Self)() -> Swift.Array<Swift.UInt8> in conformance Swift.UInt64 : PlaygroundLogger.ToBytes + 20
frame #10: 0x000000010f1dbe3d PlaygroundLogger`PlaygroundLogger.BytesStream.write (PlaygroundLogger.BytesStream)(PlaygroundLogger.ToBytes) -> PlaygroundLogger.BytesStream + 77
frame #11: 0x000000010f1dbd74 PlaygroundLogger`PlaygroundLogger.BytesStream.write (PlaygroundLogger.BytesStream)(Swift.String) -> PlaygroundLogger.BytesStream + 164
frame #12: 0x000000010f20f04b PlaygroundLogger`PlaygroundLogger.PlaygroundWriter.encode_config_info (PlaygroundLogger.PlaygroundWriter)() -> () + 203
frame #13: 0x000000010f20f2bf PlaygroundLogger`PlaygroundLogger.PlaygroundWriter.encode_header (PlaygroundLogger.PlaygroundWriter)() -> () + 127
frame #14: 0x000000010f20ecda PlaygroundLogger`PlaygroundLogger.PlaygroundScopeWriter.encode_scope_event (PlaygroundLogger.PlaygroundScopeWriter)(PlaygroundLogger.ScopeEvent) -> () + 58
frame #15: 0x000000010f1eb997 PlaygroundLogger`playground_log_scope_entry + 87
frame #16: 0x000000011ae20771
What am I doing wrong? Am I missing something?
PS1
This example works fine:
struct TrackedString {
private(set) var numberOfEdits = 0
var value: String = "" {
didSet {
numberOfEdits++
}
}
}
var stringToEdit = TrackedString()
stringToEdit.value = "Hello"
stringToEdit
stringToEdit.numberOfEdits += 10
stringToEdit
More from docs:
“the access level for the numberOfEdits property is marked with a
private(set) modifier to indicate that the property should be settable
only from within the same source file as the TrackedString structure’s
definition.”
Excerpt From: Apple Inc. “The Swift Programming Language.” iBooks.
https://itun.es/ru/jEUH0.l
But thats not what I need. Is it possible to disallow to set variable numberOfEdits outside the struct/class?
Your problem lies here:
set { self.name = "Unknown" }
You're setting the value of a computed property within its own setter. This causes infinite recursion. Don't forget that this is a computed property: it doesn't actually have storage. You don't have a variable "self.name" to put anything in; you only have a couple of functions to calculate it. Computed properties like this should use other, non-computed variables for storage. (That's why your structure example works, by the way: you're using a real property with storage.)
You're not being helped in your debugging by fact of running in a Playground. Don't get me wrong: Playgrounds are great. However, in this case, it's taking many seconds to crash, so the crash probably isn't showing up when you expect after an edit. It's also not showing you a full stack trace (which is massive for the problem you're getting, having reproduced it in a "real" app, and might have made it rather more obvious that you'd blown the stack.) When I built and ran the above as a console app, it finally blew up with a stack trace 104,832 calls deep, all but two of which were ...private_cmd.A.name.setter.... Bit of a clue :)

Resources