干货 | 一波N折的携程酒店Swift-Objc混编实践

iOS摸鱼周报 第十三期 iOS摸鱼周报,主要分享开发过程中遇到的经验教训、优质的博客、高质量的学习资料、实用的开发工具等。周报仓库在这里:https://github.com/zhangferry/iOSWeeklyLearning ,如果你有好的的内容推荐可以通过 issue 的方式进行提交。另外也可以申请成为我们的常驻编辑,一起维护这份周报。另可关注公众号:iOS成长之路,后台点击进群交流,联系我们,获取更多内容... 阅读详情

作者简介

 

睿东,2009年加入携程,从事无线研发,现负责酒店无线研发工作。

说起Swift,对iOS开发者来说那是既熟悉又陌生,虽然早在2014年苹果就发布了Swift1.0版本,但在这之后的五六年时间里,一直处于不温不火的状态。ABI的不稳定以及API的不向前兼容,更是被程序员调侃为“自从学了 Swift 之后,每年都要学一门新语言”。

这种情况一直持续到2019年3月,在WWDC19大会上,终于传来一个令人期待已久的好消息。伴随着Swift5.0发布的同时,也终于宣布了Swift ABI的稳定,开发者们不禁奔走相告。因为从此之后,Swift终于可以摆脱对编译器版本的限制,不同版本Swift编译的app无需再借助app内的runtime就能和操作系统互相之间无缝通讯。Swift 终于可以算是一门真正成熟的编程语言了。

在此之后,沉寂多年的Swift突然走上了一条快速发展的道路。苹果公司开始快速发力对Swift的布局,步伐快得令人有点猝不及防,在下半年的WWDC会上又接连推出了SwiftUI,Combine,以及RealityKit三款纯Swift的Framework或API。虽然从兼容性(仅限iOS13及以上)角度来看,他们的实用性还早,但这一系列动作已经展现出苹果公司对于Swift未来的决心,让人惊呼Swift的未来已来。

从行业流行度的数据来看,Swift发展得远比我们想象中要快。根据阿里手淘团队不久前对app store排行榜TOP1000的APP进行文件扫描分析结果得知,美区使用Swift的APP占比已经达到了78%,剩余未使用的还是一些来自中国地区的产品,由此可见Swift在国外的热度已经非常高了。即便是在中国区,TOP100的APP也有26家使用了Swift,超过了使用React Native和Flutter的数量,仅次于Objc,具体数据如下图所示:

在一些热门社区如StackOverFlow上,Swift问题的热度也已经远超Objective-C。一些Objective-C的问题开始无人关注或解答,苹果官方的开发者网站更是早在2017年便开始不再提供Objective-C代码的示例。另外,在最近两年的校园招聘中,也有越来越多的学生表示他们已经直接从Swift开始学习iOS开发。

种种迹象表明,iOS开发语言的重心已经在悄悄倒向Swift,开发者们对Swift的信心正在被重新点燃。对于我们携程酒店技术团队而言,此时对Swift展开调研是一个很好的时机,这不仅仅是为了跟上新技术的发展,也是为了避免将来有技术踏空的风险。因为也许很快Objective-C将不再是开发iOS的最优选择,并且未来会有可能很难招聘到Objective-C的开发,尤其是校园招聘。

于是,我们迅速组织研发人力,对Swift开发在携程主app内的可行性展开了调研和实践。

 

一、先从哪里开始呢

万事开头难,不过好在苹果开发者网站给出了一些迁移的经验和守则,其中第一条就说到“Remember that you can't subclass a Swift class in Objective-C.Therefore, the class you migrate can't have any Objective-C subclasses.” 既然Swift类不能被Objective-C继承,那么最适合首先迁移的还是那些底层工具类代码,同时为了让架构看上去更清晰,我们决定新建一个Swift库来管理所有迁移好的Swift代码。

虽然在选择是静态库还是动态库的问题上纠结了很久,但由于目前携程app的架构主要是由各bu之间互相依赖静态库的调用构成,所以最终我们还是选择了对架构变动影响最小的静态库方式。幸运的是,Swift编译静态库在xcode9就已经被苹果支持,所以我们的此次实践并不需要对app工程架构做出任何调整,直接以静态库的形式来引入Swift即可。

 

二、Objc& Swift混编

