美文网首页iOS Coder
swift开发之扩展实现命名空间(实例方法,类方法)

swift开发之扩展实现命名空间(实例方法,类方法)

作者: SevenPa | 来源:发表于2019-12-17 15:53 被阅读0次

\color{red}{着急的火鸡们,最终全部代码在最下面}

命名空间

对长期从事objective-c语言开发的我们来说,命名空间可能是一个比较陌生的名称。

“命名空间”,简单地说,就是不允许有相同类名的区域。从事过java或者js开发的同学可能会有经验,这类语言的命名空间其实就是他们的目录名,即只要在不同目录下,就可以允许有相同的类名。

OC就比较尴尬了,它没有命名空间一说,也就是全局都不允许有相同的类名。那如何保证这一点?苹果是建议在类名前加2-3个唯一的字符来将自己的类名与其他区分开,于是就出现了UIView, NSString, MBProgressHUD, CALayer, AFNetworking, SDWebImage等

swift中,苹果终于引入了命名空间一说,在任意类中打印一下self 会出现"命名空间.className",swift中的命名空间的使用不是一个项目,而是需要跨项目,在一个项目中,都是一个命名空间,在同一个命名空间下,所有全局变量或者函数共享,不需要import,从swift开始,官方更多的建议大家使用pod来管理第三方框架,不然倒入一个框架到处都可以用

扩展方法前缀

在OC中,苹果建议在扩展中的方法需要增加前缀,原因是防止与自带方法或者其他库的扩展中方法重名,事实上也应该这么做,因为我们有前车之鉴,往往这类由于重写了方法造成的闪退,一旦xcode不能正常捕捉错误,将很难排查。

swift扩展中,同样需要关心方法覆盖的问题,对于原生类自带的方法,我们可以覆盖重复定义,并且最终调用走的是扩展中的方法,但是扩展中的方法不能重复定义,xcode会检测并报错

自定义命名空间

综上所述,我们自己模拟出类似“命名空间”,是个不错的选择,原因如下:

1.防止扩展中的方法或属性覆盖了原来已有的,造成无法预期的错误

2.有了命名空间,我们就不需要加前缀这种影响美观的操作,代码可读性更高

3.有了命名空间,开发过程中,尤其对于新人,可一眼看出方法或属性是属于原类自带的还是扩展的,防止长时间使用造成下意识的认知疲劳

Swift扩展模拟“命名空间”

首先,我们要知道swift中几个概念:

协议:与OC中协议类似,都是定义一套遵守者需要实现的规则,但是与OC不同的是,在swift中我们也可以对协议进行扩展,最终效果是所有遵守该协议的类都会增加协议被扩展的内容

泛型:swift提供了“泛型”来最大程度使函数、变量、容器等灵活化,如果你在架构一个应用或者sdk,那么泛型可以提供最大的便利性。swift中的标准库都是通过泛型定义的,例如Array可以塞进Int,也可以塞进String

泛型约束:顾名思义,就是通过泛型,来约束协议遵守者的类型

正式开始,我们的思路是通过扩展模拟出“命名空间”,其实这不是正儿八经的命名空间,只是期望通过一个特殊的符号,将我们自己扩展的方法属性等和官方的以及第三方的区分开来,类似于:


self.circleView.wm.moveToBottom()

加入circleView是一个UIView实例,这里的wm就是我们所说的特殊符号,其实也就是一个属性,moveToBottom就是我们自己扩展出的方法。

看到这里,第一个问题就抛出来了,如何给circleView扩展一个名叫wm的属性。很多聪明火鸡们就马上会想到两种方式,一种是扩展UIView,增加一个属性;另一种是使UIView遵守一个协议,通过扩展协议来增加一个属性。

假设,我们扩展的属性类型是:


public class NameSpace {

}

方法一:


extension UIView {

  public var wm: NameSpace {

        get {

            return NameSpace()

        }

    }

}

方法二:


/// 命名空间协议

public protocol NameSpaceProtocol {

    public var wm: NameSpace { get }

}

/// 扩展协议

extension NameSpaceProtocol {

    public var wm: NameSpace {

        get {

            return NameSpace()

        }

    }

}

