iOS Lotusoot模块化工具应用的动态思路

目录
  • 组件化的要点-约定
    • 场景
    • 调用服务
    • 注册服务
  • 动态思路
  • 代码实现
    • 1、MachO 获取命名空间
    • 2、包名+类名的验证

下文,写的是 Swift 依赖

OC 库,没有命名空间

组件化的要点-约定

个人觉得

例如,URL 路由的注册,就是把约定的信息,传过去。作为服务。

Lotusoot 包含服务调用,短链的注册与调用

下面着重讲服务调用,短链略

场景

project 有两个依赖 A (协议) 和 B (协议的实现者,提供服务)

project 引用 A,知道了协议信息

project 不引用 B , project 对 B 一无所知

这样 project 把 B 去掉,编译的更快

B 依赖 A, 引用 A, 实现 A 的协议,提供服务

调用服务

        // 拿到 key
        let lotus = s(AccountLotus.self)
        // kv 取得提供服务的实例
        let accountModule: AccountLotus = LotusootCoordinator.lotusoot(lotus: lotus) as! AccountLotus
        // 调用服务
        accountModule.login(username: "zhoulingyu", password: "wow") { (error) in
            print(error ?? "1234")
        }

第 3 步,调用服务很简单

第 2 步,挺精彩,充分使用了 Swift 编译时的静态特性

协议的方法,编译时确定了

需要几个参数,啥类型的,一般都可以显式使用

不用看到一个参数字典,啊,这是啥

// 第 2 步。从下面取
// 键值对,值是提供服务的对象
var lotusootMap: Dictionary = Dictionary<String, Any>()

第 1 步, 拿到键

这里把协议名,作为 key

// 使用泛型,取其描述
// 协议,转协议名
public extension String {
    init<Subject>(_ instance: Subject) {
        self.init(describing: instance)
    }
}
/// 通过 Subject 快速获取字符串
public func s<Subject>(_ instance: Subject) -> String {
    return String(instance)
}

注册服务

1, Project 没有 import B ( 提供服务 ), 怎么使用 B 的功能?

public static func registerAll(serviceMap: Dictionary<String, String>) {
        for (lotus, lotusootName) in serviceMap {
            // lotus, 协议名
            // lotusootName, 包名.类名
            let classStringName = lotusootName
            // 反射,产生类
            // 提供服务的类,一定是 NSObject 的子类,拥有 init 方法 ( 这是个约定 )
            let classType = NSClassFromString(classStringName) as? NSObject.Type
            if let type = classType {
                // 产生对应的实例,强转为遵守协议的 ,即可
                let lotusoot = type.init()
                register(lotusoot: lotusoot, lotusName: lotus)
            }
        }
    }

2, 这里使用 python 脚本注册,编译的时候拿到信息 协议名:包名.类名

通过约定, 标记

// @NameSpace(ZLYAccountModule)
// @Lotusoot(AccountLotusoot)
// @Lotus(AccountLotus)
class AccountLotusoot: NSObject, AccountLotus {}

python 脚本找出标记,整合,放入 plist 文件中

1, 脚本入口

lotusootSuffix = 'Lotusoot'
length = len(sys.argv)
if length != 3 and length != 4:
    print 'parameter error'
    os._exit(1)
if length == 4:
    lotusootSuffix = sys.argv[3]
    lotusootFiles = findLotusoots(scanPath, lotusootSuffix + '.swift')
else:
    // 走这里
    lotusootFiles = findAmbiguityLotusoots(scanPath)

翻阅每一个 swift 文件

def findAmbiguityLotusoots(path):
    list = []
    for root, subFolders, files in os.walk(path):
        # Ignore 'Target Support Files' and 'Pods.xcodeproj'
         // 不需要处理的,不处理
        if 'Target Support Files' in subFolders:
            subFolders.remove('Target Support Files')
        // 不需要处理的,略
        if 'Pods.xcodeproj' in subFolders:
            subFolders.remove('Pods.xcodeproj')
        // 每一个文件
        for f in files:
             // 每一个 Swift 文件
            if f.endswith('.swift'):
                // 获取标记的配置
                tup = getLotusootConfig(os.path.join(root, f))
                if tup[0] and tup[1] and tup[2]:
                    // 三者都满足,把文件路径,给添加了
                    list.append(f)
    return list

扫描每一行,

获取配置,上面看到的包名,命名空间

