Swift:零散语法(上)

原创:知识点总结性文章
创作不易,请珍惜,之后会持续更新,不断完善
个人比较喜欢做笔记和写总结,毕竟好记性不如烂笔头哈哈,这些文章记录了我的IOS成长历程,希望能与大家一起进步
温馨提示:由于简书不支持目录跳转,大家可通过command + F 输入目录标题后迅速寻找到你所需要的内容

目录

  • 1、@discardableResult的作用
  • 2、@IBDesignable和@IBInspectable
  • 3、懒加载初始化UI视图
  • 4、用重命名闭包统一处理水平Item栈的调用
  • 5、到设置界面开启消息通知
  • 6、状态切换
  • 7、@escaping 逃逸闭包
  • 8、info.plist文件的属性
  • 9、URL Types和LSApplicationQueriesSchemes
  • 10、sizeToFit 和 sizeThatFits的使用区别
  • 11、自定义运算符
  • 12、final关键字
  • 13、@objc、@objcMembers关键字探讨
  • 14、xib的使用流程
  • 15、CustomStringConvertible协议
  • 16、Switch和Case的使用

1、@discardableResult的作用

在Swift中,当有返回值的方法未得到接收和使用时通常会出现警告提示,在正式编译中不会影响编译结果,但是也妨碍代码的美观整洁,在方法上加上@discardableResult就可以取消这个警告,还有一种取消警告的方法,不加@discardableResult直接加通配符接收方法返回值。

@discardableResult
class func observe (changeHandler: @escaping MLReachabilityBlock) -> MLReachability? {

_ = func1()

2、@IBDesignable和@IBInspectable

我们可以将自定义的代码实时渲染到Interface Builder中。而它们之间的桥梁就是通过两个指令来完成,即@IBDesignable@IBInspectable。我们通过@IBDesignable告诉Interface Builder这个类可以实时渲染到界面中,无论我们drawRect里面多么复杂,自定义有多复杂,Xib / Storyboard都可以把它编译出来,并且渲染展示出来。但是这个类必须是UIView或者NSView的子类。通过@IBInspectable可以定义动态属性,即可在Attributes inspector面板中可视化修改属性值。

@IBInspectable var integer: Int = 0
@IBInspectable var float: CGFloat = 0
@IBInspectable var double: Double = 0
@IBInspectable var point: CGPoint = CGPointZero
@IBInspectable var size: CGSize = CGSizeZero
@IBInspectable var customFrame: CGRect = CGRectZero
@IBInspectable var color: UIColor = UIColor.clearColor()
@IBInspectable var string: String = ""
@IBInspectable var bool: Bool = false

3、懒加载初始化UI视图

UIImageView为例:

private lazy var userImage: UIImageView = {
    let imageView = UIImageView()
    imageView.contentMode = .scaleAspectFit
    imageView.backgroundColor = .clear
    imageView.image = UIImage(named: "MineHomeDefaultUserImage")
    return imageView
}()

4、用重命名闭包统一处理水平Item栈的调用

我们可以对闭包进行重命名来进行使用。

typealias commonClosure = (MLMineHomeModel) -> Void
var commonClosure:commonClosure?

func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
    if let model = modelArray?[indexPath.item]{
        commonClosure?(model)
    }
}

cell.commonClosure = {[weak self] model in
    self?.commonClick(model)
}

然后在控制器中对点击逻辑进行统一处理。

private func commonClick(_ model:MLMineHomeModel){
    let id = model.id
    switch id {
    case 0:
        //训练提醒
        let vc = MLMineDrillRemindController()
        vc.hidesBottomBarWhenPushed = true
        navigationController?.pushViewController(vc, animated: true)
        break
    case 1:
        //健康档案
    case 2:
        //设置页面
    case 3:
        //我购买的服务
        URLNavigatorRouter.handlePageJump("/link?url=" + kH5ServiceHost + "/ih-v2/orthopaedics/service-list")
        break
    default: break
        
    }
}

5、到设置界面开启消息通知

通过下面这串代码我们可以跳转到设置里面自己APP的开启通知界面。

