In my project ,there is a demand about UITextView. when I tap the link in UITextView, our app should open a webview Controller to show the linked content; when i long press the link in UITextView, our app should show some menu about copy and paste.now i can implement the second situation in UIInputViewDelegate
(BOOL)textView:(UITextView *)textView shouldInteractWithURL:(NSURL *)URL inRange:(NSRange)characterRange
although the shouldInteractWithURL is fired, i can't recognize the relative tag between the tap event and long press event.
then i breakpoint at the end of
(BOOL)textView:(UITextView *)textView shouldInteractWithURL:(NSURL *)URL inRange:(NSRange)characterRange
method,and i use lldb to find some information about tap event and long press event as follow:
long press event:
frame #1: 0x0000000112b46214 UIKit`-[_UITextViewInteractableLink allowInteraction] + 165
frame #2: 0x0000000112b452c3 UIKit`-[_UITextViewInteractableItem handleLongPress] + 22
frame #3: 0x0000000112b45055 UIKit`-[UITextView(LinkInteraction) validateInteractionWithLinkAtPoint:] + 287
frame #4: 0x00000001125edac9 UIKit`-[UITextInteractionAssistant(UITextInteractionAssistant_Internal) longDelayRecognizer:] + 281
frame #5: 0x00000001125dbb28 UIKit`_UIGestureRecognizerSendTargetActions + 153
tap event:
frame #1: 0x0000000112b46214 UIKit`-[_UITextViewInteractableLink allowInteraction] + 165
frame #2: 0x0000000112b45181 UIKit`-[_UITextViewInteractableItem handleTap] + 33
frame #3: 0x0000000112b45055 UIKit`-[UITextView(LinkInteraction) validateInteractionWithLinkAtPoint:] + 287
frame #4: 0x00000001125ed782 UIKit`-[UITextInteractionAssistant(UITextInteractionAssistant_Internal)
i find out some difference about the two event;handleTap or handleLongPress is fired in different scene. so any solution to capture the long press event or disable the system's long press event then add my own long press event to handle long event on linkedText in UITextView?
Why go an do long press if its hyperlink it should work on just one check here https://stackoverflow.com/a/37207783/5772601
Related
We're using a "skinless" version of AK SynthOne in our app (i.e., just the engine, with a collection of presets), and seeing a consistent crash when returning from background. In order to spare the user's battery when the sequence is not playing, if the user goes to background, we call AudioKit.stop(). This allows the CPU usage to fall to zero (happy customers!), but on returning we see a consistent crash when SynthOne tries to handle its first note:
void S1NoteState::startNoteHelper(int noteNumber, int velocity, float frequency) {
oscmorph1->freq = frequency; // <-- EXC_BAD_ACCESS here!
oscmorph2->freq = frequency;
subOsc->freq = frequency;
fmOsc->freq = frequency;
...
The backtrace:
* thread #13, stop reason = EXC_BAD_ACCESS (code=1, address=0x0)
* frame #0: 0x0000000100fc5d78 Spliqs`S1NoteState::startNoteHelper(this=0x0000000108345500, noteNumber=56, velocity=54, frequency=207.652328) at S1NoteState.mm:105:21
frame #1: 0x0000000100fd6764 Spliqs`S1DSPKernel::turnOnKey(this=0x00000001088a3790, noteNumber=56, velocity=54, frequency=207.652328) at S1DSPKernel+toggleKeys.mm:85:14
frame #2: 0x0000000100fcabd0 Spliqs`S1DSPKernel::startNote(this=0x00000001088a3790, noteNumber=56, velocity=54, frequency=207.652328) at S1DSPKernel+startStopNotes.mm:45:9
frame #3: 0x0000000100fc1298 Spliqs`::-[S1AudioUnit startNote:velocity:frequency:](self=0x00000001088a3600, _cmd="startNote:velocity:frequency:", note='8', velocity='6', frequency=207.652328) at S1AudioUnit.mm:98:13
frame #4: 0x00000001014cf86c Spliqs`AKSynthOne.play(noteNumber=56, velocity=54, frequency=207.65233610331066, channel=0, self=0x0000000280de4a00) at AKSynthOne.swift:223:21
frame #5: 0x00000001016dbda4 Spliqs`AudioKit.AKPolyphonicNode.play(noteNumber: Swift.UInt8, velocity: Swift.UInt8, channel: Swift.UInt8) -> () + 328
frame #6: 0x000000010135a120 Spliqs`Conductor.handleMIDI(data1=146, data2=56, data3=54, self=0x000000010820efe0) at Conductor.swift:392:23
frame #7: 0x00000001013597a4 Spliqs`Conductor.handle(event=AudioKit.AKMIDIEvent # 0x000000016f488ba0, self=0x000000010820efe0) at Conductor.swift:374:26
frame #8: 0x0000000101358de4 Spliqs`closure #1 in Conductor.setUpMIDIHandler(packetList=0x000000016f489e98, _0=nil, self=0x000000010820efe0) at Conductor.swift:326:30
frame #9: 0x0000000101157f48 Spliqs`thunk for #escaping #callee_guaranteed (#unowned UnsafePointer<MIDIPacketList>, #unowned UnsafeMutableRawPointer?) -> () at <compiler-generated>:0
frame #10: 0x00000001d42467b8 CoreMIDI`LocalMIDIReceiverList::HandleMIDIIn(unsigned int, unsigned int, void*, MIDIPacketList const*) + 164
frame #11: 0x00000001d4246618 CoreMIDI`MIDIProcess::RunMIDIInThread() + 132
frame #12: 0x00000001d425d1e8 CoreMIDI`XThread::RunHelper(void*) + 28
frame #13: 0x00000001d4262624 CoreMIDI`CAPThread::Entry(CAPThread*) + 92
frame #14: 0x00000001c3eef908 libsystem_pthread.dylib`_pthread_body + 132
frame #15: 0x00000001c3eef864 libsystem_pthread.dylib`_pthread_start + 48
frame #16: 0x00000001c3ef7dcc libsystem_pthread.dylib`thread_start + 4
We've tried everything from just starting AudioKit, through to completely re-initializing SynthOne, but it's always the same.
We've been stuck on this one for a while, so any thoughts greatly appreciated.
UPDATE: Actually, I'm afraid I have to resuscitate this question. Although the .pause() approach in my answer below worked for going to/from background we also have a use-case where we move to offline render mode to export audio. That process definitely does require us to stop the engine (in order to switch modes), and we're getting the same SynthOne-related crash when handling the first event after AudioKit.start(). Does anybody understand why we'd hit an EXC_BAD_ACCESS when returning? I'm guessing that memory has been incorrectly reallocated/reclaimed(?) somehow, but how can I prevent it?
I suppose, more broadly, the question would be: how do I safely start/stop the AudioKit engine when using SynthOne.
Erm... yikes... how about .stopEngine() and .startEngine()...? an rtfm moment, for sure.
For anyone who might face a similar problem, these use AudioKit.engine.pause() and AudioKit.engine.start(), as opposed to using stop().
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.
I have several webviews in my app. Few of the webviews will have a textfield in them.
When running the app initially, on entering some text in the textfield and hitting the 'Done' button on the keyboard causes the app to crash. If I relaunch the app and try the same scenario, it works fine.
I am running few Javascript files as well in the WebView.
This is the error thrown:
EXC_BAD_ACCESS mh_execute_header
Attempted to dereference null pointer.
This is the trace I got from the crash details:
/usr/lib/libobjc.A.dylib:0objc_msgSend
Frameworks/UIKit.framework/UIKit:0-[_UIWebViewScrollViewDelegateForwarder forwardInvocation:]
Frameworks/CoreFoundation.framework/CoreFoundation:0___forwarding___
Frameworks/CoreFoundation.framework/CoreFoundation:0__forwarding_prep_0___
Frameworks/UIKit.framework/UIKit:0-[UIScrollView _getDelegateZoomView]
Frameworks/UIKit.framework/UIKit:0-[UIScrollView _zoomScaleFromPresentationLayer:]
Frameworks/UIKit.framework/UIKit:0-[UIWebDocumentView _zoomedDocumentScale]
Frameworks/UIKit.framework/UIKit:0-[UIWebDocumentView _layoutRectForFixedPositionObjects]
Frameworks/UIKit.framework/UIKit:0-[UIWebDocumentView _updateFixedPositionedObjectsLayoutRectUsingWebThread:synchronize:]
Frameworks/UIKit.framework/UIKit:0-[UIWebDocumentView _updateFixedPositioningObjectsLayoutAfterScroll]
Frameworks/UIKit.framework/UIKit:0-[UIWebBrowserView _updateFixedPositioningObjectsLayoutAfterScroll]
Frameworks/CoreFoundation.framework/CoreFoundation:0__CFNOTIFICATIONCENTER_IS_CALLING_OUT_TO_AN_OBSERVER__
Frameworks/CoreFoundation.framework/CoreFoundation:0_CFXRegistrationPost
Frameworks/CoreFoundation.framework/CoreFoundation:0___CFXNotificationPost_block_invoke
Frameworks/CoreFoundation.framework/CoreFoundation:0-[_CFXNotificationRegistrar find:object:observer:enumerator:]
Frameworks/CoreFoundation.framework/CoreFoundation:0_CFXNotificationPost
Frameworks/Foundation.framework/Foundation:0-[NSNotificationCenter postNotificationName:object:userInfo:]
Frameworks/UIKit.framework/UIKit:0-[UIInputWindowController postEndNotifications:withInfo:]
Frameworks/UIKit.framework/UIKit:0__77-[UIInputWindowController moveFromPlacement:toPlacement:starting:completion:]_block_invoke_2896
Frameworks/UIKit.framework/UIKit:0-[UIInputWindowController performWithSafeTransitionFrames:]
Frameworks/UIKit.framework/UIKit:0__77-[UIInputWindowController moveFromPlacement:toPlacement:starting:completion:]_block_invoke887
Frameworks/UIKit.framework/UIKit:0-[UIViewAnimationBlockDelegate _didEndBlockAnimation:finished:context:]
Frameworks/UIKit.framework/UIKit:0-[UIViewAnimationState sendDelegateAnimationDidStop:finished:]
Frameworks/UIKit.framework/UIKit:0-[UIViewAnimationState animationDidStop:finished:]
Frameworks/QuartzCore.framework/QuartzCore:0<redacted>
/usr/lib/system/libdispatch.dylib:0_dispatch_client_callout
/usr/lib/system/libdispatch.dylib:0_dispatch_main_queue_callback_4CF
Frameworks/CoreFoundation.framework/CoreFoundation:0__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__
Frameworks/CoreFoundation.framework/CoreFoundation:0__CFRunLoopRun
Frameworks/CoreFoundation.framework/CoreFoundation:0CFRunLoopRunSpecific
PrivateFrameworks/GraphicsServices.framework/GraphicsServices:0GSEventRunModal
Frameworks/UIKit.framework/UIKit:0-[UIApplication runModal:]
Frameworks/UIKit.framework/UIKit:0-[UIAlertView _showAnimated:]
Frameworks/UIKit.framework/UIKit:0-[UIWebView webView:runJavaScriptAlertPanelWithMessage:initiatedByFrame:]
Frameworks/CoreFoundation.framework/CoreFoundation:0__invoking___
Frameworks/CoreFoundation.framework/CoreFoundation:0-[NSInvocation invoke]
Frameworks/CoreFoundation.framework/CoreFoundation:0-[NSInvocation invokeWithTarget:]
PrivateFrameworks/WebKitLegacy.framework/WebKitLegacy:0<redacted>
Frameworks/CoreFoundation.framework/CoreFoundation:0___forwarding___
Frameworks/CoreFoundation.framework/CoreFoundation:0__forwarding_prep_0___
PrivateFrameworks/WebKitLegacy.framework/WebKitLegacy:0<redacted>
PrivateFrameworks/WebCore.framework/WebCore:0<redacted>
PrivateFrameworks/WebCore.framework/WebCore:0<redacted>
Frameworks/JavaScriptCore.framework/JavaScriptCore:0<redacted>
Frameworks/JavaScriptCore.framework/JavaScriptCore:0<redacted>
Frameworks/JavaScriptCore.framework/JavaScriptCore:0<redacted>
Frameworks/JavaScriptCore.framework/JavaScriptCore:0<redacted>
Frameworks/JavaScriptCore.framework/JavaScriptCore:0<redacted>
Frameworks/JavaScriptCore.framework/JavaScriptCore:0<redacted>
Frameworks/JavaScriptCore.framework/JavaScriptCore:0<redacted>
Frameworks/JavaScriptCore.framework/JavaScriptCore:0JSC::call(JSC::ExecState*, JSC::JSValue, JSC::CallType, JSC::CallData const&, JSC::JSValue, JSC::ArgList const&, WTF::NakedPtr<JSC::Exception>&)
PrivateFrameworks/WebCore.framework/WebCore:0<redacted>
PrivateFrameworks/WebCore.framework/WebCore:0<redacted>
PrivateFrameworks/WebCore.framework/WebCore:0<redacted>
PrivateFrameworks/WebCore.framework/WebCore:0<redacted>
PrivateFrameworks/WebCore.framework/WebCore:0<redacted>
PrivateFrameworks/WebCore.framework/WebCore:0<redacted>
PrivateFrameworks/WebCore.framework/WebCore:0WebCore::EventHandler::keyEvent(WebCore::PlatformKeyboardEvent const&)
PrivateFrameworks/WebCore.framework/WebCore:0WebCore::EventHandler::keyEvent(WebEvent*)
Frameworks/CoreFoundation.framework/CoreFoundation:0__invoking___
Frameworks/CoreFoundation.framework/CoreFoundation:0-[NSInvocation invoke]
Frameworks/CoreFoundation.framework/CoreFoundation:0-[NSInvocation invokeWithTarget:]
Frameworks/UIKit.framework/UIKit:0-[UIThreadSafeNode forwardInvocation:]
Frameworks/CoreFoundation.framework/CoreFoundation:0___forwarding___
Frameworks/CoreFoundation.framework/CoreFoundation:0__forwarding_prep_0___
Frameworks/UIKit.framework/UIKit:0-[UIKeyboardImpl _handleWebKeyEvent:withEventType:withInputString:withInputStringIgnoringModifiers:]
Frameworks/UIKit.framework/UIKit:0__78-[UIKeyboardImpl _handleWebKeyEvent:withIndex:inInputString:executionContext:]_block_invoke
Frameworks/UIKit.framework/UIKit:0-[UIKeyboardTaskExecutionContext returnExecutionToParentWithInfo:]
Frameworks/UIKit.framework/UIKit:0-[UIKeyboardTaskExecutionContext returnExecutionToParentWithInfo:]
Frameworks/UIKit.framework/UIKit:0-[UIKeyboardTaskQueue continueExecutionOnMainThread]
Frameworks/Foundation.framework/Foundation:0__NSThreadPerformPerform
Frameworks/CoreFoundation.framework/CoreFoundation:0__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__
Frameworks/CoreFoundation.framework/CoreFoundation:0__CFRunLoopDoSources0
Frameworks/CoreFoundation.framework/CoreFoundation:0__CFRunLoopRun
Frameworks/CoreFoundation.framework/CoreFoundation:0CFRunLoopRunSpecific
PrivateFrameworks/GraphicsServices.framework/GraphicsServices:0GSEventRunModal
Frameworks/UIKit.framework/UIKit:0UIApplicationMain
MyApp:0mh_execute_header
/usr/lib/system/libdyld.dylib:0<redacted>
I really am not sure as to why is this crash happening.
TIA!
I was able to get this crash fixed by doing the following:
Put a Javascript callback when the keyboard is dismissed
Fetch the callback in
func webView(webView: UIWebView, shouldStartLoadWithRequest request: NSURLRequest, navigationType: UIWebViewNavigationType) -> Bool
Set the content offset of WebView's ScrollView:
webView.scrollView.setContentOffset(CGPointMake(0, 0), animated: false)
I had a bunch of WebViews inside a ScrollView. I know this is not ideal, but I had to go ahead with this.
When the keyboard is dismissed, the WebView tries to scroll to a position which does not exist and cause the app to crash.
So by doing the callback method, I scroll the WebView to the top, once the keyboard is dismissed.
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.
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.