分而治之。在Objective-C和Swift中的模块化整体应用





哈Ha!我的名字叫瓦西里·科兹洛夫(Vasily Kozlov),我是Delivery Club的iOS技术负责人,我发现了该项目的整体形式。我承认我对本文将要解决的问题有所帮助,但是随着项目的进行,我悔改了我的意识。



我想告诉你如何将Objective-C和Swift中的现有项目拆分为单独的模块-框架。根据Apple的说法,框架是特定结构的目录。



最初,我们设定一个目标:隔离实现聊天功能以实现用户支持的代码,并减少构建时间。这导致有用的后果,如果没有习惯就很难遵循,并且存在于一个项目的整体世界中。



突然,臭名昭著的SOLID原则开始形成,最重要的是,问题的表述迫使我们根据它们来组织代码。通过将实体移到单独的模块中,您会自动遇到其所有依赖关系,这些依赖关系不应在此模块中,而且还会在主应用程序项目中重复。因此,组织具有通用功能的附加模块的问题已经成熟。当一个实体应该有一个目的时,这不是单一责任的原则吗?



乍一看,将带有两种语言和大量传统的项目划分为模块的复杂性可能会吓到我,但对新任务的兴趣却盛行。



在先前发现的文章中,作者承诺一个新项目通常具有简单明了步骤的无云未来。但是,当我将第一个基类移到通用代码模块时,发现了很多不明显的依赖关系,所以Xcode中用红色覆盖了很多代码行,我不想继续。



该项目包含许多遗留代码,Objective-C和Swift中类的交叉依赖,iOS开发方面的不同目标,令人印象深刻的CocoaPods列表。远离此巨石的任何步骤都会导致该项目停止在Xcode中进行构建,有时会在最意外的地方发现错误。



因此,我决定写下为使此类项目的所有者的生活更轻松而采取的行动顺序。



第一步



它们很明显,关于它们的文章很多。苹果公司试图使它们尽可能的易于使用。



1.创建第一个模块:文件→新建项目→Cocoa Touch Framework



2.将模块添加到项目工作区中











。3 .在模块的“嵌入式二进制文件”中指定主项目对模块的依赖性。如果项目中有多个目标,则该模块将需要包含在每个依赖于该目标的目标的“嵌入式二进制文件”部分中。



我只会添加我自己的一条评论:不要着急。



您知道该模块中将放置什么,这些模块将在什么基础上划分?在我的版本中,应该UIViewController与表格和单元格聊天。带有聊天功能的Cocoapods应该附加到模块上。但是结果却有所不同。我不得不推迟聊天的实现,因为UIViewController它的演示者甚至单元都基于新模块不知道的基本类和协议。



如何突出显示模块?最合乎逻辑的方法-针对“ ficham»(功能),即用于某些用户任务。例如,与技术支持聊天,注册/登录屏幕,带有主屏幕设置的底部表格。另外,最有可能的是,您将需要某种基本功能,这不是功能,而只是一组UI元素,基类等。该功能应移至与著名的Utils文件类似的通用模块中...也不要害怕拆分此模块。立方体越小,将它们安装到主楼中就越容易。在我看来,这就是可以制定出更多SOLID原则的方式



有一些现成的技巧可用于划分模块,我没有使用过,这就是为什么我破坏了如此多的副本,甚至决定谈论痛苦的副本的原因。但是,这种方法–首先采取行动,然后思考–只是让我对整体项目中依赖代码的恐惧感到震惊。当您处于旅程的开始时,您会发现很难掌握消除依赖项所需的全部更改。



因此,只需将类从一个模块移动到另一个模块,查看Xcode中泛红的内容,然后尝试找出依赖性。 Xcode 10很棘手:将文件的链接从一个模块移动到另一个模块时,它将文件放在同一位置。因此,下一步将是这样...



4.在文件管理器中移动文件,删除Xcode中的旧链接,然后将文件重新添加到新模块中。一次完成一堂课,将更容易避免陷入依赖关系中。



为了使所有分离的实体都可以从模块外部使用,您必须考虑Swift和Objective-C的特性。



5.在Swift中,所有类,枚举和协议都必须使用访问修饰符进行标记public然后可以从模块外部对其进行访问。如果将基类移至单独的框架,则应将其标记为修饰符open,否则将无法从其创建后代类。



您应该立即记住(或第一次学习)Swift中的访问级别,并获得收益!







更改移植类的访问级别时,Xcode将要求您将所有覆盖的方法的访问级别更改为相同。







然后,您需要将新框架的导入添加到使用选定功能的Swift文件中,以及一些UIKit。之后,Xcode中的错误应该更少了。



import UIKit
import FeatureOne
import FeatureTwo

class ViewController: UIViewController {
//..
}


使用Objective-C,序列会稍微复杂一些。此外,框架也不支持使用桥接头将Objective-C类导入Swift。







因此,框架设置中的Objective-C桥接标题字段必须为空。