UNUserNotificationCenter.current().getNotificationSettings {settings in
    switch settings.authorizationStatus {
    case .authorized:
        break
    case .notDetermined,.denied,.provisional,.ephemeral:
        DispatchQueue.main.async {
            MLAlertView(title: "开启消息通知", message: "请在设置中开启【允许通知】不错过医生向您发起的视频问诊以及为您开具的新处方", destructButtonIndex : 1 ,titles: ["取消", "去开启"]) {(index) in
                if index == 1 {
                    guard let url = URL(string: UIApplicationOpenSettingsURLString) else { return }
                    if UIApplication.shared.canOpenURL(url) {
                        UIApplication.shared.open(url, options: [:], completionHandler: nil)
                    }
                }
            }.show()
        }
        break
    }
}

6、状态切换

我们通过添加了一个状态控制按钮,来覆盖掉开关。由按钮来进行事件的相应,将当前开关的状态传递出去。

private lazy var openSwitch: UISwitch = {
    let openSwitch = UISwitch()
    openSwitch.onTintColor = MLTheme.color.appThemeColor
    openSwitch.thumbTintColor = .white
    openSwitch.tintColor = MLTheme.color.titleColor_DDDDDD
    return openSwitch
}()

openSwitch.addSubview(swiftStatusBtn)
swiftStatusBtn.snp.makeConstraints { make in
    make.edges.equalTo(openSwitch)
}
@objc private func swiftStatusClick(){
    openOrCloseRecordClosure?(model,!openSwitch.isOn)
}

cell.openOrCloseRecordClosure = {[weak self] taskModel,taskStatus in
    if taskStatus{//开启        
        self?.openTaskeUpload(taskModel)
    }else{//关闭
        self?.closeTaskUpload(taskModel)
    }
}
private func closeTaskUpload(_ closeModel:MLMineDirllRemindModel?){
    guard closeModel != nil else{
        return
    }
    self.viewModel.closeTaskUpload(closeModel, completion: {[weak self] error in
        guard error == nil else{
            MLToast.message(error?.serviceMessage)
            return
        }
        self?.modelArray.forEach { (model) in
            if model.taskId == closeModel?.taskId{
                model.status = false
                self?.bgTableView.reloadData()
            }
        }
    })
}

7、@escaping 逃逸闭包

要理解@escaping,首先需要理解closure, 要理解closure,首先理解匿名函数。这就是匿名函数啊,而我们叫它闭包则是因为闭包可以捕获和存储其所在上下文中任意常量和变量的引用。 这就是所谓的闭合并包裹着这些常量和变量,俗称闭包。

{ (parameters) -> return type in
    statements
}

如果一个闭包被作为一个参数传递给一个函数,并且在函数return之后才被唤起执行,那么这个闭包是逃逸闭包。不逃逸闭包的生命周期:将闭包传递给函数、函数运行闭包(或不运行)、函数返回,然后这个closure就死掉了,而逃逸闭包则会一直存在,没有因为functionreturn而死掉,所以我们叫这个闭包为逃逸闭包。在以下的情况,我们需要使用escaping closure:异步执行,并不能说函数return了就把closure杀死掉,因为这个closure可能还没有执行完毕。一般情况下,弱🐔就跟着Xcode的提示走就好了。


8、info.plist文件的属性

自定义字体

倘若我们需要在APP中使用自定义字体AlternateGotNo3D.ttf,我们除了将该字体导入到项目目录下外,还需要在info.plist文件Fonts provided by application列表下面指出该字体。


用户权限
日历使用权限

需要设置NSCalendarsUsageDescription属性才能使用日历。提示语可为:此 App 需要您的同意才能设置日历。

相机使用权限

需要设置NSCameraUsageDescription属性才能使用相机。提示语可为:此 App 需要在训练模块使用您的摄像头。

通讯录使用权限

需要设置NSContactsUsageDescription属性才能使用通讯录。提示语可为:开放权限,匹配通讯录中的好友。

位置使用权限

需要设置NSLocationAlwaysUsageDescription属性和NSLocationWhenInUseUsageDescription属性才能使用用户的位置信息。提示语可为:为了我们能够更好的为您提供医疗信息,请同意我们使用您的定位服务。

麦克风使用权限

需要设置NSMicrophoneUsageDescription属性才能使用麦克风。提示语可为:此 App 需要在发送语音时使用您的麦克风。

