Problem description

There is a system bug in iOS 10: When the app is installed for the first time, an authorization box will pop up when the app is connected to the Internet for the first time, asking “Do YOU allow XXX to access data?” . However, sometimes the system does not pop up the authorization box, so the APP cannot be connected to the Internet.

For details, see:

IOS 10 pit: New machine installs app for the first time, requests network permission “Allow data use”

Solution to the problem that iOS 10 does not prompt Whether to allow applications to access data

Key points summary:

  • Only iOS 10 or above, China bank models and devices with cellular network function have this authorization problem. WiFi iPad does not have this problem.
  • Because the authorization box pops up only when there are network operations, the app’s first network access is bound to fail.
  • When there is a bug that does not pop up the authorization box, go to the Settings to change the cellular network permissions of any APP, or open the wireless LAN assistant, let the system update the cellular network related data, you can solve this bug.

When this system bug appears, it is very troublesome for users. App also needs to provide detailed prompts to deal with this situation, which is very inelegant.

Repair methods

Spring Festival is a bit empty, found several related private API to fix this bug.

The Authorization dialog box is displayed.

The first thing I found was an API that would pop up the authorization box directly.

//Image: /System/Library/PrivateFrameworks/FTServices.framework/FTServices

@interface FTNetworkSupport : NSObject
+ (id)sharedInstance;
- (bool)dataActiveAndReachable;
@end
Copy the code

Header file reference: ftNetworksupport.h

When the app has not requested network permission before, a call to dataActiveAndReachable will pop up “Is XXX allowed to access data?” If the network permission has been determined, the dialog box will not be displayed.

Call way

Since FTNetworkSupport is in the PrivateFrameworks directory, the app does not load ftServices. framework. To use ftNetworkFrameworks, use dlopen to load ftServices. framework.

#import <dlfcn.h>Framework void * FTServicesHandle = dlopen("/System/Library/PrivateFrameworks/FTServices.framework/FTServices", RTLD_LAZY);
Class NetworkSupport = NSClassFromString(@"FTNetworkSupport");
id networkSupport = [NetworkSupport performSelector:NSSelectorFromString(@"sharedInstance")];
[networkSupport performSelector:NSSelectorFromString(@"dataActiveAndReachable")]; // Uninstall ftServices. framework dlclose(FTServicesHandle);Copy the code

This API solves the problem of network permissions causing the first network operation to fail, but it still has the bug that sometimes the authorization box does not pop up.

Have the system update cellular access data

Since changing the cellular network permissions of any app can cause the app to pop up the authorization box, all you need to do is find a way to make the system update the data related to network permissions.

Use Hopper to decomcompile the system Settings, including PreferencesUI. Framework, and find the API for changing app network permissions. We use two private C functions in CoreTelephony. Framework:

CTServerConnection* _CTServerConnectionCreateOnTargetQueue(CFAllocatorRef, NSString *, dispatch_queue_t, Void */* A block argument */)

void _CTServerConnectionSetCellularUsagePolicy(CTServerConnection *, NSString *, NSDictionary *)

Most of the time was spent testing these two functions. I tried these two functions a few months ago to fix this bug, but it didn’t work at that point, so it didn’t work.

Call way

To call private C functions, dlSYM is used as a simple illustration:

void *CoreTelephonyHandle = dlopen("/System/Library/Frameworks/CoreTelephony.framework/CoreTelephony", RTLD_LAZY); // Call a private C function with a function pointer, Use symbolic name search function from the repository address CFTypeRef connectionCreateOnTargetQueue (*) (CFAllocatorRef nsstrings *, dispatch_queue_t, void*) = dlsym(CoreTelephonyHandle,"_CTServerConnectionCreateOnTargetQueue");
int (*changeCellularPolicy)(CFTypeRef, NSString *, NSDictionary *) = dlsym(CoreTelephonyHandle, "_CTServerConnectionSetCellularUsagePolicy"); / / use the Settings app bundle id for camouflage CFTypeRef connection = connectionCreateOnTargetQueue (kCFAllocatorDefault, @"com.apple.Preferences",dispatch_get_main_queue(),NULL); // Request to change the network permission of this app to allowed. This will not be changed, but only trigger the system to update related data changeCellularPolicy(connection, @)"Bundle ID of app requiring authorization", @ {@"kCTCellularUsagePolicyDataAllowed":@YES});

dlclose(CoreTelephonyHandle);
Copy the code

Note that, in a statement connectionCreateOnTargetQueue and changeCellularPolicy function pointer, the parameter type to corresponding strictly, if the type error, may lead to a memory management system implementation for parameter error, a crash. CTServerConnection is private and is a subclass of CFTypeRef, so CFTypeRef can be used instead.

Metaphysics emerged

_CTServerConnectionSetCellularUsagePolicy function of the second parameter is the need to modify the app bundle id. When testing, it is found that when passed this parameter, the object must be an NSString created with literal syntax, such as @”com.who.testDemo”. When passed a dynamically generated NSString like [NSBundle mainBundle]. There will still be a bug that does not pop up the authorization box, that is, it has not been successfully fixed. Repeated after 5-10 consecutive tests.

However, with

NSMutableString *bundleIdentifier = [NSMutableString stringWithString:@"com.who"];
[bundleIdentifier appendString:@".testDemo"];
Copy the code

And that string is fine. The similarity is that ultimately all nsStrings are created from literal syntax.

The cause of this metaphysical problem has not yet been found.

Looking at nsStrings created by literals, it’s a little bit special. Constant Strings in Objective-C. It is a string of type __NSCFConstantString, and the memory of this object will not be freed for the entire life of the app. Does iOS XPC require strings to be used?