/// UIView实现协议

extension UIView: NameSpaceProtocol {



}

我们将这两种方式做个比较,结论还是显而易见的,方式二的好处有:

1.我们在扩展每个类的时候,不需要像方式一那样都声明一个mw的属性,而是只要实现NameSpaceProtocol就可以了

2.对于子类,如果我们不希望其有这个属性,那么方式一就无解了,方式二则可以利用泛型约束的方式,可以随心所欲的控制

3.方式二写法更多的采用了swift独有的特性,风格上更加优雅,简单说就是更装*

我们在此基础上,在对NameSpace进行扩展,就实现了最终想要的效果


extension NameSpace {

  public func moveToBottom() {



}

}

/// 此时,UIView已经达到了想要的效果

let circleView = UIView()

circleView.wm.moveToBottom()

第二个问题就来了,这里我们真正扩展的其实是NameSpace,我们这里目标只有UIView,如果接下来还要给Date, Int, String等等扩展,实际上都是对NameSpace扩展,那么如果不做区分,那在其中一个类调用方法时,Xcode会提示出所有,包括其他目标扩展出的方法,事实上真的去调用非本目标的方法,编译也是不会报错的,但是这不是我们想要的。于是,我们引入泛型约束来做区分:


/// 命名空间

public final class NameSpace<T> {



}

/// 扩展UIView

extension NameSpace where T == UIView {

}

这下舒服了,在使用过程中不是对UIView的扩展不会出现在快捷提示。这里也做了个小优化,就是不希望NameSpace再做它用,所以加了个final描述一下

第三个问题,circleView.wm.moveToBottom()这个方法,如果moveToBottom方法中需要访问circleView的方法或属性怎么整?我们知道我们实际上扩展的是NameSpace类,所以我们需要在NameSpace中记录下来原来的对象就完事了:


/// 命名空间协议

public protocol NameSpaceProtocol {

    associatedtype TargetType



    /// 实例变量及方法命名空间

    var wm: NameSpace<TargetType> { get }

}

/// 命名空间

public final class NameSpace<T> {

    internal var base: T

    init(_ base: T) {

        self.base = base

    }

}

/// 扩展协议

extension NameSpaceProtocol {

    public var wm: NameSpace<Self> {

        get {

            return NameSpace<Self>(self)

        }

    }

}

/// 在扩展过程中通过self.base访问原来对象

extension NameSpace where T == UIView {

public func moveToBottom() {

    print("my x is \(self.base.frame.origin.x)")

}

}

写到这里,已经让大部分火鸡们满足了需求,实际上网上大多数资料也就到此为止了,但是仍然有部分不满意,因为我们一直做的都是对实例属性或者是方法做扩展,如果是类属性或者方法,类似于UIColor.wm.color(hexString)这中,其实也简单,过程不过多赘述,直接贴上\color{red}{最终代码}


/// 命名空间协议

public protocol NameSpaceProtocol {

    associatedtype TargetType



    /// 实例变量及方法命名空间

    var wm: NameSpace<TargetType> { get }



    /// 类变量及方法命名空间

    static var wm: NameSpace<TargetType>.Type { get }

}

/// 命名空间

public final class NameSpace<T> {

    internal var base: T

    internal var BASE: Self.Type

    init(_ base: T) {

        self.base = base

        self.BASE = Self.self

    }

}

/// 扩展协议

extension NameSpaceProtocol {

    public var wm: NameSpace<Self> {

        get {

            return NameSpace<Self>(self)

        }

    }

    public static var wm: NameSpace<Self>.Type {

        get {

            return NameSpace<Self>.self

        }

    }

}

/// 例子:扩展UIImage

extension UIImage: NameSpaceProtocol {}

extension NameSpace where T == UIImage {



    /// 根据颜色生成图片, 类方法

    public class func image(color: UIColor?, size: CGSize = CGSize(width: 1, height: 1)) -> UIImage {

        UIGraphicsBeginImageContextWithOptions(size, false, UIScreen.main.scale);

