美文网首页
SwiftTips-DayByDay

SwiftTips-DayByDay

作者: 懒得起名的伊凡 | 来源:发表于2016-11-30 11:22 被阅读124次

    本文是对喵神书的学习总结, 仅仅只是为了加深理解记录,支持正版支持正版

    第一版就买了此书,一直没好好看,真是罪过罪过

    一些简单的代码Github地址

    2016-12-7

    创建随机数

    使用arc4random()来创建随机数是一个不错的方法,并且在swift中也可以使用,如果想要某个范围内的数,可以使用模运算%来实现。

    API
    public func arc4random() -> UInt32
    public func arc4random_uniform(_ __upper_bound: UInt32) -> UInt32

    但是有一个问题:
    在iPhone5s及之后的设备上没问题,但是在iPhone5及之前的设备上有时候会崩溃

    原因:
    Swift的Int是和CPU的架构相关的:在32位的CPU上,实际上他是Int32,在64位的CPU上,他是Int64的。而使用arc4random()返回的值始终是UInt32的,所以在32位的机器上有一个班的概率会在进行Int转换时候越界,从而崩溃。

    实践,实现创建一个Range的随机数的方法

    //创建一个Range的随机数的方法
    
    func randomInRange(inRange range: Range<Int>) -> Int{
        let count = UInt32(range.upperBound - range.lowerBound)
        return Int(arc4random_uniform(UInt32(count))) + range.lowerBound
    }
    
    for _ in 0...10 {
        let range = Range(1...6)
        print(randomInRange(inRange: range))
    }
    

    自定制print数据的格式

    实现该功能,在Objective-C中,主要靠重载description方法来实现

    在Swift中通过实现CustomStringConvertible协议来实现

    class MyPrintClass{
        var num: Int
        init() {
            num = 1
        }
    }
    
    extension MyPrintClass: CustomStringConvertible{
        var description: String{
            return "Num: \(self.num)"
        }
    }
    
    let printClass = MyPrintClass()
    print(printClass)
    
    

    其实主要是学习一种思想:实现和定义一个类型的时候,先定义最简单的类型结构,再通过扩展(extension)的方式来实现各种协议和功能

    还有另外一个协议CustomDebugStringConvertible,通过实现它可以设置在调试中使用debugger来进行打印时候的输出,类似po printClass的命令进行打印

    未实现时打印结果类似

     (lldb)  po printClass
    <MyPrintClass: 0x608000029da0>
    

    实现后打印结果类似

    (lldb) po printClass
    MyPrintClass Num : 1
    
    

    打断点观察对象时十分有用,实现方式

    class MyPrintClass {
        var num: Int
        init() {
            num = 1
        }
    }
    
    extension MyPrintClass: CustomDebugStringConvertible{
        var debugDescription: String{
            return "MyPrintClass Num : \(self.num)"
        }
    }
    

    2016-12-6

    delegate的使用

    协议-委托模式太常见了,不多说

    在OC中,使用ARC,通常将delegate设置为weak,这样可以使得delegate实际的对象被释放时候,被置为nil。但在Swift中,不能直接将其设置为weak,要解决这个问题,首先要知道原因

    swift中的protocol不仅可以被class所遵循,还可以被structenum这样的类型遵循,而对于这些类型,本身就不会通过引用计数来进行内存管理,所以不能用weak这样的概念来修饰

    明白了原因就好解决,就是设法将protocol限制在class 内

    方法一

    因为在Objective-C中的protocol只有类能实现,所以可以将protocol声明为Objective-C的,通过在protocol前添加@objc来实现

    @objc protocol MyClassDelegate {
        func method()
    }
    
    class MyClass {
        weak var delegate: MyClassDelegate?
    }
    

    方法二

    在protocol声明的后面加上class,为编译器显示的指明该protocol只能由class实现

    protocol MyClassDelegate: class{
        func method()
    }
    
    class MyClass {
        weak var delegate: MyClassDelegate?
    }
    
    

    关联对象的实现

    在Objective-C中,我们可以利用Objective-C的运行时和Key-Value Coding的特性,在运行时实现向一个对象添加值存储。在使用Category扩展现有类的功能时,直接添加实例变量是不被允许的,但可以使用property配合 Associated Object 的方式,将一个对象关联到一个已有的要扩展的对象上,从外部来看,就像是直接通过属性访问对象的实例变量一样。

    在Swift中实现向extension中使用 Associated Object的方式将对象进行关联

    class MyClass{
        
    }
    
    private var key: Void?
    
    //error: extensions may not contain stored properties
    extension MyClass {
        var title: String?{
            get{
                return objc_getAssociatedObject(self, &key) as? String
            }
            set{
                objc_setAssociatedObject(self, &key, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
            }
        }
        
    }
    
    let myClass = MyClass()
    
    if let title = myClass.title {
        print("设置了title: \(title)")
    }else{
        print("该属性为空")
    }
    
    myClass.title = "Tom"
    
    if let title = myClass.title {
        print("设置了title: \(title)")
    }else{
        print("该属性为空")
    }
    
    

    说明:
    key的类型被声明为 Void?,并通过&符号取地址作为UnsafePointer<Void>类型传入。

    2016-12-5

    GCD和延迟调用

    使用GCD来实现多线程编程,先来个最经常使用的例子

    //创建目标队列
    let workingQueue = DispatchQueue(label: "com.ivan.myqueue")
    
    //派发到刚创建的队列中,GCD会负责线程调度
    workingQueue.async { 
        //在workQueue中异步进行
        print("Working....")
        //模拟两秒的执行时间
        Thread.sleep(forTimeInterval: 2)
        print("Work finish")
        DispatchQueue.main.async {
            print("在主线程中更新UI")
        }
    }
    
    

    实现延迟调用,这样写主要是为了实现能够在调用之前取消该操作

        typealias Task = (_ cancel: Bool) -> Void
        
        func delay(_ time: TimeInterval,task: @escaping () -> ()) -> Task? {
            
            //这个时候就体会到@escaping的含义了
            func dispatch_later(block: @escaping () -> ()){
                let t = DispatchTime.now() + time
                DispatchQueue.main.asyncAfter(deadline: t, execute: block)
            }
            
            var closure: (() -> Void)? = task
            var result: Task?
            
            let delayedClosure: Task = {
                cancel in
                if let internalClosure = closure {
                    if cancel == false {
                        DispatchQueue.main.async(execute: internalClosure)
                    }
                }
                closure = nil
                result = nil
            }
            
            result = delayedClosure
            
            dispatch_later {
                if let delayedClosure = result{
                    delayedClosure(false)
                }
            }
            
            return result
        }
        
        func cancel(_ task: Task?) {
            task?(true)
        }
    

    2016-12-4

    值类型和引用类型

    在swift中,有值类型和引用类型两种,值类型在传递和赋值时将进行复制,引用类型只会使用引用对象的一个‘指向’。在swift中有几点需要注意

    • swift中的structenum定义的类型都是值类型,使用class定义的对象是引用类型
    • swift中所有的內建类型都是值类型,不仅包括IntBool,甚至连StringArrayDictionary都是值类型。

    这样做的好处是可以减少堆内存上内存分配和回收的次数。

    验证一下在swift中数组是值类型

    //验证: 数组在swift中是值类型
    var array = ["A","B","C"]
    
    var array1 = array
    
    array1.removeLast()
    
    array //["A", "B", "C"]
    
    array1 //["A", "B"]
    

    验证一下值类型中存储值类型和引用类型时的操作

    //验证:  值类型在复制时,将存储在其中的值类型进行复制,但对于其中的引用类型,只是复制一份引用
    
    //引用类型
    class MyClass{
        var num: Int
        
        init(num: Int) {
            self.num = num
        }
    }
    
    var myObj = MyClass(num: 1)
    
    var classArray = [myObj]
    
    myObj.num = 101
    
    classArray[0].num //101
    
    //值类型
    struct MyStruct{
        var num: Int
    }
    
    var myStruct = MyStruct(num: 2)
    
    var structArray = [myStruct]
    
    myStruct.num = 102
    
    structArray[0].num //2
    
    

    区分一下String和NSString

    先来个自己的总结

    ** StringNSString其实并没有太大的区别,他们两个基本上市可以无缝转换的,之所以有时候需要转换,只是他们有很少一部分没有相互实现的API,只是为了使用起来方便**

    比如遍历一个字符串

    使用String会更方便,因为它实现了ColletionType这样的协议

    var str = "Hello, playground"
    
    for character in str.characters {
        print(character)
    }
    
    

    但如果是要和Range配合使用,使用NSString会更方便一些

    //比如我们要实现一个替换第一个字符到第5个字符之前的字符的功能
    
    //使用String
    let startPosition = str.index(str.startIndex, offsetBy: 1)
    let endPosition = str.index(str.startIndex, offsetBy: 5)
    
    let range: Range = startPosition..<endPosition
    
    str.replacingCharacters(in: range, with: "TEST") //"HTEST, playground"
    
    //如果使用NSString
    
    let nsRnage = NSMakeRange(1, 4)
    
    (str as NSString).replacingCharacters(in: nsRnage, with: "TEST") //"HTEST, playground"
    

    JUST DO IT.

    2016-12-3

    在Swift中实现协议方法的可选实现

    在Objective-C中的protocol可是使用关键字@optional来实现可选方法。但是在Swift中,默认所有的方法都是必须要实现的。但是也并非不能实现,有两种方法

    将协议本身和可选方法都定义为Objective-C

    其实也就是在协议之前和协议方法之前都加上@objc,这样就可以使用optional关键字了

    有一些限制,说明一下:

    • 使用@objc修饰的protocol只能被class实现,对于structenum类型,无法令他们实现可选方法和属性了
    • 实现他的class 的方法也必须被标注为@objc,或者整个类就是继承自NSObject
    @objc protocol MyProtocol1 {
        @objc optional func method1() -> String
        
        func method2() -> Int
    }
    
    class MyClass1: MyProtocol1 {
        
        //不实现也是可以的
    //    func method1() -> String {
    //        return "Test"
    //    }
        
        func method2() -> Int {
            return 2
        }
    }
    
    //是会报错的 error: non-class type 'MyStruct1' cannot conform to class protocol 'MyProtocol1'
    struct MyStruct1: MyProtocol1{
        func method1() -> String {
            return "Struct"
        }
        
        func method2() -> Int {
            return 3
        }
    }
    

    使用extension

    这个方法是在swift2.0之后才支持的,其实就是使用protocolextension给出方法的默认实现,这样,这些方法在实际的类中就是可选实现的了

    protocol MyProtocol2 {
        func method1() -> String
        
        func method2() -> Int
    }
    
    extension MyProtocol2 {
        func method1() -> String {
            return "MyProtocol2DefaultString"
        }
        
    }
    
    class MyClass2: MyProtocol2 {
        func method2() -> Int {
            return 3
        }
    }
    

    2016-11-30

    条件编译

    swift中没有宏的概念,所以不能使用#ifdef的方法来检测某个符号是否经过宏定义

    编译标记的使用语法

    
     #if <condition>
    
     #elseif <condition> //可选
    
     #else //可选
    
     #endif
     
    
    

    condition的内建组合

    方法 可选参数
    os() masOS、iOS、tvOS、watchOS、Linux
    arch() i386、x86_64(对应模拟器上的32位和64位CPU),arm、arm54(对应真机)
    swift() >= 某个版本

    可以配合 typealias使用,更强大

    自定义condition进行条件编译

    使用方法一样,主要是如何定义编译符合

    在项目的编译选项中进行设置,Building Settings - Swift Compiler - Custom Flags,在其中的 Other Swift Flags 加上 -D 定义的编译符号 就可以了

    可用于

    • 在同一个Target中完成同一个APP的不同版本,进行区分
    • 在Debug或Release版本中进行不同的操作

    编译标记

    在OC中,使用 #param来标记代码区间
    在Swift中,使用// MARK:来标记代码区间
    还有 // TODO:// FIXME:来提示工作尚未完成或需要修改的地方

    注意:这些都是区分大小写的

    给方法添加文档说明,可使用快捷键 alt+command+/,这样在使用时候,使用alt+单击可以查看该方法的文档说明

    2016-11-29

    单例的创建

    
    class MyManager{
        static let shared = MyManager()
        private init(){
            
        }
        
        //在初始化类变量的时候,Apple会将这个初始化包装在一次 swift_once_block_invoke 中,以保证他的唯一性,类似 dispatch_once 的方式
        //加入一个私有的初始化方法,覆盖默认的公开初始化方法,这样,其他地方就不能够通过 init 来生成自己的 MyManager 实例,也保证了单例的唯一性
    }
    
    let manager = MyManager.shared
    
    
    

    实例方法的动态调用

    
    class MyClass{
        func method(num: Int) -> Int {
            return num + 1
        }
    }
    
    //如果想要调用method方法,一般是生成 MyClass 的实例,使用 .method 来调用
    
    let obj = MyClass()
    obj.method(num: 2)
    
    //或者使用 Swift 的 Type.instanceMethod 的语法来生成一个可以柯里化的方法
    let f = MyClass.method //f 的类型是 (MyClass) -> (Int) -> Int
    let obj1 = MyClass()
    //知道了f的类型,就不难理解为何这样调用了
    f(obj1)(2)
    
    //注:这种方法只适用于实例方法,对于属性的getter和setter不可以
    
    //还有一种情况,实例方法和类型方法名字冲突时
    
    class MyClass1{
        func method(num: Int) -> Int {
            return num + 1
        }
        
        class func method(num: Int) -> Int{
            return num + 2
        }
    }
    
    // 使用 MyClass1.method 默认取出来的是类型方法,解决方法是显式的加上类型声明
    
    let method1 = MyClass1.method // class func
    let method2: (Int) -> Int = MyClass1.method // class func
    let method3: (MyClass1) -> (Int) -> Int = MyClass1.method // func method 的柯里化版本
    
    

    相关文章

      网友评论

          本文标题:SwiftTips-DayByDay

          本文链接:https://www.haomeiwen.com/subject/zdttmttx.html