集成好Swift静态库之后,马上开始准备我们第一次的Objective-C和Swift混编,不幸的是模拟器启动后即崩溃了,控制台上显示“dyld: Library not loaded: @rpath/libswiftCore.dylib”,程序启动时加载Swift动态库失败了。

在stackoverflow上查阅问题后得知,我们除了需要在Runpath Search Path中添加/usr/lib/swift之外,还需要将Always Embed Swift Standard Libraries设置为Yes,如下图所示:

但这个设置似乎和我们之前理解的ABI稳定有点冲突,ios12.2之前的版本因为系统没有内置Swiftruntime和动态库,所以需要在app中打入Swift runtime。那么Always Embed Swift Standard Libraries设置为Yes之后,是不是就意味着我们在12.2之后的版本也会打上这个库呢?

答案是肯定的,但这并不意味着最终在用户端也一定会下载到这个库。App store 和操作系统在安装iOS或者watchOS的 app 时会通过一些列的优化,尽可能减少安装包的大小,使得 app 以最小合适的大小被安装到你的设备上,这个过程被称作为APP Thinning。

所以开发者只需尽管上传兼容所有版本功能的app包,系统会负责将app剪裁到最适合用户的最小体积来下发,每台设备都只会下载符合各自机型和操作系统所需要的可执行文件和资源。也就是说每个用户下载到的包大小差异取决于用户手机的操作系统版本,这个过程如下图所示:

 

三、Objc-> Swift

解决了混编问题之后,我们开始着手在Objective-C工程内尝试调用Swift模块,Swift模块编译后会生成一个以xxx-Swift.h结尾的头文件,通过导入这个头文件,如:

#import<SwiftLibA/SwiftLibA-Swift.h>

就可以在Objc项目里引用Swift方法了,试了一下,在xcode里很顺利地跑了起来。但如上文所说,携程整个app的架构是由对静态库的依赖构成,所以在CI平台上是针对各个静态库单独打包编译的。在单独编译Objc库的情况下,打包失败了,控制台又给我们留下一句话:“SwiftLibA/SwiftLibA-Swift.h' file not found”。

在解答这个问题之前,先让我们回顾一下C语言家族引入头文件的两种方式,分别是:

#include "path-spec"
#include<path-spec>

引号表示让预处理器去源文件目录下搜索头文件,尖括号则表示去环境变量所指定的目录下去搜索,了解完这个机制后,再来看上面的这个问题。Swift模块编译时产生的头文件是放在build目录中的,而不是在源文件目录下,而我们的打包脚本只会在依赖项的源文件目录中搜索,所以在单独编译Objc库的时候就会找不到Swift头文件。

要修改那个动辄上千行如天书一般难以理解的打包脚本,显然不是最快的解决方案。我们也曾动过要换动态库方式的念头,但这个对工程变动的影响太大,短时间内应该得不到支持,而且苹果也是推荐优先使用静态库,所以只能换个思路去解决这个问题。既然CI不支持在环境变量目录中去搜索头文件,那我们就把它从build目录中copy出来当源文件使用(需加入git做版本控制)。

为了方便这个操作,我们使用脚本在每次编译完成后就把最新的Swift头文件自动copy到Swift模块所在的源文件目录中,完整的脚本如下:

mkdir -p${include_dir}
cp${generated_header_file} ${include_dir}

 # 去掉xxx-Swift.h 文件头部注释中的编译器的版本号

sed -i"" "s/^\/\/ Generated by Apple.*$/\/\/ Generated byApple/g" ${generated_header_file}

 # 拷贝xxx-Swift.h 文件到工程源码目录

header_file_in_proj=${SRCROOT}/${PROJECT}-Swift.h
needs_copy=true
if [ -f"$header_file_in_proj" ]; then
    echo "${header_file_in_proj} 已存在"
   
    new_content=$(cat ${generated_header_file})
    old_content=$(cat ${header_file_in_proj})
    if [ "$new_content" ="$old_content" ];then
        echo "文件内容一致,无需再Copy:"
        echo "${generated_header_file}"
        echo "${header_file_in_proj}"
 
        needs_copy=false
    fi
fi
 
if ["$needs_copy" = true ] ; then
   
    echo "文件内容不一致,需要Copy:"
    echo "复制文件:"
    echo "${generated_header_file} "
    echo "${header_file_in_proj} "
 
    cp ${generated_header_file}${header_file_in_proj}
