8

I'm working on something that will not need to be on the App store, so I have no issues with using private APIs to meet my needs I'm trying to use the MobileWiFi. framework to read the RSSI value for the wireless network the phone is currently connected to. I've included thehttps://github.com/Cykey/ios-reversed-headers/tree/c613e45f3ee5ad9f85ec7d43906cf69ee812ec6a/MobileWiFi` headers and used a bridging header to include them in my swift project and wrote the code as follows. Please excuse me, I am a newbie.

import SystemConfiguration.CaptiveNetwork
typealias _WiFiManagerClientCreate = @convention(c) (CFAllocator, CInt) -> UnsafeRawPointer
typealias _WiFiManagerClientCopyDevices = @convention(c) (UnsafeRawPointer) -> CFArray
typealias _WiFiDeviceClientCopyProperty = @convention(c) (UnsafeRawPointer, CFString) -> CFPropertyList

if let libHandle = dlopen (Paths.ipConfiguration, RTLD_LAZY) {
        result = libHandle.debugDescription

        let _createManagerPtr = dlsym(libHandle, "WiFiManagerClientCreate")
        let _clientCopyDevicesPtr = dlsym(libHandle, "WiFiManagerClientCopyDevices")
        let _clientCopyPropertyPtr = dlsym(libHandle, "WiFiDeviceClientCopyProperty")

        if (_createManagerPtr != nil) && (_clientCopyDevicesPtr != nil) && (_clientCopyPropertyPtr != nil) {
            let _createManager = unsafeBitCast(_createManagerPtr, to: _WiFiManagerClientCreate.self)
            let _clientCopyDevices = unsafeBitCast(_clientCopyDevicesPtr, to: _WiFiManagerClientCopyDevices.self)
            let _clientCopyProperty = unsafeBitCast(_clientCopyPropertyPtr, to: _WiFiDeviceClientCopyProperty.self)

            let manager = _createManager(kCFAllocatorDefault, 0)
            let devices = _clientCopyDevices(manager)
            let client = CFArrayGetValueAtIndex(devices, 0)

            let data = _clientCopyProperty(client!, "RSSI" as CFString)
            let rssi = CFDictionaryGetValue(data as! CFDictionary, "RSSI_CTL_AGR")

            NSLog("RSSI: \(rssi)")
        }

        dlclose(libHandle)
    }

Which yields an error fatal error: unexpectedly found nil while unwrapping an Optional value which stems from trying to call _createManager

Muhammad Lukman Low
  • 8,177
  • 11
  • 44
  • 54
Yash Sharma
  • 293
  • 4
  • 14

4 Answers4

7

I ended up using this workaround:

+ (int) wifiStrength {
UIApplication *app = [UIApplication sharedApplication];
NSArray *subviews = [[[app valueForKey:@"statusBar"] valueForKey:@"foregroundView"] subviews];
NSString *dataNetworkItemView = nil;

for (id subview in subviews) {
    if([subview isKindOfClass:[NSClassFromString(@"UIStatusBarDataNetworkItemView") class]]) {
        dataNetworkItemView = subview;
        break;
    }
}

return[[dataNetworkItemView valueForKey:@"wifiStrengthRaw"] intValue];
}

Works without any entitlements or jailbreaking

Yash Sharma
  • 293
  • 4
  • 14
5

Since the Status bar in iPhone X is different from other iPhones, the ways to get WiFi information are different.

Here is the workaround on how to get it via "undocumented properties", which means Apple might change these properties in the future without letting us know. The App will crash if Apple changed the undocumented properties but we didn't adjust our code accordingly. So we have to handle the NSException in Swift.

Create a Header file, and add it in your Bridging-Header.h. You can find the file here:https://gist.github.com/zhihuitang/6d3de0963d96a552d47721a598ca79c8

//
//  OCCatch.h
//
//

#ifndef OCCatch_h
#define OCCatch_h

// add the code below to your -Bridging-Header.h

/**
 #import "OCCatch.h"
 */