相册使用权限

需要设置NSPhotoLibraryAddUsageDescription属性和NSPhotoLibraryUsageDescription属性才能使用相册。提示语可为:此 App 需要在图片消息、发布病例等模块使用您的媒体资料库。


应用访问白名单(LSApplicationQueriesSchemes)
lightsky  
shark.video  
bobo  
snssdk32  
pptv  
bilibili  
kugouURL  
gaeagj  
qqnews  
zhihu  
doubanradio  
openApp.jdMobile  
imeituan  
tmall  
dianping  
vipshop  
yanxuan  
wccbyihaodian  
taobao  
suning  
kaola  
ctrip  
kuaikanmanhua  
gugutouchmanga  
qrxs  
mailmaster  
jcnhers  
wbmain  
yixin  
ydcourse  
ntesopen  
yddict  
shanbay  
tencentweiboSdkv2  
weibosdk2.5  
sinaweibo  
sinaweibohd  
cydia  
weixin  
wechat  
weibosdk  
mqq  
mqqapi  
mqzone  
wtloginmqq2  
mqqopensdkapiV3  
mqqwpa  
mqqopensdkapiV2  
mqqOpensdkSSoL  
hbuilder  
streamapp  
baidumap  
iosamap  
qqmap

9、URL Types和LSApplicationQueriesSchemes

Scheme跳转是iOS APP通信最常用到的通信方式,APP1通过openURL:跳转到APP2,并且可以在URL中带上想要的参数,有点类似HTTP的get请求。

URL types里的字段是为了让别的应用能调起你自己的app,而LSApplicationQueriesSchemes里的字段是让系统允许你自己的app调起第三方app的。就相当于你LSApplicationQueriesSchemes中的字段是别人app中的URL types字段,而你URL types中的字段就是别人app中的LSApplicationQueriesSchemes中的字段。

当我们使用canOpenURL:判断手机内是否安装此App时,则必须在LSApplicationQueriesSchemes白名单中配置第三方Scheme: weixin,否则无法跳转,报错信息如下。此处建议使用canOpenURL:进行跳转判断,提前发现问题,规避问题。

-canOpenURL: failed for URL: "weixin://" - error: "This app is not allowed to query for scheme weixin"。

项目 ->info -> URL types-> URL Schemes,设置后就拥有了自己应用的标识schemeURL Schemes是一个可以让 app 相互之间可以跳转的协议。每个 app 的URL Scheme都是不一样的,如果存在一样的Scheme,那么系统就会响应先安装的那个 app 的 Scheme,因为后安装的 app 的Scheme被覆盖掉了,是不能被调用的。

URL Identifier是自定义的URL Schemes名字,一般采用翻转域名的方法保证该名字的唯一性,也可以不设置,若设置必须保证唯一性。

第1个QQ开头的是QQ+qq开放平台的appid的十六进制,注意我们在qq开发平台上得到只10进制的appid,直接百度一下有在线10进制转换为16进制的工具,第2个wx开头的是你的微信开放平台得到的appid。最后一个就是我们自己appURL type

在Safari浏览器中,输入scheme://,若能打开,则配置URL Schemes:成功。


10、sizeToFit 和 sizeThatFits的使用区别

UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(10, 10, 0, 0)];
[label setBackgroundColor:[UIColor grayColor]];
[label setFont:[UIFont systemFontOfSize:20]];
label.text = @"成都欢迎您!!!"
[self.view addSubview:label];

sizeToFit:直接改变了这个label的宽和高,使它根据上面字符串的大小做合适的改变。输出结果:width=163.5 height=24.0

[label sizeToFit];
NSLog(@"width=%.1f  height=%.1f ", label.frame.size.width, label.frame.size.height);

sizeThatFits并没有改变原始label的大小:

CGSize sizeThatFits = [label sizeThatFits:CGSizeZero];
NSLog(@"sizeThatFits: width=%.1f  height=%.1f", sizeThatFits.width, sizeThatFits.height);
NSLog(@"width=%.1f  height=%.1f", label.frame.size.width, label.frame.size.height);

输出结果为:

sizeThatFits: width=163.5  height=24.0
width=0.0  height=0.0