fi

至此,在Objective-C项目内调用Swift静态库的问题全部得到解决,终于能让Swift模块可以愉快的在objc项目中被随意使用了。

 

四、Swift-> Swift

本以为项目会就此进入坦途,但没过几天,就迎来了新问题。随着项目进行的需要,我们要把Swift静态库一拆为二,彼此之间单向依赖,于是我们的问题就变成了Swift静态库如何互相之间调用的问题。乍一看这并不是什么大问题,Objc调Swift都能解决,Swift调Swift还不简单,几行代码就能实现,如下: 

 

importFoundation
import SwiftLibB
 
@objcMembers
public classSwiftLibA: NSObject {
    public func sayHello(name: String) {
        SwiftLibB().sayHello(name: name)
        print("Hello, this is " +name + "!")
        print("-- Printed by SwiftLibA")
    }
}

代码非常简单,编译整个工程也没有遇到任何问题,但是跟之前遇到问题一样的是当你试图单独编译模块SwiftLibA时,再次发生了报错,“No such module 'SwiftLibB’”,编译器找不到对SwiftLibB的引用。

根据之前的经验,我们很快就断定这是同一个原因,但是上文提过我们已经把Swift头文件copy到源文件目录中了,为什么突然不起作用了呢?很显然是因为Swift模块间的互相调用跟Objc调用Swift不同,他们并不依赖那个编译出来的头文件。所以问题来了,Swift模块间是通过什么方式来对外暴露API的呢?

在官方文档中我们找到了答案, “Swift uses an opaquearchive format called “swiftmodule” to describe the interface of a library”,意思是说Swift使用一个叫swiftmodule的文件来描述一个库的接口申明,在编译目录下,我们果然找到了这个文件,如下图所示:

明白了Swift模块间的接口声明方式后,接下去就要像之前导出XXX-Swift.h文件一样,如法炮制,把swiftmodule文件也同样导出到源文件目录,然后再设置SwiftLibA的import path,并把这几个文件添加到git库中做版本管理。

一顿操作后大功告成,最后检验下成果,这时单独编译SwiftLibA终于没有问题了,于是提交代码,开始准备远程打包然后收工,但令人意外的是MCD(携程CI打包工具)竟然报错了,“error: Module compiled with Swift 5.1 cannot be imported by the Swift 5.1.2compiler”。

为什么会这样,仔细再看了下文档,原来之前的话还有后半句被我们忽略了,“However, the “swiftmodule” format is also tied to the currentversion of the compiler”。原来swiftmodule是跟编译器版本强相关的,不同版本编译器编译出来的库是不能被互相兼容的,也就是说Swift5.0虽然已经做到了运行时ABI stability,但还没有做到编译时的模块稳定(Module stability)。不过幸运的是当我们遇到这个问题的时候,Swift已经发布了5.1版本,及时加入了解决Module stability的方案,下面先用图1来表示我们最初使用Swift模块的方法。

图1

图2则是模块稳定后的解决方案,唯一的区别只是将swiftmodule文件改成了swiftinterface文件,swiftinterface文件作为 swiftmodule的一个补充,它是一个描述 module 公开接口的文本文件,不受编译器版本限制,并可以被手动编辑。

 

图2

比如,你用 Swift6编译器编译出了一个library,通过它的swiftinterface文件,这个库就也可以在 Swift7编译器上使用,如下图所示:

下面就让我们来实践一下获取,打开SwiftLibB的BuildSetting,找到Build Options -> Build Libraries for Distribution,设置为YES,如下图所示:

然后再重新编译一下,打开build目录,这时就能看到里面多了几个swiftinterface文件,这是一个可以被编辑的文件,也可以进行手动修改,如下图所示:

swiftinterface文件中的内容大概如下:

 

//swift-interface-format-version: 1.0
//swift-compiler-version: Apple Swift version 5.1 (swiftlang-1100.0.270.13clang-1100.0.33.7)
//swift-module-flags: -target x86_64-apple-ios13.0-simulator -enable-objc-interop-enable-library-evolution -swift-version 5 -enforce-exclusivity=checked -Onone-module-name SwiftLibB
importFoundation
import Swift
@objc@objcMembers public class SwiftLibB : ObjectiveC.NSObject {
  @objc public func sayHello(name: Swift.String)
  @objc override dynamic public init()
  @objc deinit
}

