Post

Replies

Boosts

Views

Activity

Is [NSKeyedUnarchiver unarchiveObjectWithFile:] reentrant?
I am experiencing strange crashes in my (ObjC) code, that insinuate some memory corruption (mostly running SHA256 decryption for file data in my code there's a // return object that was stored in encrypted file - (id) objectFromEncryptedFile:(NSString *)filePath { // read MYCrypto object that includes encrypted object of any class MYCrypto *encData = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath]; // decrypt, unarchive and return return [encData objectFromEncryptedData:encData.data]; } That seems to be called from different queues that are not synchronized. It came to me that maybe NSKeyedUnarchiver is not "thread safe" and maybe unarchiveObjectWithFile is not reentrant? Couldn't find anything in the normal documentation, but most of the old docs are no longer searchable. Can someone shed light on this?
Topic: Design SubTopic: General
0
0
387
Nov ’24
How to find WHY my app triggers LNP popoup on MacOS 15
My App is a rather small menu-bar status-item app with 2 informational windows. It does NOT make use of ANY of the APIs mentioned here: https://developer.apple.com/forums/thread/663874 that are bound to need "Local Network" hence trigger TCC dialog. Yet - on first run of the app, the dialog pops. App is Obj-C, and the only APIs used are Notification-Center (for scheduling local notifications to the user), XPC connections and calls to other (our) apps for gathering that information, plus normal AppKit windowing (Controls, Text-fields, etc.) nothing else. Obviously SOMETHING I do causes the thing - and I know for sure this app DOES NOT NEED access to the local network - only I do not know how to identify the specific API I need to avoid using (or change the way I'm using) Are there any specific system logs to watch for? Is there any official set of APIs that will trigger the dialog? Provided that I cannot avoid this - could this permission be granted via MDM profile payload? Our product comes with
8
1
1.1k
Jan ’25
Check whether XPC remote proxy responds to selector, without causing exception and connection invalidation?
I have several processes maintaining NSXPConnection to an XPC service. The connections are bi-directional. Each side service and clients) of the connection exports an object, and an XPCInterface. The @protocols are different - to the service, and from the service to clients. So long as all the "clients" fully implement their "call-back" @protocol, there's no problem. All works fine. However - If a client does NOT implement a method in the "call back protocol", or completely neglects to export an object, or interface - and the service attempts to call back using the nonexistent method -- the XPC connection invalidates immediately. So far - expected behaviour. However, if I want the service to behave to the client a little like a "delegate" style -- and check first whether the client "respondsToSelector" or even - supports an interface BEFORE calling it, then this doesn't work. When my XPC service tries the following on a client connection: if (xpcConnection.remoteObjectInterface == nil) os_log_error(myXPCLog, "client has no remote interface); the condition is never met - i.e. the "remoteObjectInterface is never nil even when the client does NOT configure its NSXPCConnection with any incoming NSXPCInterface, and does not set an "exportedObject" Furthermore, the next check: if ([proxy respondsToSelector:@selector(downloadFiltersForCustomer:withReply:)]) { } will not only fail - but will drop the connection. The client side gets the invalidation with the following error: <NSXPCConnection: 0x600000b20000> connection to service with pid 2477 named com.proofpoint.ecd: received an undecodable message for proxy 1 (no exported object to receive message). Dropping message. I guess the "undecidable message" is the respondsToSelector - because the code doesn't get to attempt anything else afterwards, the connection drops. Is there a way to do this check "quietly", or suffering only "interruption", but without losing the connection,
3
0
827
Jan ’25
es_mute_path() vs. deprecated es_mute_path_literal() - incompatibility and wrong documentation
I recently upgraded a line of code in my Endpoint-Security client, to remove a deprecation warning: for (NSString *mutePath in ignoredBinaryPaths) { //(old) res = es_mute_path_literal(self.esClient, [mutePath UTF8String]); res = es_mute_path(self.esClient, [mutePath UTF8String], ES_MUTE_PATH_TYPE_TARGET_LITERAL); if (res!=ES_RETURN_SUCCESS) os_log_error(setupLog, "Failed to white-list binary:%{public}@ error:%{errno}d", mutePath, errno); } However, after this change, I started receiving tons of ES event messages, for AUTH_OPEN and AUTH_CREATE and many others, from processes/executables I explicitly and successfully muted! Since ES is so performance sensitive - I got worried. Inspecting better the new API I found incoherent documentation and even misleading and contradicting definitions. But the ES headers say differently!!! /** * @brief Suppress all events matching a path. * * @param client The es_client_t for which the path will be muted. * @param path The path to mute. * @param type Describes the type of the `path` parameter. * * @return es_return_t A value indicating whether or not the path was successfully muted. * * @note Path-based muting applies to the real and potentially firmlinked path * of a file as seen by VFS, and as available from fcntl(2) F_GETPATH. * No special provisions are made for files with multiple ("hard") links, * or for symbolic links. * In particular, when using inverted target path muting to monitor a * particular path for writing, you will need to check if the file(s) of * interest are also reachable via additional hard links outside of the * paths you are observing. * * @see es_mute_path_events * @discussion When using the path types ES_MUTE_PATH_TYPE_TARGET_PREFIX and ES_MUTE_PATH_TYPE_TARGET_LITERAL Not all events are * supported. Furthermore the interpretation of target path is contextual. For events with more than one target path (such as * exchangedata) the behavior depends on the mute inversion state Under normal muting the event is suppressed only if ALL paths * are muted When target path muting is inverted the event is selected if ANY target path is muted For example a rename will be * suppressed if and only if both the source path and destination path are muted. Supported events are listed below. For each * event the target path is defined as: * * EXEC: The file being executed * OPEN: The file being opened * MMAP: The file being memory mapped * RENAME: Both the source and destination path. * SIGNAL: The path of the process being signalled * UNLINK: The file being unlinked * CLOSE: The file being closed * CREATE: The path to the file that will be created or replaced * GET_TASK: The path of the process for which the task port is being retrieved * LINK: Both the source and destination path * SETATTRLIST: The file for which the attributes are being set * SETEXTATTR: The file for which the extended attributes are being set * SETFLAGS: The file for which flags are being set * SETMODE: The file for which the mode is being set * SETOWNER: The file for which the owner is being set * WRITE: The file being written to * READLINK: The symbolic link being resolved * TRUNCATE: The file being truncated * CHDIR: The new working directory * GETATTRLIST: The file for which the attribute list is being retrieved * STAT: The file for which the stat is being retrieved * ACCESS: The file for which access is being tested * CHROOT: The file which will become the new root * UTIMES: The file for which times are being set * CLONE: Both the source file and target path * FCNTL: The file under file control * GETEXTATTR The file for which extended attributes are being retrieved * LISTEXTATTR The file for which extended attributes are being listed * READDIR The directory for whose contents will be read * DELETEEXTATTR The file for which extended attribues will be deleted * DUP: The file being duplicated * UIPC_BIND: The path to the unix socket that will be created * UIPC_CONNECT: The file that the unix socket being connected is bound to * EXCHANGEDATA: The path of both file1 and file2 * SETACL: The file for which ACLs are being set * PROC_CHECK: The path of the process against which access is being checked * SEARCHFS: The path of the volume which will be searched * PROC_SUSPEND_RESUME: The path of the process being suspended or resumed * GET_TASK_NAME: The path of the process for which the task name port will be retrieved * TRACE: The path of the process that will be attached to * REMOTE_THREAD_CREATE: The path of the process in which the new thread is created * GET_TASK_READ: The path of the process for which the task read port will be retrieved * GET_TASK_INSPECT: The path of the process for which the task inspect port will be retrieved * COPYFILE: The path to the source file and the path to either the new file to be created or the existing file to be overwritten */ So the behavior completely changed, you can no longer specify executables (via their binary path) from which you do NOT want any events Muting effectively became reactive, not proactive. Why this change is not documented with the deprecation? Why no alternative is suggested? why find this only because it broke my software tool behavior and performance? And last: For how long can I rely on the old, deprecated APIs, should I choose to revert my change instead of devising a whole new mechanism for muting un-interesting
8
0
247
Aug ’25
Help resolving crash after using malloc_get_all_zones()
In an ObjC framework I'm developing (a dylib) that is loaded into JRE to be used via JNI (Zulu, Graal, or "native image" from Graal+ a JAR) I implemented a naive method that collects current memory footprint of the host process: It collects 5 numbers into a simple NSDictionary with NSString keys (physical footprint, default zone bytes used and allocated, and sums for used and allocated bytes for all zones. The code ran for some time, but at certain point my process started crashing horribly in this method -- at the last line, accessing the dictionary. Here's the code: -(NSDictionary *)memoryState { NSMutableDictionary *memoryState = [NSMutableDictionary dictionaryWithCapacity:8]; // obtain process current physical memory footprint, in bytes. task_vm_info_data_t info; mach_msg_type_number_t count = TASK_VM_INFO_COUNT; kern_return_t kr = task_info(mach_task_self(), TASK_VM_INFO, (task_info_t)&info, &count); [memoryState setObject:(kr == KERN_SUCCESS) ? @(info.phys_footprint) : [NSNull null] forKey:@"physical"]; // obtain process default zone's allocated memory, in bytes. malloc_zone_t *zone = malloc_default_zone(); if (zone!=nil) { malloc_statistics_t st; malloc_zone_statistics(zone, &st); [memoryState setObject:@(st.size_in_use) forKey:@"bytesInUseDefaultZone"]; [memoryState setObject:@(st.size_allocated) forKey:@"bytesAllocatedDefaultZone"]; } uint64_t zone_count = 0, size_in_use =0, size_allocated = 0; vm_address_t *zones = NULL; unsigned int zones_count = 0; kr = malloc_get_all_zones(mach_task_self(), NULL, &zones, &zones_count); if (kr == KERN_SUCCESS && zones != NULL && zones_count > 0) { for (unsigned int i = 0; i < zones_count; i++) { malloc_zone_t *zone = (malloc_zone_t *)zones[i]; if (!zone) continue; malloc_statistics_t st; malloc_zone_statistics(zone, &st); zone_count++; size_in_use += (uint64_t)st.size_in_use; size_allocated += (uint64_t)st.size_allocated; } [memoryState setObject:@(size_in_use) forKey:@"bytesInUseAllZones"]; [memoryState setObject:@(size_allocated) forKey:@"bytesAllocatedAllZones"]; } if (zones != NULL) { vm_deallocate(mach_task_self(), (vm_address_t)zones, zones_count * sizeof(vm_address_t)); } return [memoryState copy]; } my (JRE) process started crashing badly, at the last [memoryState copy]; with crash report I could not understand (looks like an infinite recursion or loop). Any debug log messages (os_log) for this memoryState, its items or its copy would crash the same. Finally I found that commenting out the vm_deallocate() call removes the crash. Sorry to say - I could NOT find anywhere in the documentation anything about malloc_get_all_zones() returned data, and whether I need to deallocate it after use. Some darn AI analyzer pointed out I "had a leak" and that "Apple documentation" which it didn't provide, requires that I thus release this data. 1 ) Do I really have to deallocate the returned "zones" ?? even if I do, something here is strange - zones is a malloc_zone_t ** -- how can it be casted to (vm_address_t)zones Where can I read actual documentation about these low level APIs and the correct use? Thanks!
1
0
128
Feb ’26
My EndpointSecurity Client process is kicked by OS on Mac sleep/wake cycle
Hi, I develop an ES client applying rule-engine evaluating ES events (mostly File-system events). It is a bit non-standard not being deployed as a System-Extension, but rather as a global daemon. On some Macs, I sometimes see "crash reports" for the ES process, all sharing Termination Reason: Namespace ENDPOINTSECURITY, Code 2 EndpointSecurity client terminated because it failed to respond to a message before its deadline All of these happen not while normal Mac usage, but rather right at Mac wakeup time after sleep. My guess is, some ES_AUTH events (with deadline) arrive when Mac goes to sleep, and somehow my high-priority dispatch_queue handling them is "put to sleep" mid processing them, so when the Mac wakes up - event handling continues long after the deadline passed, and MacOS decides to kick the process. Questions: What is the recommended behavior with ES vs Sleep/Wake cycles? (we're not an antivirus, and we don't care much to clear events or go "blind" for such time) Can I specify somewhere in the info.plist of my bundle (this is built like an App) that my process should't be put to sleep, or that the OS should sleep it only when it becomes idle, or some other way tells the OS it is "ready for sleep" ? If not -- How do I observe the scenario so I can suspend my event handling IN TIME and resume on wake? Thanks!
4
0
147
3w
Is [NSKeyedUnarchiver unarchiveObjectWithFile:] reentrant?
I am experiencing strange crashes in my (ObjC) code, that insinuate some memory corruption (mostly running SHA256 decryption for file data in my code there's a // return object that was stored in encrypted file - (id) objectFromEncryptedFile:(NSString *)filePath { // read MYCrypto object that includes encrypted object of any class MYCrypto *encData = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath]; // decrypt, unarchive and return return [encData objectFromEncryptedData:encData.data]; } That seems to be called from different queues that are not synchronized. It came to me that maybe NSKeyedUnarchiver is not "thread safe" and maybe unarchiveObjectWithFile is not reentrant? Couldn't find anything in the normal documentation, but most of the old docs are no longer searchable. Can someone shed light on this?
Topic: Design SubTopic: General
Replies
0
Boosts
0
Views
387
Activity
Nov ’24
How to find WHY my app triggers LNP popoup on MacOS 15
My App is a rather small menu-bar status-item app with 2 informational windows. It does NOT make use of ANY of the APIs mentioned here: https://developer.apple.com/forums/thread/663874 that are bound to need "Local Network" hence trigger TCC dialog. Yet - on first run of the app, the dialog pops. App is Obj-C, and the only APIs used are Notification-Center (for scheduling local notifications to the user), XPC connections and calls to other (our) apps for gathering that information, plus normal AppKit windowing (Controls, Text-fields, etc.) nothing else. Obviously SOMETHING I do causes the thing - and I know for sure this app DOES NOT NEED access to the local network - only I do not know how to identify the specific API I need to avoid using (or change the way I'm using) Are there any specific system logs to watch for? Is there any official set of APIs that will trigger the dialog? Provided that I cannot avoid this - could this permission be granted via MDM profile payload? Our product comes with
Replies
8
Boosts
1
Views
1.1k
Activity
Jan ’25
Check whether XPC remote proxy responds to selector, without causing exception and connection invalidation?
I have several processes maintaining NSXPConnection to an XPC service. The connections are bi-directional. Each side service and clients) of the connection exports an object, and an XPCInterface. The @protocols are different - to the service, and from the service to clients. So long as all the "clients" fully implement their "call-back" @protocol, there's no problem. All works fine. However - If a client does NOT implement a method in the "call back protocol", or completely neglects to export an object, or interface - and the service attempts to call back using the nonexistent method -- the XPC connection invalidates immediately. So far - expected behaviour. However, if I want the service to behave to the client a little like a "delegate" style -- and check first whether the client "respondsToSelector" or even - supports an interface BEFORE calling it, then this doesn't work. When my XPC service tries the following on a client connection: if (xpcConnection.remoteObjectInterface == nil) os_log_error(myXPCLog, "client has no remote interface); the condition is never met - i.e. the "remoteObjectInterface is never nil even when the client does NOT configure its NSXPCConnection with any incoming NSXPCInterface, and does not set an "exportedObject" Furthermore, the next check: if ([proxy respondsToSelector:@selector(downloadFiltersForCustomer:withReply:)]) { } will not only fail - but will drop the connection. The client side gets the invalidation with the following error: <NSXPCConnection: 0x600000b20000> connection to service with pid 2477 named com.proofpoint.ecd: received an undecodable message for proxy 1 (no exported object to receive message). Dropping message. I guess the "undecidable message" is the respondsToSelector - because the code doesn't get to attempt anything else afterwards, the connection drops. Is there a way to do this check "quietly", or suffering only "interruption", but without losing the connection,
Replies
3
Boosts
0
Views
827
Activity
Jan ’25
es_mute_path() vs. deprecated es_mute_path_literal() - incompatibility and wrong documentation
I recently upgraded a line of code in my Endpoint-Security client, to remove a deprecation warning: for (NSString *mutePath in ignoredBinaryPaths) { //(old) res = es_mute_path_literal(self.esClient, [mutePath UTF8String]); res = es_mute_path(self.esClient, [mutePath UTF8String], ES_MUTE_PATH_TYPE_TARGET_LITERAL); if (res!=ES_RETURN_SUCCESS) os_log_error(setupLog, "Failed to white-list binary:%{public}@ error:%{errno}d", mutePath, errno); } However, after this change, I started receiving tons of ES event messages, for AUTH_OPEN and AUTH_CREATE and many others, from processes/executables I explicitly and successfully muted! Since ES is so performance sensitive - I got worried. Inspecting better the new API I found incoherent documentation and even misleading and contradicting definitions. But the ES headers say differently!!! /** * @brief Suppress all events matching a path. * * @param client The es_client_t for which the path will be muted. * @param path The path to mute. * @param type Describes the type of the `path` parameter. * * @return es_return_t A value indicating whether or not the path was successfully muted. * * @note Path-based muting applies to the real and potentially firmlinked path * of a file as seen by VFS, and as available from fcntl(2) F_GETPATH. * No special provisions are made for files with multiple ("hard") links, * or for symbolic links. * In particular, when using inverted target path muting to monitor a * particular path for writing, you will need to check if the file(s) of * interest are also reachable via additional hard links outside of the * paths you are observing. * * @see es_mute_path_events * @discussion When using the path types ES_MUTE_PATH_TYPE_TARGET_PREFIX and ES_MUTE_PATH_TYPE_TARGET_LITERAL Not all events are * supported. Furthermore the interpretation of target path is contextual. For events with more than one target path (such as * exchangedata) the behavior depends on the mute inversion state Under normal muting the event is suppressed only if ALL paths * are muted When target path muting is inverted the event is selected if ANY target path is muted For example a rename will be * suppressed if and only if both the source path and destination path are muted. Supported events are listed below. For each * event the target path is defined as: * * EXEC: The file being executed * OPEN: The file being opened * MMAP: The file being memory mapped * RENAME: Both the source and destination path. * SIGNAL: The path of the process being signalled * UNLINK: The file being unlinked * CLOSE: The file being closed * CREATE: The path to the file that will be created or replaced * GET_TASK: The path of the process for which the task port is being retrieved * LINK: Both the source and destination path * SETATTRLIST: The file for which the attributes are being set * SETEXTATTR: The file for which the extended attributes are being set * SETFLAGS: The file for which flags are being set * SETMODE: The file for which the mode is being set * SETOWNER: The file for which the owner is being set * WRITE: The file being written to * READLINK: The symbolic link being resolved * TRUNCATE: The file being truncated * CHDIR: The new working directory * GETATTRLIST: The file for which the attribute list is being retrieved * STAT: The file for which the stat is being retrieved * ACCESS: The file for which access is being tested * CHROOT: The file which will become the new root * UTIMES: The file for which times are being set * CLONE: Both the source file and target path * FCNTL: The file under file control * GETEXTATTR The file for which extended attributes are being retrieved * LISTEXTATTR The file for which extended attributes are being listed * READDIR The directory for whose contents will be read * DELETEEXTATTR The file for which extended attribues will be deleted * DUP: The file being duplicated * UIPC_BIND: The path to the unix socket that will be created * UIPC_CONNECT: The file that the unix socket being connected is bound to * EXCHANGEDATA: The path of both file1 and file2 * SETACL: The file for which ACLs are being set * PROC_CHECK: The path of the process against which access is being checked * SEARCHFS: The path of the volume which will be searched * PROC_SUSPEND_RESUME: The path of the process being suspended or resumed * GET_TASK_NAME: The path of the process for which the task name port will be retrieved * TRACE: The path of the process that will be attached to * REMOTE_THREAD_CREATE: The path of the process in which the new thread is created * GET_TASK_READ: The path of the process for which the task read port will be retrieved * GET_TASK_INSPECT: The path of the process for which the task inspect port will be retrieved * COPYFILE: The path to the source file and the path to either the new file to be created or the existing file to be overwritten */ So the behavior completely changed, you can no longer specify executables (via their binary path) from which you do NOT want any events Muting effectively became reactive, not proactive. Why this change is not documented with the deprecation? Why no alternative is suggested? why find this only because it broke my software tool behavior and performance? And last: For how long can I rely on the old, deprecated APIs, should I choose to revert my change instead of devising a whole new mechanism for muting un-interesting
Replies
8
Boosts
0
Views
247
Activity
Aug ’25
Help resolving crash after using malloc_get_all_zones()
In an ObjC framework I'm developing (a dylib) that is loaded into JRE to be used via JNI (Zulu, Graal, or "native image" from Graal+ a JAR) I implemented a naive method that collects current memory footprint of the host process: It collects 5 numbers into a simple NSDictionary with NSString keys (physical footprint, default zone bytes used and allocated, and sums for used and allocated bytes for all zones. The code ran for some time, but at certain point my process started crashing horribly in this method -- at the last line, accessing the dictionary. Here's the code: -(NSDictionary *)memoryState { NSMutableDictionary *memoryState = [NSMutableDictionary dictionaryWithCapacity:8]; // obtain process current physical memory footprint, in bytes. task_vm_info_data_t info; mach_msg_type_number_t count = TASK_VM_INFO_COUNT; kern_return_t kr = task_info(mach_task_self(), TASK_VM_INFO, (task_info_t)&info, &count); [memoryState setObject:(kr == KERN_SUCCESS) ? @(info.phys_footprint) : [NSNull null] forKey:@"physical"]; // obtain process default zone's allocated memory, in bytes. malloc_zone_t *zone = malloc_default_zone(); if (zone!=nil) { malloc_statistics_t st; malloc_zone_statistics(zone, &st); [memoryState setObject:@(st.size_in_use) forKey:@"bytesInUseDefaultZone"]; [memoryState setObject:@(st.size_allocated) forKey:@"bytesAllocatedDefaultZone"]; } uint64_t zone_count = 0, size_in_use =0, size_allocated = 0; vm_address_t *zones = NULL; unsigned int zones_count = 0; kr = malloc_get_all_zones(mach_task_self(), NULL, &zones, &zones_count); if (kr == KERN_SUCCESS && zones != NULL && zones_count > 0) { for (unsigned int i = 0; i < zones_count; i++) { malloc_zone_t *zone = (malloc_zone_t *)zones[i]; if (!zone) continue; malloc_statistics_t st; malloc_zone_statistics(zone, &st); zone_count++; size_in_use += (uint64_t)st.size_in_use; size_allocated += (uint64_t)st.size_allocated; } [memoryState setObject:@(size_in_use) forKey:@"bytesInUseAllZones"]; [memoryState setObject:@(size_allocated) forKey:@"bytesAllocatedAllZones"]; } if (zones != NULL) { vm_deallocate(mach_task_self(), (vm_address_t)zones, zones_count * sizeof(vm_address_t)); } return [memoryState copy]; } my (JRE) process started crashing badly, at the last [memoryState copy]; with crash report I could not understand (looks like an infinite recursion or loop). Any debug log messages (os_log) for this memoryState, its items or its copy would crash the same. Finally I found that commenting out the vm_deallocate() call removes the crash. Sorry to say - I could NOT find anywhere in the documentation anything about malloc_get_all_zones() returned data, and whether I need to deallocate it after use. Some darn AI analyzer pointed out I "had a leak" and that "Apple documentation" which it didn't provide, requires that I thus release this data. 1 ) Do I really have to deallocate the returned "zones" ?? even if I do, something here is strange - zones is a malloc_zone_t ** -- how can it be casted to (vm_address_t)zones Where can I read actual documentation about these low level APIs and the correct use? Thanks!
Replies
1
Boosts
0
Views
128
Activity
Feb ’26
My EndpointSecurity Client process is kicked by OS on Mac sleep/wake cycle
Hi, I develop an ES client applying rule-engine evaluating ES events (mostly File-system events). It is a bit non-standard not being deployed as a System-Extension, but rather as a global daemon. On some Macs, I sometimes see "crash reports" for the ES process, all sharing Termination Reason: Namespace ENDPOINTSECURITY, Code 2 EndpointSecurity client terminated because it failed to respond to a message before its deadline All of these happen not while normal Mac usage, but rather right at Mac wakeup time after sleep. My guess is, some ES_AUTH events (with deadline) arrive when Mac goes to sleep, and somehow my high-priority dispatch_queue handling them is "put to sleep" mid processing them, so when the Mac wakes up - event handling continues long after the deadline passed, and MacOS decides to kick the process. Questions: What is the recommended behavior with ES vs Sleep/Wake cycles? (we're not an antivirus, and we don't care much to clear events or go "blind" for such time) Can I specify somewhere in the info.plist of my bundle (this is built like an App) that my process should't be put to sleep, or that the OS should sleep it only when it becomes idle, or some other way tells the OS it is "ready for sleep" ? If not -- How do I observe the scenario so I can suspend my event handling IN TIME and resume on wake? Thanks!
Replies
4
Boosts
0
Views
147
Activity
3w