I'm currently developing a native plugin for a Capcitor app. I have very little experience with native iOS development, Swift and Obj-C.
I use a framework (FunSDK) which is written in C++. I call this in an Objective-C++ wrapper. As described here: https://medium.com/@cecilia.humlelu/set-up-c-library-dependencies-in-swift-projects-5dc2ccd2ddaf
The wrapper is then available in Swift via the bridging header. This works so far, I can call the functions from the framework and they are executed correctly. However, the functions of the framework are executed asynchronously. When a result is obtained, the “OnFunSDKResult” method is always executed.
During this step I get an EXC_BAD_ACCESS error while debugging. Then I ticked “Zombie objects” under the diagnostics settings. This causes execution to stop with an EXC_BREAKPOINT.
As I understand it, after executing the iniXMSDK method, the FunSDKWrapper class is cleared from memory before executing the "OnFunSDKResult" method?
I have also already integrated a completionHandler, which is only called in the "OnFunSDKResult" method so that data is transferred to the Swift class. However, that doesn't help either.
I have no idea how to proceed. I hope somebody can help me.
If you need any more information, I would be happy to provide it.
The FunSDKWrapper.mm
//
// FunSDKWrapper.m
// App
//
// Created by Sysprobs on 12/8/23.
//
#import "FunSDKWrapper.h"
#import "FunSDK/FunSDK.h"
#import <XMNetInterface/Reachability.h>
@implementation FunSDKWrapper
-(NSString *)iniXMSDK:(int)test completion:(void (^)(int result))completionHandler{
self.initCompletionHandler = completionHandler;
self.msgHandle = FUN_RegWnd((__bridge void *)self);
FUN_Init();
Fun_LogInit(self.msgHandle, "", 0, "", LOG_UI_MSG);
FUN_XMCloundPlatformInit("xxx", "xxx", "xxx", 1);
FUN_InitNetSDK();
FUN_SetFunStrAttr(EFUN_ATTR_SAVE_LOGIN_USER_INFO,SZSTR([self GetDocumentPathWith:@"UserInfo.db"]));
FUN_SetFunStrAttr(EFUN_ATTR_USER_PWD_DB, SZSTR([self GetDocumentPathWith:@"password.txt"]));
FUN_SetFunStrAttr(EFUN_ATTR_UPDATE_FILE_PATH,SZSTR([self GetDocumentPathWith:@""]));
FUN_SetFunStrAttr(EFUN_ATTR_TEMP_FILES_PATH,SZSTR([self GetDocumentPathWith:@""]));
FUN_SetFunIntAttr(EFUN_ATTR_AUTO_DL_UPGRADE, 0);
FUN_SetFunStrAttr(EFUN_ATTR_CONFIG_PATH,SZSTR([self GetDocumentPathWith:@"APPConfigs"]));
FUN_SetFunIntAttr(EFUN_ATTR_SUP_RPS_VIDEO_DEFAULT, 1);
FUN_SetFunIntAttr(EFUN_ATTR_SET_NET_TYPE, [self getNetworkType]);
FUN_SysInit("arsp.xmeye.net;arsp1.xmeye.net;arsp2.xmeye.net", 15010);
FUN_InitNetSDK();
FUN_SysGetDevList(self.msgHandle, SZSTR(@"xxxx") , SZSTR(@"xxxx"),0);
return @"test";
}
- (void)searchLanDevices {
FUN_DevSearchDevice(self.msgHandle, 4000, 0);
}
// NSDocument/fileName
- (NSString *)GetDocumentPathWith:(NSString *) fileName {
NSString* path = [self documentsPath];
if (fileName != nil) {
path = [path stringByAppendingString:@"/"];
path = [path stringByAppendingString:fileName];
}
return path;
}
//NSDocument
- (NSString *)documentsPath {
NSArray *pathArray = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *path = [pathArray lastObject];
return path;
}
-(int)getNetworkType {
Reachability*reach=[Reachability reachabilityWithHostName:@"www.apple.com"];
//判断当前的网络状态
switch([reach currentReachabilityStatus]){
case ReachableViaWiFi:
return 1;
case ReachableViaWWAN:
return 2;
default:
return 0;
break;
}
}
- (void)OnFunSDKResult:(NSNumber *) pParam {
NSInteger nAddr = [pParam integerValue];
MsgContent *msg = (MsgContent *)nAddr;
switch (msg->id) {
case EMSG_SYS_GET_DEV_INFO_BY_USER:{
self.initCompletionHandler(1);
if (msg->param1 < 0){
//fehler
NSLog(@"Fehler beim XMCloud login");
}else{
char devJson[750*500];
FUN_GetFunStrAttr(EFUN_ATTR_GET_USER_ACCOUNT_DATA_INFO, devJson, 750*500);
NSLog(@"Geraeteliste: = %s",devJson);
}
}
break;
}
}
@end
The Swift Class
import Foundation
import Capacitor
/**
* Please read the Capacitor iOS Plugin Development Guide
* here: https://capacitorjs.com/docs/plugins/ios
*/
@objc(xmsdkPlugin)
public class xmsdkPlugin: CAPPlugin {
private let implementation = xmsdk()
@objc func echo(_ call: CAPPluginCall) {
let value = call.getString("value") ?? ""
call.resolve([
"value": implementation.echo(value)
])
}
@objc func initXMSDK(_ call: CAPPluginCall) {
//let devId = call.getString("devId") ?? ""
let wrapper = FunSDKWrapper();
let resp = wrapper.iniXMSDK(1, completion: {(result) -> Void in
NSLog("Completion von iniXMSDK")
});
call.resolve([
"status": resp
])
}
}
Errorlog with EXC_BREAKPOINT:
App`invocation function for block in UI_SendMsg(int, XMSG*):
0x102bed8f4 <+0>: sub sp, sp, #0x70
0x102bed8f8 <+4>: stp x22, x21, [sp, #0x40]
0x102bed8fc <+8>: stp x20, x19, [sp, #0x50]
0x102bed900 <+12>: stp x29, x30, [sp, #0x60]
0x102bed904 <+16>: add x29, sp, #0x60
0x102bed908 <+20>: mov x19, x0
0x102bed90c <+24>: ldr x8, [x0, #0x20]
0x102bed910 <+28>: ldr w9, [x8, #0x18]
0x102bed914 <+32>: str w9, [sp, #0x8]
0x102bed918 <+36>: ldr q0, [x8, #0x20]
0x102bed91c <+40>: stur q0, [sp, #0xc]
0x102bed920 <+44>: ldr x9, [x8, #0x38]
0x102bed924 <+48>: ldr x10, [x8, #0x50]
0x102bed928 <+52>: stp x10, x9, [sp, #0x20]
0x102bed92c <+56>: ldr w9, [x8, #0x30]
0x102bed930 <+60>: str w9, [sp, #0x34]
0x102bed934 <+64>: str x8, [sp, #0x38]
0x102bed938 <+68>: adrp x0, 6283
0x102bed93c <+72>: add x0, x0, #0x60 ; g_wndIndexLock
0x102bed940 <+76>: bl 0x102bdd34c ; XBASIC::CLock::Lock at Lock.cpp:58:29
0x102bed944 <+80>: adrp x8, 6283
0x102bed948 <+84>: ldr x10, [x8, #0x38]
0x102bed94c <+88>: cbz x10, 0x102bed98c ; <+152> at UIInterface.mm:130:24
0x102bed950 <+92>: ldr w9, [x19, #0x28]
0x102bed954 <+96>: adrp x11, 6283
0x102bed958 <+100>: add x11, x11, #0x38 ; g_id_wnd + 8
0x102bed95c <+104>: mov x8, x11
0x102bed960 <+108>: ldr w12, [x10, #0x20]
0x102bed964 <+112>: cmp w12, w9
0x102bed968 <+116>: cset w12, lt
0x102bed96c <+120>: csel x8, x8, x10, lt
0x102bed970 <+124>: ldr x10, [x10, w12, uxtw #3]
0x102bed974 <+128>: cbnz x10, 0x102bed960 ; <+108> [inlined] std::__1::less<int>::operator()(int const&, int const&) const at operations.h:487:17
0x102bed978 <+132>: cmp x8, x11
0x102bed97c <+136>: b.eq 0x102bed98c ; <+152> at UIInterface.mm:130:24
0x102bed980 <+140>: ldr w10, [x8, #0x20]
0x102bed984 <+144>: cmp w9, w10
0x102bed988 <+148>: b.ge 0x102bed9dc ; <+232> at UIInterface.mm:126:21
0x102bed98c <+152>: adrp x0, 6283
0x102bed990 <+156>: add x0, x0, #0x60 ; g_wndIndexLock
0x102bed994 <+160>: bl 0x102bdd354 ; XBASIC::CLock::Unlock at Lock.cpp:63:31
0x102bed998 <+164>: adrp x0, 4324
0x102bed99c <+168>: add x0, x0, #0xb00 ; @"NO MSG Object....."
0x102bed9a0 <+172>: bl 0x1038af9a0 ; symbol stub for: NSLog
0x102bed9a4 <+176>: ldr x0, [x19, #0x20]
0x102bed9a8 <+180>: ldr x9, [x0, #0x8]
0x102bed9ac <+184>: ldaxr x8, [x9]
0x102bed9b0 <+188>: sub x10, x8, #0x1
0x102bed9b4 <+192>: stlxr w11, x10, [x9]
0x102bed9b8 <+196>: cbnz w11, 0x102bed9ac ; <+184> [inlined] InterlockedDecrement(long*) at OS.h:120:9
0x102bed9bc <+200>: sub w8, w8, #0x1
0x102bed9c0 <+204>: cmp w8, #0x0
0x102bed9c4 <+208>: b.gt 0x102beda24 ; <+304> at UIInterface.mm:143:5
0x102bed9c8 <+212>: tbnz w8, #0x1f, 0x102beda18 ; <+292> [inlined] XBASIC::IReferable::Release() at Referable.h:95:17
0x102bed9cc <+216>: ldr x8, [x0]
0x102bed9d0 <+220>: ldr x8, [x8, #0x8]
0x102bed9d4 <+224>: blr x8
0x102bed9d8 <+228>: b 0x102beda24 ; <+304> at UIInterface.mm:143:5
0x102bed9dc <+232>: ldp x20, x21, [x8, #0x28]
0x102bed9e0 <+236>: adrp x0, 6283
0x102bed9e4 <+240>: add x0, x0, #0x60 ; g_wndIndexLock
0x102bed9e8 <+244>: bl 0x102bdd354 ; XBASIC::CLock::Unlock at Lock.cpp:63:31
0x102bed9ec <+248>: cbz x20, 0x102bed998 ; <+164> at UIInterface.mm:139:13
0x102bed9f0 <+252>: adrp x8, 4330
0x102bed9f4 <+256>: ldr x0, [x8, #0x5e8]
0x102bed9f8 <+260>: add x2, sp, #0x8
0x102bed9fc <+264>: bl 0x1038b5a00 ; objc_msgSend$numberWithUnsignedInteger:
0x102beda00 <+268>: mov x3, x0
0x102beda04 <+272>: mov x0, x20
0x102beda08 <+276>: mov x2, x21
0x102beda0c <+280>: mov w4, #0x1
0x102beda10 <+284>: bl 0x1038b5aa0 ; objc_msgSend$performSelectorOnMainThread:withObject:waitUntilDone:
-> 0x102beda14 <+288>: b 0x102bed9a4 ; <+176> at UIInterface.mm:142:9
0x102beda18 <+292>: adrp x0, 3297
0x102beda1c <+296>: add x0, x0, #0xd93 ; "Check Please Error(IReferable)!"
0x102beda20 <+300>: bl 0x1038b2364 ; symbol stub for: puts
0x102beda24 <+304>: ldp x29, x30, [sp, #0x60]
0x102beda28 <+308>: ldp x20, x19, [sp, #0x50]
0x102beda2c <+312>: ldp x22, x21, [sp, #0x40]
0x102beda30 <+316>: add sp, sp, #0x70
0x102beda34 <+320>: ret
Update after Comments:
Thanks in advance for the information.
Unfortunately, the documentation for the FunSDK is not really good. This is the documentation: https://developer.jftech.com/docs/?menusId=8af0e7f3d4af49eab71cfdd8d7e47cef&siderid=6caa41621abd4e689b21a3c0339e8cd6&lang=en
And this is a demo app in which all functions of the framework are used: https://gitlab.xmcloud.io/demo/FunSDKDemo_iOS
The documentation does not describe exactly how to call the "OnFunSDKResult" method. At one point in the DemoApp there is only this comment: "All FUN interfaces with callback information will call back into this method."
The EXC_BAD_ACCESS error also only occurs when I implement the "OnFunSDKResult" method. So I assume that the framework somehow returns the data to the "OnFunSDKResult" method via the "UI_SendMsg".
I now basically understand that ARC released the wrapper. From the tutorials on memory management I still haven't figured out how to change this in my case.
Can someone give me a code example of how I can ensure that ARC only releases the wrapper at a certain time? I also read that I can explicitly disable ARC for individual classes and take over memory management myself. Is this an option so that I only release the memory manually again in the "OnFunSDKResult"?
I was now able to solve the problem by creating a strong reference from the FunSDKWrapper and the CompletionHandler.
var funSDKWrapper: FunSDKWrapper?
@objc func initXMSDK(_ call: CAPPluginCall) {
let wrapper = FunSDKWrapper();
self.funSDKWrapper = wrapper;
let strongCompletionHandler: (([AnyHashable: Any]) -> Void) = { result in
NSLog("Completion von iniXMSDK");
}
let resp = wrapper.iniXMSDK(1, completion: strongCompletionHandler);
call.resolve([
"status": resp
])
}