摆脱这种情况是有办法的,为什么这样做是单独研究的主题。



6.每个框架都有其自己的伞头文件,所有公共Objective-C接口都将通过该文件查看外界。



如果您在此伞形头文件中指定导入所有其他头文件,则它们将在Swift中可用。







import UIKit
import FeatureOne
import FeatureTwo

class ViewController: UIViewController {    
    var vc: Obj2ViewController?
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
    }


在Objective-C中,要访问模块外部的类,您必须使用其设置:公开头文件。







7.将所有文件一一传输到另一个模块后,请不要忘记Cocoapods。如果某些功能最终出现在单独的框架中,则需要重新组织Podfile。这对我来说就是这样:必须将带有图形指示符的窗格添加到常规框架中,并将聊天室(新窗格)包含在其自己的单独框架中。



有必要明确指出该项目现在不仅是一个项目,而且是一个包含子项目的工作空间:



workspace 'myFrameworkTest'


框架共有的依赖关系应移到单独的变量中,例如,networkPodsuiPods



def networkPods
     pod 'Alamofire'
end



 def uiPods
     pod 'GoogleMaps'
 end


然后,将描述主项目的依赖项:



target 'myFrameworkTest' do
project 'myFrameworkTest'
    networkPods
    uiPods
    target 'myFrameworkTestTests' do
    end
end 


聊天的框架依赖-这种方式:



target 'FeatureOne' do
    project 'FeatureOne/FeatureOne'
    uiPods
    pod 'ChatThatMustNotBeNamed'
end


水下岩石



可能可以完成,但是后来我发现了一些隐式问题,我也想提及。



所有常见的依赖关系都移到了一个单独的框架中,然后进行了聊天-移到了另一个框架上,代码变得更加简洁了,项目已构建,但是在启动时崩溃。



第一个问题是在聊天实现中。在庞大的网络中,问题发生在其他Pod中,只是google未加载图书馆:原因:未找到图片”。正是由于这个信息,秋天才发生了。



我找不到更优雅的解决方案,因此不得不在主应用程序中复制带有聊天的pod连接:



target 'myFrameworkTest' do
    project 'myFrameworkTest'
    pod 'ChatThatMustNotBeNamed'
    networkPods
    uiPods
    target 'myFrameworkTestTests' do
    end
end


因此,Cocoapods允许应用程序在启动时和项目编译时查看动态链接的库。



另一个问题是资源,我已经安全地忘记了这些资源,并且从未想到要提到这方面。尝试注册单元xib文件时,应用程序崩溃:“无法在捆绑包中加载NIB”默认的



构造函数在主应用程序模块中查找资源。自然,当您在整体项目中进行开发时,您对此一无所知。 解决方案是指定在其中定义资源类的捆绑软件,或者让编译器使用构造函数自行执行init(nibName:bundle:)UINib



init(for:)Bundle...而且,当然,不要忘记从现在开始,资源现在可以对所有模块都通用,也可以特定于一个模块。



如果模块使用xib,则Xcode将照常提供按钮并UIImageView从整个项目中选择图形资源,但是在运行时,不会加载位于其他模块中的所有资源。我使用init(named:in:compatibleWith:)class的构造函数在代码中加载了图像UIImage,其中第二个参数Bundle是图像文件所在的位置。



在细胞UITableViewUICollectionView现在也必须注册以类似的方式。而且我们必须记住,字符串表示形式的Swift类还包括模块的名称,而NSClassFromString()Objective-C的方法返回nil,因此建议您通过指定字符串(而不是类)来注册单元格。对于UITableView您可以使用以下帮助方法:



@objc public extension UITableView {

    func registerClass(_ classType: AnyClass) {
        let bundle = Bundle(for: classType)
        let name = String(describing: classType)
        register(UINib(nibName: name, bundle: bundle), forCellReuseIdentifier: name)
    }
}


结论



现在,您不必担心一个请求是否包含在不同模块中进行的项目结构更改,因为每个模块都有自己的xcodeproj文件。您可以分配工作,而不必花费几个小时将项目文件放在一起。在大型和分布式团队中拥有模块化体系结构很有用。结果,应该提高开发速度,但事实恰恰相反。与在整体中创建聊天相比,我在第一个模块上花费了更多的时间。苹果



还指出了明显的优点,-重用代码的能力。如果应用程序具有不同的目标(应用程序扩展),则这是最易于访问的方法。聊天不是最好的例子。我本来应该从布局网络层开始的,但是老实说,这是一条漫长而危险的道路,最好将其分成几小部分。由于过去几年来这是引入第二种服务来组织技术支持,因此我想在不引入的情况下实施它。在哪里保证第三个不会很快出现?



开发模块时,一种不明显的效果是更周到,更简洁的界面。开发人员必须设计类,以便可以从外部访问某些属性和方法。不可避免地,您必须考虑隐藏什么以及如何制作模块,以便可以轻松地再次使用它。



All Articles