I'm trying to convert this code snippet to Swift. I'm struggling on getting off the ground due to some difficulties.
- (BOOL) connectedToNetwork
{
// Create zero addy
struct sockaddr_in zeroAddress;
bzero(&zeroAddress, sizeof(zeroAddress));
zeroAddress.sin_len = sizeof(zeroAddress);
zeroAddress.sin_family = AF_INET;
// Recover reachability flags
SCNetworkReachabilityRef defaultRouteReachability = SCNetworkReachabilityCreateWithAddress(NULL, (struct sockaddr *)&zeroAddress);
SCNetworkReachabilityFlags flags;
BOOL didRetrieveFlags = SCNetworkReachabilityGetFlags(defaultRouteReachability, &flags);
CFRelease(defaultRouteReachability);
if (!didRetrieveFlags)
{
return NO;
}
BOOL isReachable = flags & kSCNetworkFlagsReachable;
BOOL needsConnection = flags & kSCNetworkFlagsConnectionRequired;
return (isReachable && !needsConnection) ? YES : NO;
}
The first and the main issue I'm having is on how to define and work with C structs. In the first line (struct sockaddr_in zeroAddress;
) of the above code, I think they're defining a instance called zeroAddress
from the struct sockaddr_in(?), I assume. I tried declaring a var
like this.
var zeroAddress = sockaddr_in()
But I get the error Missing argument for parameter 'sin_len' in call which is understandable because that struct takes a number of arguments. So I tried again.
var zeroAddress = sockaddr_in(sin_len: sizeof(zeroAddress), sin_family: AF_INET, sin_port: nil, sin_addr: nil, sin_zero: nil)
As expected I get some other error Variable used within its own initial value. I understand the cause of that error too. In C, they declare the instance first and then fill up the parameters. Its not possible in Swift as far as I know. So I'm truly lost at this point on what to do.
I read Apple's official document on interacting with C APIs in Swift but it has no examples in working with structs.
Can anyone please help me out here? I'd really appreciate it.
Thank you.
UPDATE: Thanks to Martin I was able to get past the initial problem. But still Swift ain't making it easier for me. I'm getting multiple new errors.
func connectedToNetwork() -> Bool {
var zeroAddress = sockaddr_in(sin_len: 0, sin_family: 0, sin_port: 0, sin_addr: in_addr(s_addr: 0), sin_zero: (0, 0, 0, 0, 0, 0, 0, 0))
zeroAddress.sin_len = UInt8(sizeofValue(zeroAddress))
zeroAddress.sin_family = sa_family_t(AF_INET)
var defaultRouteReachability: SCNetworkReachabilityRef = SCNetworkReachabilityCreateWithAddress(UnsafePointer<Void>, UnsafePointer<zeroAddress>) // 'zeroAddress' is not a type
var flags = SCNetworkReachabilityFlags()
let didRetrieveFlags = SCNetworkReachabilityGetFlags(defaultRouteReachability, UnsafeMutablePointer<flags>) // 'flags' is not a type
defaultRouteReachability.dealloc(1) // 'SCNetworkReachabilityRef' does not have a member named 'dealloc'
if didRetrieveFlags == false {
return false
}
let isReachable: Bool = flags & kSCNetworkFlagsReachable // Cannot invoke '&' with an argument list of type '(@lvalue UInt32, Int)'
let needsConnection: Bool = flags & kSCNetworkFlagsConnectionRequired // Cannot invoke '&' with an argument list of type '(@lvalue UInt32, Int)'
return (isReachable && !needsConnection) ? true : false
}
EDIT 1: Okay I changed this line to this,
var defaultRouteReachability: SCNetworkReachabilityRef = SCNetworkReachabilityCreateWithAddress(UnsafePointer<Void>(), &zeroAddress)
The new error I'm getting at this line is 'UnsafePointer' is not convertible to 'CFAllocator'. How to you pass NULL
in Swift?
Also I changed this line and the error is gone now.
let didRetrieveFlags = SCNetworkReachabilityGetFlags(defaultRouteReachability, &flags)
EDIT 2: I passed nil
in this line after seeing this question. But that answer contradicts with the answer here. It says there is no equivalent to NULL
in Swift.
var defaultRouteReachability: SCNetworkReachabilityRef = SCNetworkReachabilityCreateWithAddress(nil, &zeroAddress)
Anyway I get a new error saying 'sockaddr_in' is not identical to 'sockaddr' at the above line.
(This answer was extended repeatedly due to changes in the Swift language, which made it a bit confusing. I have now rewritten it and removed everything which refers to Swift 1.x. The older code can be found in the edit history if somebody needs it.)
This is how you would do it in Swift 2.0 (Xcode 7):
import SystemConfiguration
func connectedToNetwork() -> Bool {
var zeroAddress = sockaddr_in()
zeroAddress.sin_len = UInt8(sizeofValue(zeroAddress))
zeroAddress.sin_family = sa_family_t(AF_INET)
guard let defaultRouteReachability = withUnsafePointer(&zeroAddress, {
SCNetworkReachabilityCreateWithAddress(nil, UnsafePointer($0))
}) else {
return false
}
var flags : SCNetworkReachabilityFlags = []
if !SCNetworkReachabilityGetFlags(defaultRouteReachability, &flags) {
return false
}
let isReachable = flags.contains(.Reachable)
let needsConnection = flags.contains(.ConnectionRequired)
return (isReachable && !needsConnection)
}
Explanations:
As of Swift 1.2 (Xcode 6.3), imported C structs have a default initializer in Swift, which initializes all of the struct's fields to zero, so the socket address structure can be initialized with
var zeroAddress = sockaddr_in()
sizeofValue()
gives the size of this structure, this has
to be converted to UInt8
for sin_len
:
zeroAddress.sin_len = UInt8(sizeofValue(zeroAddress))
AF_INET
is an Int32
, this has to be converted to the correct type for sin_family
:
zeroAddress.sin_family = sa_family_t(AF_INET)
withUnsafePointer(&zeroAddress) { ... }
passes the address of the
structure to the closure where it is used as argument for
SCNetworkReachabilityCreateWithAddress()
. The UnsafePointer($0)
conversion is needed because that function expects a pointer to
sockaddr
, not sockaddr_in
.
The value returned from withUnsafePointer()
is the return value
from SCNetworkReachabilityCreateWithAddress()
and that has the
type SCNetworkReachability?
, i.e. it is an optional.
The guard let
statement (a new feature in Swift 2.0) assigns the unwrapped value to the defaultRouteReachability
variable if it is
not nil
. Otherwise the else
block is executed and the function
returns.
SCNetworkReachabilityCreateWithAddress()
returns
a managed object. You don't have to release it explicitly.As of Swift 2, SCNetworkReachabilityFlags
conforms to
OptionSetType
which has a set-like interface. You create an
empty flags variable with
var flags : SCNetworkReachabilityFlags = []
and check for flags with
let isReachable = flags.contains(.Reachable)
let needsConnection = flags.contains(.ConnectionRequired)
The second parameter of SCNetworkReachabilityGetFlags
has the type
UnsafeMutablePointer<SCNetworkReachabilityFlags>
, which means that you have to
pass the address of the flags variable.
Note also that registering a notifier callback is possible as of Swift 2, compare Working with C APIs from Swift and Swift 2 - UnsafeMutablePointer<Void> to object.
Update for Swift 3/4:
Unsafe pointers cannot be simply be converted to a pointer of a different type anymore (see - SE-0107 UnsafeRawPointer API). Here the updated code:
import SystemConfiguration
func connectedToNetwork() -> Bool {
var zeroAddress = sockaddr_in()
zeroAddress.sin_len = UInt8(MemoryLayout<sockaddr_in>.size)
zeroAddress.sin_family = sa_family_t(AF_INET)
guard let defaultRouteReachability = withUnsafePointer(to: &zeroAddress, {
$0.withMemoryRebound(to: sockaddr.self, capacity: 1) {
SCNetworkReachabilityCreateWithAddress(nil, $0)
}
}) else {
return false
}
var flags: SCNetworkReachabilityFlags = []
if !SCNetworkReachabilityGetFlags(defaultRouteReachability, &flags) {
return false
}
let isReachable = flags.contains(.reachable)
let needsConnection = flags.contains(.connectionRequired)
return (isReachable && !needsConnection)
}