@NameSpace(ZLYAccountModule)

上面看到的类名

@Lotusoot(AccountLotusoot)

上面看到的 key ( 协议名 )

@Lotus(AccountLotus)

def getLotusootConfig(file):
    lotus = ''
    lotusoot = ''
    namespace = ''
    // 翻阅,文件的每一行
    for line in open(file):
        // 上面看到的 key
        if getLotus(line):
            lotus = getLotus(line)
         // 上面看到的类名
        if getLotusoot(line):
            lotusoot = getLotusoot(line)
        // 上面看到的包名,命名空间
        if getNameSpace(line):
            namespace = getNameSpace(line)
    return (lotus, lotusoot, namespace)

… 还有好多,

逻辑是获取配置,写入一个 plist

运行的时候,启动

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
        LotusootCoordinator.registerAll()
        return true
    }

注册就是,读取刚才写入的 plist, 作为 [协议名 : 包名.类名 ] 的字典,

@objc public static func registerAll() {
        let lotusPlistPath = Bundle.main.path(forResource: "Lotusoot", ofType: "plist")
        if let lotusPlistPath = lotusPlistPath {
            let map = NSDictionary(contentsOfFile: lotusPlistPath)
            registerAll(serviceMap:  map as! Dictionary<String, String>)
        }
    }

与上文,相呼应

动态思路

进入动态思路, 动态地注册 KV ( 协议名: 服务库名.类名)

上文有一个印象,知道场景,即可

写文,当写长,

怎样体现我,10 年工作经验?

上文没有,坚持凑字数

1,project 拿到 key (协议名) ,可以的

2,project 拿到所有的依赖信息

通过 MachO 可以

3,project 拿到服务类的名称

确保 module B 的类名 = key ( 协议 ) + Cls

MachO 拿到所有依赖库的名称, 每一个 + “.” + key ( 协议 ) + Cls= MachO 拿到所有依赖库的名称, 每一个 + “.” + module B 的类名

然后,看能不能实例化,

能够实例化,就 OK

试了一圈,不能够实例化,就报错

可能依赖 B, 没有添加

可能依赖 B 的类名,写错了

project 拿到服务类的名称, 优雅的

确保 module B 的类名 = key ( 协议 ) + Cls,

硬编码,是不好的

依赖 A ( 放协议的 ), 添加一个协议

public protocol Maid{
    var name: String{ get }
}

module B 里面,必须有一个叫 key (协议) + C 的类

该类,遵守 Maid 协议。

通过协议属性,返回 B 中服务类的名称

class AccountLotusC: NSObject, Maid{
    var name: String{
        return String(reflecting: AccountLotusoot.self)
    }
}

这个过程,与上文模块化利用协议的设计,比较一致

约定是,实现协议的服务模块,

一定有一个 key + C 的类

提供服务类的名称

硬编码,比较轻微

代码实现

1、MachO 获取命名空间

import MachO
lazy var moduleNames: [String] = { () -> [String] in
        // 找到 project 名称,一会去除
        let mainNameTmp = NSStringFromClass(LotusootCoordinator.self)
        guard let mainName = mainNameTmp.components(separatedBy: ".").first else{
            fatalError("emptyMainProject")
        }
        var result = [String]()
        let cnt = _dyld_image_count()
        // 处理所有的包,系统的,用户的
         for i in 0..<cnt{
             if let tmp = _dyld_get_image_name(i){
                 let name = String(validatingUTF8: tmp)
                 // 系统的,不用管
                 if let candidate = name, candidate.hasPrefix("/Users"){
                     if let tmp = candidate.components(separatedBy: "/").last{
                         // 去除 project 的
                         if tmp != mainName{
                             // 拿到用户依赖
                             result.append(tmp)
                         }
                     }
                 }
             }
         }
         return result
    }()

以上,模拟器 OK, 真机没试过 ( 手头没开发证书 )

2、包名+类名的验证

