上一篇文章《iOS开发系列--Swift语言》中对Swift的语法特色以及它和C、ObjC等其余语言的用法区别进行了介绍。固然,这只是Swift的入门基础,可是仅仅了解这些对于使用Swift进行iOS开发仍是不够的。在这篇文章中将继续介绍一些Swift开发中一些不常关注可是又必备的知识点,以便对Swift有进一步的了解。html
和其余高级语言同样Swift中也增长了访问控制,在Swift中提供了private、internal、public三种访问级别,可是不一样的是Swift中的访问级别是基于模块(module,或者target)和源文件(.swift文件)的,而不是基于类型、命名空间声明。git
下面是关于Swift关于不一样成员访问级别的约定规则:github
上面这些规则看上去比较繁琐,但其实不少内容理解起来也是瓜熟蒂落的(若是你是一个语言设计者相信大部分规则也会这么设计),下面经过一个例子对于规则3作一解释,这一点和其余语言有所不一样可是却更加实用。在使用ObjC开发时你们一般会有这样的经验:在一个类中但愿某个属性对外界是只读的,可是本身又须要在类中对属性进行写操做,此时只能直接访问属性对应的成员变量,而不能直接访问属性进行设置。可是Swift为了让语法尽量精简,并无成员变量的概念,此时就能够经过访问控制来实现。编程
Person.swiftswift
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)。安全
熟悉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配置)。app
下面不妨看一个命名空间的例子,建立一个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沉重的历史包袱,毕竟ObjC的历史太过悠久,相比于不少现代化语言它缺乏一些很酷的语法特性,并且ObjC的语法和其余语言相比差异很大。可是Apple同时也不能忽视ObjC的地位,毕竟ObjC通过二十多年的历史积累了大量的资源(开发者、框架、类库等),所以在Swift推出的初期必须考虑兼容ObjC。但同时Swift和ObjC是基于两种不一样的方式来实现的(例如ObjC能够在运行时决定对象类型,可是Swift为了提升效率要求在编译时就必须肯定对象类型),因此要无缝兼容须要作大量的工做。而做为开发人员咱们有必要了解两种语言之间的转化关系才能对Swift有更深入的理解。
其实从前面的例子中你们不难发现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标记)。
当前ObjC已经积累了大量的第三方库,相信在Swift发展的前期调用已经存在的ObjC是比较常见的。在Swift和ObjC的兼容性容许你在一个项目中使用两种语言混合编程(称为“mix and match”),而无论这个项目本来是基于Swift的仍是ObjC的。不管是Swift中调用ObjC仍是ObjC中调用Swift都是经过头文件暴漏对应接口的,下图说明了这种交互方式:
不难发现,要在Swift中调用ObjC必须借助于一个桥接头文件,在这个头文件中将ObjC接口暴漏给Swift。例如你能够建立一个“xx.h”头文件,而后使用“#import”导入须要在Swift中使用的ObjC类,同时在Build Settings的“Objective-C Bridging Header”中配置桥接文件“xx.h”。可是好在这个过程Xcode能够帮助你完成,你只须要在Swift项目中添加ObjC文件,Xcode就会询问你是否建立桥接文件,你只须要点击“Yes”就能够帮你完成上面的操做:
为了演示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() } }
运行效果
从前面的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中根本不会生成对应的方法声明。
因为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。固然,这仅限于类,对应结构体无能为力。
和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...")
两个对象之间的引用关系以下图:
为了不这个问题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...")
如今两个对象之间的引用关系以下图:
固然相似于上面的引用关系实际遇到的并很少,更多的仍是存在于闭包之中(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做为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