通过上面的两个小例子,我们可以验证出上面的结论:sizeToFit:会计算出最优的size而且会改变自己的sizesizeThatFits:会计算出最优的size但是不会改变自己的 size


11、自定义运算符

相等运算符==

在swift中,对于值类型(主要指结构体和枚举类型),在使用过程中总是会通过复制来进行传递,可以直接通过相等运算符来判断两者是否相等,而由于类类型是引用类型,所以并不能直接使用相等符号来进行判断。但是如果就是想要使用==来实现判断两个变量或者常量是否指向了同一个类实例对象的引用,可不可以呢?

使用相等运算符实现判断引用指向是否相同

在Swift中,相等运算符的默认实现用来判断两个值类型的值是否相等,所以在一定意义上讲,只要等重写运算符对应的方法就可以对方法的实现逻辑进行修改。

class Man {
    var age = 0
    var idCardNumber: String
    var name: String?
    
    init(idCardNumber: String) {
        self.idCardNumber = idCardNumber
    }
}
let man = Man(idCardNumber: "410422199901010101")
man.name = "Ericy"

let other = Man(idCardNumber: "410422199901010101")
other.name = "Jack"

if man == other {
    print("Equal")
} else {
    print("Not equal")
}

在这个实现中,如果直接使用==来进行两个引用进行判断就会报错:

Binary operator '==' cannot be applied to two 'Man' operands

这里使用==所要表达的意思就是想要判断这两个引用的值是否相等(指针的值),所以swift并不认为这是你想要的,抛出了错误来提示你这不是你想要的操作。这时候可以尝试重新定义相等运算符的实现:

class Man {
    var age = 0
    var idCardNumber: String
    var name: String?
    init(idCardNumber: String) {
        self.idCardNumber = idCardNumber
    }

    static func == (man: Man, other: Man) -> Bool {
        return man.idCardNumber == other.idCardNumber
    }
}

这时候使用相等运算符来对两个Man类型的引用类型进行判断就不再会报错。所以当有如下需求时,可以尝试重新定义运算符实现:

  • 想要的实现与原始的实现不完全相同
  • 对应的结构中并未支持该运算符的实现
自定义运算符

在一些情况下,会想要自定义一些运算符来比较形象地实现自定义的逻辑。例如想要在自增运算的基础上,实现双自增运算,将这个运算符定义为 +++ 前置运算符,每次可以让数据自增2。首先,需要在全局声明新增的运算符:主要有三类前缀运算符(prefix), 中缀运算符(infix)和后缀运算符(suffix)。这里声明前缀运算符:prefix operator +++。实现运算符操作:

class Man {
    var age = 0
    var idCardNumber: String
    var name: String?

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

    static prefix func +++ (_ man: inout Man) -> Man {
        man.age += 2
        return man
    }
}

使用新增运算符:

var man = Man(idCardNumber: "410422199901010101")
man.age = 23
+++man
print("man.age --> \(man.age)")

12、final关键字

final关键字在大多数的编程语言中都存在,表示不允许对其修饰的内容进行继承或者重新操作。Swift中,final关键字可以在classfuncvar前修饰。final正确的使用场景 - 权限控制,也就是说这个类或方法不希望被继承和重写。

类或者方法的功能确实已经完备了

这种通常是一些辅助性质的工具类或者方法,特别那种只包含类方法而没有实例方法的类。比如MD5加密类这种,算法都十分固定,我们基本不会再继承和重写。

避免子类继承和修改造成危险

有些方法如果被子类继承重写会造成破坏性的后果,导致无法正常工作,则需要将其标为final加以保护。

为了让父类中某些代码一定会执行

父类的方法如果想要其中一些关键代码在继承重写后仍必须执行(比如状态配置、认证等)。我们可以把父类的方法定义成final,同时将内部可以继承的部分剥离出来,供子类继承重写。下面通过一段代码演示:

class Parent {
   final func method1() {
       //权限验证(必须执行)
       //.....
        
       method2()
        
       //下面是日志记录(必须执行)
       //..........
   }
    
   func method2(){
       //父类的实现
       //......
   }
}
class Child : Parent {
   //只能重写父类的method2方法,不能重写method1方法
   override func method2() {
       //子类的实现
       //......
   }
}