@objc public static func lotusoot(lotus: String) -> Any? {
        // 已经缓存了
        if let val = sharedInstance.lotusootMap[lotus]{
            return val
        }
        else{
            var i = 0
            let names = LotusootCoordinator.sharedInstance.moduleNames
            let cnt = names.count
            // 遍历,用户包
            while i < cnt{
                // 按照约定,尝试制造助手类
                let classType = NSClassFromString(names[i] + "." + lotus + "C") as? NSObject.Type
                if let type = classType {
                    // 实例化,助手类
                    let assist = type.init()
                    if let maid = assist as? Maid{
                         // 拿到 module B 的服务类的名称
                        let classType = NSClassFromString(maid.name) as? NSObject.Type
                        if let type = classType {
                            // 将 module B 的服务类,实例化
                            let lotusoot = type.init()
                            register(lotusoot: lotusoot, lotusName: lotus)
                        }
                        // 默认是,一个 module 一个服务类,
                        // 排除掉,使用过的用户类
                        LotusootCoordinator.sharedInstance.moduleNames.remove(at: i)
                        break
                    }
                }
                i+=1
            }
            if let val = sharedInstance.lotusootMap[lotus]{
                return val
            }
            else{
                fatalError("name Module of" + lotus)
            }
        }
    }

GitHub repo

