在苹果下面开发了近乎一个月的时间了,项目的目标很明确,就是把前面安卓下面的程序移植到iOS下面。本来以为iOS是基于unix的系统,而unix又和c有天然的亲和关系,想着能够避免安卓系统java平台下调用c的尴尬,欣然入手开始了我的iOS编程之旅。
没想到第一个钉子就是在iOS平台的编程语言选择问题,虽然c/c++和c非常类似,但是绝非first class的语言,而object-c又是那么的看起来怪异异常,权衡之后还是选择了swift,谁让它长的那么像javascript呢。事实证明苹果对这个语言确实是花了心思的使其和object-c和c的兼容到了一个令人欣喜的高度,虽然几度折磨的我痛不欲生,尤其是是内存重新解释的时候。
在解决了语言的选择问题之后,苹果还是给了我一点点小的惊喜,那就是可以使用免费账户进行真机调试了,虽然看起来这是一个微不足道的功能,但是对于像这种系统程序的开发来说确实是一个大的便利。但也就是这个便利一开始就把我给干翻了。
让我们回顾一下安卓下面的那个程序的基本功能分解,就知道我为什么要用干翻这个词语了:
1. 获取本机的应用列表:苹果以私人信息为由禁止正常获取。
2. 获取本机的ip地址:苹果以私人信息为由禁止正常获取。
3. 获取本机的mac地址:苹果以私人信息为由禁止正常获取。
4. 获取本机的Wi-Fi信息:换API,废API,移除API,权限控制,真不知道为什么对这个功能这么严防死堵,有知道内情的不妨告知一下。
5. 让程序自动退入后台:没有公开的API,谷歌答案。
6. 让程序间歇执行:两种方案需要自己验证。
7. info.plist : 编辑竟然没有撤销功能,最后添加项目居然会清空,血的教训,不可不防。
8. entitlements:权限控制文件,一个神气的文件。
上面基本把这次开发遇到的坑都罗列了一遍。下面就慢慢的开启本次开发的冒险之旅。
柿子从软的捏,本着c开发这么多年的经验,直接从条目1,条目2,条目3开始入手,获取本机ip和mac地址,先从先易后难的Mac地址获取开始,没想到的是这个坑到现在为止,我还没有填上,真是世事难料啊。
Linux下有一个直接调用操作系统的函数,sysctrl,因为有了这个神一般的函数存在,所以我有了上述的决定,出乎意料顺利的是在模拟器上面获取运行应用列表,费了一些周折的是获取本机ip地址和Mac,再次证明了世间自有公道,付出才有回报。拿到真机上一跑,才发现Mac地址是个固定值:02:00:00:00:00:00,应用程序列表,别说运行的啥都拿不到,为空!反倒是废了一番折腾的本机ip地址倒是能够成功获取。究其所以然大概是因为获取本机ip的c头文件<ifaddrs.h>不在swift的基本framework Darwin 里面。
应用程序列表的获取,没有非什么周折,但是这个版本的基本废掉,所以也就不在叙述。Mac地址的周折主要在于sysctl返回值是一个内存块,怎么来解释这个内存快成了当时最主要的问题,后来拜谷歌所赐,参考了NSData这个大神类的帮助手工自拼接出这块内存的含义,再次说明了c语言在重新定义内存方面的神奇和搞笑。IP地址的周折则主要是使用brighging这种方式来引入新的头文件而引起的,以至于现在看来这还是swift语言的一个败笔,其实既然能import其他framework那么兼容一下include关键字应该也是顺理成章的事情啊,为什么非要上蹿下跳的去配置什么brighging头文件。
到目前为止,一个下马威彻底形成了,三个最软的柿子,两个都磕到了牙,其实后面还会看到,第三个获取本机ip地址还是被这种方法给废弃,但是实用的是另一种更不光彩的方法。
既然使用最原始最直接的方法大败而归,那么相对于一些近代一点的方法又会怎么样呢?我一直认为微软的COM是近代软件开发的基石,号称看不起微软的苹果又是采用了什么样的机制来搭建自己的系统呢?
附录1,获取运行时刻的app列表
static public func listAllRunningApp()->[String]{
var mib : [Int32] = [CTL_KERN, KERN_PROC, KERN_PROC_ALL ,0]
var size = 0
var st = sysctl(&mib,UInt32(mib.count),nil,&size,nil,0)
var process = [kinfo_proc](count: size,repeatedValue : kinfo_proc())
st = sysctl(&mib, UInt32(mib.count),&process,&size,nil,0)
if st != 0{
return [String]()
}
var ret = [String]()
for var info in process {
let appName = withUnsafePointer(&info.kp_proc.p_comm){
String.fromCString(UnsafePointer($0))!
}
if !ret.contains(appName){
ret.append(appName)
}
}
MonitorLog.Log(0, context: "appNames = \(ret)")
return ret;
}
附录2,获取本机的Mac地址
static public func getMacAddress()->String{
let index = Int32(if_nametoindex("en0"))
let bsdData = "en0".dataUsingEncoding(NSUTF8StringEncoding)!
var mib : [Int32] = [CTL_NET,AF_ROUTE,0,AF_LINK,NET_RT_IFLIST,index]
var len = 0;
if sysctl(&mib,UInt32(mib.count), nil, &len,nil,0) < 0 {
MonitorLog.Log(0, context: "Error: could not determine length of info data structure ")
return "00:00:00:00:00:00"
}
var buffer = [CChar](count: len, repeatedValue: 0)
if sysctl(&mib, UInt32(mib.count), &buffer, &len, nil, 0) < 0 {
MonitorLog.Log(0, context: "Error: could not read info data structure");
return "00:00:00:00:00:00"
}
let infoData = NSData(bytes: buffer, length: len)
var interfaceMsgStruct = if_msghdr()
infoData.getBytes(&interfaceMsgStruct, length: sizeof(if_msghdr))
let socketStructStart = sizeof(if_msghdr) + 1
let socketStructData = infoData.subdataWithRange(NSMakeRange(socketStructStart, len - socketStructStart))
let rangeOfToken = socketStructData.rangeOfData(bsdData, options: NSDataSearchOptions(rawValue: 0), range: NSMakeRange(0, socketStructData.length))
let macAddressData = socketStructData.subdataWithRange(NSMakeRange(rangeOfToken.location + 3, 6))
var macAddressDataBytes = [UInt8](count: 6, repeatedValue: 0)
macAddressData.getBytes(&macAddressDataBytes, length: 6)
return macAddressDataBytes.map({ String(format:"%02x", $0) }).joinWithSeparator(":")
}
附录3,获取本机的IP地址,记住一定要要桥引入相关的c头文件
// Return IP address of WiFi interface (en0) as a String, or `nil`
static public func getWiFiAddress() -> String {
var address : String = "0.0.0.0"
// Get list of all interfaces on the local machine:
var ifaddr : UnsafeMutablePointer<ifaddrs> = nil
if getifaddrs(&ifaddr) != 0{
MonitorLog.Log(0, context: "got addr failed ...")
return address
}
// For each interface ...
for (var ptr = ifaddr; ptr != nil; ptr = ptr.memory.ifa_next) {
let interface = ptr.memory
// Check for IPv4 or IPv6 interface:
let addrFamily = interface.ifa_addr.memory.sa_family
if addrFamily != UInt8(AF_INET) && addrFamily != UInt8(AF_INET6) {
MonitorLog.Log(0, context: "unknow sa_family")
continue
}
// Check interface name:
if let name = String.fromCString(interface.ifa_name) where name != "en0"{
MonitorLog.Log(0, context: "not the wifi address information")
continue
}
// Convert interface address to a human readable string:
var addr = interface.ifa_addr.memory
var hostname = [CChar](count: Int(NI_MAXHOST), repeatedValue: 0)
getnameinfo(&addr, socklen_t(interface.ifa_addr.memory.sa_len),
&hostname, socklen_t(hostname.count),nil, socklen_t(0), NI_NUMERICHOST)
address = String.fromCString(hostname)!
}
freeifaddrs(ifaddr)
return address
}