13、@objc、@objcMembers关键字探讨

我们说Objective-c是一门动态语言,决策会尽可能的推迟到运行时。而Swit是一门静态语言,也就是说Swift的对象类型、调用的方法都是在编译期就确定的,这也是为什么Swift比Objective-c性能高的原因。但是在Swift中所有继承自NSObject的类,仍然保留了Objective-c的动态性。如果想要使用它的动态性就需要加上@objc关键字,本篇文章就来讲一下,哪些情况需要用到@objc

Protocol如果是optional(非必须实现)的,必须加上@objc

如下,便是一段Swift的Protocol代码,如果这样写,此Protocol中的几个方法,都必须实现:

protocol PlayerDelegate: class {
    func playerReadyToPlay(player: CustomMediaPlayer, totalTime: Double)
    func playerCacheProgress(player: CustomMediaPlayer, progress: Float)
    func playerPlayProgress(player: CustomMediaPlayer, currentTime: Double)
}

如果里面某些方法是非必须实现的,则需要在方法前加上@objc optional,在protocol前也需要加上@objc

@objc protocol PlayerDelegate: class {
    @objc optional func playerReadyToPlay(player: CustomMediaPlayer, totalTime: Double)
    @objc optional func playerCacheProgress(player: CustomMediaPlayer, progress: Float)
    func playerPlayProgress(player: CustomMediaPlayer, currentTime: Double)
}
利用#selector调用的方法

在Swift中#selector,相当于Objective-c中的@selector,而@selector会在运行时进行方法调用。所以在Swift中使用#selector调用方法,需要在方法前加上@objc。拿button调用方法举例:

button.addTarget(self, action: #selector(click), for: .touchUpInside)
@objc private func click() {}
使用KVC时

KVC其实也是利用了Objective-c的运行时特性,所以使用KVC也需要在属性前加上@objc,如下代码:使用valueforkey获取name值,或利用setvalue:forkey赋值,name前需加@objc

class People: NSObject {
    @objc var name = ""
    var age = 10
}

使用valueforkey获取name值:

let people = People()
people.name = "谢佳培"
people.age = 24

let name = people.value(forKey: "name")
print(name)

people.setValue("刘雅彤", forKey: "name")
NSPredicate筛选

NSPredicate是利用对象属性的key进行筛选的属性,要加上@objc,实际上NSPredicate也是利用了对象的Objective-c对象的KVC特性,如下代码:

class People: NSObject {
    @objc var name = ""
    @objc var age = 0
}
let array = NSMutableArray()
let people1 = People()
people1.name = "张三"
people1.age = 15
array.add(people1)
    
let people2 = People()
people2.name = "李四"
people2.age = 18
array.add(people2)

找到age等于15的人,并打印名字:

let predicate = NSPredicate(format: "age == %d", 15)
if let result = array.filtered(using: predicate) as? [People], result.count > 0 {
    let people = result.first
    print(people?.name)
}
Objective-c与Swift混合开发,Swift方法/属性需要被Objective-c调用的

Swift类中的属性和方法加上@objc

class People: NSObject {
    @objc var name = ""
    
    @objc func playGame() {
        print("玩游戏")
    }
}

类前不需要加@objc,只要是继承自NSObject的类,被Objective-c调用时,系统会默认为被调用的类加为@objc

People *people = [[People alloc] init];
people.name = @"谢佳培";
[people playGame];
Objective-c与Swift混合开发,Swift的枚举需要被Objective-c使用的

Swift的枚举,必须是Int类型,才能被Objective-c调用

@objc enum PeopleSex: Int {
    case female = 0
    case male = 1
}
@objc与@objcMembers的区别

在Swift中,继承自NSObject的类如果有比较多的属性或方法都需要加上@objc的话,会多比较多的代码,那么可以利用@objcMembers减少代码。被@objcMembers修饰的类,会默认为类、子类、类扩展和子类扩展的所有属性和方法都加上@objc。当然如果想让某一个扩展关闭@objc,则可以用@nonobjc进行修饰。


14、xib的使用流程

新建一个自定义组件实现类 XibView( 继承 UIView),因为是继承 UIViewAlso create XIB file无法勾选,后面我们会手动创建一个同名的xib文件并关联。进行 Xib 与自定义组件类XibView关联,注意修改的是 XibViewFile’s OwnerCustom Class,不要修改成 XibView.xibViewCustom Class 了。将xib文件中 ViewSize 修改为 Freedom 并将其调整成合适的尺寸。最后再放置控件添加约束。

编写自定义控件类XibView,注意在类的前面要添加 @IBDesignable

@IBDesignable class XibView: UIView {
    @IBOutlet weak var titleLable: UILabel!
    @IBOutlet weak var countLable: UILabel!
    @IBOutlet weak var imageView: UIImageView!
    ......  
}

在属性观察器前设置@IBInspectable@IBInspectable保证视图能够实时预览显示。设置属性观察器,保证属性值改变后实时刷新。设置标题:

@IBInspectable var title: String = "喜欢的女孩"{
    // 设置属性观察器,保证属性值改变后实时刷新
    didSet {
        titleLable.text = title
    }
}

设置图片:

@IBInspectable var imageName: String = "girl"{
    didSet {
        imageView.image = UIImage(named: imageName)
    }
}

设图片颜色:

@IBInspectable var imageColor: UIColor = UIColor.black {
    didSet {
        imageView.tintColor = imageColor
    }
}

然后我们就可以在XIB中看到可以直接配置的几个属性:

上面是Swift添加可以配置属性的方式,使用的是在属性前面添加@IBInspectable关键字,而当我们使用OC语言的时候可以直接在属性前面添加IBInspectable关键字,并不需要@符号。

@property (nonatomic, assign) IBInspectable CGFloat borderWidth;

初始化时将xib中的view添加进来:

var contentView: UIView!

override init(frame: CGRect) {
    super.init(frame: frame)

    contentView = loadViewFromNib()
    addSubview(contentView)
    initial()
}

加载xib

func loadViewFromNib() -> UIView {
    let className = type(of: self)
    let bundle = Bundle(for: className)
    let name = NSStringFromClass(className).components(separatedBy: ".").last
    let nib = UINib(nibName: name!, bundle: bundle)
    let view = nib.instantiate(withOwner: self, options: nil).first as! UIView
    return view
}

初始化属性配置:

func initial(){
    titleLable.text = title
    imageView.image = UIImage(named: imageName)
    imageView.tintColor = imageColor
    
    let tapGesture = UITapGestureRecognizer(target: self, action: #selector(click))
    imageView.addGestureRecognizer(tapGesture)
    imageView.isUserInteractionEnabled = true
}

增加图片的点击事件:

var imageClick:(() -> ())?
@objc func click() {
    if imageClick != nil {
        imageClick!()
    }
}

最后使用的方式按照纯代码创建视图的方式来使用就好了。

let xibView = XibView(frame: CGRect(x: 0, y: 100, width: 300, height: 300))
xibView.imageClick = {
    print("点击了图片")
}
view.addSubview(xibView)

看下实际使用的效果:


15、CustomStringConvertible协议

CustomStringConvertibleCustomDebugStringConvertible这两个协议类似于Objective-C中的重写description方法,继承协议实现descriptiondebugDescription 属性,即可打印出想要的数据内容。接下来我们来看一下这两个协议在Swift中的使用。

Struct 中的应用

首先我们定义一个结构体,输出这个结构体,看 print 的结果:

struct Person {
    var age: Int
    var name: String
    var job: String
}
let meetings = Person(age: 24, name: "xiejiapei", job: "iOSDeveloper")
print(meetings)

输出结果为:

"Person(age: 24, name: "haha", job: "iOSDeveloper")\n"

接下来我们让这个结构体继承自CustomStringConvertibleCustomDebugStringConvertible这两个协议,查看输出结果:

struct Person:CustomStringConvertible,CustomDebugStringConvertible {
    var age: Int
    var name: String
    var job: String
    
    var description: String {
        return "\(age) \(name) \(job)"
    }
    
    var debugDescription: String {
        return "\(name) \(age) \(job)"
    }
}
let meetings = Person(age: 24, name: "xiejiapei", job: "iOSDeveloper")
print(meetings)
debugPrint(meetings)

通过这两个协议就可以自定义输出的参数,这样在调试的时候就会方便很多。

"24 xiejiapei iOSDeveloper\n"
"xiejiapei 24 iOSDeveloper\n"
Class 中的应用

定义一个类的话, 当我们使用print 时候并不会输出类中的变量:

class Wheel {
    var spokes: Int = 0
    var diameter: Double = 0.0
    
    init(spokes:Int = 32,diameter:Double = 26.0) {
        self.spokes = spokes
        self.diameter = diameter
    }
    
    func removeSpokes() {
        spokes = spokes > 0 ? spokes-- : spokes
    }
}
var wheel = Wheel(spokes: 36,diameter: 29)
print(wheel)

输出结果为:

"Wheel\n"

如果想要改变 print 的输出结果,我们需要让类遵守这两个协议,最好用 Extension

class Wheel {
    var spokes: Int = 0
    var diameter: Double = 0.0
    
    init(spokes:Int = 32,diameter:Double = 26.0) {
        self.spokes = spokes
        self.diameter = diameter
    }
    
    func removeSpokes() {
        spokes = spokes > 0 ? spokes-- : spokes
    }
}
extension Wheel: CustomDebugStringConvertible,CustomStringConvertible {
    var description: String {
        return "wheel has \(spokes) spokes"
    }
    
    var debugDescription: String {
        return "wheel has \(spokes) spokes spokes "
    }
}
var wheel = Wheel(spokes: 36,diameter: 29)
print(wheel)
debugPrint(wheel)

输出结果为:

"wheel has 36 spokes\n"
"wheel has 36 spokes spokes \n"
ViewController、NSObject 中的应用

如果想要改变一个 ViewController 中的 print 方法,先看默认的输出结果:

class WheelsViewController: UIViewController {
    var wheels = [Wheel]()
}
var wheels = WheelsViewController()
print(wheels)

输出结果为:

"<__lldb_expr_124.WheelsViewController: 0x7fb91a634dc0>\n"

这里只输出了className 的内存地址,如果想要改变,并不能用 Extension 的方法,因为 UIViewController已经继承了这两个协议,那么就只能用 override 来实现 DIYprint

class WheelsViewController: UIViewController {
    var wheels = [Wheel]()
}
extension WheelsViewController {
    override var description: String {
        return "wheelsViewController has \(wheels)"
    }
    
    override var debugDescription: String {
        return "DEBUG__wheelsViewController has \(wheels)"
    }
}

这样就得到了我们想要 print 出来的信息方便了调试。

var w = WheelsViewController()
w.wheels = [wheel]
print(w)
debugPrint(w)
"wheelsViewController has [wheel has 36 spokes spokes ]\n"
"DEBUG__wheelsViewController has [wheel has 36 spokes spokes ]\n"

16、Switch和Case的使用

在这个最简单的Switch语句中,与其他语言很不同的一点是:不用显式的加上break。Swift的Switch语句会自动在case结尾处加上break,执行完满足条件的case部分后,就自动退出了。

var i = 0  
switch i {  
    case 0:  
        "0" //被输出  
    case 1:  
        "1"  
    default:  
        "2"  
} 

但是在某些情况下,我们可能会希望Switch能同时执行两条case,那么可以这样:

var i = 3  
switch i {  
    case 0,3:  
        "0" //被输出  
    case 1:  
        "1"  
    default:  
        "2"  
} 

case后的多个条件中,用逗号隔开即可。如果就是想执行完一条case之后,想继续执行下一条case,就要用到一个新的关键字。使用新的关键字fallthrough能使Switch在执行完一个case之后,紧接着执行下一条case

var i = 0  
switch i {  
    case 0:  
        "0" //被输出  
        fallthrough  
    case 1:  
        "1" //被输出  
    case 2:  
        "2"  
    default:  
        "default"  
}

Switch语句一定要涵盖所有的情况,这并不是说一定要有default,只要上面的case能满足所有的情况,就可以不用写default

需要注意的地方有两点:
Switch当然也支持显式的break,通常只有一种情况下你会这么做,那就是当你也不想在default里做任何事情的时候,这个时候你就可以在default里显式地加上一句break

fallthrough并不是在任何情况下都有效,当你在Switch里使用Value Binding技术的时候,fallthrough就被禁止了。

支持多种数据类型

在Objective-C里,Switch语句只能支持整形的数据(或者一个字符),但是在Swift里,Switch能支持多种数据类型,包括浮点、布尔、字符串等。

支持浮点:

let float = 1.5  
switch float {  
    case 1.5:  
        "1.5"   //被输出  
    default:  
        "default"  
}  

支持布尔:

let isSuccess = true  
switch isSuccess {  
    case true:  
        "true"   //被输出  
    default:  
        "default"  
}  

支持字符串:

let name = "Bannings"  
switch name {  
    case "Bannings":  
        "Bannings"   //被输出  
    default:  
        "default"  
}  

支持区间运算符。不仅仅是循环结构里可以用区间运算符,在Switch里同样可以用区间运算符:

var i = 15  
switch i {  
    case 0 ... 10:  
        "0~10"  
    case 11 ... 20:  
        "11~20" //被输出  
    default:  
        "default"  
}  

支持元组。作为被大大增强的Switch,元组也是被支持的:

let request = (true,"success")  
switch request {  
    case (true, "success"):  
        "success"//被输出  
    case (false, "failed"):  
        "failed"  
    default:  
        "default"  
}  

使用元组和使用其他数据类型一致,不过元组还有一项特点,对于不想关心的值,可以用下划线跳过,如:

let (name,  _, age) = ("Bannings" ,true, 22)  

那么在使用Switch时,同样支持这项特性:

let request = (true,"success")  
switch request {  
    case (_, "success"):  
        "success"   //被输出  
    case (false, _):  
        "failed"  
    default:  
        "default"  
} 

对于不关心的值跳过,只要满足另一个值就行了。需要注意一点的是,如果元组中的值也是数字类型,那么也是可以用区间运算符的:

let request = (10,"failed")  
switch request {  
    case (_, "success"):  
        "success"  
    case (1 ... 10, _):  
        "1~10"    //被输出  
    default:  
        "default"  
} 

针对元组,Switch还支持类似于Optional BindingValue Binding,就是能把元组中的各个值提取出来,然后直接在下面使用:

let request = (0,"success")  
switch request {  
    case (0, let state):  
        state    //被输出:success  
    case (let errorCode, _):  
        "error code is \(errorCode)"  
}  // 涵盖了所有可能的case,不用写default了  

这样也是可以的:把let放在外面和放在里面为每一个元素单独写上let是等价的。当你在一个case里使用Value Binding的时候,如果你同时也在它的上一个case里使用了fallthrough,这是编译器所不允许的。

let request = (0,"success")  
switch request {  
    case let (errorCode, state):  
        state    //被输出:success  
    case (let errorCode, _):  
        "error code is \(errorCode)"  
} 

支持额外的逻辑判断。在使用Switch语句时,Switch会适当地导向相应的case部分,这其实就相当于做了一次逻辑判断,但Switch语句还支持额外的逻辑判断:

let request = (0,"success")  
switch request {  
    case (0, let state) where state != "success":  
        state  
    case (let errorCode, _):  
        "error code is \(errorCode)"//被输出:"error code is 0"  
}  
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。
禁止转载,如需转载请通过简信或评论联系作者。

相关阅读更多精彩内容

  • 原创:知识点总结性文章创作不易,请珍惜,之后会持续更新,不断完善个人比较喜欢做笔记和写总结,毕竟好记性不如烂笔头哈...
    时光啊混蛋_97boy阅读 1,114评论 0 0
  • 小视频 001--swift简史小视频 002--Playground体验 003--常量&变量 一、swift简...
    谦谦君子修罗刀阅读 7,210评论 8 66
  • YES 2014WWDC发布 常量和变量使用注意 在实际过程中,建议先定义常量,如果需要修改再改变为变量(更加安全...
    南冯阅读 648评论 0 0
  • Swift 简介 查看Swift当前版本 简介 Swift 语言由苹果公司在 2014 年推出,用来撰写 OS X...
    mian小爬阅读 489评论 0 1
  • 主要总结一些平时遇到的疑难点,在此总结出来,持续更新。可能有些误导大家的地方,欢迎指正。 难点 get,set,w...
    Jimmy木阅读 2,720评论 1 10

友情链接更多精彩内容