所以,除了swiftmodule外,我们还需要把swiftinterface文件也一起提供给第三方调用者,并一起copy到源文件目录。

模块的稳定意味者二进制库的稳定,Swift库之间的调用终于不用再依赖源码或者编译器版本,这对于Swift的发展来说是一个很大的进步,将更有助于推动Swift的发展。

 

五、Swift-> Objc

原本以为到这里应该是解决完了所有问题,但计划不如变化来得快。虽然在设计之初我们在原则上约定了只允许ojbc引用swift,不允许被反过来引用,但很快我们就不得不推翻了这个约定。因为我们发现这是一件不可避免的事情,比如我们很多引用都来自携程公共团队的底层模块,这些模块都是基于objc的,甚至还有一些第三方的objc库,在公共底层库没转Swift之前,这就是一个无法被避免的问题。

不过好在苹果官网早就提供了解决方案,在《ImportingObjective-C into Swift》一文中分别提供了Objc文件是在同一app target内被引用还是作为Framework使用时的两套解决方案。

在同一app target内被引用时较为简单,只需创建一个以“-Bridging-Header.h”为后缀名的文件即可,并把需要暴露给Swift的objc 头文件在这里进行编辑就可以了,具体如何创建这个文件本文就不做赘述了。

 

我们在文章开头部分曾介绍过携程app架构主要采用的是静态库依赖的构成方式,所以上面的方案对我们并不适用。因为Swift终于引入了命名空间的概念(Objective-C一直以来令人诟病的地方之一就是没有命名空间),但是和C#这样显式在文件中指定命名空间的做法不同。Swift 的命名空间是基于 module 而不是在代码中显式地指明,每个 module 代表了 Swift 的一个命名空间,在这种情况下我们的Swift静态库无法采用Bridging header方式,这时就必须要把这些头文件导入到Objective-C的umbrella header中,Swift 会通过这个文件看到所有你在 umbrella header 中公开暴露出来的头文件。

看到这里我们不禁有个疑问,到底什么是umbrellaheader?其实这并非是个新鲜玩意,相反,这是早在2012年就由苹果在LLVM DevMeeting提出并实现的概念,目的就是要颠覆传统的头文件引用方式。

我们知道在C/C++以及Object-C这一系列C语言家族的编程语言里,在需要引用到其他库的时候,通常是通过引用头文件的方式来访问。但这类机制有很多问题,其中最大的问题是预编译效率不高,因为头文件的描述是基于文本(textual)形式的,所以预编译器需要对其进行语义分析。由于这个过程是递归进行的,所以会导致编译时间变得非常不可控,假设有N个源文件每个都有M个头文件,那么所带来的编译成本就是N x M,即便有很多头文件是重复引用的也是如此。

所以LLVM引入Module的概念来解决这个问题,Module采用更高效的树形结构描述来导入头文件,整个Module只会编译一次,头文件也只解析一次,避免了被重复引用,这样一来之前M x N的问题就变成了简单的M+N。

而Module机制中一个很重要的文件就是modulemap,它是module和头文件之间产生联系的关键,是用来描述头文件和module结构在逻辑上的对应关系。如果一个库(library)想要作为module被使用,那就必须要有一个对应的“module.modulemap”文件,在这个文件中声明要引用的头文件,并和那些头文件放在一起,一个C标准库的 module map 文件可能是这样的:

 

modulemap 中的内容是通过 module map 语言来实现的,module map 语言中有一些保留字,其中带umbrella关键字的header申明就叫做umbrella header,作用是可以把它所在目录下的所有头文件都包含进来,这样开发者中只要导入一次就可以使用这个 library 的所有 API 。

创建modulemap的方法很简单,如果是动态库在编译的时候系统会自动替我们生成,如果是静态库则需要我们手动生成并编辑这个文件。

做到这里不禁会联想到目前携程app项目内头文件引用的灾难,导致编译效率极其低下,其实是时候用module的思路来重构一下我们的项目了,当然这又会是一项庞大的工程。

 

六、总结

至此,我们终于解决完了Swift在携程app内应用的所有已知问题,让Swift以静态库的形式完美集成到项目中,并可以在Swift和Objective-C之间互相调用,和携程的CI平台也能无缝集成。目前在实际项目中已经开始使用Swift来写部分需求,未来的一些新功能我们也会考虑直接用Swift来开发。