到此这篇关于iOS Lotusoot模块化工具应用的动态思路的文章就介绍到这了,更多相关iOS Lotusoot模块化内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • iOS 模块化之JLRoute路由示例

    JLRoutes是一个调用极少代码 , 可以很方便的处理不同URL schemes以及解析它们的参数,并通过回调block来处理URL对应的操作 , 可以用于处理复杂跳转逻辑的三方库. 1.在日常开发中 , push , present 出现在整个程序的各个地方 , 如果你想快速理清一个项目的整体逻辑 , 非常麻烦 . 大多数情况 , 你得找到代码目录 ,根据层级结构分出关系 , 然后找到对应的push位置 , 寻找下一级页面 , 如果本身项目的目录就非常乱 , 那么如果要了解一个项目的整体跳转

  • iOS中关于模块化开发解决方案(纯干货)

    关于iOS模块化开发解决方案网上也有一些介绍,但真正落实在在具体的实例却很少看到,计划编写系统文章来介绍关于我对模块化解决方案的理解,里面会有包含到一些关于解耦.路由.封装.私有Pod管理等内容:并编写的一个实例项目放在git进行开源[jiaModuleDemo],里面现在已经放着一些封装的功能模块:会不断的进行更新,假如你感兴趣可以Star一下,项目也不断的更新完善优化:如果你有更好的方案或者说好的建议可以lssues,我会在短时间进行更新并修改相应的问题: 一:项目中存在的问题 1:当公司里

  • iOS模块化开发浅析

    背景:由于目前所在公司的iOS项目的依赖管理是比较原始的状态,但是APP功能又是越来越复杂的,这就带来的很多问题,比如开发时编译时间过长.模块间耦合严重.模块依赖混乱等.最近又听说这个项目中的部分功能可能需要独立出一个新APP,本着"Don't repeat yourself"的原则,我们试着抽离出原项目中的各个模块,并在新的APP中集成这些模块. 最近算是初步完成了新APP的模块化,也算是从中总结了一些经验拿出来分享一下.同时也完成了一个模块化框架TinyPart欢迎star. 模块

  • 第二次聊一聊JS require.js模块化工具的基础知识

    前一篇:JS模块化工具我们以非常简单的方式引入了requirejs:http://www.jb51.net/article/82527.htm,这一篇将讲述一下requirejs中的一些基本知识,包括API使用方式等 基本API require会定义三个变量:define,require,requirejs,其中require === requirejs,一般使用require更简短 define 从名字就可以看出这个api是用来定义一个模块 require 加载依赖模块,并执行加载完后的回调函

  • 第一次接触JS require.js模块化工具

    随着网站功能逐渐丰富,网页中的js也变得越来越复杂和臃肿,原有通过script标签来导入一个个的js文件这种方式已经不能满足现在互联网开发模式,我们需要团队协作.模块复用.单元测试等等一系列复杂的需求. RequireJS是一个非常小巧的JavaScript模块载入框架,是AMD规范最好的实现者之一.最新版本的RequireJS压缩后只有14K,堪称非常轻量.它还同时可以和其他的框架协同工作,使用RequireJS必将使您的前端代码质量得以提升. requirejs能带来什么好处 官方对requ

  • 提高iOS开发效率的小技巧与思路

    先用一张图展示学习iOS开发应该掌握的知识体系: 1.全图片作为背景的时候,可能遇到的问题.,滑回的时候,图片停留了一会才滑回去. 原因: 这种界面一般使用一般用imageView的第三种填充方式. 这种填充方式可以让图片不被压缩变形的前提下,尽可能去填充整个控件,但是设置这个枚举的填充方式的时候,记得按照下图这样设置,将超出控件范围的给切割掉 设置约束的时候,记得选择currentview的那个对象 2.设备适配的问题 还是上面这张图片,按照设计在6p上面来设置自动约束,约好后,在5s上面的时

  • iOS开发中音频工具类的封装以及音乐播放器的细节控制

    一.控制器间数据传递 两个控制器之间数据的传递 第一种方法: 复制代码 代码如下: self.parentViewController.music=self.music[indexPath.row]; 不能满足 第二种做法:把整个数组传递给它 第三种做法:设置一个数据源,设置播放控制器的数据源是这个控制器.self.parentViewController.dataSource=self;好处:没有耦合性,任何实现了协议的可以作为数据源. 第四种做法:把整个项目会使用到的音频资源交给一个工具类去

  • iOS ScrollView嵌套tableView联动滚动的思路与最佳实践

    前言 随着业务的发展,页面的复杂度越来越高,嵌套滚动视图的方式也越来越受设计师们的青睐,在各大电商App十分常见.如下Demo图: 但是这样的交互官方并不推荐,而且对开发来说确是不那么友好,需要处理滚动手势的冲突,页面的多层级嵌套都给开发带来了一定程度的麻烦.接下里我聊聊我们的实现思路. 思路和过程 对应这种页面结构应该毫无疑问是最底层是一个纵向滚动的scrollView,它的页面上面放一个固定高度的header,紧接着下面一个支持横向滚动切换的容器scrollView,容器上面才是各个页面具体

  • iOS实现新年抽奖转盘效果的思路

    临近春节,相信不少app都会加一个新的需求--新年抽奖 不多废话,先上GIF效果图 DEMO链接 1. 跑马灯效果 2. 抽奖效果 实现步骤: 一.跑马灯效果 其实很简单,就是通过以下两张图片,用NSTimer无限替换,达到跑马灯的效果 实现代码: _rotaryTable = [[UIImageView alloc] initWithFrame:CGRectMake((kScreenWidth-366*XT)/2, 218*XT, 366*XT, 318*XT)]; _rotaryTable.

  • xcode 详解创建静态库和动态库的方法

    xcode 创建静态库和动态库 1.linux中静态库和动态库区别: 库从本质上来说是一种可执行代码的二进制格式,可以被载入内存中执行.库分静态库和动态库两种. 静态库:这类库的名字一般是libxxx.a:利用静态函数库编译成的文件比较大,因为整个函数库的所有数据都会被整合进目标代码中,他的优点就显而易见了,即编译后的执行程序不需要外部的函数库支持,因为所有使用的函数都已经被编译进去了.当然这也会成为他的缺点,因为如果静态函数库改变了,那么你的程序必须重新编译. 动态库:这类库的名字一般是lib

  • React Native搭建iOS开发环境

    一.写在前面 1. 什么是React-Native? React-Native是:Facebook 在2015年初React.js技术研讨大会上公布的一个开源项目.支持用开源的JavaScript库React.js来开发iOS和Android原生App.初期仅支持iOS平台,同年9月份,该开源项目同时支持Android平台. React Native的原理是:在JavaScript中用React抽象操作系统原生的UI组件,代替DOM元素来渲染,比如以<View>取代<div>,以&

  • JavaScript 高级篇之DOM文档,简单封装及调用、动态添加、删除样式(六)

    http://www.cnblogs.com/TomXu/archive/2012/02/16/2351331.html , 在回来看这里文章,你一定会有更深刻的认识.因为我在这里介绍概念上的东西比较少,看下面的例子,对初学的朋友可能会有些吃力! 1.DOM的架构 复制代码 代码如下: <html> <head> <title>document</title> </head> <body> <h1>CSS Demo<

  • 一个方法搞定iOS下拉放大及上推缩小

    下面这种效果在ios应用中很常见: 实现思路: 1、创建头部的视图和tableview,需要注意的是tableview要设置contentInset,contentInsent 的顶部要和头部视图的背景图的高度一样,否则会有空隙(或是有遮挡). myTableView.contentInset = UIEdgeInsetsMake(headRect.size.height-navHeight-navHeight, 0, 0, 0); 2、对头部视图的背景图片的尺寸进行处理,当然,你也可以直接找一

随机推荐

其他