Accessing IP Address with NSHost - objective-c

I am trying to get the IP Address using NSHost. With the NSHost object I can use the addresses method to access an array of objects one of which is the IP Address. I fear though that the IP Address may change position in the array from one machine to the other. Is there a way to access this information in a universal way?
There was an attempt to answer this question in a previous post, but as you can see it falls short.
IP Address? - Cocoa
Here is my code:
+(NSString *) ipAddress {
NSHost * h = [[[NSHost currentHost] addresses] objectAtIndex:1];
return h ;
}

The only thing I can think of is to use something like "http://www.dyndns.org/cgi-bin/check_ip.cgi" others may have a better way.
This is an example,(i.e a quick cobbled together code)
NSUInteger an_Integer;
NSArray * ipItemsArray;
NSString *externalIP;
NSURL *iPURL = [NSURL URLWithString:#"http://www.dyndns.org/cgi-bin/check_ip.cgi"];
if (iPURL) {
NSError *error = nil;
NSString *theIpHtml = [NSString stringWithContentsOfURL:iPURL
encoding:NSUTF8StringEncoding
error:&error];
if (!error) {
NSScanner *theScanner;
NSString *text = nil;
theScanner = [NSScanner scannerWithString:theIpHtml];
while ([theScanner isAtEnd] == NO) {
// find start of tag
[theScanner scanUpToString:#"<" intoString:NULL] ;
// find end of tag
[theScanner scanUpToString:#">" intoString:&text] ;
// replace the found tag with a space
//(you can filter multi-spaces out later if you wish)
theIpHtml = [theIpHtml stringByReplacingOccurrencesOfString:
[ NSString stringWithFormat:#"%#>", text]
withString:#" "] ;
ipItemsArray =[theIpHtml componentsSeparatedByString:#" "];
an_Integer=[ipItemsArray indexOfObject:#"Address:"];
externalIP =[ipItemsArray objectAtIndex: ++an_Integer];
}
NSLog(#"%#",externalIP);
} else {
NSLog(#"Oops... g %d, %#",
[error code],
[error localizedDescription]);
}
}
[pool drain];
return 0;}

I have used this on many machines without problems.
-(void) getIPWithNSHost{
NSArray *addresses = [[NSHost currentHost] addresses];
for (NSString *anAddress in addresses) {
if (![anAddress hasPrefix:#"127"] && [[anAddress componentsSeparatedByString:#"."] count] == 4) {
stringAddress = anAddress;
break;
} else {
stringAddress = #"IPv4 address not available" ;
}
}
//NSLog (#"getIPWithNSHost: stringAddress = %# ",stringAddress);
}
NSString *stringAddress; is declared else where

I wanted to update my original answer on getting an external ip.
There is not much change but I wanted to show how to get and parse the HTML with use NSXMLDocument and Xquary
This also gives a small illustration of how you can parse HTML by getting the nodes. Which in my opinion is more straight forward. Although NSXMLDocument is initially for XML it will parse the HTML DOM tree
NSString *externalIP;
///--DYNDNS.ORG URL
NSURL *iPURL = [NSURL URLWithString:#"http://www.dyndns.org/cgi-bin/check_ip.cgi"];
if (iPURL) {
NSError *err_p = nil;
//--use NSXMLDocument to get the url:(*Requests NSXMLNode to preserve whitespace characters (such as tabs and carriage returns) in the XML source that are not part of node content*)
NSXMLDocument * xmlDoc = [[NSXMLDocument alloc] initWithContentsOfURL:iPURL
options:(NSXMLNodePreserveWhitespace|
NSXMLNodePreserveCDATA)
error:&err_p];
if (xmlDoc == nil) {
//-- That did not work so lets see if we can change the malformed XML into valid XML during processing of the document.
xmlDoc = [[NSXMLDocument alloc] initWithContentsOfURL:iPURL
options:NSXMLDocumentTidyXML
error:&err_p];
}
if (!err_p) {
NSError * error;
//-- We will use XQuary to get the text from the child node. Dyndns.org page is very simple. So we just need to get the Body text.
NSString *xpathQueryTR = #"//body/text()";
//-- we get the first node's string value. We use string value to in effect cast to NSString.
//We the seperate the string into components using a space. and obtain the last object in the returned array.
//--This gives us the IP string without the "Current IP Address:" string.
externalIP = [[[[[xmlDoc nodesForXPath:xpathQueryTR error:&error]objectAtIndex:0] stringValue]componentsSeparatedByString:#" "]lastObject];
if (!error) {
NSLog(#"%#",externalIP);
}else {
NSLog(#"Oops... g %ld, %#",
(long)[error code],
[error localizedDescription]);
}
}else {
NSLog(#"Oops... g %ld, %#",
(long)[err_p code],
[err_p localizedDescription]);
}
}

Made an utility class to find the IP addresses. Minimalistic approach. You can robustify it with more conditions or regex checking.
NSLog(#"Addresses: %#", [[NSHost currentHost] addresses]);
This is the list returned by NSHost
"fe80::1610:9fff:fee1:8c2f%en0",
"192.168.212.61",
"fe80::2829:3bff:fee6:9133%awdl0",
"fe80::e54b:8494:bbc8:3989%utun0",
"fd68:cc16:fad8:ded9:e54b:8494:bbc8:3989",
"10.11.51.61",
"::1",
"127.0.0.1",
"fe80::1%lo0"
Test method,
- (void)testHost {
NSLog(#"Addresses: %#", [[NSHost currentHost] addresses]);
for (NSString *s in [[NSHost currentHost] addresses]) {
IPAddress *addr = [[IPAddress alloc] initWithString:s];
if (![addr isLocalHost] && [addr isIPV4]) {
// do something
}
}
}
IPAddress.h
#import <Foundation/Foundation.h>
#interface IPAddress : NSObject
#property (nonatomic, strong) NSString *IPAddress;
- (id)initWithString:(NSString *)ipaddress;
- (BOOL)isLocalHost;
- (BOOL) isIPV4;
- (BOOL) isIPV6;
#end
IPAddress.m
#import "IPAddress.h"
#implementation IPAddress
- (id)initWithString:(NSString *)ipaddress {
self = [super init];
if (self) {
self.IPAddress = ipaddress;
}
return self;
}
- (BOOL)isLocalHost {
if (self.IPAddress == nil) return NO;
if ([#"127.0.0.1" compare:self.IPAddress options:NSCaseInsensitiveSearch] == NSOrderedSame) {
return YES;
}
if ([#"localhost" compare:self.IPAddress options:NSCaseInsensitiveSearch] == NSOrderedSame) {
return YES;
}
if ([#"::1" compare:self.IPAddress options:NSCaseInsensitiveSearch] == NSOrderedSame) {
return YES;
}
return NO;
}
- (BOOL) isIPV4 {
NSArray *ar = [self.IPAddress componentsSeparatedByString:#"."];
if (ar.count == 4) {
return YES;
}
return NO;
}
- (BOOL) isIPV6 {
if (![self isIPV4]) {
if ([self.IPAddress rangeOfString:#":"].location != NSNotFound) {
return YES;
}
}
return NO;
}
#end

As the answers to the question you mention above have said, there are a variety of IP addresses that a single machine can have. If that is what you want, then you might be better off using the names method of NSHost to get an array of names, which you can then filter for the suffix (i.e *.lan) to get the name of the host you want with this name. In my case. the .lan address returns my network ip address as a dotted quad.
If you want to find the external ip address, then this is a good answer to look at.

My first Answer is to supply the Private IP address assigned to the Machine on private network from say your router.
If you want to see the public IP, which is the one facing the internet. Normally assigned by your service provider. You may want to look at the answer by Jim Dovey --> here
I tested it and it worked well, but read the rest of the comments and answers which point to ambiguities in trying to get a public IP.

You can create a category on NSHost and do something like this:
#import <arpa/inet.h>
#import <ifaddrs.h>
#import <net/if.h>
.h
+ (NSDictionary *) interfaceIP4Addresses;
+ (NSDictionary *) interfaceIP6Addresses;
+ (NSDictionary *) interfaceIPAddresses;
.m
typedef NS_ENUM(NSUInteger, AddressType) {
AddressTypeBoth = 0,
AddressTypeIPv4 = 1,
AddressTypeIPv6 = 2
};
#implementation SomeClass
#pragma mark - Helper Methods:
+ (NSDictionary *) _interfaceAddressesForFamily:(AddressType)family {
NSMutableDictionary *interfaceInfo = [NSMutableDictionary dictionary];
struct ifaddrs *interfaces;
if ( (0 == getifaddrs(&interfaces)) ) {
struct ifaddrs *interface;
for ( interface=interfaces; interface != NULL; interface=interface->ifa_next ) {
if ( (interface->ifa_flags & IFF_UP) && !(interface->ifa_flags & IFF_LOOPBACK) ) {
const struct sockaddr_in *addr = (const struct sockaddr_in *)interface->ifa_addr;
if ( addr && addr->sin_family == PF_INET ) {
if ( (family == AddressTypeBoth) || (family == AddressTypeIPv4) ) {
char ip4Address[INET_ADDRSTRLEN];
inet_ntop( addr->sin_family, &(addr->sin_addr), ip4Address, INET_ADDRSTRLEN );
[interfaceInfo setObject:[NSString stringWithUTF8String:interface->ifa_name]
forKey:[NSString stringWithUTF8String:ip4Address]];
} } else if ( addr && addr->sin_family == PF_INET6 ) {
if ( (family == AddressTypeBoth) || (family == AddressTypeIPv6) ) {
char ip6Address[INET6_ADDRSTRLEN];
inet_ntop( addr->sin_family, &(addr->sin_addr), ip6Address, INET6_ADDRSTRLEN );
[interfaceInfo setObject:[NSString stringWithUTF8String:interface->ifa_name]
forKey:[NSString stringWithUTF8String:ip6Address]];
} }
}
} freeifaddrs( interfaces );
} return [NSDictionary dictionaryWithDictionary:interfaceInfo];
}
#pragma mark - Class Methods:
+ (NSDictionary *) interfaceIP4Addresses { return [self _interfaceAddressesForFamily:AddressTypeIPv4]; }
+ (NSDictionary *) interfaceIP6Addresses { return [self _interfaceAddressesForFamily:AddressTypeIPv6]; }
+ (NSDictionary *) interfaceIPAddresses { return [self _interfaceAddressesForFamily:AddressTypeBoth]; }
#end
This works really fast and well. If you need other info or to monitor then use System Configuration framework.

Related

Get IP Address on iPhone | Objective-C or Swift

I just found this code to get my iPhone's IP Address using Objective-C but it's kinda slow for the project I'm working on:
- (NSString*)getExternalIP{
NSString *externalIP = #"";
NSArray *ipItemsArray = [[NSArray alloc] init];
NSURL *iPURL = [NSURL URLWithString:#"http://www.dyndns.org/cgi-bin/check_ip.cgi"];
if (iPURL) {
NSError *error = nil;
NSString *theIpHtml = [NSString stringWithContentsOfURL:iPURL encoding:NSUTF8StringEncoding error:&error];
if (!error){
NSScanner *theScanner;
NSString *text = nil;
theScanner = [NSScanner scannerWithString:theIpHtml];
while ([theScanner isAtEnd] == NO) {
[theScanner scanUpToString:#"<" intoString:NULL] ;
[theScanner scanUpToString:#">" intoString:&text] ;
theIpHtml = [theIpHtml stringByReplacingOccurrencesOfString:[NSString stringWithFormat:#"%#>", text] withString:#" "] ;
ipItemsArray = [theIpHtml componentsSeparatedByString:#" "];
int an_Integer= (int)[ipItemsArray indexOfObject:#"Address:"];
externalIP =[ipItemsArray objectAtIndex: ++an_Integer];
}
return externalIP;
}
}
return #"error";}
Do you know an alternative solution or a way to fix this one?
NOTE: it has to work on Wi-fi and cellular.
Thanks!
I am found ip address using this way. First you can import two library.
#include <ifaddrs.h>
#include <arpa/inet.h>
then after you can use this action
- (NSString *)getIPAddress {
NSString *address = #"error";
struct ifaddrs *interfaces = NULL;
struct ifaddrs *temp_addr = NULL;
int success = 0;
// retrieve the current interfaces - returns 0 on success
success = getifaddrs(&interfaces);
if (success == 0) {
// Loop through linked list of interfaces
temp_addr = interfaces;
while(temp_addr != NULL) {
if(temp_addr->ifa_addr->sa_family == AF_INET) {
// Check if interface is en0 which is the wifi connection on the iPhone
if([[NSString stringWithUTF8String:temp_addr->ifa_name] isEqualToString:#"en0"]) {
// Get NSString from C String
address = [NSString stringWithUTF8String:inet_ntoa(((struct sockaddr_in *)temp_addr->ifa_addr)->sin_addr)];
}
}
temp_addr = temp_addr->ifa_next;
}
}
// Free memory
freeifaddrs(interfaces);
return address;
}
and call this action in your code like this way.
NSString *strYourIP = [self getIPAddress];
I hope this is use full to you....
Before using the code in the question, think about this for a bit:
What's going to happen if dyndns.org starts to lag?
In my experience, the response time of this service varies so badly that it's downright unusable for anything which might be performance sensitive. (I had response times varying from 0 sec to 50+ sec, tested on multiple decent connections.) It would be much better to find / set up a service for this purpose which is more reliable. If you have control over a web service for the application you're working with, I think it's trivial to add. (In PHP, it's a one-liner.)

iOS Programmatically find IP Address [duplicate]

I would like to obtain my iPad's IP address programmatically.
How can I query the networking subsystem to find out what my IPv4 (and IPv6) addresses are?
PS: Can I disable IPv6 somehow?
The following code finds all IPv4 and IPv6 addresses on an iOS or OSX device. The first getIPAddress method acts more or less as the older code in this answer: you can prefer either one or the other type address, and it always prefers WIFI over cellular (obviously you could change this).
More interestingly it can return a dictionary of all addresses found, skipping addresses for not up interfaces, or addresses associated with loopback. The previous code as well as other solutions on this topic will not properly decode IPv6 (inet_ntoa cannot deal with them). This was pointed out to me by Jens Alfke on an Apple forum - the proper function to use is inet_ntop (look at the man page, and or refer to this inet_ntop article also provided by Jens.
The dictionary keys have the form "interface" "/" "ipv4 or ipv6".
#include <ifaddrs.h>
#include <arpa/inet.h>
#include <net/if.h>
#define IOS_CELLULAR #"pdp_ip0"
#define IOS_WIFI #"en0"
//#define IOS_VPN #"utun0"
#define IP_ADDR_IPv4 #"ipv4"
#define IP_ADDR_IPv6 #"ipv6"
- (NSString *)getIPAddress:(BOOL)preferIPv4
{
NSArray *searchArray = preferIPv4 ?
#[ /*IOS_VPN #"/" IP_ADDR_IPv4, IOS_VPN #"/" IP_ADDR_IPv6,*/ IOS_WIFI #"/" IP_ADDR_IPv4, IOS_WIFI #"/" IP_ADDR_IPv6, IOS_CELLULAR #"/" IP_ADDR_IPv4, IOS_CELLULAR #"/" IP_ADDR_IPv6 ] :
#[ /*IOS_VPN #"/" IP_ADDR_IPv6, IOS_VPN #"/" IP_ADDR_IPv4,*/ IOS_WIFI #"/" IP_ADDR_IPv6, IOS_WIFI #"/" IP_ADDR_IPv4, IOS_CELLULAR #"/" IP_ADDR_IPv6, IOS_CELLULAR #"/" IP_ADDR_IPv4 ] ;
NSDictionary *addresses = [self getIPAddresses];
NSLog(#"addresses: %#", addresses);
__block NSString *address;
[searchArray enumerateObjectsUsingBlock:^(NSString *key, NSUInteger idx, BOOL *stop)
{
address = addresses[key];
if(address) *stop = YES;
} ];
return address ? address : #"0.0.0.0";
}
- (NSDictionary *)getIPAddresses
{
NSMutableDictionary *addresses = [NSMutableDictionary dictionaryWithCapacity:8];
// retrieve the current interfaces - returns 0 on success
struct ifaddrs *interfaces;
if(!getifaddrs(&interfaces)) {
// Loop through linked list of interfaces
struct ifaddrs *interface;
for(interface=interfaces; interface; interface=interface->ifa_next) {
if(!(interface->ifa_flags & IFF_UP) /* || (interface->ifa_flags & IFF_LOOPBACK) */ ) {
continue; // deeply nested code harder to read
}
const struct sockaddr_in *addr = (const struct sockaddr_in*)interface->ifa_addr;
char addrBuf[ MAX(INET_ADDRSTRLEN, INET6_ADDRSTRLEN) ];
if(addr && (addr->sin_family==AF_INET || addr->sin_family==AF_INET6)) {
NSString *name = [NSString stringWithUTF8String:interface->ifa_name];
NSString *type;
if(addr->sin_family == AF_INET) {
if(inet_ntop(AF_INET, &addr->sin_addr, addrBuf, INET_ADDRSTRLEN)) {
type = IP_ADDR_IPv4;
}
} else {
const struct sockaddr_in6 *addr6 = (const struct sockaddr_in6*)interface->ifa_addr;
if(inet_ntop(AF_INET6, &addr6->sin6_addr, addrBuf, INET6_ADDRSTRLEN)) {
type = IP_ADDR_IPv6;
}
}
if(type) {
NSString *key = [NSString stringWithFormat:#"%#/%#", name, type];
addresses[key] = [NSString stringWithUTF8String:addrBuf];
}
}
}
// Free memory
freeifaddrs(interfaces);
}
return [addresses count] ? addresses : nil;
}
EDIT1: Code updated on May 16, 2014 (bug pointed out by lhunath, see comments). Loopback addresses now returned, but its easy for you to uncomment the test to exclude them yourself.
EDIT2: (by some unknown person): Improved further March 13, 2015: In case the user uses a VPN (regardless over WiFi or Cellular), the previous code would have failed. Now, it works even with VPN connections. VPN connections are given precedence over WiFi and Cell because that's how the device handles it. This should even work for Macs as the VPN connection on a Mac is also using IF utun0 but not tested.
EDIT3: (9/8/2016) Given the problems experienced by #Qiulang (see comments) with the VPN code (which someone else added), I've commented it out. If anyone knows definitively how to specify a user VPN please chime in with a comment.
In your implementation file .m ,
#import <ifaddrs.h>
#import <arpa/inet.h>
// Get IP Address
- (NSString *)getIPAddress {
NSString *address = #"error";
struct ifaddrs *interfaces = NULL;
struct ifaddrs *temp_addr = NULL;
int success = 0;
// retrieve the current interfaces - returns 0 on success
success = getifaddrs(&interfaces);
if (success == 0) {
// Loop through linked list of interfaces
temp_addr = interfaces;
while(temp_addr != NULL) {
if(temp_addr->ifa_addr->sa_family == AF_INET) {
// Check if interface is en0 which is the wifi connection on the iPhone
if([[NSString stringWithUTF8String:temp_addr->ifa_name] isEqualToString:#"en0"]) {
// Get NSString from C String
address = [NSString stringWithUTF8String:inet_ntoa(((struct sockaddr_in *)temp_addr->ifa_addr)->sin_addr)];
}
}
temp_addr = temp_addr->ifa_next;
}
}
// Free memory
freeifaddrs(interfaces);
return address;
}
Many existing solutions only consider wireless interfaces, which won't work for wired connections via an Ethernet adapter (ie. no Wifi or 3G); see this more recent solution which considers IP addresses obtained through wired interfaces as well.
iPad: How to get IP address programmatically WIRED (not via wireless)
Get IP address using Swift 3:
func getIPAddress() -> String {
var address: String = "error"
var interfaces: ifaddrs? = nil
var temp_addr: ifaddrs? = nil
var success: Int = 0
// retrieve the current interfaces - returns 0 on success
success = getifaddrs(interfaces)
if success == 0 {
// Loop through linked list of interfaces
temp_addr = interfaces
while temp_addr != nil {
if temp_addr?.ifa_addr?.sa_family == AF_INET {
// Check if interface is en0 which is the wifi connection on the iPhone
if (String(utf8String: temp_addr?.ifa_name) == "en0") {
// Get NSString from C String
address = String(utf8String: inet_ntoa((temp_addr?.ifa_addr as? sockaddr_in)?.sin_addr))
}
}
temp_addr = temp_addr?.ifa_next
}
}
// Free memory
freeifaddrs(interfaces)
return address
}
#DavidH's answer works fine till I got this result from some 4G cellular network:
{
"lo0/ipv4" = "127.0.0.1";
"lo0/ipv6" = "fe80::1";
"pdp_ip0/ipv4" = "10.132.76.168";
"utun0/ipv6" = "fe80::72c3:e25e:da85:b730";
}
I am not using vpn so I have no idea why I had a utun0/ipv6.
--- Updated ---
I further debug this issue and found that I can get an fake vpn address even in other 4G networks (is this iOS bug??),
{
""awdl0/ipv6"" = ""fe80::c018:9fff:feb2:988"";
""en0/ipv6"" = ""fe80::181a:2e43:f91b:db2b"";
""lo0/ipv4"" = ""127.0.0.1"";
""lo0/ipv6"" = ""fe80::1"";
""pdp_ip0/ipv4"" = ""10.48.10.210"";
""utun0/ipv4"" = ""192.168.99.2"";
}
If I did use vpn I will get this:
{
"lo0/ipv4" = "127.0.0.1";
"lo0/ipv6" = "fe80::1";
"pdp_ip0/ipv4" = "10.49.187.23";
"utun0/ipv6" = "fe80::5748:5b5d:2bf0:658d";
"utun1/ipv4" = "192.168.99.2"; //the real one
}
So it is utun1 NOT utun0
Without figuring out why I will just have to drop vpn check :(
---- update ----
I raised a bug (28131847) to apple and replied with "Not all utun interfaces are for VPN. There are other OS features that use utun interfaces."
But when I asked how to get a valid vpn IP address then, their answer was rather disappointed, "You can go into Settings -> VPN and look at your VPN configuration to see if the VPN is active. In some cases you can see the assigned IP address there as well. We are now closing this bug report." :(
---- update 2016/11/04 ----
I hit the problem again and I need to further modify #DavidH's answer to fix it:
I was in 4G network and I got this address:
addresses: {
"awdl0/ipv6" = "fe80::98fd:e6ff:fea9:3afd";
"en0/ipv6" = "fe80::8dd:7d92:4159:170e";
"lo0/ipv4" = "127.0.0.1";
"lo0/ipv6" = "fe80::1";
"pdp_ip0/ipv4" = "10.37.212.102";
"utun0/ipv6" = "fe80::279c:ea56:a2ef:d128";
}
With his original answer I will get the wifi IP fe80::8dd:7d92:4159:170e, which was fake and connection failed.
So I modified the code to like,
[searchArray enumerateObjectsUsingBlock:^(NSString *key, NSUInteger idx, BOOL *stop)
{
if ((internetReach.isReachableViaWiFi && [key hasPrefix:IOS_WIFI]) ||
(internetReach.isReachableViaWWAN && [key hasPrefix:IOS_CELLULAR])) {
address = addresses[key];
if(address) *stop = YES;
}
} ];
The current solution doesn't return the en0 device on OS X, the following code uses the System Configuration Framework to get the interfaces then uses standard C functions to get the IP address.
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <sys/ioctl.h>
#include <net/if.h>
#define IFT_ETHER 0x6
#include <SystemConfiguration/SCDynamicStore.h>
+(void)getInterfaces
{
SCDynamicStoreRef storeRef = SCDynamicStoreCreate(NULL, (CFStringRef)#"FindCurrentInterfaceIpMac", NULL, NULL);
CFPropertyListRef global = SCDynamicStoreCopyValue (storeRef,CFSTR("State:/Network/Interface"));
id primaryInterface = [(__bridge NSDictionary *)global valueForKey:#"Interfaces"];
for (NSString* item in primaryInterface)
{
if(get_iface_address([item UTF8String]))
{
NSString *ip = [NSString stringWithUTF8String:get_iface_address([item UTF8String])];
NSLog(#"interface: %# - %#",item,ip);
} else
NSLog(#"interface: %#",item);
}
}
static char * get_iface_address (char *interface)
{
int sock;
uint32_t ip;
struct ifreq ifr;
char *val;
if (!interface)
return NULL;
/* determine UDN according to MAC address */
sock = socket (AF_INET, SOCK_STREAM, 0);
if (sock < 0)
{
perror ("socket");
return NULL;
}
strcpy (ifr.ifr_name, interface);
ifr.ifr_addr.sa_family = AF_INET;
if (ioctl (sock, SIOCGIFADDR, &ifr) < 0)
{
perror ("ioctl");
close (sock);
return NULL;
}
val = (char *) malloc (16 * sizeof (char));
ip = ((struct sockaddr_in *) &ifr.ifr_addr)->sin_addr.s_addr;
ip = ntohl (ip);
sprintf (val, "%d.%d.%d.%d",
(ip >> 24) & 0xFF, (ip >> 16) & 0xFF, (ip >> 8) & 0xFF, ip & 0xFF);
close (sock);
return val;
}
This answer was inspired by #DavidH's answer. I fixed some issues, replaced inet_ntop with getnameinfo which allows a cleaner approach. Note that this yields a dictionary that maps an interface name to an array of IP addresses (an interface can have multiple IPv4 and IPv6's associated with it, technically). It does not distinguish between IPv4 and IPv6:
// Get all our interface addresses.
struct ifaddrs *ifAddresses;
if (getifaddrs( &ifAddresses ) != 0) {
NSLog( #"Couldn't get interface addresses: %d", errno );
return nil;
}
int error;
char host[MAX( INET_ADDRSTRLEN, INET6_ADDRSTRLEN )];
_ipAddressesByInterface = [NSMutableDictionary dictionaryWithCapacity:8];
for (struct ifaddrs *ifAddress = ifAddresses; ifAddress; ifAddress = ifAddress->ifa_next) {
if (!(ifAddress->ifa_flags & IFF_UP) || (ifAddress->ifa_flags & IFF_LOOPBACK))
// Ignore interfaces that aren't up and loopback interfaces.
continue;
if (ifAddress->ifa_addr->sa_family != AF_INET && ifAddress->ifa_addr->sa_family != AF_INET6)
// Ignore non-internet addresses.
continue;
if ((error = getnameinfo( ifAddress->ifa_addr, ifAddress->ifa_addr->sa_len, host, sizeof( host ), NULL, 0, NI_NUMERICHOST )) != noErr) {
// Couldn't to format host name for this address.
NSLog( #"Couldn't resolve host name for address: %s", gai_strerror( error ) );
continue;
}
NSString *ifName = [NSString stringWithCString:ifAddress->ifa_name encoding: NSUTF8StringEncoding];
NSMutableArray *ifIpAddresses = _ipAddressesByInterface[ifName];
if (!ifIpAddresses)
ifIpAddresses = _ipAddressesByInterface[ifName] = [NSMutableArray arrayWithCapacity:2];
[ifIpAddresses addObject:[NSString stringWithCString:host encoding: NSUTF8StringEncoding]];
}
freeifaddrs( ifAddresses );
return _ipAddressesByInterface;
Great solution for swift in This file which serves all the details.
In One of my app I need to fetch wifi IP address. I have used answers above, in swift 3 like this:
let WIFI_IF = "en0"
let UNKNOWN_IP_ADDRESS = ""
var addresses: [AnyHashable: Any] = ["wireless": UNKNOWN_IP_ADDRESS, "wired": UNKNOWN_IP_ADDRESS, "cell": UNKNOWN_IP_ADDRESS]
var interfaces: UnsafeMutablePointer<ifaddrs>? = nil
var temp_addr: UnsafeMutablePointer<ifaddrs>? = nil
var success: Int = 0
success = Int(getifaddrs(&interfaces))
if success == 0 {
temp_addr = interfaces
while temp_addr != nil {
if temp_addr?.pointee.ifa_addr == nil {
continue
}
if temp_addr?.pointee.ifa_addr.pointee.sa_family == UInt8(AF_INET) {
if (String(utf8String: (temp_addr?.pointee.ifa_name)!) == WIFI_IF) {
addresses["wireless"] = String(utf8String: inet_ntoa(((temp_addr?.pointee.ifa_addr as? sockaddr_in)?.sin_addr)!))
}
}
temp_addr = temp_addr?.pointee.ifa_next
}
}
In this code, It crashes because I have to check for nil in each statement I have used as optional with ?. So it is better for me to use given linked file in my class. It becomes easy for me to check now like:
class func getWifiIPAddress() -> String {
var wifiIp = ""
let WIFI_IF = "en0"
let allInterface = Interface.allInterfaces()
for interf in allInterface {
if interf.name == WIFI_IF {
if let address = interf.address {
if address.contains(".") {
wifiIp = address
break
}
}
}
}
return wifiIp
}
I have parsed string for "." because Interface Class returns two interface in my iPhone for en0 address like "fb00::" and address like "101.10.1.1"
I created a simple file for getting the ip address. I based this solution on # lundhjem's, #DavidH's and #Ihunath's answers. It considers wired connections. I haven't included VPN in this solution though.
PCNetwork.h
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
#interface PCNetwork : NSObject
+ (NSString *)getIPAddress; // Prefers IPv4
+ (NSString *)getIPAddress:(BOOL)preferIPv4;
+ (NSDictionary *)getIPAddresses;
#end
NS_ASSUME_NONNULL_END
PCNetwork.m
#import "PCNetwork.h"
#include <ifaddrs.h>
#include <arpa/inet.h>
#include <net/if.h>
#define IP_UNKNOWN #"0.0.0.0"
#define IP_ADDR_IPv4 #"ipv4"
#define IP_ADDR_IPv6 #"ipv6"
#implementation PCNetwork
#pragma mark - IP
+ (NSString *)getIPAddress {
return [self getIPAddress:YES];
}
+ (NSString *)getIPAddress:(BOOL)preferIPv4 {
NSArray *searchArray = [self getAllIFSearchArray:preferIPv4];
NSDictionary *addresses = [self getIPAddresses];
DLog(#"addresses: %#", addresses);
__block NSString *address = nil;
[searchArray enumerateObjectsUsingBlock:^(NSString *key, NSUInteger idx, BOOL *stop) {
address = addresses[key];
if(address) *stop = YES;
}];
return address ?: IP_UNKNOWN;
}
+ (NSDictionary *)getIPAddresses {
NSMutableDictionary *addresses = [NSMutableDictionary dictionary];
struct ifaddrs *interfaces;
BOOL success = !getifaddrs(&interfaces); // Retrieve the current interfaces : returns 0 on success
if (success) {
struct ifaddrs *temp_interface;
for (temp_interface = interfaces; temp_interface; temp_interface = temp_interface->ifa_next) { // Loop through linked list of interfaces
if (!(temp_interface->ifa_flags & IFF_UP) || (temp_interface->ifa_flags & IFF_LOOPBACK)) { // Ignore interfaces that aren't up and loopback interfaces.
continue;
}
if (!temp_interface->ifa_addr) {
continue;
}
const struct sockaddr_in *temp_addr = (const struct sockaddr_in*)temp_interface->ifa_addr;
if (temp_addr->sin_family == AF_INET || temp_addr->sin_family == AF_INET6) {
char addrBuf[MAX(INET_ADDRSTRLEN, INET6_ADDRSTRLEN)];
NSString *name = [NSString stringWithUTF8String:temp_interface->ifa_name];
NSString *type = nil;
if (temp_addr->sin_family == AF_INET) {
if (inet_ntop(AF_INET, &temp_addr->sin_addr, addrBuf, INET_ADDRSTRLEN)) {
type = IP_ADDR_IPv4;
}
} else {
const struct sockaddr_in6 *addr6 = (const struct sockaddr_in6*)temp_interface->ifa_addr; // AF_INET6
if (inet_ntop(AF_INET6, &addr6->sin6_addr, addrBuf, INET6_ADDRSTRLEN)) {
type = IP_ADDR_IPv6;
}
}
if (type) {
NSString *key = [NSString stringWithFormat:#"%#/%#", name, type];
addresses[key] = [NSString stringWithUTF8String:addrBuf];
}
}
}
freeifaddrs(interfaces); // Free memory
}
return addresses.count ? addresses.copy : nil;
}
#pragma mark - Inter Frame Spacing
+ (NSArray *)getAllIFSearchArray:(BOOL)preferIPv4 {
NSArray *KNOWN_WIFI_IFS = #[#"en0"];
NSArray *KNOWN_WIRED_IFS = #[#"en1",#"en2",#"en3",#"en4"];
NSArray *KNOWN_CELL_IFS = #[#"pdp_ip0",#"pdp_ip1",#"pdp_ip2",#"pdp_ip3"];
NSMutableArray *searchArray = [NSMutableArray array];
// Add wifi
[searchArray addObjectsFromArray:[self getIFSearchArrayWith:KNOWN_WIFI_IFS preferIPv4:preferIPv4]];
// Add cell
[searchArray addObjectsFromArray:[self getIFSearchArrayWith:KNOWN_CELL_IFS preferIPv4:preferIPv4]];
// Add wired
[searchArray addObjectsFromArray:[self getIFSearchArrayWith:KNOWN_WIRED_IFS preferIPv4:preferIPv4]];
return searchArray.copy;
}
+ (NSArray *)getIFSearchArrayWith:(NSArray *)iFList preferIPv4:(BOOL)preferIPv4 {
NSMutableArray *searchArray = [NSMutableArray array];
for (NSString *iFType in iFList) {
if (preferIPv4) {
[searchArray addObject:[NSString stringWithFormat:#"%#/%#", iFType, IP_ADDR_IPv4]];
[searchArray addObject:[NSString stringWithFormat:#"%#/%#", iFType, IP_ADDR_IPv6]];
} else {
[searchArray addObject:[NSString stringWithFormat:#"%#/%#", iFType, IP_ADDR_IPv6]];
[searchArray addObject:[NSString stringWithFormat:#"%#/%#", iFType, IP_ADDR_IPv4]];
}
}
return searchArray.copy;
}
#end
in iOS 13.4.1 is not work for me .
i use this fix it.
+ (NSString *)getIPAddress{
NSArray *searchArray =
#[ IOS_VPN #"/" IP_ADDR_IPv4, IOS_VPN #"/" IP_ADDR_IPv6, IOS_WIFI #"/" IP_ADDR_IPv4, IOS_WIFI #"/" IP_ADDR_IPv6, IOS_4_3G #"/" IP_ADDR_IPv4, IOS_4_3G #"/" IP_ADDR_IPv6, IOS_CELLULAR #"/" IP_ADDR_IPv4, IOS_CELLULAR #"/" IP_ADDR_IPv6];
__block NSDictionary *addresses = [self getIPAddressArray];
__block NSString *address;
[searchArray enumerateObjectsUsingBlock:^(NSString *key, NSUInteger idx, BOOL *stop)
{
address = addresses[key];
if ([key rangeOfString:#"ipv6"].length > 0 && ![[NSString stringWithFormat:#"%#",addresses[key]] hasPrefix:#"(null)"] ) {
if ( ![addresses[key] hasPrefix:#"fe80"]) {
// isIpv6 = YES;
*stop = YES;
}
}else{
if([self isValidatIP:address]) {
*stop = YES;
}
}
} ];
return address ? address : #"error";
}
+ (NSString *)getIPType{
NSString *ipAddress = [self getIPAddress];
if ([self isValidatIP:ipAddress]) {
return #"04";//ipv4
}else{
return #"06";//ipv6
}
}
+ (NSDictionary *)getIPAddressArray{
NSMutableDictionary *addresses = [NSMutableDictionary dictionaryWithCapacity:8];
// retrieve the current interfaces - returns 0 on success
struct ifaddrs *interfaces;
if(!getifaddrs(&interfaces)) {
// Loop through linked list of interfaces
struct ifaddrs *interface;
for(interface=interfaces; interface; interface=interface->ifa_next) {
if(!(interface->ifa_flags & IFF_UP) /* || (interface->ifa_flags & IFF_LOOPBACK) */ ) {
continue; // deeply nested code harder to read
}
const struct sockaddr_in *addr = (const struct sockaddr_in*)interface->ifa_addr;
char addrBuf[ MAX(INET_ADDRSTRLEN, INET6_ADDRSTRLEN) ];
if(addr && (addr->sin_family==AF_INET || addr->sin_family==AF_INET6)) {
NSString *name = [NSString stringWithUTF8String:interface->ifa_name];
NSString *type;
if(addr->sin_family == AF_INET) {
if(inet_ntop(AF_INET, &addr->sin_addr, addrBuf, INET_ADDRSTRLEN)) {
type = IP_ADDR_IPv4;
}
} else {
const struct sockaddr_in6 *addr6 = (const struct sockaddr_in6*)interface->ifa_addr;
if(inet_ntop(AF_INET6, &addr6->sin6_addr, addrBuf, INET6_ADDRSTRLEN)) {
type = IP_ADDR_IPv6;
}
}
if(type) {
NSString *key = [NSString stringWithFormat:#"%#/%#", name, type];
addresses[key] = [NSString stringWithUTF8String:addrBuf];
}
}
}
// Free memory
freeifaddrs(interfaces);
}
return [addresses count] ? addresses : nil;
}
+ (BOOL)isValidatIP:(NSString *)ipAddress {
if (ipAddress.length == 0) {
return NO;
}
NSString *urlRegEx = #"^([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\."
"([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\."
"([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\."
"([01]?\\d\\d?|2[0-4]\\d|25[0-5])$";
NSError *error;
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:urlRegEx options:0 error:&error];
if (regex != nil) {
NSTextCheckingResult *firstMatch=[regex firstMatchInString:ipAddress options:0 range:NSMakeRange(0, [ipAddress length])];
if (firstMatch) {
NSRange resultRange = [firstMatch rangeAtIndex:0];
NSString *result=[ipAddress substringWithRange:resultRange];
//输出结果
NSLog(#"%#",result);
return YES;
}
}
return NO;
}

Send email to multiple recipients with SKPSMTPMessage?

I need to send email in background, so I have to use the library named: SMTP. And the main class I used is: SKPSMTPMessage. The problem is "ccEmail", when I add more than 2 recipients, it can't send email. (that takes too long time to go to delegate methods). It works well with recipient <= 2.
smtpEmail.ccEmail = #"xyz#gmail.com, xyz1#gmail.com, xyz2#gmail.com";
Anyone knows this, please help me. Thanks you so much !
There is my changes in the parseBuffer function:
case kSKPSMTPWaitingFromReply:
{
if ([tmpLine hasPrefix:#"250 "]) {
if (!multipleRcptTo) {
NSMutableString *multipleRcptToString = [NSMutableString string];
[multipleRcptToString appendString:[self formatAddresses:toEmail]];
[multipleRcptToString appendString:[self formatAddresses:ccEmail]];
[multipleRcptToString appendString:[self formatAddresses:bccEmail]];
multipleRcptTo = [[multipleRcptToString componentsSeparatedByString:#"\r\n"] mutableCopy];
[multipleRcptTo removeLastObject];
}
if ([multipleRcptTo count] > 0) {
NSString *rcptTo = [NSString stringWithFormat:#"%#\r\n", [multipleRcptTo objectAtIndex:0]];
[multipleRcptTo removeObjectAtIndex:0];
//DEBUGLOG(#"C: %#", rcptTo);
if (CFWriteStreamWriteFully((CFWriteStreamRef)outputStream, (const uint8_t *)[rcptTo UTF8String], [rcptTo lengthOfBytesUsingEncoding:NSUTF8StringEncoding]) < 0)
{
error = [outputStream streamError];
encounteredError = YES;
}
else
{
[self startShortWatchdog];
}
}
if ([multipleRcptTo count] == 0) {
sendState = kSKPSMTPWaitingToReply;
}
}
break;
}
and add this into header:
NSMutableArray *multipleRcptTo;
EDIT : Also change below method as multipleRcptTo is used as NSMutableString which is local declaration :
- (NSString *)formatAddresses:(NSString *)addresses {
NSCharacterSet *splitSet = [NSCharacterSet characterSetWithCharactersInString:#";,"];
NSMutableString *multipleRcpt = [NSMutableString string];
if ((addresses != nil) && (![addresses isEqualToString:#""])) {
if( [addresses rangeOfString:#";"].location != NSNotFound || [addresses rangeOfString:#","].location != NSNotFound ) {
NSArray *addressParts = [addresses componentsSeparatedByCharactersInSet:splitSet];
for( NSString *address in addressParts ) {
[multipleRcpt appendString:[self formatAnAddress:address]];
}
}
else {
[multipleRcpt appendString:[self formatAnAddress:addresses]];
}
}
return(multipleRcpt);
}
SKPSMTPMessage sends to the SMTP address all at once, and must send one by one.

IP Address? - Cocoa

How would I make a GUI program that displays your Ip address with a click of a button? Please, no difficult explanations, I just started Cocoa not long ago.
Thanks,
Kevin
You can get IP address through two ways:
1- if you want to get the local ip address on the current used netwrok, you can use the following method to retrive it:
-(NSString *)getIPAddress
{
NSString *address = #"error";
struct ifaddrs *interfaces = NULL;
struct ifaddrs *temp_addr = NULL;
int success = 0;
// retrieve the current interfaces - returns 0 on success
success = getifaddrs(&interfaces);
if (success == 0)
{
// Loop through linked list of interfaces
temp_addr = interfaces;
while(temp_addr != NULL)
{
if(temp_addr->ifa_addr->sa_family == AF_INET)
{
// Get NSString from C String
address = [NSString stringWithUTF8String:inet_ntoa(((struct sockaddr_in *)temp_addr->ifa_addr)->sin_addr)];
}
temp_addr = temp_addr->ifa_next;
}
}
// Free memory
freeifaddrs(interfaces);
return address;
}
2- if you want to get the external IP address then you need to use the following method:
-(NSString*)getIP
{
NSUInteger an_Integer;
NSArray * ipItemsArray;
NSString *externalIP;
NSURL *iPURL = [NSURL URLWithString:#"http://www.dyndns.org/cgi-bin/check_ip.cgi"];
if (iPURL) {
NSError *error = nil;
NSString *theIpHtml = [NSString stringWithContentsOfURL:iPURL encoding:NSUTF8StringEncoding error:&error];
if (!error) {
NSScanner *theScanner;
NSString *text = nil;
theScanner = [NSScanner scannerWithString:theIpHtml];
while ([theScanner isAtEnd] == NO) {
// find start of tag
[theScanner scanUpToString:#"<" intoString:NULL] ;
// find end of tag
[theScanner scanUpToString:#">" intoString:&text] ;
// replace the found tag with a space
//(you can filter multi-spaces out later if you wish)
theIpHtml = [theIpHtml stringByReplacingOccurrencesOfString:
[ NSString stringWithFormat:#"%#>", text]
withString:#" "] ;
ipItemsArray =[theIpHtml componentsSeparatedByString:#" "];
an_Integer=[ipItemsArray indexOfObject:#"Address:"];
externalIP =[ipItemsArray objectAtIndex: ++an_Integer];
}
NSLog(#"%#",externalIP);
} else {
NSLog(#"Oops... g %d, %#", [error code], [error localizedDescription]);
}
}
return externalIP;
}
For determining the IP address, I found
this.
As for making it into a Cocoa app, add an NSTextField (label) to your main window in Interface Builder, put in a button, add in an application controller (a subclass of NSObject that you make), put in the outlet and the action, do the proper connenctions, and in the "get IP" method, put in that code and set the value for the label's stringValue.
You can use [[NSHost currentHost] address], but it won't always display what you like. On my system, for example, it gives my IPv6 address.
EDIT: On my system, [[[NSHost currentHost] addresses] objectAtIndex:0] has my IPv4 address.
[[NSHost currentHost] addresses] will get you an array of IPs. Read the documentation for NSHost.
As for displaying that in a GUI, I recommend getting Aaron Hillegass' book Cocoa Programming for Mac OS X, or any Cocoa beginners book should teach that.
I just wrote this, may need some work but seems to work well on my machine...
- (NSString *)getLocalIPAddress
{
NSArray *ipAddresses = [[NSHost currentHost] addresses];
NSArray *sortedIPAddresses = [ipAddresses sortedArrayUsingSelector:#selector(localizedCaseInsensitiveCompare:)];
NSNumberFormatter *numberFormatter = [[NSNumberFormatter alloc] init];
numberFormatter.allowsFloats = NO;
for (NSString *potentialIPAddress in sortedIPAddresses)
{
if ([potentialIPAddress isEqualToString:#"127.0.0.1"]) {
continue;
}
NSArray *ipParts = [potentialIPAddress componentsSeparatedByString:#"."];
BOOL isMatch = YES;
for (NSString *ipPart in ipParts) {
if (![numberFormatter numberFromString:ipPart]) {
isMatch = NO;
break;
}
}
if (isMatch) {
return potentialIPAddress;
}
}
// No IP found
return #"?.?.?.?";
}
We can use hostWithName: method with current host name. This will return only single local IPv4 and IPv6 IP, which we can filter easily.
We can get the current system host name using [[NSHost currentHost] name].
+(NSString *)getLocalIPAddress{
NSArray *ipAddresses = [[NSHost hostWithName:[[NSHost currentHost] name]] addresses];
for (NSString *ipAddress in ipAddresses) {
if ([ipAddress componentsSeparatedByString:#"."].count == 4) {
return ipAddress;
}
}
return #"Not Connected.";
}
So, this will solve all the problems mentions in comments of other answers. Also, this significantly work more faster than other solution mention here.

Objective-C: Reading a file line by line

What is the appropriate way of dealing with large text files in Objective-C? Let's say I need to read each line separately and want to treat each line as an NSString. What is the most efficient way of doing this?
One solution is using the NSString method:
+ (id)stringWithContentsOfFile:(NSString *)path
encoding:(NSStringEncoding)enc
error:(NSError **)error
and then split the lines with a newline separator, and then iterate over the elements in the array. However, this seems fairly inefficient. Is there no easy way to treat the file as a stream, enumerating over each line, instead of just reading it all in at once? Kinda like Java's java.io.BufferedReader.
This will work for general reading a String from Text.
If you would like to read longer text (large size of text), then use the method that other people here were mentioned such as buffered (reserve the size of the text in memory space).
Say you read a Text File.
NSString* filePath = #""//file path...
NSString* fileRoot = [[NSBundle mainBundle]
pathForResource:filePath ofType:#"txt"];
You want to get rid of new line.
// read everything from text
NSString* fileContents =
[NSString stringWithContentsOfFile:fileRoot
encoding:NSUTF8StringEncoding error:nil];
// first, separate by new line
NSArray* allLinedStrings =
[fileContents componentsSeparatedByCharactersInSet:
[NSCharacterSet newlineCharacterSet]];
// then break down even further
NSString* strsInOneLine =
[allLinedStrings objectAtIndex:0];
// choose whatever input identity you have decided. in this case ;
NSArray* singleStrs =
[currentPointString componentsSeparatedByCharactersInSet:
[NSCharacterSet characterSetWithCharactersInString:#";"]];
There you have it.
That's a great question. I think #Diederik has a good answer, although it's unfortunate that Cocoa doesn't have a mechanism for exactly what you want to do.
NSInputStream allows you to read chunks of N bytes (very similar to java.io.BufferedReader), but you have to convert it to an NSString on your own, then scan for newlines (or whatever other delimiter) and save any remaining characters for the next read, or read more characters if a newline hasn't been read yet. (NSFileHandle lets you read an NSData which you can then convert to an NSString, but it's essentially the same process.)
Apple has a Stream Programming Guide that can help fill in the details, and this SO question may help as well if you're going to be dealing with uint8_t* buffers.
If you're going to be reading strings like this frequently (especially in different parts of your program) it would be a good idea to encapsulate this behavior in a class that can handle the details for you, or even subclassing NSInputStream (it's designed to be subclassed) and adding methods that allow you to read exactly what you want.
For the record, I think this would be a nice feature to add, and I'll be filing an enhancement request for something that makes this possible. :-)
Edit: Turns out this request already exists. There's a Radar dating from 2006 for this (rdar://4742914 for Apple-internal people).
This should do the trick:
#include <stdio.h>
NSString *readLineAsNSString(FILE *file)
{
char buffer[4096];
// tune this capacity to your liking -- larger buffer sizes will be faster, but
// use more memory
NSMutableString *result = [NSMutableString stringWithCapacity:256];
// Read up to 4095 non-newline characters, then read and discard the newline
int charsRead;
do
{
if(fscanf(file, "%4095[^\n]%n%*c", buffer, &charsRead) == 1)
[result appendFormat:#"%s", buffer];
else
break;
} while(charsRead == 4095);
return result;
}
Use as follows:
FILE *file = fopen("myfile", "r");
// check for NULL
while(!feof(file))
{
NSString *line = readLineAsNSString(file);
// do stuff with line; line is autoreleased, so you should NOT release it (unless you also retain it beforehand)
}
fclose(file);
This code reads non-newline characters from the file, up to 4095 at a time. If you have a line that is longer than 4095 characters, it keeps reading until it hits a newline or end-of-file.
Note: I have not tested this code. Please test it before using it.
Mac OS X is Unix, Objective-C is C superset, so you can just use old-school fopen and fgets from <stdio.h>. It's guaranteed to work.
[NSString stringWithUTF8String:buf] will convert C string to NSString. There are also methods for creating strings in other encodings and creating without copying.
You can use NSInputStream which has a basic implementation for file streams. You can read bytes into a buffer (read:maxLength: method). You have to scan the buffer for newlines yourself.
The appropriate way to read text files in Cocoa/Objective-C is documented in Apple's String programming guide. The section for reading and writing files should be just what you're after. PS: What's a "line"? Two sections of a string separated by "\n"? Or "\r"? Or "\r\n"? Or maybe you're actually after paragraphs? The previously mentioned guide also includes a section on splitting a string into lines or paragraphs. (This section is called "Paragraphs and Line Breaks", and is linked to in the left-hand-side menu of the page I pointed to above. Unfortunately this site doesn't allow me to post more than one URL as I'm not a trustworthy user yet.)
To paraphrase Knuth: premature optimisation is the root of all evil. Don't simply assume that "reading the whole file into memory" is slow. Have you benchmarked it? Do you know that it actually reads the whole file into memory? Maybe it simply returns a proxy object and keeps reading behind the scenes as you consume the string? (Disclaimer: I have no idea if NSString actually does this. It conceivably could.) The point is: first go with the documented way of doing things. Then, if benchmarks show that this doesn't have the performance you desire, optimise.
A lot of these answers are long chunks of code or they read in the entire file. I like to use the c methods for this very task.
FILE* file = fopen("path to my file", "r");
size_t length;
char *cLine = fgetln(file,&length);
while (length>0) {
char str[length+1];
strncpy(str, cLine, length);
str[length] = '\0';
NSString *line = [NSString stringWithFormat:#"%s",str];
% Do what you want here.
cLine = fgetln(file,&length);
}
Note that fgetln will not keep your newline character. Also, We +1 the length of the str because we want to make space for the NULL termination.
Just like #porneL said, the C api is very handy.
NSString* fileRoot = [[NSBundle mainBundle] pathForResource:#"record" ofType:#"txt"];
FILE *file = fopen([fileRoot UTF8String], "r");
char buffer[256];
while (fgets(buffer, 256, file) != NULL){
NSString* result = [NSString stringWithUTF8String:buffer];
NSLog(#"%#",result);
}
To read a file line by line (also for extreme big files) can be done by the following functions:
DDFileReader * reader = [[DDFileReader alloc] initWithFilePath:pathToMyFile];
NSString * line = nil;
while ((line = [reader readLine])) {
NSLog(#"read line: %#", line);
}
[reader release];
Or:
DDFileReader * reader = [[DDFileReader alloc] initWithFilePath:pathToMyFile];
[reader enumerateLinesUsingBlock:^(NSString * line, BOOL * stop) {
NSLog(#"read line: %#", line);
}];
[reader release];
The class DDFileReader that enables this is the following:
Interface File (.h):
#interface DDFileReader : NSObject {
NSString * filePath;
NSFileHandle * fileHandle;
unsigned long long currentOffset;
unsigned long long totalFileLength;
NSString * lineDelimiter;
NSUInteger chunkSize;
}
#property (nonatomic, copy) NSString * lineDelimiter;
#property (nonatomic) NSUInteger chunkSize;
- (id) initWithFilePath:(NSString *)aPath;
- (NSString *) readLine;
- (NSString *) readTrimmedLine;
#if NS_BLOCKS_AVAILABLE
- (void) enumerateLinesUsingBlock:(void(^)(NSString*, BOOL *))block;
#endif
#end
Implementation (.m)
#import "DDFileReader.h"
#interface NSData (DDAdditions)
- (NSRange) rangeOfData_dd:(NSData *)dataToFind;
#end
#implementation NSData (DDAdditions)
- (NSRange) rangeOfData_dd:(NSData *)dataToFind {
const void * bytes = [self bytes];
NSUInteger length = [self length];
const void * searchBytes = [dataToFind bytes];
NSUInteger searchLength = [dataToFind length];
NSUInteger searchIndex = 0;
NSRange foundRange = {NSNotFound, searchLength};
for (NSUInteger index = 0; index < length; index++) {
if (((char *)bytes)[index] == ((char *)searchBytes)[searchIndex]) {
//the current character matches
if (foundRange.location == NSNotFound) {
foundRange.location = index;
}
searchIndex++;
if (searchIndex >= searchLength) { return foundRange; }
} else {
searchIndex = 0;
foundRange.location = NSNotFound;
}
}
return foundRange;
}
#end
#implementation DDFileReader
#synthesize lineDelimiter, chunkSize;
- (id) initWithFilePath:(NSString *)aPath {
if (self = [super init]) {
fileHandle = [NSFileHandle fileHandleForReadingAtPath:aPath];
if (fileHandle == nil) {
[self release]; return nil;
}
lineDelimiter = [[NSString alloc] initWithString:#"\n"];
[fileHandle retain];
filePath = [aPath retain];
currentOffset = 0ULL;
chunkSize = 10;
[fileHandle seekToEndOfFile];
totalFileLength = [fileHandle offsetInFile];
//we don't need to seek back, since readLine will do that.
}
return self;
}
- (void) dealloc {
[fileHandle closeFile];
[fileHandle release], fileHandle = nil;
[filePath release], filePath = nil;
[lineDelimiter release], lineDelimiter = nil;
currentOffset = 0ULL;
[super dealloc];
}
- (NSString *) readLine {
if (currentOffset >= totalFileLength) { return nil; }
NSData * newLineData = [lineDelimiter dataUsingEncoding:NSUTF8StringEncoding];
[fileHandle seekToFileOffset:currentOffset];
NSMutableData * currentData = [[NSMutableData alloc] init];
BOOL shouldReadMore = YES;
NSAutoreleasePool * readPool = [[NSAutoreleasePool alloc] init];
while (shouldReadMore) {
if (currentOffset >= totalFileLength) { break; }
NSData * chunk = [fileHandle readDataOfLength:chunkSize];
NSRange newLineRange = [chunk rangeOfData_dd:newLineData];
if (newLineRange.location != NSNotFound) {
//include the length so we can include the delimiter in the string
chunk = [chunk subdataWithRange:NSMakeRange(0, newLineRange.location+[newLineData length])];
shouldReadMore = NO;
}
[currentData appendData:chunk];
currentOffset += [chunk length];
}
[readPool release];
NSString * line = [[NSString alloc] initWithData:currentData encoding:NSUTF8StringEncoding];
[currentData release];
return [line autorelease];
}
- (NSString *) readTrimmedLine {
return [[self readLine] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
}
#if NS_BLOCKS_AVAILABLE
- (void) enumerateLinesUsingBlock:(void(^)(NSString*, BOOL*))block {
NSString * line = nil;
BOOL stop = NO;
while (stop == NO && (line = [self readLine])) {
block(line, &stop);
}
}
#endif
#end
The class was done by Dave DeLong
As others have answered both NSInputStream and NSFileHandle are fine options, but it can also be done in a fairly compact way with NSData and memory mapping:
BRLineReader.h
#import <Foundation/Foundation.h>
#interface BRLineReader : NSObject
#property (readonly, nonatomic) NSData *data;
#property (readonly, nonatomic) NSUInteger linesRead;
#property (strong, nonatomic) NSCharacterSet *lineTrimCharacters;
#property (readonly, nonatomic) NSStringEncoding stringEncoding;
- (instancetype)initWithFile:(NSString *)filePath encoding:(NSStringEncoding)encoding;
- (instancetype)initWithData:(NSData *)data encoding:(NSStringEncoding)encoding;
- (NSString *)readLine;
- (NSString *)readTrimmedLine;
- (void)setLineSearchPosition:(NSUInteger)position;
#end
BRLineReader.m
#import "BRLineReader.h"
static unsigned char const BRLineReaderDelimiter = '\n';
#implementation BRLineReader
{
NSRange _lastRange;
}
- (instancetype)initWithFile:(NSString *)filePath encoding:(NSStringEncoding)encoding
{
self = [super init];
if (self) {
NSError *error = nil;
_data = [NSData dataWithContentsOfFile:filePath options:NSDataReadingMappedAlways error:&error];
if (!_data) {
NSLog(#"%#", [error localizedDescription]);
}
_stringEncoding = encoding;
_lineTrimCharacters = [NSCharacterSet whitespaceAndNewlineCharacterSet];
}
return self;
}
- (instancetype)initWithData:(NSData *)data encoding:(NSStringEncoding)encoding
{
self = [super init];
if (self) {
_data = data;
_stringEncoding = encoding;
_lineTrimCharacters = [NSCharacterSet whitespaceAndNewlineCharacterSet];
}
return self;
}
- (NSString *)readLine
{
NSUInteger dataLength = [_data length];
NSUInteger beginPos = _lastRange.location + _lastRange.length;
NSUInteger endPos = 0;
if (beginPos == dataLength) {
// End of file
return nil;
}
unsigned char *buffer = (unsigned char *)[_data bytes];
for (NSUInteger i = beginPos; i < dataLength; i++) {
endPos = i;
if (buffer[i] == BRLineReaderDelimiter) break;
}
// End of line found
_lastRange = NSMakeRange(beginPos, endPos - beginPos + 1);
NSData *lineData = [_data subdataWithRange:_lastRange];
NSString *line = [[NSString alloc] initWithData:lineData encoding:_stringEncoding];
_linesRead++;
return line;
}
- (NSString *)readTrimmedLine
{
return [[self readLine] stringByTrimmingCharactersInSet:_lineTrimCharacters];
}
- (void)setLineSearchPosition:(NSUInteger)position
{
_lastRange = NSMakeRange(position, 0);
_linesRead = 0;
}
#end
This answer is NOT ObjC but C.
Since ObjC is 'C' based, why not use fgets?
And yes, I'm sure ObjC has it's own method - I'm just not proficient enough yet to know what it is :)
from #Adam Rosenfield's answer, the formatting string of fscanf would be changed like below:
"%4095[^\r\n]%n%*[\n\r]"
it will work in osx, linux, windows line endings.
Using category or extension to make our life a bit easier.
extension String {
func lines() -> [String] {
var lines = [String]()
self.enumerateLines { (line, stop) -> () in
lines.append(line)
}
return lines
}
}
// then
for line in string.lines() {
// do the right thing
}
I found response by #lukaswelte and code from Dave DeLong very helpful. I was looking for a solution to this problem but needed to parse large files by \r\n not just \n.
The code as written contains a bug if parsing by more than one character. I've changed the code as below.
.h file:
#import <Foundation/Foundation.h>
#interface FileChunkReader : NSObject {
NSString * filePath;
NSFileHandle * fileHandle;
unsigned long long currentOffset;
unsigned long long totalFileLength;
NSString * lineDelimiter;
NSUInteger chunkSize;
}
#property (nonatomic, copy) NSString * lineDelimiter;
#property (nonatomic) NSUInteger chunkSize;
- (id) initWithFilePath:(NSString *)aPath;
- (NSString *) readLine;
- (NSString *) readTrimmedLine;
#if NS_BLOCKS_AVAILABLE
- (void) enumerateLinesUsingBlock:(void(^)(NSString*, BOOL *))block;
#endif
#end
.m file:
#import "FileChunkReader.h"
#interface NSData (DDAdditions)
- (NSRange) rangeOfData_dd:(NSData *)dataToFind;
#end
#implementation NSData (DDAdditions)
- (NSRange) rangeOfData_dd:(NSData *)dataToFind {
const void * bytes = [self bytes];
NSUInteger length = [self length];
const void * searchBytes = [dataToFind bytes];
NSUInteger searchLength = [dataToFind length];
NSUInteger searchIndex = 0;
NSRange foundRange = {NSNotFound, searchLength};
for (NSUInteger index = 0; index < length; index++) {
if (((char *)bytes)[index] == ((char *)searchBytes)[searchIndex]) {
//the current character matches
if (foundRange.location == NSNotFound) {
foundRange.location = index;
}
searchIndex++;
if (searchIndex >= searchLength)
{
return foundRange;
}
} else {
searchIndex = 0;
foundRange.location = NSNotFound;
}
}
if (foundRange.location != NSNotFound
&& length < foundRange.location + foundRange.length )
{
// if the dataToFind is partially found at the end of [self bytes],
// then the loop above would end, and indicate the dataToFind is found
// when it only partially was.
foundRange.location = NSNotFound;
}
return foundRange;
}
#end
#implementation FileChunkReader
#synthesize lineDelimiter, chunkSize;
- (id) initWithFilePath:(NSString *)aPath {
if (self = [super init]) {
fileHandle = [NSFileHandle fileHandleForReadingAtPath:aPath];
if (fileHandle == nil) {
return nil;
}
lineDelimiter = #"\n";
currentOffset = 0ULL; // ???
chunkSize = 128;
[fileHandle seekToEndOfFile];
totalFileLength = [fileHandle offsetInFile];
//we don't need to seek back, since readLine will do that.
}
return self;
}
- (void) dealloc {
[fileHandle closeFile];
currentOffset = 0ULL;
}
- (NSString *) readLine {
if (currentOffset >= totalFileLength)
{
return nil;
}
#autoreleasepool {
NSData * newLineData = [lineDelimiter dataUsingEncoding:NSUTF8StringEncoding];
[fileHandle seekToFileOffset:currentOffset];
unsigned long long originalOffset = currentOffset;
NSMutableData *currentData = [[NSMutableData alloc] init];
NSData *currentLine = [[NSData alloc] init];
BOOL shouldReadMore = YES;
while (shouldReadMore) {
if (currentOffset >= totalFileLength)
{
break;
}
NSData * chunk = [fileHandle readDataOfLength:chunkSize];
[currentData appendData:chunk];
NSRange newLineRange = [currentData rangeOfData_dd:newLineData];
if (newLineRange.location != NSNotFound) {
currentOffset = originalOffset + newLineRange.location + newLineData.length;
currentLine = [currentData subdataWithRange:NSMakeRange(0, newLineRange.location)];
shouldReadMore = NO;
}else{
currentOffset += [chunk length];
}
}
if (currentLine.length == 0 && currentData.length > 0)
{
currentLine = currentData;
}
return [[NSString alloc] initWithData:currentLine encoding:NSUTF8StringEncoding];
}
}
- (NSString *) readTrimmedLine {
return [[self readLine] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
}
#if NS_BLOCKS_AVAILABLE
- (void) enumerateLinesUsingBlock:(void(^)(NSString*, BOOL*))block {
NSString * line = nil;
BOOL stop = NO;
while (stop == NO && (line = [self readLine])) {
block(line, &stop);
}
}
#endif
#end
I am adding this because all other answers I tried fell short one way or another. The following method can handle large files, arbitrary long lines, as well as empty lines. It has been tested with actual content and will strip out newline character from the output.
- (NSString*)readLineFromFile:(FILE *)file
{
char buffer[4096];
NSMutableString *result = [NSMutableString stringWithCapacity:1000];
int charsRead;
do {
if(fscanf(file, "%4095[^\r\n]%n%*[\n\r]", buffer, &charsRead) == 1) {
[result appendFormat:#"%s", buffer];
}
else {
break;
}
} while(charsRead == 4095);
return result.length ? result : nil;
}
Credit goes to #Adam Rosenfield and #sooop
I see a lot of these answers rely on reading the whole text file into memory instead of taking it one chunk at a time. Here's my solution in nice modern Swift, using FileHandle to keep memory impact low:
enum MyError {
case invalidTextFormat
}
extension FileHandle {
func readLine(maxLength: Int) throws -> String {
// Read in a string of up to the maximum length
let offset = offsetInFile
let data = readData(ofLength: maxLength)
guard let string = String(data: data, encoding: .utf8) else {
throw MyError.invalidTextFormat
}
// Check for carriage returns; if none, this is the whole string
let substring: String
if let subindex = string.firstIndex(of: "\n") {
substring = String(string[string.startIndex ... subindex])
} else {
substring = string
}
// Wind back to the correct offset so that we don't miss any lines
guard let dataCount = substring.data(using: .utf8, allowLossyConversion: false)?.count else {
throw MyError.invalidTextFormat
}
try seek(toOffset: offset + UInt64(dataCount))
return substring
}
}
Note that this preserves the carriage return at the end of the line, so depending on your needs you may want to adjust the code to remove it.
Usage: simply open a file handle to your target text file and call readLine with a suitable maximum length - 1024 is standard for plain text, but I left it open in case you know it will be shorter. Note that the command will not overflow the end of the file, so you may have to check manually that you've not reached it if you intend to parse the entire thing. Here's some sample code that shows how to open a file at myFileURL and read it line-by-line until the end.
do {
let handle = try FileHandle(forReadingFrom: myFileURL)
try handle.seekToEndOfFile()
let eof = handle.offsetInFile
try handle.seek(toFileOffset: 0)
while handle.offsetInFile < eof {
let line = try handle.readLine(maxLength: 1024)
// Do something with the string here
}
try handle.close()
catch let error {
print("Error reading file: \(error.localizedDescription)"
}
Here's a nice simple solution i use for smaller files:
NSString *path = [[NSBundle mainBundle] pathForResource:#"Terrain1" ofType:#"txt"];
NSString *contents = [NSString stringWithContentsOfFile:path encoding:NSASCIIStringEncoding error:nil];
NSArray *lines = [contents componentsSeparatedByCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:#"\r\n"]];
for (NSString* line in lines) {
if (line.length) {
NSLog(#"line: %#", line);
}
}
Use this script, it works great:
NSString *path = #"/Users/xxx/Desktop/names.txt";
NSError *error;
NSString *stringFromFileAtPath = [NSString stringWithContentsOfFile: path
encoding: NSUTF8StringEncoding
error: &error];
if (stringFromFileAtPath == nil) {
NSLog(#"Error reading file at %#\n%#", path, [error localizedFailureReason]);
}
NSLog(#"Contents:%#", stringFromFileAtPath);