在这次的实践过程中我们领略到了Swift作为一门先进语言的魅力,众多的新特性让研发效率有了显著提高,经过我们Swift重写的framework代码量都有不同程度的下降。

由于篇幅和主题的原因,本文就止步于探讨将Swift集成到Objc工程中的一些问题和经验。对于Swift语言本身的一些探讨有机会可以另作分享,我们相信更现代、更安全的 Swift 会变得越来越流行,希望有越来越多的开发者可以早日加入Swift的阵营。

【推荐阅读】

《携程架构实践》

京东

当当

《携程人工智能实践》

京东

当当

 “携程技术”公众号

  分享,交流,成长

swift-多级分类菜单多级列表仿口碑分类菜单电商筛选菜单 多级分类菜单 多级列表 仿口碑分类菜单 电商筛选菜单 立即下载

相关推荐

swift-Umbrella☂️Swift的分析抽象层

Umbrella ☂️ Swift的分析抽象层

Swift之Umbrella header .h not found

最近在学习Swift,从网上下载了个Swift项目看看,结果pods update后遇到了: error: umbrella header 'Realm.h' not found umbrella header "Realm.h" error: could not build Objective-C module 'Realm' 经自己尝试未果,Stackoverflow后找

Companion_1314的专栏 3527

Hotels_Server:酒店预订系统后台管理系统

Hotels_Server 住哪儿项目后台管理系统 住哪儿App的后台管理功能,包括主题酒店,特色酒店发布,注册用户的统计表,订单的统计表,酒店的统计表。Controller目录下的Api下就是给App开发的api。想写api的童鞋他们可以看看。 包括php系统开发的常见功能:复杂表单的提交,包括多图片上传 基于Bootstrap框架的界面布局 封装PDO数据库操作类,实现增删改查等常见功能,调用简单 随机酒店推荐策略 多条件模糊搜索酒店算法 jquery表格插件Datatables的使用大大丰富了数据的展现方式,相互干扰性。具有自动分页,模糊搜索等功能 封装了响应类,可以传递4个参数show($ code,$ message ='',$ data = array(),$ type = self :: JSON)来展示json或者array或者xml。 工程结构一幕了然,便于扩展 效果截图 ToDo:随后可能会实现MVC框架优化。客户端接口想实现缓存出来,当客户端请求的数据存在于缓存的时候直接从缓存中读取。 如果有不懂的地方可以加入QQ交流群讨论: 。这个QQ群讨论技术范围包括:i

操作系统文件属性

初学者经常使用“ll”命令,但对使用命令后屏幕出现的信息不太了解,今天来和大家分享一下。 01.文件数据的inode信息 — inode 索引节点 作用:当我们查找数据信息时,操作系统的后台的工作时,通过inode表查找到相应数据存储在磁盘的位置,然后再显示出来 02.文件的数据类型 — 普通文件 目录 连接文件 设备文件 作用:是表明数据的文件类型 普通文件“-“ 目录 “d" 链接文件“l” 设备文件”b/c“ 03.权限位信息:r 读 w 写

cxs_123的博客 689

swift和OC混合编程实例源代码

Xcode 7.1项目中Objective-C和swift混合编程,文章的源代码。

swift-Hotels一个开源的酒店预定App及其后台管理系统

Hotels一个开源的酒店预定App及其后台管理系统

Swift和Objective-C混编的注意啦

链接:https://www.jianshu.com/p/2ed48b954612前言Swift已推出数年,与Objective-C相比Swift的语言机制及使用简易程度上更接地气,大大降低了iOS入门门槛。当然这对新入行的童鞋们来讲,的确算是福音,但对于整个iOS编程从业者来讲,真真是,曾几何时“高大上”,转瞬之间“矮矬穷”。再加上培训班横行,批量批发之下,iOS再也看不到当年的辉煌。往事不再提...

蓝叶菱的专栏 5630

iOS创建与使用静态库

