首页
统计
留言板
关于
Search
1
Rust 包装 objc Block
166 阅读
2
Flutter 调用 Rust 生成的 共享/静态 库
157 阅读
3
02. Rust 内存管理 Copy & Clone(上)
157 阅读
4
用 Rust 开发 iOS 应用(粗糙版)
104 阅读
5
纯 C 写个 iOS App(误)
99 阅读
默认分类
Rust
Apple
iOS
Swift
Android
emulator
NES
登录
/
注册
Search
标签搜索
Rust
iOS
NES
Swift
Android
杂项
limit
累计撰写
18
篇文章
累计收到
0
条评论
首页
栏目
默认分类
Rust
Apple
iOS
Swift
Android
emulator
NES
页面
统计
留言板
关于
搜索到
18
篇与
的结果
2023-04-11
用 Rust 写个 CLI 解决 UWP Bilibili 下载的视频无法播放问题
发现问题众所周知,使用 wINDOWS 微软商店下载的 Bilibili 客户端是有视频下载功能的,不过它有个问题,下载后的视频无法直接使用一般的打开。 稍微研究(使用搜索引擎)一下发觉,原来是该客户端下载后的文件开头补了三个 0xFF。解决方案最简单的方法就是直接使用一些支持 hex 编辑的 editor 手动把它们删掉再保存,不过如果有多个文件(很明显这是常态),那一个一个手动处理太机械了,于是直接写个命令行工具解决。实现原理也很简单,就是直接跳过读取开头三个 0xFF 再写入到新文件。开始编码创建项目创建 Rust 项目先,直接执行 cargo new xxx 就把项目生成好啦。然后添加几个第三方库,首先自然是 clap,然后是错误处理,这种小工具就不手写自定义错误处理啦,使用 anyhow,还可以加个进度条指示器indictif,因为要批处理文件,文件夹内可能有非视频文件,所以加个 regex 包用正则过滤一下文件。其中 clap 用得是它的派生宏版本。cargo add clap -F derive cargo add anyhow cargo add indicatif cargo add regex可以 cat 查看一下 Cargo.toml 内容$ cat ./Cargo.toml --------------------------- [package] name = "xxx" version = "0.1.0" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] anyhow = "1.0.70" clap = { version = "4.2.1", features = ["derive"] } indicatif = "0.17.3" regex = "1.7.3"正式编码先定义好传参,其实就是接收输入的目录,所以用派生宏包一下结构体 Args#[derive(Parser, Debug)] #[clap(author, version, about, long_about = None)] struct Args { #[clap(value_parser)] input: String, }然后在 main 函数写上下面的代码,主要是获取待处理的目录,当它不是一个文件夹时直接 panicfn main() { let args: Args = Args::parse(); let p = Path::new(&args.input); if !p.is_dir() { panic!("The input is not directory!"); } read_file(p); }我们还得把 read_file 函数补上,这一步就是基本逻辑就是使用 std::fs::read_dir 读取文件夹内的子路径,判断一下读取到的是否是文件或文件夹,如果是文件夹就递归继续读取,直到读到文件为止,读取到文件就用 process_file 处理它,出错了不管直接 unwrap 死就死吧,小项目是这样的捏。fn read_file(dir: &Path) { for sub_path in read_dir(dir).expect("Failed to read file path.").flatten() { let p = sub_path.path(); if p.is_file() { process_file(&p).unwrap(); } else { read_file(&p); } } }个么很自然地就是实现 process_file 函数,先取到文件名(其实是取得完整的文件路径),然后通过正则过滤一下副档名是 mp4 的文件,读取文件大小给进度条结构用,判断下原文件开头三个字节是 0xFF 下继续进行下一步操作,主要是为了防止处理错文件。然后创建个空文件,循环读取原文件内容写入到新文件,后面再把原文件删除,把新文件修改一下名字,至此处理完成。const BUFFER_SIZE: usize = 1024; fn process_file(p: &Path) -> Result<(), anyhow::Error> { let filename = p .file_name() .expect("Failed to get filename.") .to_str() .expect("Failed to unwrap path str."); let mut buffer = [0u8; BUFFER_SIZE]; let file_regex = Regex::new(r"\.mp4$").expect("Failed to new file regex."); if file_regex.is_match(filename) { let mut original_file = File::open(p)?; let file_size = original_file.metadata()?.len(); let pb = ProgressBar::new(file_size); let mut count = original_file.read(&mut buffer)?; if buffer[0] == 0xFF && buffer[1] == 0xFF && buffer[2] == 0xFF { let new_filename = String::from("new_") + filename; let new_filename = p.with_file_name(new_filename); let new_file_path = Path::new(&new_filename); let mut new_file = File::create(new_file_path)?; let mut is_first = true; while count != 0 { if is_first { new_file.write_all(&buffer[3..count])?; pb.inc((count - 3) as u64); is_first = false; } else { new_file.write_all(&buffer[..count])?; pb.inc(count as u64); } count = original_file.read(&mut buffer)?; } remove_file(p)?; rename(new_file_path, p)?; pb.finish_with_message("done"); } } Ok(()) }然后就可以通过一般的视频播放器打开文件啦。
2023年04月11日
94 阅读
0 评论
0 点赞
2023-03-29
用 Metal 画一个三角形(Swift 函数式风格)
用 Metal 画一个三角形(Swift 函数式风格)由于今年换了一份工作,平时上班用得语言换成 Rust/OCaml/ReScript 啦,所以导致我现在写代码更倾向于写函数式风格的代码。 顺便试试 Swift 在函数式方面能达到啥程度。主要是我不会 Swift,仅仅为了好玩。创建工程随便创建个工程,小玩具就不打算跑在手机上了,因为我的设备是 ARM 芯片的,所以直接创建个 Mac 项目,记得勾上包含测试。构建 MTKView 子类现在来创建个 MTKView 的子类,其实我现在已经不接受这种所谓的面向对象,开发者用这种方式,就要写太多篇幅来描述一个上下文结构跟函数就能实现的动作。import MetalKit class MetalView: MTKView { required init(coder: NSCoder) { super.init(coder: coder) device = MTLCreateSystemDefaultDevice() render() } } extension MetalView { func render() { // TODO: 具体实现 } }我们这里给 MetalView extension 了一个 render 函数,里面是后续要写得具体实现。普通的方式画一个三角形先用常见的方式来画一个三角形class MetalView: MTKView { required init(coder: NSCoder) { super.init(coder: coder) device = MTLCreateSystemDefaultDevice() render() } } extension MetalView { func render() { guard let device = device else { fatalError("Failed to find default device.") } let vertexData: [Float] = [ -1.0, -1.0, 0.0, 1.0, 1.0, -1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0 ] let dataSize = vertexData.count * MemoryLayout<Float>.size let vertexBuffer = device.makeBuffer(bytes: vertexData, length: dataSize, options: []) let library = device.makeDefaultLibrary() let renderPassDesc = MTLRenderPassDescriptor() let renderPipelineDesc = MTLRenderPipelineDescriptor() if let currentDrawable = currentDrawable, let library = library { renderPassDesc.colorAttachments[0].texture = currentDrawable.texture renderPassDesc.colorAttachments[0].clearColor = MTLClearColor(red: 0.0, green: 0.5, blue: 0.5, alpha: 1.0) renderPassDesc.colorAttachments[0].loadAction = .clear renderPipelineDesc.vertexFunction = library.makeFunction(name: "vertexFn") renderPipelineDesc.fragmentFunction = library.makeFunction(name: "fragmentFn") renderPipelineDesc.colorAttachments[0].pixelFormat = .bgra8Unorm let commandQueue = device.makeCommandQueue() guard let commandQueue = commandQueue else { fatalError("Failed to make command queue.") } let commandBuffer = commandQueue.makeCommandBuffer() guard let commandBuffer = commandBuffer else { fatalError("Failed to make command buffer.") } let encoder = commandBuffer.makeRenderCommandEncoder(descriptor: renderPassDesc) guard let encoder = encoder else { fatalError("Failed to make render command encoder.") } if let renderPipelineState = try? device.makeRenderPipelineState(descriptor: renderPipelineDesc) { encoder.setRenderPipelineState(renderPipelineState) encoder.setVertexBuffer(vertexBuffer, offset: 0, index: 0) encoder.drawPrimitives(type: .triangle, vertexStart: 0, vertexCount: 3, instanceCount: 1) encoder.endEncoding() commandBuffer.present(currentDrawable) commandBuffer.commit() } } } }然后是我们需要注册的 Shader 两个函数#include <metal_stdlib> using namespace metal; struct Vertex { float4 position [[position]]; }; vertex Vertex vertexFn(constant Vertex *vertices [[buffer(0)]], uint vid [[vertex_id]]) { return vertices[vid]; } fragment float4 fragmentFn(Vertex vert [[stage_in]]) { return float4(0.7, 1, 1, 1); }在运行之前需要把 StoryBoard 控制器上的 View 改成我们写得这个 MTKView 的子类。自定义操作符函数式当然不是指可以定义操作符,但是没有这些操作符,感觉没有魂灵,所以先定义个管道符代码实现precedencegroup SingleForwardPipe { associativity: left higherThan: BitwiseShiftPrecedence } infix operator |> : SingleForwardPipe func |> <T, U>(_ value: T, _ fn: ((T) -> U)) -> U { fn(value) }测试管道符因为创建项目的时候,勾上了 include Tests,直接写点测试代码,执行测试。final class using_metalTests: XCTestCase { // ... func testPipeOperator() throws { let add = { (a: Int) in return { (b: Int) in return a + b } } assert(10 |> add(11) == 21) let doSth = { 10 } assert(() |> doSth == 10) } }目前随便写个测试通过嘞。Functional Programming现在需要把上面的逻辑分割成小函数,事实上,因为 Cocoa 的基础是建立在面向对象上的,我们还是没法完全摆脱面向对象,目前先小范围应用它。生成 MTLBuffer先理一下逻辑,代码开始是创建顶点数据,生成 bufferfileprivate let makeBuffer = { (device: MTLDevice) in let vertexData: [Float] = [ -1.0, -1.0, 0.0, 1.0, 1.0, -1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0 ] let dataSize = vertexData.count * MemoryLayout<Float>.size return device.makeBuffer(bytes: vertexData, length: dataSize, options: []) }创建 MTLLibrary接着是创建 MTLLibrary 来注册两个 shader 方法,还创建了一个 MTLRenderPipelineDescriptor 对象用于创建 MTLRenderPipelineState,但是创建的 MTLLibrary 对象是一个 Optional 的,所以其实得有两步,总之先提取它再说吧fileprivate let makeLib = { (device: MTLDevice) in device.makeDefaultLibrary() }抽象 map 函数根据我们有限的函数式编程经验,像 Optional 这种对象大概率有一个 map 函数,所以我们自家实现一个,同时还要写成柯里化的(建议自动柯里语法糖化入常),因为这里有逃逸闭包,所以要加上 @escapingfunc map<T, U>(_ transform: @escaping (T) throws -> U) rethrows -> (T?) -> U? { return { (o: T?) in return try? o.map(transform) } }处理 MTLRenderPipelineState这里最终目的就是 new 了一个 MTLRenderPipelineState,顺带处理把程序的一些上下文给渲染管线描述器(MTLRenderPipelineDescriptor),譬如我们用到的着色器(Shader)函数,像素格式。最后一行直接 try! 不处理错误啦,反正出问题直接会抛出来的fileprivate let makeState = { (device: MTLDevice) in return { (lib: MTLLibrary) in let renderPipelineDesc = MTLRenderPipelineDescriptor() renderPipelineDesc.vertexFunction = lib.makeFunction(name: "vertexFn") renderPipelineDesc.fragmentFunction = lib.makeFunction(name: "fragmentFn") renderPipelineDesc.colorAttachments[0].pixelFormat = .bgra8Unorm return (try! device.makeRenderPipelineState(descriptor: renderPipelineDesc)) } }暂时收尾已经不想再抽取函数啦,其实还能更细粒度地处理,因为函数式有个纯函数跟副作用的概念,像 Haskell 里是可以用 Monad 来处理副作用的情况,这个主题留给后续吧。先把 render 改造一下fileprivate let render = { (device: MTLDevice, currentDrawable: CAMetalDrawable?) in return { state in let renderPassDesc = MTLRenderPassDescriptor() if let currentDrawable = currentDrawable { renderPassDesc.colorAttachments[0].texture = currentDrawable.texture renderPassDesc.colorAttachments[0].clearColor = MTLClearColor(red: 0.0, green: 0.5, blue: 0.5, alpha: 1.0) renderPassDesc.colorAttachments[0].loadAction = .clear let commandQueue = device.makeCommandQueue() guard let commandQueue = commandQueue else { fatalError("Failed to make command queue.") } let commandBuffer = commandQueue.makeCommandBuffer() guard let commandBuffer = commandBuffer else { fatalError("Failed to make command buffer.") } let encoder = commandBuffer.makeRenderCommandEncoder(descriptor: renderPassDesc) guard let encoder = encoder else { fatalError("Failed to make render command encoder.") } encoder.setRenderPipelineState(state) encoder.setVertexBuffer(device |> makeBuffer, offset: 0, index: 0) encoder.drawPrimitives(type: .triangle, vertexStart: 0, vertexCount: 3, instanceCount: 1) encoder.endEncoding() commandBuffer.present(currentDrawable) commandBuffer.commit() } } }然后再调用,于是就变成下面这副鸟样子class MetalView: MTKView { required init(coder: NSCoder) { super.init(coder: coder) device = MTLCreateSystemDefaultDevice() device |> map { makeLib($0) |> map(makeState($0)) |> map(render($0, self.currentDrawable)) } } }最后执行出这种效果
2023年03月29日
61 阅读
0 评论
0 点赞
2022-11-23
Swift 学习
介绍最近打算重新学习一下 Swift,因为之前一直使用 objc 的思维模式写 Swift,另外想进一步了解 Swift 语言的设计实现,应该能丰富我的认知。特意找了个旧设备装上 macOS 10.14.6 系统,并安装上 Xcode 11 ,新版本语法特性太多,暂时先不考虑,只从早期 ABI 稳定又稍微升级过的 5.1 版本开始。swift -version # Apple Swift version 5.1.3 (swiftlang-1100.0.282.1 clang-1100.0.33.15) # Target: x86_64-apple-darwin18.7.0Swift 编译流程开始之前,先了解下 Swift 语言的编译流程图中命令其实是独立的,这样画只是说明它经历过哪些环节,先是从 Swift 代码开始,编译器会对代码进行分词,解析,生成抽象语法树,这期间会进行语义分析,譬如类型检查之类的操作。接着就是把生成的 ast 转成 sil,既然有 sil,那自然就是优化 sil,完了就生成 LLVM 的 IR 码,然后通过 LLVM 把得到的 IR 码转成机器代码(期间会进行编译,链接库等操作,然后才生成对应平台的二进制文件)。Swift 基本命令swiftc 这个是编译器的命令,可以通过 swiftc --help 来查看有哪些参数。OVERVIEW: Swift compiler USAGE: swiftc MODES: -dump-ast Parse and type-check input file(s) and dump AST(s) -dump-parse Parse input file(s) and dump AST(s) -dump-scope-maps <expanded-or-list-of-line:column> Parse and type-check input file(s) and dump the scope map(s) -dump-type-info Output YAML dump of fixed-size types from all imported modules -dump-type-refinement-contexts Type-check input file(s) and dump type refinement contexts(s) -emit-assembly Emit assembly file(s) (-S) -emit-bc Emit LLVM BC file(s) -emit-executable Emit a linked executable -emit-imported-modules Emit a list of the imported modules -emit-ir Emit LLVM IR file(s) -emit-library Emit a linked library -emit-object Emit object file(s) (-c) -emit-sibgen Emit serialized AST + raw SIL file(s) -emit-sib Emit serialized AST + canonical SIL file(s) -emit-silgen Emit raw SIL file(s) -emit-sil Emit canonical SIL file(s) -index-file Produce index data for a source file -parse Parse input file(s) -print-ast Parse and type-check input file(s) and pretty print AST(s) -resolve-imports Parse and resolve imports in input file(s) -typecheck Parse and type-check input file(s) OPTIONS: -api-diff-data-dir <path> Load platform and version specific API migration data files from <path>. Ignored if -api-diff-data-file is specified. -api-diff-data-file <path> API migration data is from <path> -application-extension Restrict code to those available for App Extensions -assert-config <value> Specify the assert_configuration replacement. Possible values are Debug, Release, Unchecked, DisableReplacement. -continue-building-after-errors Continue building, even after errors are encountered -debug-info-format=<value> Specify the debug info format type to either 'dwarf' or 'codeview' -debug-info-store-invocation Emit the compiler invocation in the debug info. -debug-prefix-map <value> Remap source paths in debug info -disable-autolinking-runtime-compatibility-dynamic-replacements Do not use autolinking for the dynamic replacement runtime compatibility library -disable-autolinking-runtime-compatibility Do not use autolinking for runtime compatibility libraries -disable-migrator-fixits Disable the Migrator phase which automatically applies fix-its -driver-time-compilation Prints the total time it took to execute all compilation tasks -dump-migration-states-dir <path> Dump the input text, output text, and states for migration to <path> -dump-usr Dump USR for each declaration reference -D <value> Marks a conditional compilation flag as true -embed-bitcode-marker Embed placeholder LLVM IR data as a marker -embed-bitcode Embed LLVM IR bitcode as data -emit-dependencies Emit basic Make-compatible dependencies files -emit-loaded-module-trace-path <path> Emit the loaded module trace JSON to <path> -emit-loaded-module-trace Emit a JSON file containing information about what modules were loaded -emit-module-interface-path <path> Output module interface file to <path> -emit-module-interface Output module interface file -emit-module-path <path> Emit an importable module to <path> -emit-module Emit an importable module -emit-objc-header-path <path> Emit an Objective-C header file to <path> -emit-objc-header Emit an Objective-C header file -emit-tbd-path <path> Emit the TBD file to <path> -emit-tbd Emit a TBD file -enable-library-evolution Build the module to allow binary-compatible library evolution -enforce-exclusivity=<enforcement> Enforce law of exclusivity -fixit-all Apply all fixits from diagnostics without any filtering -framework <value> Specifies a framework which should be linked against -Fsystem <value> Add directory to system framework search path -F <value> Add directory to framework search path -gdwarf-types Emit full DWARF type info. -gline-tables-only Emit minimal debug info for backtraces only -gnone Don't emit debug info -g Emit debug info. This is the preferred setting for debugging with LLDB. -help Display available options -import-underlying-module Implicitly imports the Objective-C half of a module -index-file-path <path> Produce index data for file <path> -index-ignore-system-modules Avoid indexing system modules -index-store-path <path> Store indexing data to <path> -I <value> Add directory to the import search path -j <n> Number of commands to execute in parallel -L <value> Add directory to library link search path -l<value> Specifies a library which should be linked against -migrate-keep-objc-visibility When migrating, add '@objc' to declarations that would've been implicitly visible in Swift 3 -migrator-update-sdk Does nothing. Temporary compatibility flag for Xcode. -migrator-update-swift Does nothing. Temporary compatibility flag for Xcode. -module-cache-path <value> Specifies the Clang module cache path -module-link-name <value> Library to link against when using this module -module-name <value> Name of the module to build -nostdimport Don't search the standard library import path for modules -num-threads <n> Enable multi-threading and specify number of threads -Onone Compile without any optimization -Osize Compile with optimizations and target small code size -Ounchecked Compile with optimizations and remove runtime safety checks -output-file-map <path> A file which specifies the location of outputs -O Compile with optimizations -o <file> Write output to <file> -parse-as-library Parse the input file(s) as libraries, not scripts -parse-sil Parse the input file as SIL code, not Swift source -parseable-output Emit textual output in a parseable format -profile-coverage-mapping Generate coverage data for use with profiled execution counts -profile-generate Generate instrumented code to collect execution counts -profile-use=<profdata> Supply a profdata file to enable profile-guided optimization -remove-runtime-asserts Remove runtime safety checks. -require-explicit-availability-target <target> Suggest fix-its adding @available(<target>, *) to public declarations without availability -require-explicit-availability Require explicit availability on public declarations -Rpass-missed=<value> Report missed transformations by optimization passes whose name matches the given POSIX regular expression -Rpass=<value> Report performed transformations by optimization passes whose name matches the given POSIX regular expression -runtime-compatibility-version <value> Link compatibility library for Swift runtime version, or 'none' -sanitize-coverage=<type> Specify the type of coverage instrumentation for Sanitizers and additional options separated by commas -sanitize=<check> Turn on runtime checks for erroneous behavior. -save-optimization-record-path <value> Specify the file name of any generated YAML optimization record -save-optimization-record Generate a YAML optimization record file -save-temps Save intermediate compilation results -sdk <sdk> Compile against <sdk> -serialize-diagnostics Serialize diagnostics in a binary format -static-executable Statically link the executable -static-stdlib Statically link the Swift standard library -static Make this module statically linkable and make the output of -emit-library a static library. -suppress-warnings Suppress all warnings -swift-version <vers> Interpret input according to a specific Swift language version number -target-cpu <value> Generate code for a particular CPU variant -target-variant <value> Generate code that may run on a particular variant of the deployment target -target <value> Generate code for the given target -tools-directory <directory> Look for external executables (ld, clang, binutils) in <directory> -track-system-dependencies Track system dependencies while emitting Make-style dependencies -use-ld=<value> Specifies the linker to be used -verify-debug-info Verify the binary representation of debug output. -version Print version information and exit -vfsoverlay <value> Add directory to VFS overlay file -v Show commands to run and use verbose output -warn-implicit-overrides Warn about implicit overrides of protocol members -warn-swift3-objc-inference-complete Warn about deprecated @objc inference in Swift 3 for every declaration that will no longer be inferred as @objc in Swift 4 -warn-swift3-objc-inference-minimal Warn about deprecated @objc inference in Swift 3 based on direct uses of the Objective-C entrypoint -warnings-as-errors Treat warnings as errors -whole-module-optimization Optimize input files together instead of individually -working-directory <path> Resolve file paths relative to the specified directory -Xcc <arg> Pass <arg> to the C/C++/Objective-C compiler -Xlinker <value> Specifies an option which should be passed to the linker先写一段简单的 Swift 代码import Foundation print("hello, world")然后用 -dump-ast 导出它的语法树,应该就能看到打印出来的抽象语法树。swiftc -dump-ast main.swift再来看看 Swift 中间代码长啥样,使用 -emit-sil 参数。swiftc -emit-sil main.swift至于其他命令,也是通过类似方式执行就能得到对应的结果。基础语法Constants 及 VariablesSwift 用 let 表示常量, var 表示变量,代码长下面这样。我看官方文档说 let 表示常量,我个人认为 let 表示的是一个变量不可变,当然还是以官方为准比较好。 下面第一行代码,如果 let a 但是并不指定类型也不赋值,就会报错let a: Int a = 11 let b = 12 var c = 11 c = 14编译器会提示这样的错误let a // Type annotation missing in pattern标识符标识符也就是 identifier,譬如下面的 a 跟 test 就是标识符let a = 10 func test() {}Swift 语言的标识符不能使用数字、空白字符(譬如 tab、换行符)、箭头之类的特殊字符,不能用数字开头的这很好理解,因为编译器会很难区分当前读取的一连串字符到底是数字(譬如语言中的整型,浮点型)还是单纯的标识符。不过像下面这种代码是支持的func 🐂🍺() -> Int { return 666 } print(🐂🍺())值类型IntegersSwift 的整型是 Int 表示,也有对应精度的 Int8、Int16、Int32、Int64、UInt8、UInt16、UInt32、UInt64,Int 对应 32 位平台上就会自动使用 Int32,对应 64 位平台上就会使用 Int64,观感上类似 objc 的 NSInteger,如果要写一些早期 8bit 的游戏平台仿真器(譬如 NES)就会用到 UInt8 这个类型,通常在 iOS 开发中直接用 Int 就好。Swift 也可以使用类似下面这样的数值表示let a = 16 // 十进制的 16 let b = 0b1_0000 // 二进制的 16 let c = 0o20 // 八进制的 16 let d = 0x10 // 十六进制的 16Floating-Point NumbersSwift 的浮点型也是可以套用其他语言的认知,分 Float、Double 两种,另外浮点型支持科学计数法let a = 12.0 let b = 1.2e1 // 科学计数法 a == b // true let c = 0x06p1 // 6 * (2 ^ 1) a == c // true let d = 0x06p-1 // 6 * (2 ^ -1) d == 3.0 // true let e = 0x1.1p0 // (1 + 1.0 / 16.0) * (2 ^ 0)Numeric Type ConversionSwift 的数字(譬如 Int16 跟 Int8,Int 跟 Double)之间做运算,需要转成相同的类型。其实这些在其他语言看来是基本类型的东西(譬如 Int)在 Swift 看来是结构体,它们的 + 号也是 Int/Double 结构体上定义的静态方法,然后这个静态方法限定了左右两边相加的类型,所以如果要把一个整型跟一个浮点型相加,那就把其中一个数的值给另一个数相同类型的构造函数构造出实例,于是下面就变成了 Double(a) + blet a = 3 let b = 0.14 // let result = a + b // Binary operator '+' cannot be applied to operands of type 'Int' and 'Double' let result = Double(a) + b // 3.14如果不指定类型,直接把两个 literal(字面量)相加,就不需要写构造let result = 3 + 0.14 // 3.14Tuples苹果官方文档上有下面一段代码,因为我学过 Rust,一看这个东西,就感觉它跟 Rust 用法应该差不多。let http404Error = (404, "Not Found")应该是通过 http404Error.0 、http404Error.1 这种方式访问数据,然后之前用过一段时间 Swift,知道它也有模式匹配,所以肯定也能这样取数据let (code, message) = http404Error因为很多 OCaml 风格的语言(譬如 Rust, ReScript)都用 _ 的符号表示不使用某个值,所以下面这段代码理解当然地从脑海中冒出来。let (code, _) = http404Error当然官方文档还给出了另一种构造跟访问元组的方式let http200Status = (statusCode: 200, description: "OK") print(http200Status.statusCode) print(http200Status.1)StringSwift 的字符串字面量直接用双引号包裹就行,这玩意还支持 + 之类的运算符,还有类似模板字符串的东西,多行字符串也是可以的。Swift 字符串操作太多了,后面再细看一下。var str = "some str" str += " end" str = "\(str)." // "some str end." str = """ line break """Character如果给一个字符串指定 Character 类型,就得到一个字符了,当然当前字符串超出一个字符,就会收到编译器错误let char: Character = "🍺" print(char) // 🍺 let c: Character = "123" // Cannot convert value of type 'String' to specified type 'Character'未完待续
2022年11月23日
80 阅读
0 评论
0 点赞
2022-10-16
GitHub SSH 设置
记录一下在 Mac 上设置 GitHub 的 SSH keys。设置 Git 用户先在终端执行一下 git config --global --list,看看当前设备的 git 有没有设置好相关的用户名跟邮箱。 有的话就会正常显示user.name=Your Name user.email=your-name@example.com如果没有的话,就会出现类似这种错误fatal: unable to read config file '~/.gitconfig': No such file or directory既然没有那就设置一下git config --global user.name "Your Name" git config --global user.email "Your Email"设置完成后要想确认一下,就重新执行git config --global --list生成 SSH Key这个就比较容易,直接执行这条命令,主要是设置一下邮箱地址ssh-keygen -t rsa -C "your-email-address"一路回车就行,一般会显示下面这些内容,具体内容以自己的系统为准Generating public/private rsa key pair. Enter file in which to save the key (~/.ssh/id_rsa): Enter passphrase (empty for no passphrase): Enter same passphrase again: Your identification has been saved in ~/.ssh/id_rsa. Your public key has been saved in ~/.ssh/id_rsa.pub. The key fingerprint is: SHA256:IuTZyVphdJVuRBBYBjCYtCTD1rJcgumXe0oXdErnUeo limitliu@qq.com The key's randomart image is: +---[RSA 2048]----+ |=o+oo.o+B*o. | |oOo+ =.*. o | |+ * = B..o | | + = B.+ o | | . = BES. | | o * . | | . = | | . | | | +----[SHA256]-----+生成后把内容拷贝到自己的剪贴板,public key 文件生成在哪可以看上面执行后的结果,里面有对应文件路径,我这里生成得文件是在 ~/.ssh/id_rsa.pub 这个路径,Mac 可以直接这样执行这条命令来复制cat ~/.ssh/id_rsa.pub | pbcopy其实就是通过管道符把 cat 到的内容传给 pbcopy,也就是传给 Mac 的剪贴板。在 GitHub 上设置这一步就是正常的打开 GitHub 网站,登录自己需要配置的那个账号,点击头像有个 Settings,然后再点击 SSH and GPG keys(不想手动找就直接点这个链接跳转),我这里只设置 SSH keys。点击 New SSH key然后就会打开下面这些东西,照着图片做就行
2022年10月16日
84 阅读
0 评论
0 点赞
2022-05-30
Flutter 调用 Rust 生成的 共享/静态 库
日常发病前面写过一两篇使用水文, 用 Rust 写点东西, 编译成 shared/static lib 给 Android 或者 iOS 端调用.这篇水文需要用到之前写得 MD5 的那个 Rust 项目. Android 使用 Rust 生成的动态库 用 Rust 开发 iOS 应用(粗糙版) 今天发病的主题是 Flutter FFI 相关.创建 Flutter 项目这里直接通过下面这条命令创建flutter create --platforms=android,ios --template=plugin ffi_demo这里使用的是 plugin 的模板, 如果直接使用 project 模板也是可以的, 现在直接用 Android Studio 打开项目. 我们先来看看项目结构tree -L 1 . ├── CHANGELOG.md ├── LICENSE ├── README.md ├── analysis_options.yaml ├── android ├── example ├── ffi_demo.iml ├── ios ├── lib ├── pubspec.lock ├── pubspec.yaml └── test 5 directories, 7 files处理 Android 端Android 端的比较好处理, 把开头 Android 的那篇文章的 md5 项目生成的动态库拷贝出来, 放到 android/src/main/jniLibs/arm64-v8a 下面, 事实上这里不需要用到 CMakeLists, 所以直接放到 jniLibs 下面, 不过有个问题, Android 项目并不知道你放这边了, 所以还得配置一下 build.gradle 的 sourceSetsandroid { // ... sourceSets { // ... main.jniLibs.srcDirs = ['src/main/jniLibs'] } // ... }这一步完成后, 我们先把 Android 这边的事放一放处理 iOS 端由于之前的 md5 那个项目, 没编译 iOS 的静态库, 所以我们先得把 iOS 静态库生成一下.喜闻乐见的添加对应 target 环节rustup target add aarch64-apple-ios如果你想跑在 Intel 设备的 iOS 仿真器上, 需要添加 x86 的 target, 这里只添加了手机 aarch64 的, 如果你想跑在 M1 设备的 iOS 仿真器上, 你需要添加rustup target add aarch64-apple-ios-sim接着跑到 md5 的项目中运行构造命令cargo build --target aarch64-apple-ios --release构建成功后把 target 目录对应平台的静态库拷贝出来放到 ios/Frameworks 下, 没有 Frameworks 文件夹就自己创建, 文件夹名字随便起, 然后把 ffi_demo.podspec 文件改一下, 总之找到对应的 .podspec 文件, 添加指定静态库的路径Pod::Spec.new do |s| # ... s.vendored_libraries = 'Frameworks/libmd5.a' # ... end处理 Flutter 端上面的准备工作完成后, 我们可以来暴露接口给 Dart 使用啦! 找到 lib/ffi_demo.dart 文件, 修改里面的代码import 'dart:io'; import 'dart:ffi'; // third part package import 'package:ffi/ffi.dart'; typedef LLMD5 = Pointer<Int8> Function(Pointer<Int8>); class FfiDemo { String md5(String str) { final DynamicLibrary nativeLib = Platform.isAndroid ? DynamicLibrary.open("libmd5.so") : DynamicLibrary.process(); LLMD5 md5 = nativeLib.lookupFunction<LLMD5, LLMD5>("ll_md5"); var result = md5(str.toNativeUtf8().cast<Int8>()); return result.cast<Utf8>().toDartString(); } }其实就是把 dart 的字符串转成 C 的字符串传给 md5 函数使用, 返回的结果再转成 dart 的字符串, 我们代码中用到一个第三方包来处理 dart 字符串跟 C 字符串的转换, 直接执行添加第三方包的命令flutter pub add ffi执行 exampleAndroid为了验证我们的插件是否正常工作, 我们把 example 里面的 lib/main.dart 修改一下import 'package:flutter/material.dart'; import 'package:ffi_demo/ffi_demo.dart'; void main() { runApp(const MyApp()); } class MyApp extends StatefulWidget { const MyApp({Key? key}) : super(key: key); @override State<MyApp> createState() => _MyAppState(); } class _MyAppState extends State<MyApp> { String _md5Str = ''; final _ffiDemoPlugin = FfiDemo(); @override void initState() { super.initState(); initMD5(); } void initMD5() { String str = ''; try { str = _ffiDemoPlugin.md5("foo"); } on Exception { str = 'MD5 failed'; } if (!mounted) return; setState(() { _md5Str = str; }); } @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( appBar: AppBar( title: const Text('Plugin example app'), ), body: Center( child: Text('$_md5Str\n'), ), ), ); } }然后连接对应 ABI 的 Android 手机, 用 Android Studio 执行一下, 可以看到屏幕中间显示了一串经过 md5 运算后的字符串.iOSiOS 这边, 因为我们修改了 podspec 文件, 所以我们需要在 example/ios 目录下执行pod install然后就是更新 xcworkspace, 完成后, 就可以在 Pod 项目中找到我们的静态库了然后用 Android Studio 打开插件项目(直接用 Xcode 执行 Runner 可能会出现找不到符号的问题), 执行到对应的设备(或者仿真器), 就能看到对应的效果Flutter FFI 使用起来比较简单, 某些情况也很有用, 譬如要使用 FFmpeg 理论上可以通过 FFI 来达成我们的目标.
2022年05月30日
157 阅读
0 评论
0 点赞
1
2
...
4