swift4 取得ios 标识_ios swift 判断 ios版本 - CSDN
  • 想要获取系统唯一标识符的话,比如说不管用户卸载还是重新安装都可以唯一标识的可以直接滑动到底部查看先将各种实现的方法列出来,可能你需要的只是获取的方式: Git 标识符工具类Demo链接 —》 DYDeviceInfo 传送...

    关于获取设备各种标识符,最近整理了一下。基本上有IDFA、IDFV、IMEI、IMSI、UUID、UDID、MAC地址; 想要获取系统唯一标识符的话,比如说不管用户卸载还是重新安装都可以唯一标识的可以直接滑动到底部查看

    先将各种实现的方法列出来,可能你需要的只是获取的方式:
    Git 标识符工具类Demo链接 —》 DYDeviceInfo 传送门
    如果有帮到你的话,点个Star就是最好的肯定!

    • IDFA:
      广告标示符,它是由系统存储着的,iOS6及以后使用。但是如果用户还原位置与隐私的话这个广告标识符就会重新生成(设置程序 -》 通用 -》 还原 -》还原位置与隐私) 。或者用户更明确的将设置中的广告还原掉(设置程序 -》 通用-》 关于本机 -》广告 -》 还原广告标示符) ,IDFA也会重新生成。而且用户可以在设置-》隐私 -》广告里面限制IDFA获取,一般用户都不知道有这个,哈哈,不过还是不能用来做唯一标识的id呦。
    #import <AdSupport/ASIdentifierManager.h>
    ASIdentifierManager *asIM = [[ASIdentifierManager alloc] init];
    NSString *idfa = [asIM.advertisingIdentifier UUIDString];
    • IDFV:
      iOS6.0及以后使用,是给Vendor标识用户用的,vendor:卖主,小贩。经过测试发现com.test.app1和com.test.app2具有相同的idfv,而如果是com.app1和com.app2则是两个不同的idfv。准确点说,就是通过BundleID的反转的前两部分进行匹配,如果相同就是同一个Vender,共享同一个idfv的值。
    NSString *idfv = [[UIDevice currentDevice].identifierForVendor UUIDString];
    • IMEI,IMSI:
      IMEI(International Mobile Equipment Identity)是国际移动设备身份码的缩写,国际移动装备辨识码,是由15位数字组成的”电子串号”,它与每台手机一一对应,而且该码是全世界唯一的。每一部手机在组装完成后都将被赋予一个全球唯一的一组号码,这个号码从生产到交付使用都将被制造生产的厂商所记录。手机用户可以在手机中查到自己手机的IMEI码。
      重点来了 !iOS5以后不能再获取了,但通过私有Api能获取,这是在网上能查到的。Git上的erica的UIDevice扩展文件,以前可用但由于IOKit framework没有公开,所以也无法使用。就算手动导入,依旧无法使用,看来获取IMEI要失败了,同时失败的还有IMSI。不过还存在另外一种可能,Stack Overflow上有人提供采用com.apple.coretelephony.Identity.get entitlement方法,but device must be jailbroken;在此附上链接,供大家参考:http://stackoverflow.com/questions/16667988/how-to-get-imei-on-iphone-5/16677043#16677043
      如果实现了,自己拿来玩就行,别上架,这是会被拒掉的。

    • Mac地址,这个是做IDFA的时候,从友盟收集到的方法:
      要导入一堆头文件:

    #include <sys/sysctl.h>
    #include <sys/socket.h>
    #include <net/if.h>
    #include <net/if_dl.h>
        int mib[6];
        size_t len;
        char *buf;
        unsigned char *ptr;
        struct if_msghdr *ifm;
        struct sockaddr_dl *sdl;
    
        mib[0] = CTL_NET;
        mib[1] = AF_ROUTE;
        mib[2] = 0;
        mib[3] = AF_LINK;
        mib[4] = NET_RT_IFLIST;
    
        if ((mib[5] = if_nametoindex("en0")) == 0) {
            printf("Error: if_nametoindex error\n");
            return NULL;
        }
    
        if (sysctl(mib, 6, NULL, &len, NULL, 0) < 0) {
            printf("Error: sysctl, take 1\n");
            return NULL;
        }
    
        if ((buf = malloc(len)) == NULL) {
            printf("Could not allocate memory. error!\n");
            return NULL;
        }
    
        if (sysctl(mib, 6, buf, &len, NULL, 0) < 0) {
            printf("Error: sysctl, take 2");
            free(buf);
            return NULL;
        }
    
        ifm = (struct if_msghdr *)buf;
        sdl = (struct sockaddr_dl *)(ifm + 1);
        ptr = (unsigned char *)LLADDR(sdl);
        NSString *macStr = [NSString stringWithFormat:@"%02X:%02X:%02X:%02X:%02X:%02X",*ptr, *(ptr+1), *(ptr+2), *(ptr+3), *(ptr+4), *(ptr+5)];
        free(buf);

    - UUID ,获取唯一标识符

    每次运行都会发生变化,最理想的就是保存在keychain里面,以此作为标识用户设备的唯一标识符

        CFUUIDRef uuid = CFUUIDCreate(NULL);
        assert(uuid != NULL);
        CFStringRef uuidStr = CFUUIDCreateString(NULL, uuid);
    
        DYLog(@"uuidStr------》%@",uuidStr);

    之前使用了Git上的一个第三方库,SSKeychain,将UUID保存在keychain里面,每次调用先检查钥匙串里面有没有,有则使用,没有则写进去,保证其唯一性,具体使用如下:

    CFUUIDRef uuid = CFUUIDCreate(NULL);
    assert(uuid != NULL);
    CFStringRef uuidStr = CFUUIDCreateString(NULL, uuid);
    NSString *identifierNumber = [SSKeychain passwordForService:@"com.test.app1"account:@"user"];
    
    if (!identifierNumber){
       [SSKeychain setPassword: [NSString stringWithFormat:@"%@", uuidStr] forService:@"com.test.app1"account:@"user"];
       identifierNumber = [SSKeychain passwordForService:@"com.test.app1"account:@"user"];
    }
    

    Git 标识符工具类Demo链接 —》 DYDeviceInfo 传送门

    展开全文
  • iOS开发系列--Swift进阶

    2018-08-07 22:45:34
    概述 上一篇文章《iOS开发系列--Swift语言》中对Swift的语法特点以及它和C、ObjC等其他语言的用法区别进行了介绍。当然,这只是Swift的入门基础,但是仅仅了解这些对于使用Swift进行
    原文地址为:iOS开发系列--Swift进阶

    概述

    上一篇文章《iOS开发系列--Swift语言》中对Swift的语法特点以及它和C、ObjC等其他语言的用法区别进行了介绍。当然,这只是Swift的入门基础,但是仅仅了解这些对于使用Swift进行iOS开发还是不够的。在这篇文章中将继续介绍一些Swift开发中一些不常关注但是又必备的知识点,以便对Swift有进一步的了解。

    1. 访问控制
    2. Swift命名空间
    3. Swift和ObjC互相调用
      1. Swift和ObjC映射关系
      2. Swift调用ObjC
      3. ObjC调用Swift
      4. 扩展—Swift调用C
    4. 反射
      1. 扩展—KVO
    5. 内存管理
      1. 循环引用
      2. 指针与内存
      3. 扩展—Core Foundation

    访问控制

    和其他高级语言一样Swift中也增加了访问控制,在Swift中提供了private、internal、public三种访问级别,但是不同的是Swift中的访问级别是基于模块(module,或者target)和源文件(.swift文件)的,而不是基于类型、命名空间声明。

    • private:只能访问当前源文件中的实体(注意Swift中的private和其他语言不太一样,它是基于源文件的,作用范围是整个源文件,如果一个源文件中有两个类,那么一个类可以访问另外一个类的私有成员)。
    • internal:可以访问当前模块中的其他任何实体,但是在模块外无法访问,这是所有实体的默认访问级别(通常在一个单目标Application中不需要自行设置访问级别)。
    • public:可以访问当前模块及其他模块中的任何实体(通常用于Framework)。

    下面是关于Swift关于不同成员访问级别的约定规则:

    1. 如果一个类的访问级别是private那么该类的所有成员都是private(此时成员无法修改访问级别),如果一个类的访问级别是internal或者public那么它的所有成员都是internal(如果类的访问级别是public,成员默认internal,此时可以单独修改成员的访问级别),类成员的访问级别不能高于类的访问级别(注意:嵌套类型的访问级别也符合此条规则);
    2. 常量、变量、属性、下标脚本访问级别低于其所声明的类型级别,并且如果不是默认访问级别(internal)要明确声明访问级别(例如一个常量是一个private类型的类类型,那么此常量必须声明为private);
    3. 在不违反1、2两条规则的情况下,setter的访问级别可以低于getter的访问级别(例如一个属性访问级别是internal,那么可以添加private(set)修饰将setter权限设置为private,在当前模块中只有此源文件可以访问,对外部是只读的);
    4. 必要构造方法(required修饰)的访问级别必须和类访问级别相同,结构体的默认逐一构造函数的访问级别不高于其成员的访问级别(例如一个成员是private那么这个构造函数就是private,但是可以通过自定义来声明一个public的构造函数),其他方法(包括其他构造方法和普通方法)的访问级别遵循规则1;
    5. 子类的访问级别不高于父类的访问级别,但是在遵循三种访问级别作用范围的前提下子类可以将父类低访问级别的成员重写成更高的访问级别(例如父类A和子类B在同一个源文件,A的访问级别是public,B的访问级别是internal,其中A有一个private方法,那么A可以覆盖其private方法并重写为internal);
    6. 协议中所有必须实现的成员的访问级别和协议本身的访问级别相同,其子协议的访问级别不高于父协议;
    7. 如果一个类继承于另一个类的同时实现了某个协议那么这个类的访问级别为父类和协议的最低访问级别,并且此类中方法访问级别和所实现的协议中的方法相同;
    8. 扩展的成员访问级别遵循规则1,但是对于类、结构体、枚举的扩展可以明确声明访问级别并且可以更低(例如对于internal的类,你可以声明一个private的扩展),而协议的访问级别不可以明确声明;
    9. 元组的访问级别是元组中各个元素的最低访问级别,注意:元组的访问级别是自动推导的,无法直接使用以上三个关键字修饰其访问级别;
    10. 函数的访问级是函数的参数、返回值的最低级别,并且如果其访问级别和默认访问级别(internal)不符需要明确声明;
    11. 枚举成员的访问级别等同于枚举的访问级别(无法单独设置),同时枚举的原始值、关联值的访问级别不能低于枚举的访问级别;
    12. 泛型类型或泛型函数的访问级别是泛型类型、泛型函数、泛型类型参数三者中最低的一个;
    13. 类型别名的访问级别不能高于原类型的访问级别;

     上面这些规则看上去比较繁琐,但其实很多内容理解起来也是顺理成章的(如果你是一个语言设计者相信大部分规则也会这么设计),下面通过一个例子对于规则3做一解释,这一点和其他语言有所不同但是却更加实用。在使用ObjC开发时大家通常会有这样的经验:在一个类中希望某个属性对外界是只读的,但是自己又需要在类中对属性进行写操作,此时只能直接访问属性对应的成员变量,而不能直接访问属性进行设置。但是Swift为了让语法尽可能精简,并没有成员变量的概念,此时就可以通过访问控制来实现。

    Person.swift

    import Foundation

    public class Person {
    //设置setter私有,但是getter为public
    public private(set) var name:String

    public init(name:String){
    self.name = name
    }

    public func showMessage(){
    println("name=\(name)")
    }
    }

    main.swift

    import Foundation

    var p = Person(name:"Kenshin")
    //此时不能设置name属性,但是可读
    //p.name = "Kaoru"
    println("name=\(p.name)")
    p.showMessage() 

    Xcode中的每个构建目标(Target)可以当做是一个模块(Module),这个构建目标可以是一个Application,也可以是一个通用的Framework(更多的时候是一个Application)。

    Swift命名空间

    熟悉ObjC的朋友都知道ObjC没有命名空间,为了避免类名重复苹果官方推荐使用类名前缀,这种做法从一定程度上避免了大部分问题,但是当你在项目中引入一个第三方库而这个第三方库引用了一个和你当前项目中用到的同一个库时就会出现问题。因为静态库最终会编译到同一个域,最终导致编译出错。当然作为一个现代化语言Swift一定会解决这个问题,可是如果查看Swift的官方文档,里面关于Swift的命名空间并没有太多详细的说明。但是Swift的作者Chris Lattner在Twitter中回答了这个问题:

    Namespacing is implicit in swift, all classes (etc) are implicitly scoped by the module (Xcode target) they are in. no class prefixes needed

    Swift中是实现了命名空间功能的,只是这个命名空间不像C#的namespace或者Java中的package那样需要显式在文件中指定,而是采用模块(Module)的概念:在同一个模块中所有的Swift类处于同一个命名空间,它们之间不需要导入就可以相互访问。很明显Swift的这种做法是为了最大限度的简化Swift编程。其实一个module就可以看成是一个project中的一个target,在创建项目的时候默认就会创建一个target,这个target的默认模块名称就是这个项目的名称(可以在target的Build Settings—Product Module Name配置)。

    下面不妨看一个命名空间的例子,创建一个Single View Application应用“NameSpaceDemo”。默认情况下模块名称为“NameSpaceDemo”,这里修改为“Network”,并且添加”HttpRequest.swift"。然后添加一个Cocoa Touch Framework类型的target并命名为“IO”,添加“File.swift”。然后在ViewController.swift中调用HttpRequest发送请求,将请求结果利用File类来保存起来。

    File.swift

    import Foundation

    public class File {
    public var path:String!

    public init(path:String) {
    self.path = path
    }

    public func write(content:String){
    var error:NSError?
    content.writeToFile(path, atomically: true, encoding:NSUTF8StringEncoding, error: &error)
    if error != nil {
    println("write failure...")
    }
    }

    public func read() ->String?{
    var error:NSError?
    var content = String(contentsOfFile: path, encoding: NSUTF8StringEncoding, error: &error)
    if error != nil {
    println("write failure...")
    }
    return content
    }
    }

    HttpRequest.swift

    import Foundation

    class HttpRequest {
    class func request(urlStr:String,complete:(responseText:String?)->()){
    var url = NSURL(string: urlStr)
    let task = NSURLSession.sharedSession().dataTaskWithURL(url!) { (data, response, error) -> Void in
    var str:String?
    if error == nil {
    str = NSString(data: data, encoding: NSUTF8StringEncoding) as? String
    }
    complete(responseText: str)
    }
    task.resume()
    }
    }

     

    ViewController.swift

    import UIKit

    //导入模块
    import IO

    class ViewController: UIViewController {
    let url = "http://www.cnblogs.com/kenshincui"
    let filePath = "/Users/KenshinCui/Desktop/file.txt"

    override func viewDidLoad() {
    super.viewDidLoad()
    //加上命名空间Network调用,注意这里命名空间可以省略
    Network.HttpRequest.request(url, complete: { (responseText) -> () in
    if let txt = responseText {

    //调用模块中的类和方法
    var file = File(path: self.filePath)
    file.write(txt)
    // println(file.read()!)

    }else{
    println("error...")
    }
    })

    }
    }

    可以看到首先同一个Module中的HttpRequest类可以加上命名空间调用(当然这里可以省略),另外对于不同Modle下的File类通过导入IO模块可以直接使用File类,但是这里需要注意访问控制,可以看到File类及其成员均声明为了public访问级别。 用模块进行命名空间划分的方式好处就是可以不用显式指定命名空间,然而这种方式无法在同一个模块中再进行划分,不过这个问题可以使用Swift中的嵌套类型来解决。在下面的例子中仍然使用前面的两个类HttpRequest和File类来演示,不同的是两个类分别嵌套在两个结构体Network和IO之中。

    Network.HttpRequest.swift

    import Foundation

    struct Network {
    class HttpRequest {
    class func request(urlStr:String,complete:(responseText:String?)->()){
    var url = NSURL(string: urlStr)
    let task = NSURLSession.sharedSession().dataTaskWithURL(url!) { (data, response, error) -> Void in
    var str:String?
    if error == nil {
    str = NSString(data: data, encoding: NSUTF8StringEncoding) as? String
    }
    complete(responseText: str)
    }
    task.resume()
    }
    }
    }

    IO.File.swift

    import Foundation

    struct IO {
    class File {
    var path:String!

    init(path:String) {
    self.path = path
    }

    func write(content:String){
    var error:NSError?
    content.writeToFile(path, atomically: true, encoding:NSUTF8StringEncoding, error: &error)
    if error != nil {
    println("write failure...")
    }
    }

    func read() ->String?{
    var error:NSError?
    var content = String(contentsOfFile: path, encoding: NSUTF8StringEncoding, error: &error)
    if error != nil {
    println("write failure...")
    }
    return content
    }
    }
    }

    main.swift

    import Foundation

    let url = "http://www.cnblogs.com/kenshincui"
    let filePath = "/Users/KenshinCui/Desktop/file.txt"

    Network.HttpRequest.request(url, complete: { (responseText) -> () in
    if let txt = responseText {

    var file = IO.File(path: filePath)
    file.write(txt)
    //println(file.read()!)

    }else{
    println("error...")
    }
    })

    sleep(30) //延迟30s避免命令行程序运行完进程结束,等待网络请求

    Swift和ObjC互相调用

    Swift的设计的初衷就是摆脱ObjC沉重的历史包袱,毕竟ObjC的历史太过悠久,相比于很多现代化语言它缺少一些很酷的语法特性,而且ObjC的语法和其他语言相比差别很大。但是Apple同时也不能忽视ObjC的地位,毕竟ObjC经过二十多年的历史积累了大量的资源(开发者、框架、类库等),因此在Swift推出的初期必须考虑兼容ObjC。但同时Swift和ObjC是基于两种不同的方式来实现的(例如ObjC可以在运行时决定对象类型,但是Swift为了提高效率要求在编译时就必须确定对象类型),所以要无缝兼容需要做大量的工作。而作为开发人员我们有必要了解两种语言之间的转化关系才能对Swift有更深刻的理解。

    Swift和ObjC映射关系

    其实从前面的例子中大家不难发现Swift和ObjC必然存在着一定的映射关系,例如对于文件的操作使用了字符串的writeToFile方法,在网络请求时使用的NSURLSession,虽然调用方式不同但是其参数完全和做ObjC开发时调用方式一致。原因就是Swift编译器自动做了映射,下面列举了部分Swift和ObjC的映射关系帮助大家理解:

    Swift ObjC 备注
    AnyObject id(ObjC中的对象任意类型) 由于ObjC中的对象可能为nil,所以Swift中如果用到ObjC中类型的参数会标记为对应的可选类型
    Array、Dictionary、Set NSArray、NSDictionary、NSSet 注意:ObjC中的数组和字典不能存储基本数据类型,只能存储对象类型,这样一来对于Swift中的Int、UInt、Float、Double、Bool转化时会自动桥接成NSNumber
    Int NSInteger、NSUInteger 其他基本类型情况类似,不再一一列举
    NSObjectProtocol NSObject协议(注意不是NSObject类) 由于Swift在继承或者实现时没有类的命名空间的概念,而ObjC中既有NSObject类又有NSObject协议,所以在Swift中将NSObject协议对应成了NSObjectProtocol
    CGContext CGContextRef

    Core Foundation中其他情况均是如此,由于Swift本身就是引用类型,在Swift不需要再加上“Ref”

    ErrorType NSError  
    “ab:" @selector(ab:)

    Swift可以自动将字符串转化成成selector

    @NSCopying copy属性  
    init(x:X,y:Y) initWithX:(X)x y:(Y)y 构造方法映射,Swift会去掉“With”并且第一个字母小写作为其第一个参数,同时也不需要调用alloc方法,但是需要注意ObjC中的便利工厂方法(构建对象的静态方法)对应成了Swift的便利构造方法
    func xY(a:A,b:B) void xY:(A)a b:(B)b  
    extension(扩展) category(分类) 注意:不能为ObjC中存在的方法进行extension
    Closure(闭包) block(块) 注意:Swift中的闭包可以直接修改外部变量,但是block中要修改外部变量必须声明为__block

    Swift兼容大部分ObjC(通过类似上面的对应关系),多数ObjC的功能在Swift中都能使用。当然,还是有个别地方Swift并没有考虑兼容ObjC,例如:Swift中无法使用预处理指令(例如:宏定义,事实上在Swift中推举使用常量定义);Swift中也无法使用performSelector来执行一个方法,因为Swift认为这么做是不安全的。

    相反,如果在ObjC中使用Swift也同样是可行的(除了个别Swift新增的高级功能)。Swift中如果一个类继承于NSObject,那么他会自动和ObjC兼容,这样ObjC就可以按照上面的对应关系调用Swift的方法、属性等。但是如果Swift中的类没有继承于NSObject呢?此时就需要使用一个关键字“@objc”进行标注,ObjC就可以像使用正常的ObjC编码一样调用Swift了(事实上继承于NSObject的类之所以在ObjC中能够直接调用也是因为编译器会自动给类和非private成员添加上@objc,类似的@IBoutlet、@IBAction、@NSManaged修饰的方法属性Swift编译器也会自动添加@objc标记)。

    Swift调用ObjC 

    当前ObjC已经积累了大量的第三方库,相信在Swift发展的前期调用已经存在的ObjC是比较常见的。在Swift和ObjC的兼容性允许你在一个项目中使用两种语言混合编程(称为“mix and match”),而不管这个项目原本是基于Swift的还是ObjC的。无论是Swift中调用ObjC还是ObjC中调用Swift都是通过头文件暴漏对应接口的,下图说明了这种交互方式:

    SwiftInteractObjC

    不难发现,要在Swift中调用ObjC必须借助于一个桥接头文件,在这个头文件中将ObjC接口暴漏给Swift。例如你可以创建一个“xx.h”头文件,然后使用“#import”导入需要在Swift中使用的ObjC类,同时在Build Settings的“Objective-C Bridging Header”中配置桥接文件“xx.h”。但是好在这个过程Xcode可以帮助你完成,你只需要在Swift项目中添加ObjC文件,Xcode就会询问你是否创建桥接文件,你只需要点击“Yes”就可以帮你完成上面的操作:

    CreateObjCBridgingHeaderTip

    为了演示Swift中调用ObjC的简洁性, 下面创建一个基于Swift的Single View Application类型的项目,现在有一个基于ObjC的“KCLoadingView”类,它可以在网络忙时显示一个加载动画。整个类的实现很简单,就是通过一个基础动画实现一个图片的旋转。

    KCLoadingView.h

    #import <UIKit/UIKit.h>

    /**
    * 加载视图,显示加载效果
    */
    @interface KCLoadingView : UIImageView

    /**
    * 启动,开始旋转
    */
    - (void)start;

    /**
    * 停止
    */
    - (void)stop;

    @end

    KCLoadingView.m

    #import "KCLoadingView.h"

    static NSString *const kAnimationKey = @"rotationAnimation";
    @interface KCLoadingView ()
    @property(strong, nonatomic) CABasicAnimation *rotationAnimation;
    @end

    @implementation KCLoadingView
    #pragma mark - 生命周期及其基类方法
    - (instancetype)initWithFrame:(CGRect)frame {
    if (self = [super initWithFrame:frame]) {
    [self setup];
    }
    return self;
    }

    #pragma mark - 公共方法
    - (void)start {
    [self.layer addAnimation:self.rotationAnimation forKey:kAnimationKey];
    }

    - (void)stop {
    [self.layer removeAnimationForKey:kAnimationKey];
    }

    #pragma mark - 私有方法
    - (void)setup {
    self.image = [UIImage imageNamed:@"loading"];

    CABasicAnimation *rotationAnimation = [CABasicAnimation animationWithKeyPath:@"transform.rotation.z"];
    rotationAnimation.toValue = [NSNumber numberWithFloat:M_PI * 2.0];
    rotationAnimation.duration = 0.7;
    rotationAnimation.cumulative = YES;
    rotationAnimation.repeatCount = HUGE_VALF;
    self.rotationAnimation = rotationAnimation;
    [self.layer addAnimation:rotationAnimation forKey:kAnimationKey];
    }

    @end

    当将这个类加入到项目时就会提示你是否创建一个桥接文件,在这个文件中导入上面的“KCLoadingView”类。现在这个文件只有一行代码

    ObjCBridge-Bridging-Header.h

    #import "KCLoadingView.h" 

    接下来就可以调用这个类完成一个加载动画,调用关系完全顺其自然,开发者根本感觉不到这是在调用一个ObjC类。

    ViewController.swfit

    import UIKit

    class ViewController: UIViewController {
    lazy var loadingView:KCLoadingView = {
    var size=UIScreen.mainScreen().bounds.size
    var lv = KCLoadingView()
    lv.frame.size=CGSizeMake(37.0, 37.0)
    lv.center=CGPointMake(size.width*0.5, size.height*0.5)
    return lv
    }()

    lazy private var converView:UIView = {
    var cv = UIView(frame: UIScreen.mainScreen().bounds)
    cv.backgroundColor = UIColor(red: 0.0, green: 0.0, blue: 0.0, alpha: 0.5)
    return cv
    }()

    override func loadView() {
    //设置背景
    var image = UIImage(named: "iOS9")
    var background = UIImageView(frame: UIScreen.mainScreen().bounds)
    background.userInteractionEnabled=true
    background.image=image
    self.view = background
    }

    override func viewDidLoad() {
    super.viewDidLoad()

    //设置蒙层
    self.view.addSubview(self.converView)

    //添加加载控件
    self.view.addSubview(self.loadingView)

    loadingView.start()
    }

    override func touchesBegan(touches: Set, withEvent event: UIEvent) {
    loadingView.stop()
    }
    }

    运行效果

    SwiftUseObjC 

    ObjC调用Swift

    从前面的Swift和ObjC之间的交互图示可以看到ObjC调用Swift是通过Swift生成的一个头文件实现的,好在这个头文件是由编译器自动完成的,开发者不需要关注,只需要记得他的格式即可“项目名称-Swift.h”。如果在ObjC项目中使用了Swift,只要在ObjC的“.m”文件中导入这个头文件就可以直接调用Swift,注意这个生成的文件并不在项目中,它在项目构建的一个文件夹中(可以按住Command点击头文件查看)。同样通过前面的例子演示如何在ObjC中调用Swift,新建一个基于ObjC的项目(项目名称“UseSwiftInObjC”),并且这次加载动画控件使用Swift编写。

    LoadingView.swift

    import UIKit

    public class LoadingView:UIImageView {
    let basicAnimationKey = "rotationAnimation"
    lazy var rotationAnimation:CABasicAnimation = {
    var animation = CABasicAnimation(keyPath: "transform.rotation.z")
    animation.toValue = 2*M_PI
    animation.duration = 0.7
    animation.cumulative = true
    animation.repeatCount = .infinity
    return animation
    }()

    convenience init(){
    self.init(frame: CGRectZero)
    }

    override init(frame: CGRect) {
    super.init(frame: frame)
    self.image = UIImage(named: "loading")
    }

    required public init(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
    self.image = UIImage(named: "loading")
    }

    public func start() {
    self.layer.addAnimation(self.rotationAnimation, forKey: basicAnimationKey)
    }

    public func stop() {
    self.layer.removeAnimationForKey(basicAnimationKey)
    }
    }

     然后可以直接在ObjC代码中导入自动生成的文件“UseSwiftInObjC-Swift.h”并调用。

    ViewController.m

    #import "ViewController.h"
    #import "UseSwiftInObjC-Swift.h"

    @interface ViewController ()
    @property (strong,nonatomic) UIView *converView;
    @property (strong,nonatomic) LoadingView *loadingView;
    @end

    @implementation ViewController
    -(void)loadView{
    UIImage *image = [UIImage imageNamed:@"iOS9"];
    UIImageView *background = [[UIImageView alloc]initWithImage:image];
    background.userInteractionEnabled = YES;
    self.view = background;
    }

    - (void)viewDidLoad {
    [super viewDidLoad];

    [self.view addSubview:self.converView];
    [self.view addSubview:self.loadingView];
    [self.loadingView start];
    }

    -(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
    [self.loadingView stop];
    }

    #pragma mark - 属性
    /**
    * 遮罩层
    */
    -(UIView *)converView{
    if (!_converView) {
    _converView = [[UIView alloc] initWithFrame:[UIScreen mainScreen].bounds];
    _converView.backgroundColor = [UIColor colorWithRed:0.0 green:0.0 blue:0.0 alpha:0.5];
    }
    return _converView;
    }
    /**
    * 加载指示器
    */
    -(LoadingView *)loadingView{
    if(!_loadingView){
    CGSize screenSize = [UIScreen mainScreen].bounds.size;
    CGFloat loadingViewWidth = 37.0;
    _loadingView=[[LoadingView alloc]init];
    _loadingView.frame=CGRectMake((screenSize.width-loadingViewWidth)*0.5, (screenSize.height - loadingViewWidth)*0.5, loadingViewWidth, loadingViewWidth);
    }
    return _loadingView;
    }

    @end

    虽然生成的头文件并不会直接放到项目中,但是可以直接按着Command键查看生成的文件内容,当然这个文件比较长,里面使用了很多宏定义判断,这里只关心最主要部分。

    UseSwiftInObjC-Swift.h

    SWIFT_CLASS("_TtC14UseSwiftInObjC11LoadingView")
    @interface LoadingView : UIImageView
    - (SWIFT_NULLABILITY(nonnull) instancetype)initWithCoder:(NSCoder * __nonnull)aDecoder OBJC_DESIGNATED_INITIALIZER;
    - (void)start;
    - (void)stop;
    @end

    可以清晰的看到Swift确实进行了桥接,通过头文件将接口暴漏给了ObjC。但是注意前面说过的访问控制,如果类和方法在Swift中不声明为public,那么在ViewController.m中是无法调用的。事实上,如果方法不是public在UseSwiftInObjC-Swift.h中根本不会生成对应的方法声明。

    扩展—Swift调用C

    由于ObjC是C的超集,使得在ObjC可以无缝访问C语言。但是Swift的产生就是ObjC without C,因此在Swift中不可能像在ObjC中混编入C一样简单。但是考虑到C语言的强大以及历时那么多年留下了丰富的类库,有时候又不得不使用它,Swift中还是保留了与一定数量的C语言类型和特性的兼容。前面介绍过关于如何在Swift中使用ObjC的知识,事实上在Swift中使用C也是类似的(因为ObjC是C的超集,ObjC既然可以桥接,C自然也可以),你需要一个桥接文件,不同的是ObjC中的很多内容在桥接到Swift时都是类似,很容易上手。例如ObjC中使用的NSObject,在Swift中仍然对应NSObject,很多时候开发人员感觉不到这种转化,只是编程语言发生了变化。但是C导入Swift就需要必须要了解具体的对应关系:

    C类型 Swift类型 说明
    基本类型    
    char,signed char CChar 类似的unsigned char对应CUnsignedChar
    int CInt 类似的unsigned int对应CUnsignedInt
    short CShort 类似的unsigned short对应CUnsignedShort
    long CLong 类似的unsigned long对应CUnsignedLong
    long long CLongLong 类似的unsigned long long 对应 CUnsignedLongLong
    float CFloat  
    double CDouble  
    构造体类型   注意:结构体实现
    枚举typedef NS_ENUM(NSInteger,A){AB,AC} enum A:Int{case B,C} 去掉对应的前缀 ,注意C中的NS_Options会对应成Swift中实现OptionSetType的结构体
    结构体 对应Swift中的结构体   
    联合   Swift中不能完全支持联合,建议使用枚举关联值代替
    指针类型   C语言中的指针类型映射成了Swift中的泛型
    Type * UnsafeMutablePointer<Type> 作为返回类型、变量、参数类型时
    const Type * UnsafePointer<Type> 作为返回类型、变量、参数类型时
    Type *const * UnsafePointer<Type> 对于类类型
    Type *__strong * UnsafeMutablePointer<Type> 对于类类型
    Type * * AutoreleasingUnsafePointer<Type> 对于类类型
    函数指针 闭包  

     对于其他类型的映射关系都很容易理解,这里主要说一下指针的内容。通过上表可以看到在C中定义的一些指针类型当在Swift中使用时会有对应的类型,但是如果一个参数为某种指针类型,实际调用时应该使用何种Swift数据类型的数据作为参数调用呢?例如参数为UnsafePointer<Type>,是否只能传入UnsafePointer<Type>呢,其实也可以传入nil,并且最终调用时将会转化为null指针来调用。下表列出了这种参数调用对应关系:

    可用类型 最终转化类型
     UnsafePointer<Type>   注意:如果Type为Void则可以代表任何类型
     nil  null
     UnsafePointer<Type>、UnsafeMutablePointer<Type>、AutoreleasingUnsafeMutablePointer<Type>  UnsafePointer<Type>
     String  如果Type为Int、或者Int8将最终转化为UTF8字符串
     &typeValue  元素地址
     Type类型的数组([typeValue1,typeValue2])  数组首地址
     UnsafeMutablePointer<Type>  注意:如果Type为Void则可以代表任何类型
     nil null 
     UnsafeMutablePointer<Type>  UnsafeMutablePointer<Type> 
     &typeValue  元素地址
     Type类型的数组的地址(&[typeValue1,typeValue2])  数组地址
     AutoreleasingUnsafeMutablePointer<Type>  
     nil null 
     AutoreleasingUnsafeMutablePointer<Type>  AutoreleasingUnsafeMutablePointer<Type>
     &typeValue  元素地址

    下面不妨看一下如何在Swift中使用C语言,假设现在有一个用于字符串拼接的C库函数“stringAppend(char*,const char *)”,将其对应的文件导入到一个Swift项目中按照提示添加桥接头文件并在桥接头文件中引入对应的C文件。

    string.h

    #ifndef __UseCInSwift__Common__
    #define __UseCInSwift__Common__void stringAppend(char *source, char *toAppend)

    #include

    void stringAppend(char *source,const char *toAppend);

    #endif

    string.c

    #include "string.h"

    void stringAppend(char *source,const char *toAppend) {
    unsigned long sourceLen = strlen(source);
    char *pSource = source + sourceLen;
    const char *pAppend = toAppend;
    while (*pAppend != '\0') {
    *pSource++ = *pAppend++;
    }
    }

    UseCInSwift-Bridging-Header.h

    #import "string.h"

    然后在Swift中调用上面的C函数

    import Foundation

    var sourceStr:String = "Hello"
    var appendStr:String = ",World!"

    var sourceCStr = (sourceStr as NSString).UTF8String
    var sourceMutablePointer:UnsafeMutablePointer = UnsafeMutablePointer(sourceCStr)

    stringAppend(sourceMutablePointer,appendStr)

    println(String.fromCString(sourceMutablePointer)!) //结果:Hello,World!

     可以看到“char *”参数转化成了Swift中的UnsafeMutablePointer<Int8>,而将”const char *”转化成了UnsafePointer<Int8>。根据上面表格中的调用关系,如果参数为UnsafeMutablePointer<Type>可以传入nil、UnsafeMutablePointer<Type>或者元素地址,很明显这里需要使用UnsafeMutablePointer<Int8>;而如果参数为UnsafePointer<Type>并且Type为Int8或者Int则可以直接传入String类型的参数,因此也就有了上面的调用关系。

    当然,上面这种方式适合所有在Swift中引入C语言的情况,但是为了方便调用,在Swift中默认已经module了常用的C语言类库Darwin,这个类库就作为了标准的Swift类库不需要再进行桥接,可以直接导入模块(例如import Darwin,但是事实上Foundation模块已经默认导入了Darwin,而UIKit又导入了Foundation模块,因此通常不需要手动导入Darwin)。那么对于没有模块化的C语言类库(包括第三方类库和自己定义的C语言文件等)能不能不使用桥接文件呢?答案就是使用隐藏符号“@asmname”,通过@asmname可以将C语言的函数不经过桥接文件直接映射为Swift函数。例如可以移除上面的桥接头文件,修改main.swift函数,通过@asmname加stringAppend映射成为Swift函数(注意重新映射的Swift函数名称不一定和C语言函数相同):

     main.swift

    import Foundation

    //通过asmname将C函数stringAppend()映射到Swift函数,事实上这里的Swift函数名可以任意命名
    @asmname("stringAppend") func stringAppend(var sourceStr:UnsafeMutablePointer,var apendStr:UnsafePointer ) -> Void

    var sourceStr:String = "Hello"
    var appendStr:String = ",World!"

    var sourceCStr = (sourceStr as NSString).UTF8String
    var sourceMutablePointer:UnsafeMutablePointer = UnsafeMutablePointer(sourceCStr)

    stringAppend(sourceMutablePointer,appendStr)

    println(String.fromCString(sourceMutablePointer)!) //结果:Hello,World!

    更多Swift标准类库信息可以查看:https://github.com/andelf/Defines-Swift 

    反射

    熟悉C#、Java的朋友不难理解反射的概念,所谓反射就是可以动态获取类型、成员信息,在运行时可以调用方法、属性等行为的特性。 在使用ObjC开发时很少强调其反射概念,因为ObjC的Runtime要比其他语言中的反射强大的多。在ObjC中可以很简单的实现字符串和类型的转换(NSClassFromString()),实现动态方法调用(performSelector: withObject:),动态赋值(KVC)等等,这些功能大家已经习以为常,但是在其他语言中要实现这些功能却要跨过较高的门槛,而且有些根本就是无法实现的。不过在Swift中并不提倡使用Runtime,而是像其他语言一样使用反射(Reflect),即使目前Swift中的反射还没有其他语言中的反射功能强大(Swift还在发展当中,相信后续版本会加入更加强大的反射功能)。

    在Swift中反射信息通过MirrorType协议来描述,而Swift中所有的类型都能通过reflect函数取得MirrorType信息。先看一下MirrorType协议的定义(为了方便大家理解,添加了相关注释说明):

    protocol MirrorType {

    /// 被反射的成员,类似于一个实例做了as Any操作
    var value: Any { get }

    /// 被反射成员的类型
    var valueType: Any.Type { get }

    /// 被反射成员的唯一标识
    var objectIdentifier: ObjectIdentifier? { get }

    /// 被反射成员的子成员数(例如结构体的成员个数,数组的元素个数等)
    var count: Int { get }

    // 取得被反射成员的字成员,返回值对应字成员的名称和值信息
    subscript (i: Int) -> (String, MirrorType) { get }

    /// 对于反射成员的描述
    var summary: String { get }

    /// 显示在Playground中的“值”信息
    var quickLookObject: QuickLookObject? { get }

    /// 被反射成员的类型的种类(例如:基本类型、结构体、枚举、类等)
    var disposition: MirrorDisposition { get }
    }

    获取到一个变量(或常量)的MirrorType之后就可以访问其类型、值、类型种类等元数据信息。在下面的示例中将编写一个函数简单实现一个类似于ObjC中“valueForKey:”的函数。

    import UIKit

    struct Person {
    var name:String
    var age:Int = 0

    func showMessage(){
    print("name=\(name),age=\(age)")
    }
    }


    //定义一个方法获取实例信息
    func valueForKey(key:String,obj:Any) -> Any?{
    //获取元数据信息
    var objInfo:MirrorType = reflect(obj)
    //遍历子成员
    for index in 0..<objInfo.count {
    //如果子成员名称等于key则获取对应值
    let (name,mirror) = objInfo[index]
    if name == key {
    return mirror.value
    }
    }
    return nil;
    }

    var p = Person(name: "Kenshin", age: 29)
    //先查看一下对象描述信息,然后对照结果是否正确
    dump(p)
    /*结果:
    __lldb_expr_103.Person
    - name: Kenshin
    - age: 29
    */

    var name = valueForKey("name", p)
    print("p.name=\(name)") //结果:p.name=Optional("Kenshin")

    可以看到,通过反射可以获取到变量(或常量)的信息,并且能够读取其成员的值,但是Swift目前原生并不支持给某个成员动态设置值(MirrorType的value属性是只读的)。如果想要进行动态设置,可以利用前面介绍的Swift和ObjC兼容的知识来实现,Swift目前已经导入了Foundation,只要这个类是继承于NSObject就会有对应的setValue:forKey:方法来使用KVC。当然,这仅限于类,对应结构体无能为力。

    扩展--KVO

    和KVC一样,在Swift中使用KVO也仅限于NSObject及其子类,因为KVO本身就是基于KVC进行动态派发的,这些都属于运行时的范畴。Swift要实现这些动态特性需要在类型或者成员前面加上@objc(继承于NSObject的子类及非私有成员会自动添加),但并不是说加了@objc就可以动态派发,因为Swift为了性能考虑会优化为静态调用。如果确实需要使用这些特性Swift提供了dynamic关键字来修饰,例如这里要想使用KVO除了继承于NSObject之外就必须给监控的属性加上dynamic关键字修饰。下面的演示中说明了这一点:

    import Foundation

    class Acount:NSObject {
    dynamic var balance:Double = 0.0
    }

    class Person:NSObject {
    var name:String
    var account:Acount?{
    didSet{
    if account != nil {
    account!.addObserver(self, forKeyPath: "balance", options: .Old, context: nil);
    }
    }
    }

    init(name:String){
    self.name = name
    super.init()
    }

    override func observeValueForKeyPath(keyPath: String, ofObject object: AnyObject, change: [NSObject : AnyObject], context: UnsafeMutablePointer) {
    if keyPath == "balance" {
    var oldValue = change[NSKeyValueChangeOldKey] as! Double
    var newValue = (account?.balance)!
    print("oldValue=\(oldValue),newValue=\(newValue)")
    }
    }
    }

    var p = Person(name: "Kenshin Cui")
    var account = Acount()
    account.balance = 10000000.0
    p.account = account
    p.account!.balance = 999999999.9 //结果:oldValue=10000000.0,newValue=999999999.9

    注意:对于系统类(或一些第三方框架)由于无法修改其源代码如果要进行KVO监听,可以先继承此类然后进行使用dynamic重写;此外,并非只有KVO需要加上dynamic关键字,对于很多动态特性都是如此,例如要在Swift中实现Swizzle方法替换,方法前仍然要加上dynamic,因为方法的替换也需要动态派发。

    内存管理

    循环引用

    Swift使用ARC来自动管理内存,大多数情况下开发人员不需要手动管理内存,但在使用ObjC开发时,大家都会遇到循环引用的问题,在Swift中也不可避免。 举例来说,人员有一个身份证(Person有idCard属性),而身份证就有一个拥有者(IDCard有owner属性),那么对于一个Person对象一旦建立了这种关系之后就会和IDCard对象相互引用而无法被正确的释放。

    例如下面的代码在执行完test之后p和idCard两个对象均不会被释放:

    import Foundation

    class Person {
    var name:String
    var idCard:IDCard

    init(name:String,idCard:IDCard){
    self.name = name
    self.idCard = idCard
    idCard.owner = self
    }

    deinit{
    println("Person deinit...")
    }
    }

    class IDCard {
    var no:String
    var owner:Person?

    init(no:String){
    self.no = no
    }

    deinit{
    println("IDCard deinit...")
    }
    }

    func test(){
    var idCard = IDCard(no:"100188888888888888")
    var p = Person(name: "Kenshin Cui",idCard:idCard)
    }

    //注意test执行完之后p和idCard均不会被释放(无法执行deinit方法)
    test()

    println("wait...")

    两个对象之间的引用关系如下图:

    CircularRefrence

    为了避免这个问题Swift采用了和ObjC中同样的概念:弱引用,通常将被动的一方的引用设置为弱引用来解决循环引用问题。例如这里可以将IDCard中的owner设置为弱引用。因为IDCard对于Person的引用变成了弱引用,而Person持有IDCard的强引用,这样一来Person作为主动方,只要它被释放后IDCard也会跟着释放。如要声明弱引用可以使用weak和unowned关键字,前者用于可选类型后者用于非可选类型,相当于ObjC中的__weak和__unsafe_unretained(因为weak声明的对象释放后会设置为nil,因此它用来修饰可选类型)。

    import Foundation

    class Person {
    var name:String
    var idCard:IDCard

    init(name:String,idCard:IDCard){
    self.name = name
    self.idCard = idCard
    idCard.owner = self
    }

    deinit{
    println("Person deinit...")
    }
    }

    class IDCard {
    var no:String
    //声明为弱引用
    weak var owner:Person?

    init(no:String){
    self.no = no
    }

    deinit{
    println("IDCard deinit...")
    }
    }

    func test(){
    var idCard = IDCard(no:"100188888888888888")
    var p = Person(name: "Kenshin Cui",idCard:idCard)
    }

    //注意test执行完之后p会被释放,其后idCard跟着被释放
    test()

    println("wait...")

    现在两个对象之间的引用关系如下图:

    WeakRefrence

    当然类似于上面的引用关系实际遇到的并不多,更多的还是存在于闭包之中(ObjC中多出现于Block中),因为闭包会持有其内部引用的元素。下面简单修改一下上面的例子,给Person添加一个闭包属性,并且在其中访问self,这样闭包自身就和Person类之间形成循环引用。

    import Foundation

    class Person {
    let name:String

    //下面的默认闭包实现中使用了self,会引起循环引用
    lazy var description:()->NSString = {
    return "name = \(self.name)"
    }

    init(name:String){
    self.name = name
    }

    deinit{
    println("Person deinit...")
    }
    }

    func test(){
    var p = Person(name: "Kenshin Cui")
    println(p.description())
    }

    test()

    println("wait...")
    /**打印结果
    name = Kenshin Cui
    wait...
    */

    Swift中使用闭包捕获列表来解决闭包中的循环引用问题,这种方式有点类似于ObjC中的weakSelf方式,当时语法更加优雅, 具体实现如下:

    import Foundation

    class Person {
    let name:String

    //使用闭包捕获列表解决循环引用
    lazy var description:()->NSString = {
    [unowned self] in
    return "name = \(self.name)"
    }

    init(name:String){
    self.name = name
    }

    deinit{
    println("Person deinit...")
    }
    }

    func test(){
    var p = Person(name: "Kenshin Cui")
    println(p.description())
    }

    test()

    println("wait...")
    /**打印结果
    name = Kenshin Cui
    Person deinit...
    wait...
    */

    指针与内存

    除了循环引用问题,Swift之所以将指针类型标识为“unsafe”是因为指针没办法像其他类型一样进行自动内存管理,因此有必要了解一下指针和内存的关系。在Swift中初始化一个指针必须通过alloc和initialize两步,而回收一个指针需要调用destroy和dealloc(通常dealloc之后还会将指针设置为nil)。

    import Foundation

    class Person {
    var name:String

    init(name:String){
    self.name = name
    }

    deinit{
    println("Person\(name) deinit...")
    }
    }

    func test(){
    var p = Person(name: "Kenshin Cui")

    //虽然可以使用&p作为参数进行inout参数传递,但是无法直接获取其地址,下面的做法是错误的
    //var address = &p

    /*创建一个指向Person的指针pointer*/
    //申请内存(alloc参数代表申请n个Person类型的内存)
    var pointer:UnsafeMutablePointer = UnsafeMutablePointer.alloc(1)
    //初始化
    pointer.initialize(p)

    //获取指针指向的对象
    var p2 = pointer.memory
    println(p===p2) //结果:true,因为p和p2指向同一个对象
    //修改对象的值
    p2.name = "Kaoru"
    println(p.name) //结果:Kaoru


    //销毁指针
    pointer.destroy()
    //释放内存
    pointer.dealloc(1)
    //指向空地址
    pointer = nil
    }

    test()

    println("waiting...")
    /**打印结果

    Kaoru
    PersonKaoru deinit...
    waiting...

    */

    运行程序可以看到p对象在函数执行结束之后被销毁,但是如果仅仅将pointer设置为nil是无法销毁Person对象的,这很类似于之前的MRC内存管理,在Swift中使用指针需要注意:谁创建(alloc,malloc,calloc)谁释放。 当然上面演示中显然对于指针的操作略显麻烦,如果需要对一个变量进行指针操作可以借助于Swift中提供的一个方法withUnsafePointer。例如想要利用指针修改Person的name就可以采用下面的方式:

    var p = Person(name: "Kenshin Cui")

    var p2 = withUnsafeMutablePointer(&p, {
    (pointer:UnsafeMutablePointer) -> Person in
    pointer.memory.name = "Kaoru"
    return pointer.memory
    })

    println(p.name) //结果:Kaoru

    在前面的C语言系列文章中有一部分内容用于介绍如何利用指针遍历一个数组,当然在Swift中仍然可以采用这种方式,但是在Swift中如果想要使用指针操作数组中每个元素的话通常借助于另一个类型UnsafeMutableBufferPointer。这个类表示一段连续内存,通常用于表示数组或字典的指针类型。

    import Foundation

    var array:[String] = ["Kenshin","Kaorsu","Tom"]

    //UnsafeBufferPointer和UnsafeMutableBufferPointer用于表示一段连续内存的指针,例如:数组或字典
    //下面创建一个指向数组的指针
    var pointer = UnsafeMutableBufferPointer(start: &array, count: 3)

    //baseAddress属性表示内存首地址
    var baseAddress = pointer.baseAddress as UnsafeMutablePointer
    println(baseAddress.memory) //结果:Kenshin

    //利用指针遍历数组
    for index in 1...pointer.count {
    println(baseAddress.memory)
    //向后移动指针,向前移动使用baseAddress.predecessor()
    baseAddress = baseAddress.successor()
    }
    /**打印结果
    Kenshin
    Kaorsu
    Tom
    */

     扩展—Core Foundation

    Core Foundation作为iOS开发中最重要的框架之一,在iOS开发中有着重要的地位,但是它是一组C语言接口,在使用时需要开发人员自己管理内存。在Swift中使用Core Foundation框架(包括其他Core开头的框架)需要区分这个API返回的对象是否进行了标注:

    1.如果已经标注则在使用时完全不用考虑内存管理(它可以自动管理内存)。

    2.如果没有标注则编译器不会进行内存管理托管,此时需要将这个非托管对象转化为托管对象(当然你也可以使用retain()、release()或者autorelease()手动管理内存,但是不推荐这么做)。当然,苹果开发工具组会尽可能的标注这些API以实现C代码和Swift的自动桥接,但是在此之前未标注的API会返回Unmanaged<Type>结构,可以调用takeUnretainedValue()和takeRetainedValue()方法将其转化为可以自动进行内存管理的托管对象(具体是调用前者还是后者,需要根据是否需要开发者自己进行内存管理而定,其本质是使用takeRetainedValue()方法,在对象使用完之后会调用一次release()方法。按照Core Foundation的命名标准,通常如果函数名中含“Create”、“Copy”、“Retain”关键字需要调用takeRetainedValue()方法来转化成托管对象)。

    当然,上述两种方式均是针对系统框架而言,如果是开发者编写的类或者第三方类库,应该尽可能按照Cocoa规范命名并且在合适的地方使用CF_RETURNS_RETAINED和CF_RETURNS_NOT_RETAINED来进行标注以便可以进行自动内存管理。

    备注:

    1.在Swift中内存的申请除了使用alloc其实也可以使用malloc或者calloc,此时释放时使用free函数;

    2.关于更多内存管理的内容可以参见前面的文章:iOS开发系列http://www.cnblogs.com/kenshincui/p/3870325.html


    转载请注明本文地址:iOS开发系列--Swift进阶
    展开全文
  • get请求 //获取当前时间 let now = Date() //当前时间的时间戳 let timeInterval:TimeInterval = now.timeIntervalSince1970 let timeStamp = String(timeInterval) let url = URL(string: "...

    get请求

    //获取当前时间
    let now = Date()
    //当前时间的时间戳
    let timeInterval:TimeInterval = now.timeIntervalSince1970
    let timeStamp = String(timeInterval)
    let url = URL(string: "http://47.92.107.28:8000/static/banner.f?_=\(timeStamp)")!
    //发起请求,返回Json数据格式的数据
    Alamofire.request(url,method: .get,parameters: nil,encoding: URLEncoding.default,headers:nil).responseJSON { response
                in
                switch response.result.isSuccess {
                case true:
                    if let value = response.result.value{
                        self.imgageData = []
                        //获取返回的值,转为json对象
                        let img_json = JSON(value)
                        //json转字符串
                        let json_str = img_json.rawString()
                        let zhu_url = "http://47.92.107.28:8000"
                        //遍历json数据
                        for(key,item) in img_json["imgs"] {
                            //print("src的值:\(item["src"])")
                            //如果取得的 src 的值为 String类型的话就添加到数组中
                            if let img_url = item["src"].string{
                                //将图片路径添加到数组中
                                self.imgageData.append(zhu_url+img_url)
                            }
                        }
                        //将数组转为字符串
                        let str = self.imgageData.joined()
                        //print("请求到返回的数据\(json_str)")                  
                    }
                case false:
                    print(response.result.error)
                    UIAlertController.showAlert(message: "网络连接失败")
                }
            }

    Get请求、有参数、使用Basic Auth授权访问(例如:jira)

    var header:HTTPHeaders = [:]
            if let authorizationHeader = Request.authorizationHeader(user: "xxxx", password: "xxxxxx") {
                header[authorizationHeader.key] = authorizationHeader.value
            }
            // 此处的 URLEncoding.default (URLEncoding.queryString )会将parameters 拼接到url后面
            Alamofire.request("https://httpbin.org/get", method: HTTPMethod.get, parameters: ["key":"value"], encoding: URLEncoding.default, headers: header).responseJSON { (response) in
                if(response.error == nil){
                    LLog("请求成功")
                    LLog(response.result.value)
                }else{
                    LLog("请求失败\(String(describing: response.error))")
                }
    
            }

    POST请求

    Alamofire.request("https://httpbin.org/post", method: .post, parameters: ["key1":"value1","key2":"value2"], encoding: URLEncoding.default, headers: nil).responseJSON { (response) in
    
                if(response.error == nil){
                    LLog("请求成功")
                    LLog(response.result.value)
                }else{
                    LLog("请求失败\(String(describing: response.error))")
                }
    
            }

    post请求,提交json格式的数据

    // JSONEncoding.default === JSONEncoding.prettyPrinted
        Alamofire.request("https://httpbin.org/post", method: .post, parameters: ["key1":"value1","key2":"value2"], encoding: JSONEncoding.default, headers: nil).responseJSON { (response) in
    
                if(response.error == nil){
                    LLog("请求成功")
                    LLog(response.result.value)
                }else{
                    LLog("请求失败\(String(describing: response.error))")
                }
    
            }

    打开相册上传图片和视频

    import UIKit
    import AVFoundation
    import MobileCoreServices
    import AssetsLibrary
    import AVKit
    import Alamofire
    import MediaPlayer
    
    class ViewController: UIViewController,UIImagePickerControllerDelegate, UINavigationControllerDelegate {
    
        //设置标志,用于标识上传那种类型文件(图片/视频)
        var flag = ""
        //设置服务器地址
        let uploadURL = "https://www.hellomiao.cn/hellomiao/upload.php"
        override func viewDidLoad() {
            super.viewDidLoad()
    
    
        }
    
        //按钮事件:上传图片
        @IBAction func uploadImage(_ sender: Any) {
            photoLib()
        }
    
        //按钮事件:上传视频
        @IBAction func uploadVideo(_ sender: Any) {
            videoLib()
        }
    
        //图库 - 照片
        func photoLib(){
            //
            flag = "图片"
            //判断设置是否支持图片库
            if UIImagePickerController.isSourceTypeAvailable(.photoLibrary){
                //初始化图片控制器
                let picker = UIImagePickerController()
                //设置代理
                picker.delegate = self
                //指定图片控制器类型
                picker.sourceType = UIImagePickerControllerSourceType.photoLibrary
                //弹出控制器,显示界面
                self.present(picker, animated: true, completion: {
                    () -> Void in
                })
            }else{
                print("读取相册错误")
            }
        }
    
    
        //图库 - 视频
        func videoLib(){
            flag = "视频"
            if UIImagePickerController.isSourceTypeAvailable(.photoLibrary) {
                //初始化图片控制器
                let imagePicker = UIImagePickerController()
                //设置代理
                imagePicker.delegate = self
                //指定图片控制器类型
                imagePicker.sourceType = .photoLibrary;
                //只显示视频类型的文件
                imagePicker.mediaTypes =  [kUTTypeMovie as String]
                //不需要编辑
                imagePicker.allowsEditing = false
                //弹出控制器,显示界面
                self.present(imagePicker, animated: true, completion: nil)
            }
            else {
                print("读取相册错误")
            }
        }
    
        //选择成功后代理
        func imagePickerController(_ picker: UIImagePickerController,didFinishPickingMediaWithInfo info: [String : Any]) {
            if flag == "视频" {
    
                //获取选取的视频路径
                let videoURL = info[UIImagePickerControllerMediaURL] as! URL
                let pathString = videoURL.path
                print("视频地址:\(pathString)")
                //图片控制器退出
                self.dismiss(animated: true, completion: nil)
                let outpath = NSHomeDirectory() + "/Documents/\(Date().timeIntervalSince1970).mp4"
                //视频转码
                self.transformMoive(inputPath: pathString, outputPath: outpath)
            }else{
                //flag = "图片"
    
                //获取选取后的图片
                let pickedImage = info[UIImagePickerControllerOriginalImage] as! UIImage
                //转成jpg格式图片
                guard let jpegData = UIImageJPEGRepresentation(pickedImage, 0.5) else {
                    return
                }
                //上传
                self.uploadImage(imageData: jpegData)
                //图片控制器退出
                self.dismiss(animated: true, completion:nil)
            }
        }
    
        //上传图片到服务器
        func uploadImage(imageData : Data){
            Alamofire.upload(
                multipartFormData: { multipartFormData in
                    //采用post表单上传
                    // 参数解释:
                    //withName:和后台服务器的name要一致 ;fileName:可以充分利用写成用户的id,但是格式要写对; mimeType:规定的,要上传其他格式可以自行百度查一下
                    multipartFormData.append(imageData, withName: "file", fileName: "123456.jpg", mimeType: "image/jpeg")
                    //如果需要上传多个文件,就多添加几个
                    //multipartFormData.append(imageData, withName: "file", fileName: "123456.jpg", mimeType: "image/jpeg")
                    //......
    
            },to: uploadURL,encodingCompletion: { encodingResult in
                switch encodingResult {
                case .success(let upload, _, _):
                    //连接服务器成功后,对json的处理
                    upload.responseJSON { response in
                        //解包
                        guard let result = response.result.value else { return }
                        print("json:\(result)")
                    }
                    //获取上传进度
                    upload.uploadProgress(queue: DispatchQueue.global(qos: .utility)) { progress in
                        print("图片上传进度: \(progress.fractionCompleted)")
                    }
                case .failure(let encodingError):
                    //打印连接失败原因
                    print(encodingError)
                }
            })
        }
    
        //上传视频到服务器
        func uploadVideo(mp4Path : URL){
            Alamofire.upload(
                //同样采用post表单上传
                multipartFormData: { multipartFormData in
                    multipartFormData.append(mp4Path, withName: "file", fileName: "123456.mp4", mimeType: "video/mp4")
                    //服务器地址
            },to: uploadURL,encodingCompletion: { encodingResult in
                switch encodingResult {
                case .success(let upload, _, _):
                    //json处理
                    upload.responseJSON { response in
                        //解包
                        guard let result = response.result.value else { return }
                        print("json:\(result)")
                    }
                    //上传进度
                    upload.uploadProgress(queue: DispatchQueue.global(qos: .utility)) { progress in
                        print("视频上传进度: \(progress.fractionCompleted)")
                    }
                case .failure(let encodingError):
                    print(encodingError)
                }
            })
        }
    
        /// 转换视频
        ///
        /// - Parameters:
        ///   - inputPath: 输入url
        ///   - outputPath:输出url
        func transformMoive(inputPath:String,outputPath:String){
    
    
            let avAsset:AVURLAsset = AVURLAsset(url: URL.init(fileURLWithPath: inputPath), options: nil)
            let assetTime = avAsset.duration
    
            let duration = CMTimeGetSeconds(assetTime)
            print("视频时长 \(duration)");
            let compatiblePresets = AVAssetExportSession.exportPresets(compatibleWith: avAsset)
            if compatiblePresets.contains(AVAssetExportPresetLowQuality) {
                let exportSession:AVAssetExportSession = AVAssetExportSession.init(asset: avAsset, presetName: AVAssetExportPresetMediumQuality)!
                let existBool = FileManager.default.fileExists(atPath: outputPath)
                if existBool {
                }
                exportSession.outputURL = URL.init(fileURLWithPath: outputPath)
    
    
                exportSession.outputFileType = AVFileTypeMPEG4
                exportSession.shouldOptimizeForNetworkUse = true;
                exportSession.exportAsynchronously(completionHandler: {
    
                    switch exportSession.status{
    
                    case .failed:
    
                        print("失败...\(String(describing: exportSession.error?.localizedDescription))")
                        break
                    case .cancelled:
                        print("取消")
                        break;
                    case .completed:
                        print("转码成功")
                        let mp4Path = URL.init(fileURLWithPath: outputPath)
                        self.uploadVideo(mp4Path: mp4Path)
                        break;
                    default:
                        print("..")
                        break;
                    }
                })
            }
        }
    
    }

    访问图片是需要权限

    断点下载

    import UIKit
    import Alamofire
    
    class ViewController: UIViewController {
    
        //停止下载按钮
        @IBOutlet weak var stopBtn: UIButton!
        //继续下载按钮
        @IBOutlet weak var continueBtn: UIButton!
        //下载进度条
        @IBOutlet weak var progress: UIProgressView!
    
        //下载文件的保存路径(
        var destination:DownloadRequest.DownloadFileDestination!
        //用于停止下载时,保存已下载的部分
        var cancelledData: Data?
    
        //下载请求对象
        var downloadRequest: DownloadRequest!
    
        override func viewDidLoad() {
            super.viewDidLoad()
    
            //设置下载路径。保存到用户文档目录,文件名不变,如果有同名文件则会覆盖
             self.destination = { _, response in
                let documentsURL = FileManager.default.urls(for: .documentDirectory,
                                                            in: .userDomainMask)[0]
                let fileURL = documentsURL.appendingPathComponent(response.suggestedFilename!)
                //两个参数表示如果有同名文件则会覆盖,如果路径中文件夹不存在则会自动创建
                return (fileURL, [.removePreviousFile, .createIntermediateDirectories])
            }
    
            //页面加载完毕就自动开始下载
            self.downloadRequest =  Alamofire.download(
                "http://dldir1.qq.com/qqfile/qq/QQ7.9/16621/QQ7.9.exe", to: destination)
            self.downloadRequest.downloadProgress(queue: DispatchQueue.main,
                                                  closure: downloadProgress) //下载进度
            self.downloadRequest.responseData(completionHandler: downloadResponse) //下载停止响应
        }
    
        //下载过程中改变进度条
    
        func downloadProgress(progress: Progress) {
            //进度条更新
            self.progress.setProgress(Float(progress.fractionCompleted), animated:true)
            print("当前进度:\(progress.fractionCompleted*100)%")
        }
    
    
        //下载停止响应(不管成功或者失败)
        func downloadResponse(response: DownloadResponse<Data>) {
            switch response.result {
            case .success(let data):
                //self.image = UIImage(data: data)
                print("文件下载完毕: \(response)")
            case .failure:
                self.cancelledData = response.resumeData //意外终止的话,把已下载的数据储存起来
            }
        }
    
        //停止按钮点击
        @IBAction func stopBtnClick(_ sender: AnyObject) {
            self.downloadRequest?.cancel()
            self.stopBtn.isEnabled = false
            self.continueBtn.isEnabled = true
        }
    
        //继续按钮点击
        @IBAction func continueBtnClick(_ sender: AnyObject) {
            if let cancelledData = self.cancelledData {
                self.downloadRequest = Alamofire.download(resumingWith: cancelledData,
                                                          to: destination)
                self.downloadRequest.downloadProgress(queue: DispatchQueue.main,
                                                      closure: downloadProgress) //下载进度
                self.downloadRequest.responseData(completionHandler: downloadResponse) //下载停止响应
                self.stopBtn.isEnabled = true
                self.continueBtn.isEnabled = false
            }
        }
    
        override func didReceiveMemoryWarning() {
            super.didReceiveMemoryWarning()
        }
    }

    详细参考

    展开全文
  • iOS 7中苹果再一次无情的封杀mac地址,现在已经不能获取ios7设备的物理地址。那么在开发中如何才能标识设备的唯一性呢?apple公司提供的方法是通过keychain来存一些标志信息,然后通过存的标志信息来让应用程序来...

    iOS 7中苹果再一次无情的封杀mac地址,现在已经不能获取ios7设备的物理地址。那么在开发中如何才能标识设备的唯一性呢?apple公司提供的方法是通过keychain来存一些标志信息,然后通过存的标志信息来让应用程序来识别该设备的唯一性。

     apple公司写了一个简单的操作keychain的工具类:https://developer.apple.com/library/ios/samplecode/GenericKeychain/Listings/Classes_KeychainItemWrapper_m.html可以下载,把KeychainItemWrapper.h,.m文件引用xcode中,keychainItemWrapper.m文件可能出错,这里是由于arc编译造成的,我们可以根据提示进行解决,也可以用如下图的方式解决:

     

     

     

    接着设置keychain共享:如图所示

        

    此我们可以在项目中看到xxxxx.entitlements结尾的文件。<?xml version="1.0" encoding="UTF-8"?>

    <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">

    <plist version="1.0">

    <string>$(AppIdentifierPrefix)h.HelloWorld</string>

    </plist>

    其中的${AppldentifierPrefix}是开发者账户的的前缀,是apple的公司提供的,https://developer.apple.com/membercenter/index.action,可以用自已的账户进行查看。

    存取:key chain:

    1. -(void) setKeyChainValue  
    2. {  
    3.     KeychainItemWrapper *keyChainItem=[[KeychainItemWrapper alloc]initWithIdentifier:@"TestUUID" accessGroup:@"XXXXXX.h.HelloWorld"];  
    4.     NSString *strUUID = [keyChainItem objectForKey:(id)kSecValueData];  
    5.     if (strUUID==nil||[strUUID isEqualToString:@""])  
    6.     {  
    7.         [keyChainItem setObject:[self gen_uuid] forKey:(id)kSecValueData];  
    8.     }  
    9.     [keyChainItem release];  
    10.   
    11. }  
    12.   
    13. -(NSString *) gen_uuid  
    14. {  
    15.     CFUUIDRef uuid_ref=CFUUIDCreate(nil);  
    16.     CFStringRef uuid_string_ref=CFUUIDCreateString(nil, uuid_ref);  
    17.     CFRelease(uuid_ref);  
    18.     NSString *uuid=[NSString stringWithString:uuid_string_ref];  
    19.     CFRelease(uuid_string_ref);  
    20.     return uuid;  
    21. }  
    -(void) setKeyChainValue
    {
        KeychainItemWrapper *keyChainItem=[[KeychainItemWrapper alloc]initWithIdentifier:@"TestUUID" accessGroup:@"XXXXXX.h.HelloWorld"];
        NSString *strUUID = [keyChainItem objectForKey:(id)kSecValueData];
        if (strUUID==nil||[strUUID isEqualToString:@""])
        {
            [keyChainItem setObject:[self gen_uuid] forKey:(id)kSecValueData];
        }
        [keyChainItem release];
    
    }
    
    -(NSString *) gen_uuid
    {
        CFUUIDRef uuid_ref=CFUUIDCreate(nil);
        CFStringRef uuid_string_ref=CFUUIDCreateString(nil, uuid_ref);
        CFRelease(uuid_ref);
        NSString *uuid=[NSString stringWithString:uuid_string_ref];
        CFRelease(uuid_string_ref);
        return uuid;
    }

       应用程序第一次在某台设备上运行时,我们的应用程序保存一个uuid,来标识该设备。等设备把程序删除时,该uuid依然存在于设备中。

    展开全文
  • 上一篇文章《iOS开发系列--Swift语言》 中对Swift的语法特点以及它和C、ObjC等其他语言的用法区别进行了介绍。当然,这只是Swift的入门基础,但是仅仅了解这些对于使用Swift 进行iOS开发还是不够的。在这篇文章中将...
  • 什么是Segue? 在storyBoard上每一根用来界面跳转的线,都是一个UIStoryBoardSegue对象.(简称Segue) ...给Segue设置唯一标识: @property (nonatomic, readonly) NSString *identifier; 来源控制器 @propert
  • Swift 4迁移总结:喜忧参半,新的起点 每日一篇优秀博文 这次Swift 3 到 4 的迁移代码要改动的地方比较少,花了一个下午的时间就完成了迁移。Swift 把原来 4.0 的目标从 ABI 稳定改为了源码兼容,此次代码的兼容...
  • 我之前写过一篇文章:Swift - 使用ALAssetsLibrary获取相簿里所有图片,视频(附样例)。介绍了如何使用 AssetsLibrary 框架来读取并显示系统中的所有照片。 几年以来,相机应用和照片应用发生了显著的变化,增加...
  • iOS UIStoryboardSegue

    2017-08-11 15:21:49
    Storyboard上每一根用来界面跳转的线,都是一个UIStoryboardSegue对象(简称Segue)UIStoryboardSegue的属性//每一个Segue对象,都有3个属性//唯一标识 @property (nonatomic, readonly) NSString *identifier; //...
  • 本文是翻译的 APNs 的官方说明 自己英文不是太好,花了不少时间来翻译,其实之前我是看...该服务健全、安全、高效,开发者可以方便的向 iOS tvOS macOS 终端设备推送通知。当应用在用户设备上运行的时候会在用户设备
  • iOS设计规范中要求,当应用退出的时候(包括被终止运行时候),画面中UI元素状态需要保持的,当再次进来的时候看状态与退出是一样的。iOS6之后苹果提供以下API使得UI状态保持和恢复变得很容易了。 在iOS6中我们可以...
  • iOS7之前,开发者为了寻求自定义Navigation Controller的Push/Pop动画,只能受限于子类化一个UINavigationController,或是用自定义的动画去覆盖它。但是随着iOS7的到来,Apple针对开发者推出了新的工具,以更灵活...
  • Swift进阶

    2019-10-06 10:19:06
    上一篇文章《iOS开发系列--Swift语言》中对Swift的语法特点以及它和C、ObjC等其他语言的用法区别进行了介绍。当然,这只是Swift的入门基础,但是仅仅了解这些对于使用Swift进行iOS开发还是不够的。在这篇文章中将...
  • Swift提供了所有c类语言的控制流结构。包括for和while循环来执行一个任务多次;if和switch语句来执行确定的条件下不同的分支的代码;break和continue关键字能将运行流程转到你代码的另一个点上。 除了C语言传统的...
  • 一个更完善的Swift倒计时按钮(附后台权限申请) 如今越来越多app使用手机号码作为用户名,其中总是要涉及到验证码的发送。 倒计时按钮实现关键点: 当前视图控制器销毁后倒计时计数的再恢复 不同页面可能使用同一...
  • 上一篇文章《iOS开发系列--Swift语言》中对Swift的语法特点以及它和C、ObjC等其他语言的用法区别进行了介绍。当然,这只是Swift的入门基础,但是仅仅了解这些对于使用Swift进行iOS开发还是不够的。在这篇文章中将...
1 2 3 4 5 ... 18
收藏数 353
精华内容 141
关键字:

swift4 取得ios 标识