在日常项目开发中,不论是为了两个公司项目上的业务交流还是为了减少项目的编译时间,有的时候我们会把项目中的私密内容打包成静态库,或者是把项目中变动较少一部分打包成静态库以便提高编译效率,那么下面我们就来学习一下”iOS-静态库的创建与使用”: (一)iOS静态库、动态库与Framework静态库与动态库的区别 (1)什么是库? 库(Library)直白一点说就是一段编译好的二进制代码,加上头文件就可以供别人使用;(例如: iOS中Objective-C编译下的.h和.m文件,打包静态库后会变为.h和.a文件); (2)什么是静态库? ①静态库即静态链接库(例如: windows下的.lib、Ma

ios 在oc工程中使用swift文件混编

1首先需要将 always embed swift standard libraries 设置 为yes,如上图2然后设置SWIF语言的版本,如下图

liubo的博客 2779

GoldenDB 兼容模式冲突场景解析:MySQL 连接模式 vs Oracle 语法兼容

GoldenDB 兼容模式冲突场景解析:MySQL 连接模式 vs Oracle 语法兼容

weixin_45207751的博客 2252

RK3588开发板DDR频率上不去?手把手教你用ddrbin_tool降频调优(附完整配置流程)

本文详细介绍了RK3588开发板DDR频率调优的完整解决方案,重点使用ddrbin_tool工具进行降频配置。从硬件原理分析到实战操作,包括参数导出、频率调整、二进制生成及烧录验证,帮助开发者解决DDR高频不稳定问题,提升系统可靠性。适用于嵌入式开发、工业控制等场景。

weixin_42713608的博客 544

iOS 库的 umbrella header 里的 versionNumber 是什么

//! Project version number for LZDUtility. FOUNDATION_EXPORT double LZDUtilityVersionNumber; //! Project version string for LZDUtility. FOUNDATION_EXPORT const unsigned char LZDUtilityVersionString[...

acalism的专栏 701

Apk脱壳圣战之---脱掉“梆梆加固”的保护壳

一、前言 现如今Android用户的安全意识不是很强,又有一些恶意开发者利用应用的名字吸引眼球,包装一个恶意锁机收费的应用,在用户被骗的安装应用之后,立马手机锁机,需要付费方可解锁。这样的恶意软件是非常让人痛恨的。所以本文就用一个案例来分析如何破解这类应用,获取解锁密码,让被骗的用户可以找回爽快! 二、分析软件锁机原理 本文用的是一款叫做:安卓性能激活.apk,关于样本apk文件后面会...

尼古拉斯.赵四的博客 5061

Swift和Objective-C混合编译

1,在已有的Swift项目添加OC文件,或者在已有的OC项目添加Swift文件,会提示是否创建Objective-C Bridging Header。 2,点击“Create Bridging Header”,会创建bridge文件,系统默认的文件名是项目名+bridge+Header.h,并且系统默认会在 Build Settings-&gt;Objective-C Bridging He...

培根芝士的专栏 726

保姆级教程:在摩尔线程MTT S80上快速部署Ollama运行DeepSeek R1模型(附性能测试)

本文提供了一份详细的保姆级教程,指导用户在摩尔线程MTT S80显卡上部署Ollama并运行DeepSeek R1模型。内容涵盖从驱动安装、Docker环境配置到模型拉取、性能测试的全流程,并分享了实测数据与优化技巧,旨在帮助开发者快速在国产GPU平台上实现高效的大语言模型推理。

weixin_29091837的博客 525

量化策略分享之ST股弱转强,回测年化123%

本策略的核心是精选ST股票,国九条过滤,防止直接暴雷退市,只做ST股票的弱转强,弱转强的认定标准是前日涨停但昨日不涨停,昨日收盘价大于前一日最低价,昨日收盘价大于10日线,昨日成交量大于前一日的成交量但小于前一日成交量的10倍,股价大于1;这类股票强势,有资金介入,能获得超额收益。

qq_29301281的博客 1268

终于来了,Swift App 不在包含 Swift 标准库

Swift apps no longer include dynamically linked libraries for the Swift standard libr...

寻找改变未来的算法 401

【ComfyUI】FluxKontext 身形调优与体态增强

AI身形调优与体态增强解决方案 该工作流通过Flux系列模型实现人物体态的智能化增强,主要特点包括: 采用UNet主体模型+双文本编码器+身形增强LoRA的架构组合 通过ReferenceLatent保持原始姿态的同时强化胸腰臀比例 完整工作流包含图像加载、文本编码、潜空间映射等8个核心节点 支持自动化的前后效果对比展示 应用优势: 保持人物自然姿态的前提下实现局部强化 支持通过Prompt精确控制增强方向 适用于摄影后期、角色美化等创作场景 输出效果稳定且风格统一 使用方

Mr数据杨 1300

ios-swift-导入Alamofire出坑

前言:在最近的swift项目中要用到网络请求,就用到了Alamofire网络库(是AFNetworking库的swift版本)。将Alamofire利用Cocoapods导入到项目里面,总是下载不了新的版本,下载不了新的版本就是报错,我用的xcode是最新版本8.3.2,swift框架是swift3.0最新框架。我就开始上网查呀,结果废了好大劲才解决。 问题所在:根本原因是cocoapods的版本太

haha_hello的博客 1748

拼多多Android面试题及参考答案

Android 系统采用分层架构设计,从底层到上层依次为Linux 内核层硬件抽象层(HAL)系统运行时库层应用框架层应用层,共五层。位置:Linux 内核层与系统运行时库层之间,是“硬件与系统的隔离层”。核心作用:屏蔽硬件差异,为上层提供统一的硬件访问接口,解决 Android 开源与硬件厂商闭源的矛盾,具体功能如下:硬件能力抽象:将硬件的具体实现(如不同厂商的摄像头芯片、音频芯片)封装为标准化接口(如Camera HALAudio HAL。

大模型大数据攻城狮的专栏 1284

DeFi核心协议代码解析

本文解析了DeFi两大核心协议Uniswap V2和Compound的智能合约架构与关键代码。Uniswap V2采用恒定乘积公式(x*y=k)实现自动做市,核心合约包括Factory、Pair和Router,具备流动性铸造、代币兑换和TWAP预言机功能。Compound借贷协议通过cToken合约管理存款和借款,采用动态利率模型和抵押率机制,包含资金利用率计算、清算触发等核心算法。两协议均采用模块化设计,通过智能合约实现去中心化金融服务的核心逻辑。

闲人编程的博客 935

iOS 安装包瘦身(下篇)

本文来自网易云社区作者:饶梦云2.4. 清理无用代码2.4.1. Dead Code StrippingActivating this setting causes the -dead_strip flag to be passed to ld(1) via cc(1) to turn on dead code stripping. Remove functions and data that a...

网易数帆社区博客 1134

Data-Structures-and-Algorithm-Analysis-in-Java.pdf

作者: Weiss, Mark A. 出版社: Addison Wesley Data Structures and Algorithm Analysis in Java is an “advanced algorithms” book that fits between traditional CS2 and Algorithms Analysis courses. In the old ACM Curriculum Guidelines, this course was known as CS7. This text is for readers who want to learn good programming and algorithm analysis skills simultaneously so that they can develop such programs with the maximum amo...

Swift Standard Library: Documented and undocumented built-in functions in the Swift standard library

Swift Standard Library: Documented and undocumented built-in functions in the Swift standard library – the complete list with all 74 functions Posted on June 14, 2014 by practicalswift Swift

chaoyuan899的专栏 1346

VC中sqlite的使用

在VC中对sqlite的使用,实现数据库大批数据的快速插入,读取数据库中的数据在ListCtrl中显示

LinePhone3.2.7(android端最新版本) sip客户端

SIP客户端For android:LinePhone3.2.7(最新版本),官网地址:http://www.linphone.org/

解决Xcode Umbrella Header for Module Bolts does not include header ‘文件名’

进入引入的Framework,需要在module.modulemap中,增加一个header文件,会看到如下格式: framework module framework名 {   umbrellaheader "framework名.h"   export *   module * {export * }      explicitmodule 要加入的文件名 {

dongyangTan的博客 3676

ObjectiveC开发教程--字符串的连接

NSString *type = @"hello";     NSString *subtype = @"good";     NSString *typesub = [NSString stringWithFormat:@"%@>%@",type,subtype];     NSString *typesub1 = [type stringByAppendingString:su

David.Wang 做你喜欢的事情 1761

ObjectiveC类的使用

基本使用 lijun.h // // Header.h // example // // Created by junli on 15/4/3. // Copyright (c) 2015年 junli. All rights reserved. // #ifndef example_Header_h #define example_Header_h /* Title:demo

lee353086的专栏 1216
上一篇: UED专栏 | 带你认识携程插画系统1.0
下一篇: 干货 | 敏捷模式下携程的接口自动化平台演变
携程技术
博客等级 码龄10年 3716粉丝 324原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值