//   How to use it in Swift?
/**
 let exception = tryBlock {
        let statusBar = UIApplication.shared.value(forKey: "statusBar") as? UIView
        //......
    }
  if let exception = exception {  
    print("exception: \(exception)")
  }  
*/

#import <Foundation/Foundation.h>

NS_INLINE NSException * _Nullable tryBlock(void(^_Nonnull tryBlock)(void)) {
    @try {
        tryBlock();
    }
    @catch (NSException *exception) {
        return exception;
    }
    return nil;
}

#endif /* OCCatch_h */

In iPhoneX, we can get numberOfActiveBars of WiFi, which ranges from 0 to 3. In other iPhones other than iPhoneX, we can get WiFi RSSI.

On iPhoneX, please use getWiFiNumberOfActiveBars():

 private func getWiFiNumberOfActiveBars() -> Int? {
    let app = UIApplication.shared
    var numberOfActiveBars: Int?
    let exception = tryBlock {
        guard let containerBar = app.value(forKey: "statusBar") as? UIView else { return nil }
        guard let statusBarMorden = NSClassFromString("UIStatusBar_Modern"), containerBar .isKind(of: statusBarMorden), let statusBar = containerBar.value(forKey: "statusBar") as? UIView else { return nil }

        guard let foregroundView = statusBar.value(forKey: "foregroundView") as? UIView else { return nil }

        for view in foregroundView.subviews {
            for v in view.subviews {
                if let statusBarWifiSignalView = NSClassFromString("_UIStatusBarWifiSignalView"), v .isKind(of: statusBarWifiSignalView) {
                    if let val = v.value(forKey: "numberOfActiveBars") as? Int {
                        numberOfActiveBars = val
                        break
                    }
                }
            }
            if let _ = numberOfActiveBars {
                break
            }
        }
    }
    if let exception = exception {
        print("getWiFiNumberOfActiveBars exception: \(exception)")
    }

    return numberOfActiveBars
}

On iPhone devices other than iPhoneX, please use getWiFiRSSI():

private func getWiFiRSSI() -> Int? {
    let app = UIApplication.shared
    var rssi: Int?
    let exception = tryBlock {
        guard let statusBar = app.value(forKey: "statusBar") as? UIView else { return }
        if let statusBarMorden = NSClassFromString("UIStatusBar_Modern"), statusBar .isKind(of: statusBarMorden) { return }

        guard let foregroundView = statusBar.value(forKey: "foregroundView") as? UIView else { return  }

        for view in foregroundView.subviews {
            if let statusBarDataNetworkItemView = NSClassFromString("UIStatusBarDataNetworkItemView"), view .isKind(of: statusBarDataNetworkItemView) {
                if let val = view.value(forKey: "wifiStrengthRaw") as? Int {
                    rssi = val
                    break
                }
            }
        }
    }
    if let exception = exception {
        print("getWiFiRSSI exception: \(exception)")
    }
    return rssi
}

So far I haven't found a way to get WiFi RSSI on iPhoneX. If you guys know how to do it, please also let me know. thanks.

here is the demo project in Github

DàChún
  • 4,751
  • 1
  • 36
  • 39
  • What is strange is that someone like ARWifiScanner and Dr.Wifi does actually show the db signal value, also on iPhone X, so it _is_ possible. However when speaking directly to Apple Engineers they say there is no way (obviously they mean documented way) but still. – Peter Jun 01 '18 at 09:36
  • 2
    The ARSignalMaster takes a screenshot of the statusbar, detects how many bars there are and then sets a dBm value and a little random number generator around it. So fake – Peter Jun 08 '18 at 08:30
  • 1
    I did an educational decompile of their solution, since the Apple Technician our company had a dialog with specifically said they did not use the hotspot helper API but would not otherwise specify how it could be done. – Peter Aug 10 '18 at 14:35
  • Doesn't compile. You need to return nil in your `guard` statements. – Lirik Nov 21 '19 at 19:13
0

I think it is not possible for the latest version of iOS. It is possible upto iOS4 only.

0

Apple doesnt like this kind of approach and menaces app review rejection

Karsten
  • 1,869
  • 22
  • 38