Time is limited, we’ll look into this problem later.

Use the console to track interprocess communication

These private apis all use interprocess communication, which is a bit cumbersome to debug trace.

You can use the console on your Mac to view the live log of your device and look for communication behavior. Open the console app and select the iOS device connected to the Mac on the left to see the device log.

Below is invoked the _CTServerConnectionSetCellularUsagePolicy after the log, the incoming bundle id use the string literal creation:

_CTServerConnectionSetCellularUsagePolicy
CommCenter
CommCenter – The iPhone Wiki

Here is to use [NSBundle mainBundle] bundleIdentifier incoming _CTServerConnectionSetCellularUsagePolicy log when the second argument:

_CTServerConnectionSetCellularUsagePolicy

Check network permissions

Because dataActiveAndReachable has asynchronous operations, you cannot uninstall FTServices.framework with DLClose immediately. The solution is to listen until cellular permission is enabled and then uninstall.

CTCellularData in CoreTelephony can be used to monitor your app’s cellular network permissions, and it’s not a proprietary API. You can also use it to help users detect if cellular permissions have been turned off, and to provide reminders in case the app fails to connect to the Internet because the user has turned off network permissions.

The CTCellularData header is as follows:

typedef NS_ENUM(NSUInteger, CTCellularDataRestrictedState) {kCTCellularDataRestrictedStateUnknown, / / permissions unknown kCTCellularDataRestricted, / / cellular access is shut down, Have network access fully closed or only WiFi access two cases kCTCellularDataNotRestricted / / cellular access open}; @interface CTCellularData : NSObject callback for permission changes @property (copy, nullable) CellularDataRestrictionDidUpdateNotifier cellularDataRestrictionDidUpdateNotifier; // The current cellular permission @property (nonatomic,readonly) CTCellularDataRestrictedState restrictedState;
@end
Copy the code

Usage:

#import <CoreTelephony/CTCellularData.h>CTCellularData *cellularDataHandle = [[CTCellularData alloc] init]; CellularDataHandle. CellularDataRestrictionDidUpdateNotifier = ^ (CTCellularDataRestrictedState state) {/ / change the callback honeycomb permissions};Copy the code

Key points to note when using:

  • CTCellularDataOnly cellular rights can be detected, not WiFi rights.
  • aCTCellularDataWhen an instance is created,restrictedStateiskCTCellularDataRestrictedStateUnknownAfter,cellularDataRestrictionDidUpdateNotifierThere will be a callback to get the correct permission status.
  • When the user changes the app’s permissions in the Settings,cellularDataRestrictionDidUpdateNotifierA callback will be received. If you want to stop listening, you must change thecellularDataRestrictionDidUpdateNotifierSet tonil.
  • Assigned tocellularDataRestrictionDidUpdateNotifierBlock is not automatically released, even if you give a local variable’s blockCTCellularDataThe instance is set to listen and will still receive a callback when permissions change, so remember to set the blocknil.

Check the state line model and whether there is a cellular function

Non-domestic models and devices without cellular functionality do not need to be repaired. So also look for the relevant private API for detection.

The private API used is as follows:

//Image: /System/Library/PrivateFrameworks/AppleAccount.framework/AppleAccount @interface AADeviceInfo : NSObject /// Cellular capability - (bool)hasCellularCapability; // the regionCode of the device, for example, the regionCode of the machine is CH - (id)regionCode; @endCopy the code

Header file reference: aaDeviceInfo.h

It is used in the same way as ftServices. framework.

A way to test whether the fix was successful

My test method is to modify the bundle identifier and display name of the project every time it is run, so that the system treats it as a new app every time and uses Release mode to test whether the authorization box can pop up every time. Since the Bundle identifier needs to be modified constantly, a script was written to run automatically at each build, automatically adding the number after the bundle identifier in several places. This script is already included in the demo.

You can also test to see if the authorization box pops up when you connect to the Internet without performing a fix. My test results show that the authorization box does not pop up after about 5-10 runs. You need to change the project to Release mode to appear, and there will be no bugs in Debug mode.

Note that because the build automatically after accumulation, the relationship between ZIKCellularAuthorization. AppBundleIdentifier in h is the value of the next app runtime. If you find this Script confusing, switch it off in the Build Phases/Run Script and add a # before the sh ${PROJECT_DIR}/ increaseBundleid. sh Script.

There is no test to overwrite whether apps with the same Bundle Identifier installed or updated version number will also have this bug, it is now assumed that the bug will only occur during the first installation.

App Store review issues

Because of the use of the private API, this has been obtrussed, but the obtrussed can only bypass static checking, and now the App Store audit checks calls to dynamic methods such as Dlopen, DLSYm, NSClassFromString, etc., so using the private API in these ways will still be detected. Solutions:

1. Make the app execute the fix after a fixed time, for example, check the date in the code after the expected 2018.01.01 review is completed, and execute the fix after 2018.01.01. This time needs to be properly estimated.

2. The Apple review team seems to be based in the US and can judge the system language and fix it only in Chinese.

For now, these judgments need to be made by the user.

But now that iOS10 is in the past, the problem doesn’t seem to be particularly serious. You consider whether to use it, the biggest role of this article is to give a research method of reference.

Tool code and Demo

The address is in ZIKCellularAuthorization and the private API used has been obfuscated. Change Build Configuration to Release mode before testing. Help please click a Star~

reference

IOS 10 pit: New machine installs app for the first time, requests network permission “Allow data use”

Solution to the problem that iOS 10 does not prompt Whether to allow applications to access data