        if let color = color, let currentContext = UIGraphicsGetCurrentContext() {

            let fillRect = CGRect(x: 0, y: 0, width: size.width, height: size.height);

            currentContext.setFillColor(color.cgColor)

            currentContext.fill(fillRect)

            let colorImage = UIGraphicsGetImageFromCurrentImageContext()

            UIGraphicsEndImageContext()

            return colorImage ?? UIImage()

        }

        return UIImage()

    }



  /// 宽度,实例属性

  public var width: CGFloat {

      return self.base.size.width

}

}

/// 实际使用

let image = UIImage.wm.image(color: .red, size: CGSize(width: 100, height: 200))

print("the image width is\(image.wm.width)")

注意一:在扩展NameSpace之前,我们需要将目标实现一下NameSpaceProtocol协议,但是实际开发过程中你会发现有些会报警告说已经实现过了,不必惊慌,那是因为父类实现过,子类就不必实现了,比如可以将NSObject实现NameSpaceProtocol协议,之后UIView等类就不用再写这一步骤了

注意二:也许有火鸡想利用runtime扩展属性,请注意,在扩展NameSpace时,属性runtime方式添加时,务必添加到self.base中:


/// 响应对象

    private var target: ButtonActionTarget? {

        get {

            return objc_getAssociatedObject(self.base, "buttonActionTarget") as? ButtonActionTarget

        }

        set {

            if let newValue = newValue {

                objc_setAssociatedObject(self.base, "buttonActionTarget", newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)

            }

        }

    }

如果你添加到self,会发现并不生效,也就是get的时候一直为nil,这是因为本身NameSpace的作用域并不大,因为我们在扩展NameSpaceProtocol时只是临时初始化了NameSpace,并没有引用保存。

然后关于Self, .self, .Type的理解,大家可以执行查询,不过简单来说:

Self:用在协议中,代表的是协议自身或者实现者或者子类的类型

.self:用在哪代表的就是什么的自身,比如用在实例后面就是实例本身,类型后面就是类型本身

.Type:获取调用者的类型

最后再次声明下为什么我们要实现这个命名空间的效果:

1.在调用的时候,Xcode的快捷提示中不会显示目标的自带方法,不会产生混淆,对新人来说非常友好

2.加了一层命名空间,有效避免覆盖重写的风险

3.更加优雅,许多知名的第三方也都这么做了,比如RxSwift

相关文章

  • swift开发之扩展实现命名空间(实例方法,类方法)

    命名空间 对长期从事objective-c语言开发的我们来说,命名空间可能是一个比较陌生的名称。 “命名空间”,简...

  • swift方法

    oc中只有类可以定义方法,swift类/结构体/枚举都能定义方法 swift的类型方法类似oc的类方法 实例方法(...

  • Swift - 方法

    在Swift中,方法可以在类、结构体、枚举中定义,比OC更加灵活。1.实例方法实例方法由类的特定实例调用,实例方法...

  • Swift实例方法的本质

    Swift中的实例方法 在Swift中,实例方法就是类方法以实例为参数并返回一个可以被实例调用的方法。可以说Swi...

  • Swift类与OC类方法相互调用的

    1、OC调用Swift实例方法 例如在ViewController.m类里调用Swift的logMe实例方法,就可...

  • iOS开发 - 「Swift 学习」Swift语言标识符命名规则

    Swift中变量、常量、方法、函数、枚举、结构体、类、协议等命名规则: 开发中需要对 变量、常量、方法、函数、枚举...

  • Swift 类方法和实例方法

    Objective-C中有类方法和实例方法,下面我们来看看Swift中怎么定义类方法和实例方法的 Objectiv...

  • Unity3d-C#扩展方法 C#扩展方法

    扩展方法 所谓的扩展方法,其实就是无需继承就可以改写类,让类中增加方法。直接通过实例调用的静态方法,因此即使实例是...

  • runtime(一)

    总结:Classget 类名,父类;实例变量;属性;实例方法,类方法,方法实现;Copy 实例变量列表;属性列...

  • 第十四节 Swift中的方法

    Swift 中的方法就是函数,分为实例方法和类型方法。 实例方法在特定类型实例中调用,代码示例 Couter 类定...

网友评论

    本文标题:swift开发之扩展实现命名空间(实例方法,类方法)

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