What happens if I use LAPolicyDeviceOwnerAuthentication on iOS 8? - ios

In my app, I would like to know if the user has setup a passcode or fingerprint (touchID). There's a pretty easy method just for that: [LAContext canEvaluatePolicy:LAPolicyDeviceOwnerAuthentication error:error].
However, Apple's docs say LAPolicyDeviceOwnerAuthentication is only available on iOS9 and above. I would rather not crash iOS 8 users without explanation, even if it is getting older. The thing is, I've tried it on an iOS8.4 simulator, and it seems to compile and just work.
What ill effects can happen if I use LAPolicyDeviceOwnerAuthentication on iOS 8?

I use code similar to this:
LAPolicy localAuthPolicy = LAPolicyDeviceOwnerAuthenticationWithBiometrics;
if (![[UIDevice currentDevice].systemVersion hasPrefix:#"8."]) {
localAuthPolicy = LAPolicyDeviceOwnerAuthentication;
}
This ensures I only use LAPolicyDeviceOwnerAuthentication on iOS 9 and later.
It's undocumented what actually happens on an iOS 8 device when you attempt to use LAPolicyDeviceOwnerAuthentication. It's very unlikely anything will crash but the authentication may return NO due to an unknown value or it may succeed because iOS 8 only had one valid value and it may not do any checking.
Unfortunately Objective-C doesn't provide any runtime checks for valid enum values like you can do with constants, methods, and classes.

If you use LAPolicyDeviceOwnerAuthentication on anything below iOS 9, the app will crash. That is what happened to my app when I didn't realize that this was not available on anything below iOS 9, but my app supported iOS 8.x as its minimum supported OS version.

Related

DispatchQueue.main.async availability iOS 10 works on prior iOS

After migration to new swift 3we've got a lot of automatic syntax changes among which:
DispatchQueue.main.async(execute: {
// Do something
})
documentation says that it's available in iOS 10 and later.
So I expected to see unrecognized selector error when running on iOS 8 but it still works.
So I'm just wondering if it would affect some users since our deployment target is iOS 8?
I just tested it on an iPod touch running 8.4 and DispatchQueue.main.async{} works just fine.
The docs are misleading (wrong?)
I went to log a documentation problem, but wait, among lots of other stuff that's been removed from the Xcodebuilt-in documentation system, you can't log issues with the docs anymore!
I suggest filing a radar bug, then.

IOMobileFramebufferGetLayerDefaultSurface not working on iOS 9

My main question is, how can I reverse engineer a private API function that already exists, but has been modified in a new version of iOS?
I have created an iOS application to record the screen content using IOSurface and IOMobileFramebuffer. The main functions the framebuffer use to open it are IOMobileFramebufferGetMainDisplay(connect) and IOMobileFramebufferGetLayerDefaultSurface.
These functions have been used since the very first version of the app, and they have worked on all versions of iOS 7 and 8. However, on the latest iOS 9 beta, which is beta 5, the function IOMobileFramebufferGetLayerDefaultSurface does not work. The function does not return 0, as it should when it successfully opens the framebuffer.
This other user on StackOverflow seems to also be experiencing the same issue: IOMobileFramebufferGetLayerDefaultSurface function failed on iOS 9. We have a reference to IOMobileFramebufferConnection named “_framebufferConnection” and an IOSurfaceRef named “_screenSurface” Here is the current code:
IOMobileFramebufferGetMainDisplay(&_framebufferConnection);
IOMobileFramebufferGetLayerDefaultSurface(_framebufferConnection, 0, &_screenSurface;
As stated before, these work perfectly on iOS 7-8, but on iOS 9, the second function crashes. I have also looked at the binaries with the symbols for both versions and compared them. The second parameter of the LDR is slightly different in iOS 9, when compared to the iOS 8.4.1 binary. So, back to the main question, how can I reverse engineer IOMobileFramebufferGetLayerDefaultSurface, or see how in what way it’s actually been modified on iOS 9?
To answer the question of "how in what way it’s actually been modified on iOS 9", I did some digging into IOMobileFramebufferGetLayerDefaultSurface on iOS8 vs iOS9 (GM). Here are the results of what I found:
Setup:
IOMobileFramebufferRef fb;
IOMobileFramebufferGetMainDisplay(&fb);
iOS8 Implementation:
Calls through to kern_GetLayerDefaultSurface
Which accesses underlying IOConnection
io_connect_t fbConnect = *(io_connect_t *)((char *)fb + 20)
To retrieve the IOSurfaceID via
IOSurfaceID surfaceID;
uint32_t outCount = 1;
IOConnectCallScalarMethod(fbConnect, 3, {0, 0}, 2, &surfaceID, &outCount)
Returns IOSurfaceLookup(surfaceID)
iOS9 Implementation:
Same steps as above aside from the return
Then tries to retrieve a mach port to access the surface via
io_service_t fbService = *(io_service_t *)((char *)fb + 16)
mach_port_t surfacePort;
IOServiceOpen(fbService, mach_task_self(), 3, &surfacePort)
On success, return IOSurfaceLookupFromMachPort(surfacePort)
It is on the last step that IOServiceOpen returns error 0x2c7 (unsupported function). Notice that the 3rd argument specifying the type of connection is 3 instead of the usual 0 when opening the framebuffer service. It is almost certain that this new connection type has permissions restrictions that prevent anyone but Apple from retrieving a mach port to access the IOMFB surface.
What's somewhat interesting is that the call to IOConnectCallScalarMethod still works to retrieve the ID of the IOMFB surface. However, it can no longer be accessed using IOSurfaceLookup because the surface is no longer global. It's a little surprising that it was global in the first place!
Hope this helps demystify why IOMFB can no longer be used to record the screen.
Source: My own use of LLDB with an iPhone6 running iOS 8.4 and an iPhone6+ running iOS9 GM
I believe #nevyn is correct. However, I would like to elaborate a bit more. I have looked into this exact issue extensively, and the IOMobileFramebufferGetLayerDefaultSurface function does return -536870201, while it should return 0 if it runs the function without any problems. This error is on the internet, but it only appears when users encounter generic problems with QuickTime. It could be that Apple has indeed locked up the framework completely, and needs an Apple-only entitlement to access the framebuffer. We cannot add these entitlements, since it also has to be on the provisioning profile. I currently am trying to read and interpret the disassembly and doing some reverse engineering work on the IOMobileFramebuffer binary to see if any of the parameters have changed since the last iOS version. I will surely update this answer if I discover anything. But if this is the case, I would suggest trying to find another method of trying to capture/record the screen content.
-UPDATE-
It seems as if there is evidence that this would be the case, if you read this, it shows the exact same error code, and it means that the function is "unsupported", and returns an IOKit error. At least we know what this means now. However, I am still unsure of how to fix it, or to make the function work. I will continue looking into this.
UPDATE 2
I have actually discovered a brand new class in iOS 9, "FigScreenCaptureController", and it is part of the MediaToolbox framework! What the strange thing is though, is why would Apple include this only in iOS 9? So, maybe there will be a way to record the display through this...I will be looking into this class more in depth very soon.
Not entirely correct - it's just a matter of an entitlement, as you can see if you dump the kext:
$ jtool -d __TEXT.__cstring 97.IOMobileGraphicsFamily.kext | grep com.apple
0xffffff80220c91a2: com.apple.private.allow-explicit-graphics-priority
If you self sign (jtool --sign --ent) with this , everything works well.
This does mean that on non-JB devices you can't use it. But with a jailbreak the immense power is in your hands once more.
IOMobileFramebuffer is completely locked down on iOS 9 and cannot be used from non-Apple apps anymore. AFAICT, this closes the last private API to capture the screen efficiently. ReplayKit is the only replacement, but does not allow programmatic access to the actual video data.

How to define code for different iOS versions

I am working on an iOS app. I want it to support iOS 7 and 8. It is going pretty nicely, however there are lots of different parts of the app which use Apple APIs. Some of these APIs work in both iOS 8 and 7. However, some of them are deprecated in iOS 8. So I therefore went to the Apple developer site to see what to replace them with (new methods/etc....).
However, I now have the problem that the app will work on iOS 8 fine, but certain parts of it don't work properly on iOS 7 as I'm trying to use an iOS 8 API...... (lol).
So I just wanted to know, what is the best way to implement code which works on iOS 8 and 7. I had a few ideas (below), but I'm not sure which is best:
IDEA 1
Whenever I have code which doesn't work on both OS's, I use an if function (which calls a macro) like so:
if (SYSTEM_VERSION_LESS_THAN(#"8.0")) {
// iOS 7 device. Use iOS 7 apis.
}
else {
// iOS 8 (or higher) - use iOS 8 apis.
}
IDEA 2
I was thinking about using ifdef definitions all around the app like so:
#ifdef __IPHONE_8_0
// iOS 8 code here....
#else
// iOS 7 code here....
#endif
Which way is better? I would have thought that the second idea is much faster and uses less resources right?
Or are both my ideas rubbish? Is there a much better way about solving this problem?
Thanks for your time, Dan.
I don't suggest checking the Version and writing code based on that. Instead you need to check whether that API is available or not.
For checking a class available or not:
Class checkClass = NSClassFromString(#"CheckingClass");
if (checkClass)
{
// Available
}
else
{
// Not Available
}
If you need to check a feature/function available;
if ([checkClass respondsToSelector:#selector(yourMethod:)])
{
// Feature/ Method Available
}
else
{
// Feature/ Method Not Available
}
NOTE:
Deprecated API's doesn't mean that you shouldn't use that in current version. It means, it won't work from next version onwards, and only work till current version.
The ifdef-way won't work, because preprocessor statements are evaluated at compile-time; but only at runtime we know which ios-version we have to deal with.
You would use macros for example if you wanted to support Mac OS X and iOS with the same code, because you know at compile-time if the binary will be for Mac OS or iOS.
So you need in this case approach 1 - or, even better, you should use respondsToSelector: to check for availability instead of testing the iOS version if possible.
However, because you are only dealing with deprecation warnings, you don't have to do anything and should simply continue using the deprecated APIs until the app no longer needs to support ios7.

How to detect if I am accessing iOS6-only methods? [duplicate]

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
Is there a way for XCode to warn about new API calls?
I'm building an app that will support iOS 4.3 through iOS 6.x. However, I unknowingly used a method that is marked __OSX_AVAILABLE_STARTING(__MAC_NA, __IPHONE_6_0). This means that the selector does not exist in previous versions of iOS.
Of course, when I tested my code on an iOS 5.x device, the app crashed. I figured out what was going on, and inserted a respondsToSelector check, and default to the "iOS 4.x way" of doing things when it fails this check.
How can I prevent these bugs in the future? Is there a compile-time way to figure out that I am accessing iOS-6-only methods, so that I can add appropriate iOS feature-checks?
I don't want to accidentally ship a product that works fine on my iOS6 devices, only to have it fail on someone's iOS4.3 device, because the selector does not exist.
The answer here worked for me: https://stackoverflow.com/a/8919108/208989
Download this header: https://github.com/mattjgalloway/MJGFoundation/blob/master/Source/Utilities/MJGAvailability.h
And put the following at the top of your .pch file:
#define __IPHONE_OS_VERSION_SOFT_MAX_REQUIRED __IPHONE_4_3
#import "MJGAvailability.h"

Developing apps for multiple iOS version

I am testing an app on an iPhone 4 with iOS 5.1 and an iPad 4 with iOS 6.0. I looked around and surprisingly did not find similar questions:
1- My app has some methods that have been deprecated in iOS 6.0 so I believe I have to build some if/then conditions to test for system version using: [[UIDevice currentDevice] systemVersion], and then use the version appropriate methods. Is that the most efficient way?
2- My understanding is, with only one target, the "project" Deployment Target and the "Targets" deployment target serve the same purpose. And I need to have one of them or both as iOS 5.1 since that is the minimum iOS supported. What is confusing is that if the target is built based on iOS 5.1, how would it run on the iPad4 with iOS 6.0:
Does the iPad OS checks for target versions before running or just tries to run the code and it happens that the iOS 5.1 target does not have any code that the 6.0 is incompatible with?
Even if that is the case though, how could a 5.1 target support 6.0 methods that I built to conditionally replace deprecated methods?
Many thanks!
Deprecated methods
Deprecated methods can be used if you are targetting iOS versions that were released before those methods were deprecated. But assuming your deployment target is set correctly, you won't get any compiler errors unless those deprecated methods were always deprecated for the versions you are targetting. In other words, if you are seeing deprecation warnings in your code you need to fix them or check that your deployment target setting is correct. Do not ignore them!
Xcode setting levels
You mention the fact that you can define the deployment target setting at both the target and project level. Xcode build settings at the target level will override project settings. So define the deployment target at one of these levels only, then go to the other and hit delete so you don't have duplicate values. If you only have one target then it doesn't really matter if you define it at the target or project level.
Backwards and forwards compatibility
Finally, there are many factors that come into play for backwards and forwards compatibility. Sometimes there will be new iOS 6 methods like supportedInterfaceOrientations which will simply be ignored on older iOS versions. Other times you need to add explicit checks:
If you are calling a method on an object and that method was only introducted with iOS 6, you will need to add a respondsToSelector: check like this:
// only available on iOS 6
if ([locationManager respondsToSelector:#selector(pausesLocationUpdatesAutomatically)]) {
locationManager.pausesLocationUpdatesAutomatically = YES;
}
If you want to check if a particular class exists on the current iOS version, you can check the return value of the +class method like this:
// Only available on iOS 6
if ([UICollectionView class]) {
// ...
} else {
// class doesn't exist in this iOS version
}
If you want to check if a particular function is available, do a simple if statement on it:
// Only available in iOS 6
if (ABAddressBookCreateWithOptions) {
ABAddressBookCreateWithOptions(...);
} else {
ABAddressBookCreate(...);
}
Finally, if you want to check if a constant is available, check it's address:
// Only available in iOS 4
if (&UIApplicationProtectedDataDidBecomeAvailable) {
// subscribe to notification
}
Your Base SDK setting should always be set to "latest".
If you follow all these guidelines you will be able to solve most of your problems without having to add explicit version checks. Checking the iOS version or device ID is very brittle and is likely to cause your app to break in future versions. You really want to avoid it.
You can use [[UIDevice currentDevice] systemVersion] to detect the OS version, that would work. Rather than detecting the OS version, you could use respondsToSelector: to see what methods are present, NSClassFromString() != nil to see if classes are present, and fallback if that method is not available.
iOS is backward compatible, so iOS 6 will run iOS 5 apps just fine. However, if you want to use iOS 6 specific feature but still support iOS 5.1, you have to :
Compile using the 6.0 SDK so that the compilers knows new APIs
Set your target version to the lowest supported, aka. 5.1. Any system higher than that number will run the code. Any lower will not try.
Use one of the method described in 1. to make sure that each device execute codes the OS support.
I hope that's clear, don't hesitate if you have more questions
What you must do is detect functionality, not iOS version, please check out this thread:
Conditional support of iOS 6 features in an iOS 5 app
You shouldn't check against the system version but rather check against the functionality you are trying to use. On that note remember that deprecated does not mean removed so whatever you are trying to do may very well be there for iOS6. To check against a specific functionality you would do something like:
if([TheClassInQuestion class] != nil)
{
//use it
}
iOS is backwards compatible. If you compile your app for iOS 5.1 it will be perfectly run on iOS 6 as well. No need to re-implement deprecated classes.
But if you change your deployment version from 5.0 to 6.0, Xcode will show you warnings if you use deprecated methods
If you want to use methods, which are available only in iOS 6, you can use this check:
if ([self respondsToSelector:#selector(doSomething:)]) {
[self doSomething];
}

Resources