草庐IT

iOS开发-Swift进阶之闭包,使用&捕获原理!

iOS是大鑫呀 2023-03-28 原文
swift进阶总汇

本文主要分析闭包以及闭包捕获变量的原理

闭包

闭包是一个捕获了全局上下文的常量或者变量的函数,通俗来讲,闭包可以是常量也可以是函数

  • 【全局函数是一种特殊的闭包】:定义一个全局函数,只是当前的全局函数并不捕获值
func test(){ print("test") }
  • 【函数闭包】:下面的函数是一个闭包,函数中的incrementer是一个内嵌函数,可以从makeIncrementer中捕获变量runningTotal
func makeIncrementer() -> () -> Int{ var runningTotal = 10 //内嵌函数,也是一个闭包 func incrementer() -> Int{ runningTotal += 1 return runningTotal } return incrementer }
  • 【闭包表达式 / 匿名函数】:下面是一个闭包表达式,即一个匿名函数,而且是从上下文中捕获变量和常量
//闭包表达式 { (param) -> ReturnType in //方法体 } 使用闭包的好处

  • 1、利用上下文推断参数和返回值类型

  • 2、单表达式可以隐式返回,即省略return关键字

  • 3、参数名称的简写,例如 $0表示第一个参数

  • 4、尾随闭包表达式

闭包表达式

OC与swift的对比

  • OC中的Block其实是一个匿名函数,需要具备以下特点:

    • 1、作用域 {}

    • 2、参数和返回值

    • 3、函数体(in)之后的代码

  • swift中的闭包,可以当做变量,也可以当做参数传递

var clourse: (Int)->(Int) = { (age: Int) in return age } 闭包表达式的使用方式

作为一个开发者,有一个学习的氛围跟一个交流圈子特别重要,这是一个我的iOS开发交流群:130 595 548,不管你是小白还是大牛都欢迎入驻 ,让我们一起进步,共同发展!(群内会免费提供一些群主收藏的免费学习书籍资料以及整理好的几百道面试题和答案文档!)

  • **【可选类型的闭包表达式】**1、将闭包表达式声明成一个可选类型
//声明一个可选类型的闭包 <!--错误写法--> var clourse: (Int) -> Int? clourse = nil <!--正确写法--> var clourse: ((Int) -> Int)? clourse = nil
  • **【闭包常量】**2、通过let将闭包声明成一个常量(即一旦赋值之后就不能更改
//2、通过let将闭包声明为一个常量,即一旦赋值后就不能改变了 let clourse: (Int) -> Int clourse = {(age: Int) in return age } //报错:Immutable value 'clourse' may only be initialized once clourse = {(age: Int) in return age }

  • **【闭包参数】**3、将闭包作为 函数的参数
//3、将闭包作为函数的参数 func test(param: () -> Int){ print(param()) } var age = 10 test { () -> Int in age += 1 return age }

尾随闭包

当闭包作为函数的最后一个参数,如果当前的闭包表达式很长,我们可以通过尾随闭包的书写方法来提高代码的可读性

//闭包表达式作为函数的最后一个参数 func test(_ a: Int, _ b: Int, _ c: Int, by: (_ item1: Int, _ item2: Int, _ item3: Int) -> Bool) -> Bool{ return by(a, b, c) } //常规写法 test(10, 20, 30, by: {(_ item1: Int, _ item2: Int, _ item3: Int) -> Bool in return (item1 + item2 < item3) }) //尾随闭包写法 test(10, 20, 30) { (item1, item2, item3) -> Bool in return (item1 + item2 < item3) }
  • 我们平常使用的array.sorted其实就是一个尾随闭包,且这个函数就只有一个参数,如下所示
//array.sorted就是一个尾随闭包 var array = [1, 2, 3] //1、完整写法 array.sorted { (item1: Int, item2: Int) -> Bool in return item1 < item2} //2、省略参数类型:通过array中的参数推断类型 array.sorted { (item1, item2) -> Bool in return item1 < item2} //3、省略参数类型 + 返回值类型:通过return推断返回值类型 array.sorted { (item1, item2) in return item1 < item2} //4、省略参数类型 + 返回值类型 + return关键字:单表达式可以隐士表达,即省略return关键字 array.sorted { (item1, item2) in item1 < item2} //5、参数名称简写 array.sorted {return $0 < $1} //6、参数名称简写 + 省略return关键字 array.sorted {$0 < $1} //7、最简:直接传比较符号 array.sorted (by: <)

捕获一个变量

下面代码的打印结果是什么?

func makeIncrementer() -> () -> Int{ var runningTotal = 10 //内嵌函数,也是一个闭包 func incrementer() -> Int{ runningTotal += 1 return runningTotal } return incrementer } let makeInc = makeIncrementer() print(makeInc()) print(makeInc()) print(makeInc()) <!--打印结果--> 11 12 13 打印结果如下,从结果中可以看出,每次的结构都是在上次函数执行的基础上累加的,但是我们所知的runningTotal是一个临时变量,按理说每次进入函数都是10,这里为什么会每次累加呢? 主要原因:内嵌函数捕获了runningTotal,不再是单纯的一个变量了

  • 如果是下面这种方式调用呢?
print(makeIncrementer()()) print(makeIncrementer()()) print(makeIncrementer()()) <!--打印结果--> 11 11 11 为什么这种方式每次打印的结果就是同一个呢?

1、SIL分析

将上述代码通过SIL分析:

  • 1、通过alloc_box申请了一个堆上的引用计数,并将引用计数地址给了RunningTotal,将变量存储到堆上

  • 2、通过project_box从堆上取出变量

  • 3、将取出的变量交给闭包进行调用

    结论:所以,捕获值的本质是 将变量存储到堆上

2、断点验证

  • 也可以通过断点来验证,在makeIncrementer方法内部调用了swift_allocObject方法

总结

  • 一个闭包能够从上下文捕获已经定义的常量和变量,即使这些定义的常量和变量的原作用域不存在,闭包仍然能够在其函数体内引用和修改这些值

  • 当每次修改捕获值时,修改的是堆区中的value值

  • 当每次重新执行当前函数时,都会重新创建内存空间

所以上面的案例中我们知道:

  • makeInc是用于存储makeIncrementer函数调用的全局变量,所以每次都需要依赖上一次的结果

  • 而直接调用函数时,相当于每次都新建一个堆内存,所以每次的结果都是不关联的,即每次结果都是一致的

闭包是引用类型

这里还要一个疑问,makeInc存储的到底是什么?个人猜测存储的是runningTotal的堆区地址,下面我们通过分析来验证是否如此

但是此时我们发现,通过SIL并没有办法分析出什么,那么可以将SIL降一级,通过IR代码来观察数据的构成

在分析之前,首先来了解下IR的基本语法

IR基本语法

  • 通过以下命令将代码转换为IR文件
swiftc -emit-ir 文件名 > ./main.ll && code main.ll 例如: - cd 文件所在路径 - swiftc -emit-ir main.swift > ./main.ll && open main.ll
  • 数组
/* - elementnumber 数组中存放数据的数量 - elementtype 数组中存放数据的类型 */ [<elementnumber> x <elementtype>] <!--举例--> /* 24个i8都是0 - iN:表示多少位的整型,即8位的整型 - 1字节 */ alloca [24 x i8], align 8
  • 结构体
/* - T:结构体名称 - <type list> :列表,即结构体的成员列表 */ //和C语言的结构体类似 %T = type {<type list>} <!--举例--> /* - swift.refcounted:结构体名称 - %swift.type*:swift.type指针类型 - i64:64位整型 - 8字节 */ %swift.refcounted = type { %swift.type*, i64}
  • 指针类型
<type> * <!--举例--> //64位的整型 - 8字节 i64*
  • getelementptr指令 在LLVM中获取数组和结构体的成员时通过getelementptr,语法规则如下:
<result> = getelementptr <ty>, <ty>* <ptrval>{, [inrange] <ty> <id x>}* <result> = getelementptr inbounds <ty>, <ty>* <ptrval>{, [inrange] <ty> <idx>}* <!--举例--> struct munger_struct{ int f1; int f2; }; void munge(struct munger_struct *P){ P[0].f1 = P[1].f1 + P[2].f2; } //使用 struct munger_struct* array[3]; int main(int argc, const char * argv[]) { munge(array); return 0; } 通过下面的命令将c/c++编译成IR

clang -S -emit-llvm 文件名 > ./main.ll && code main.ll <!--举例--> clang -S -emit-llvm ${SRCROOT}/06-EnumTestC/main.c > ./main.ll && code main.ll

  • 第一个索引:%struct.munger_struct* %13, i32 0 等价于 第一个索引类型 + 第一个索引值 ==》 共同决定 第一个索引的偏移量
  • 第二个索引:i32 0
再结合图来理解

int main(int argc, const char * argv[]) { int array[4] = {1, 2, 3, 4}; int a = array[0]; return 0; } 其中int a = array[0];这句对应的LLVM代码应该是这样的: /* - [4 x i32]* array:数组首地址 - 第一个0:相对于数组自身的偏移,即偏移0字节 0 * 4字节 - 第二个0:相对于数组元素的偏移,即结构体第一个成员变量 0 * 4字节 */ a = getelementptr inbounds [4 x i32], [4 x i32]* array, i64 0, i64 0
  • 可以看到其中的第一个0,使用基本类型[4 x i32],因此返回的指针前进0 * 16字节,即当前数组首地址

  • 第二个index,使用基本类型 i32,返回的指针前进0字节,即当前数组的第一个元素,返回的指针类型是 i32*

总结

  • 第一个索引不会改变返回的指针的类型,即ptrval前面的*对应什么类型,返回的就是什么类型

  • 第一个索引的偏移量是由第一个索引的值第一个ty指定的基本类型共同确定的

  • 后面的索引是在数组或者结构体内进行索引

  • 每增加一个索引,就会使得该索引使用基本类型和返回的指针类型去掉一层(例如 [4 x i32] 去掉一层是 i32)

IR分析

分析IR代码

  • 查看makeIncrementer方法
    • 1、首先通过swift_allocObject创建swift.refcounted结构体
    • 2、然后将swift.refcounted转换为<{ %swift.refcounted, [8 x i8] }>*结构体(即Box)
    • 3、取出结构体中index等于1的成员变量,存储到[8 x i8]*连续的内存空间中
    • 4、将内嵌函数的地址存储到i8即void地址中
    • 5、最后返回一个结构体

其结构体定义如下

仿写

通过上述的分析,仿写其内部的结构体,然后构造一个函数的结构体,将makeInc的地址绑定到结构体中

struct HeapObject { var type: UnsafeRawPointer var refCount1: UInt32 var refCount2: UInt32 } //函数返回值结构体 //BoxType 是一个泛型,最终是由传入的Box决定的 struct FunctionData<BoxType>{ //内嵌函数地址 var ptr: UnsafeRawPointer var captureValue: UnsafePointer<BoxType> } //捕获值的结构体 struct Box<T> { var refCounted: HeapObject var value: T } //封装闭包的结构体,目的是为了使返回值不受影响 struct VoidIntFun { var f: () ->Int } //下面代码的打印结果是什么? func makeIncrementer() -> () -> Int{ var runningTotal = 10 //内嵌函数,也是一个闭包 func incrementer() -> Int{ runningTotal += 1 return runningTotal } return incrementer } let makeInc = VoidIntFun(f: makeIncrementer()) let ptr = UnsafeMutablePointer<VoidIntFun>.allocate(capacity: 1) //初始化的内存空间 ptr.initialize(to: makeInc) //将ptr重新绑定内存 let ctx = ptr.withMemoryRebound(to: FunctionData<Box<Int>>.self, capacity: 1) { $0.pointee } print(ctx.ptr) print(ctx.captureValue.pointee) <!--打印结果--> 0x0000000100002bc0 Box<Int>(refCounted: _7_Clourse.HeapObject(type: 0x0000000100004038, refCount1: 3, refCount2: 2), value: 10)
  • 终端命令查找0000000100002bc0(其中0x0000000100002bc0内嵌函数的地址
nm -p /Users/chenjialin/Library/Developer/Xcode/DerivedData/07、Clourse-bsccpnlhsrkbzkdglsojfgisewnx/Build/Products/Debug/07、Clourse | grep 0000000100002bc0 其中s10_7_Clourse15makeIncrementerSiycyF11incrementerL_SiyFTA是内嵌函数的地址对应的符号

结论:所以当我们var makeInc2 = makeIncrementer()使用时,相当于给makeInc2就是FunctionData结构体,其中关联了内嵌函数地址,以及捕获变量的地址,所以才能在上一个的基础上进行累加

捕获两个变量的情况

上面的案例中,我们分析了闭包捕获一个变量的情况,如果是将捕获一个变量更改为捕获两个变量呢?如下所示修改makeIncrementer函数

func makeIncrementer(forIncrement amount: Int) -> () -> Int{ var runningTotal = 0 //内嵌函数,也是一个闭包 func incrementer() -> Int{ runningTotal += amount return runningTotal } return incrementer }
  • 查看其IR代码

内部结构仿写

根据捕获一个变量的仿写,继续仿写捕获两个变量的情况

//2、闭包捕获多个值的原理 struct HeapObject { var type: UnsafeRawPointer var refCount1: UInt32 var refCount2: UInt32 } //函数返回值结构体 //BoxType 是一个泛型,最终是由传入的Box决定的 struct FunctionData<BoxType>{ var ptr: UnsafeRawPointer//内嵌函数地址 var captureValue: UnsafePointer<BoxType> } //捕获值的结构体 struct Box<T> { var refCounted: HeapObject var value: T } //封装闭包的结构体,目的是为了使返回值不受影响 struct VoidIntFun { var f: () ->Int } //下面代码的打印结果是什么? func makeIncrementer(forIncrement amount: Int) -> () -> Int{ var runningTotal = 0 //内嵌函数,也是一个闭包 func incrementer() -> Int{ runningTotal += amount return runningTotal } return incrementer } var makeInc = makeIncrementer(forIncrement: 10) var f = VoidIntFun(f: makeInc) let ptr = UnsafeMutablePointer<VoidIntFun>.allocate(capacity: 1) //初始化的内存空间 ptr.initialize(to: f) //将ptr重新绑定内存 let ctx = ptr.withMemoryRebound(to: FunctionData<Box<Int>>.self, capacity: 1) { $0.pointee } print(ctx.ptr) print(ctx.captureValue) <!--打印结果--> 0x0000000100002910 0x00000001040098e0
  • 通过终端命令查看第一个地址是否是内嵌函数的地址

    注:(函数必须使用VoidIntFun包装下,否则转换后的地址不是内嵌函数的地址),如下所示

  • 通过cat查看 第一个地址,即内嵌函数的地址

    • x/8g 第二个地址

    • 继续查看内存情况

如果将runningTotal改成12呢?来验证是否如我们猜想的一样。事实证明,确实是存储的runningTotal

所以,闭包捕获两个变量时,Box结构体内部发生了变化,修改后的仿写代码如下:

//2、闭包捕获多个值的原理 struct HeapObject { var type: UnsafeRawPointer var refCount1: UInt32 var refCount2: UInt32 } //函数返回值结构体 //BoxType 是一个泛型,最终是由传入的Box决定的 struct FunctionData<BoxType>{ var ptr: UnsafeRawPointer//内嵌函数地址 var captureValue: UnsafePointer<BoxType> } //捕获值的结构体 struct Box<T> { var refCounted: HeapObject //valueBox用于存储Box类型 var valueBox: UnsafeRawPointer var value: T } //封装闭包的结构体,目的是为了使返回值不受影响 struct VoidIntFun { var f: () ->Int } //下面代码的打印结果是什么? func makeIncrementer(forIncrement amount: Int) -> () -> Int{ var runningTotal = 12 //内嵌函数,也是一个闭包 func incrementer() -> Int{ runningTotal += amount return runningTotal } return incrementer } var makeInc = makeIncrementer(forIncrement: 10) var f = VoidIntFun(f: makeInc) let ptr = UnsafeMutablePointer<VoidIntFun>.allocate(capacity: 1) //初始化的内存空间 ptr.initialize(to: f) //将ptr重新绑定内存 let ctx = ptr.withMemoryRebound(to: FunctionData<Box<Int, Int>>.self, capacity: 1) { $0.pointee } print(ctx.ptr) print(ctx.captureValue.pointee) print(ctx.captureValue.pointee.valueBox) <!--打印结果--> 0x0000000100002b30 Box<Int>(refCounted: _7_Clourse.HeapObject(type: 0x0000000100004090, refCount1: 3, refCount2: 4), valueBox: 0x00000001006094a0, value: 10) 0x00000001006094a0 疑问:如果是捕获3个变量呢?

  • 如下所示,是捕获三个值的内存情况

  • 通过IR文件发现,从返回值倒推

<!--返回值--> ret { i8*, %swift.refcounted* } %15 <!--%15--> %15 = insertvalue { i8*, %swift.refcounted* } { i8* bitcast (i64 (%swift.refcounted*)* @"$s4main15makeIncrementer12forIncrement7amount2SiycSi_SitF11incrementerL_SiyFTA" to i8*), %swift.refcounted* undef }, %swift.refcounted* %10, 1 <!--%10--> //与捕获两个变量相比,区别在于 i64 32 变成了 i64 40 %10 = call noalias %swift.refcounted* @swift_allocObject( %swift.type* getelementptr inbounds (%swift.full_boxmetadata, %swift.full_boxmetadata* @metadata.3, i32 0, i32 2), i64 40, i64 7) #1 所以Box结构体改为

//捕获值的结构体 struct Box<T> { var refCounted: HeapObject //这也是一个HeapObject var valueBox: UnsafeRawPointer var value1: T var value2: T } 最终完整的仿写代码为

//3、捕获3个值 struct HeapObject { var type: UnsafeRawPointer var refCount1: UInt32 var refCount2: UInt32 } //函数返回值结构体 //BoxType 是一个泛型,最终是由传入的Box决定的 struct FunctionData<BoxType>{ var ptr: UnsafeRawPointer//内嵌函数地址 var captureValue: UnsafePointer<BoxType> } //捕获值的结构体 struct Box<T> { var refCounted: HeapObject //valueBox用于存储Box类型 var valueBox: UnsafeRawPointer var value1: T var value2: T } //封装闭包的结构体,目的是为了使返回值不受影响 struct VoidIntFun { var f: () ->Int } //下面代码的打印结果是什么? func makeIncrementer(forIncrement amount: Int, amount2: Int) -> () -> Int{ var runningTotal = 1 //内嵌函数,也是一个闭包 func incrementer() -> Int{ runningTotal += amount runningTotal += amount2 return runningTotal } return incrementer } var makeInc = makeIncrementer(forIncrement: 10, amount2: 2) var f = VoidIntFun(f: makeInc) let ptr = UnsafeMutablePointer<VoidIntFun>.allocate(capacity: 1) //初始化的内存空间 ptr.initialize(to: f) //将ptr重新绑定内存 let ctx = ptr.withMemoryRebound(to: FunctionData<Box<Int>>.self, capacity: 1) { $0.pointee } print(ctx.ptr) print(ctx.captureValue.pointee.value1) print(ctx.captureValue.pointee.value2) <!--打印结果--> 10 2 从打印结果可以看出,正好是传入的两个参数值

总结

  • 1、捕获值原理:在堆上开辟内存空间,并将捕获的值放到这个内存空间里

  • 2、修改捕获值时:实质是修改堆空间的值

  • 3、闭包是一个引用类型(引用类型是地址传递),闭包的底层结构(是结构体:函数地址 + 捕获变量的地址 == 闭包

  • 4、函数也是一个引用类型(本质是一个结构体,其中只保存了函数的地址),例如还是以makeIncrementer函数为例

func makeIncrementer(inc: Int) -> Int{ var runningTotal = 1 return runningTotal + inc } var makeInc = makeIncrementer 分析其IR代码,函数在传递过程中,传递的就是函数的地址

将仿写的FunctionData进行修改

struct FunctionData{ var ptr: UnsafeRawPointer//内嵌函数地址 var captureValue: UnsafePointer<BoxType> } 然后改版后的结构仿写如下

//函数也是引用类型 struct FunctionData{ //函数地址 var ptr: UnsafeRawPointer var captureValue: UnsafeRawPointer? } //封装闭包的结构体,目的是为了使返回值不受影响 struct VoidIntFun { var f: (Int) ->Int } func makeIncrementer(inc: Int) -> Int{ var runningTotal = 1 return runningTotal + inc } var makeInc = makeIncrementer var f = VoidIntFun(f: makeInc) let ptr = UnsafeMutablePointer<VoidIntFun>.allocate(capacity: 1) //初始化的内存空间 ptr.initialize(to: f) //将ptr重新绑定内存 let ctx = ptr.withMemoryRebound(to: FunctionData.self, capacity: 1) { $0.pointee } print(ctx.ptr) print(ctx.captureValue) <!--打印结果--> 0x0000000100003370 nil 通过cat命令查看该地址,地址就是makeIncrementer函数的地址

总结

作为一个开发者,有一个学习的氛围跟一个交流圈子特别重要,这是一个我的iOS开发交流群:130 595 548,不管你是小白还是大牛都欢迎入驻 ,让我们一起进步,共同发展!(群内会免费提供一些群主收藏的免费学习书籍资料以及整理好的几百道面试题和答案文档!)

  • 一个闭包能够从上下文中捕获已经定义的常量/变量,即使其作用域不存在了,闭包仍然能够在其函数体内引用、修改

    • 1、每次修改捕获值:本质修改的是堆区中的value值

    • 2、每次重新执行当前函数,会重新创建新的内存空间

  • 捕获值原理:本质是在堆区开辟内存空间,并将捕获值存储到这个内存空间

  • 闭包是一个引用类型(本质是函数地址传递),底层结构为:闭包 = 函数地址 + 捕获变量的地址

  • 函数也是引用类型(本质是结构体,其中保存了函数的地址)

有关iOS开发-Swift进阶之闭包,使用&捕获原理!的更多相关文章

  1. ruby - 如何使用 Nokogiri 的 xpath 和 at_xpath 方法 - 2

    我正在学习如何使用Nokogiri,根据这段代码我遇到了一些问题:require'rubygems'require'mechanize'post_agent=WWW::Mechanize.newpost_page=post_agent.get('http://www.vbulletin.org/forum/showthread.php?t=230708')puts"\nabsolutepathwithtbodygivesnil"putspost_page.parser.xpath('/html/body/div/div/div/div/div/table/tbody/tr/td/div

  2. ruby - 使用 RubyZip 生成 ZIP 文件时设置压缩级别 - 2

    我有一个Ruby程序,它使用rubyzip压缩XML文件的目录树。gem。我的问题是文件开始变得很重,我想提高压缩级别,因为压缩时间不是问题。我在rubyzipdocumentation中找不到一种为创建的ZIP文件指定压缩级别的方法。有人知道如何更改此设置吗?是否有另一个允许指定压缩级别的Ruby库? 最佳答案 这是我通过查看ruby​​zip内部创建的代码。level=Zlib::BEST_COMPRESSIONZip::ZipOutputStream.open(zip_file)do|zip|Dir.glob("**/*")d

  3. ruby - 为什么我可以在 Ruby 中使用 Object#send 访问私有(private)/ protected 方法? - 2

    类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc

  4. ruby-on-rails - 使用 Ruby on Rails 进行自动化测试 - 最佳实践 - 2

    很好奇,就使用ruby​​onrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提

  5. ruby - 在 Ruby 中使用匿名模块 - 2

    假设我做了一个模块如下:m=Module.newdoclassCendend三个问题:除了对m的引用之外,还有什么方法可以访问C和m中的其他内容?我可以在创建匿名模块后为其命名吗(就像我输入“module...”一样)?如何在使用完匿名模块后将其删除,使其定义的常量不再存在? 最佳答案 三个答案:是的,使用ObjectSpace.此代码使c引用你的类(class)C不引用m:c=nilObjectSpace.each_object{|obj|c=objif(Class===objandobj.name=~/::C$/)}当然这取决于

  6. ruby - 使用 ruby​​ 和 savon 的 SOAP 服务 - 2

    我正在尝试使用ruby​​和Savon来使用网络服务。测试服务为http://www.webservicex.net/WS/WSDetails.aspx?WSID=9&CATID=2require'rubygems'require'savon'client=Savon::Client.new"http://www.webservicex.net/stockquote.asmx?WSDL"client.get_quotedo|soap|soap.body={:symbol=>"AAPL"}end返回SOAP异常。检查soap信封,在我看来soap请求没有正确的命名空间。任何人都可以建议我

  7. python - 如何使用 Ruby 或 Python 创建一系列高音调和低音调的蜂鸣声? - 2

    关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。

  8. ruby-on-rails - 'compass watch' 是如何工作的/它是如何与 rails 一起使用的 - 2

    我在我的项目目录中完成了compasscreate.和compassinitrails。几个问题:我已将我的.sass文件放在public/stylesheets中。这是放置它们的正确位置吗?当我运行compasswatch时,它不会自动编译这些.sass文件。我必须手动指定文件:compasswatchpublic/stylesheets/myfile.sass等。如何让它自动运行?文件ie.css、print.css和screen.css已放在stylesheets/compiled。如何在编译后不让它们重新出现的情况下删除它们?我自己编译的.sass文件编译成compiled/t

  9. ruby - 使用 ruby​​ 将 HTML 转换为纯文本并维护结构/格式 - 2

    我想将html转换为纯文本。不过,我不想只删除标签,我想智能地保留尽可能多的格式。为插入换行符标签,检测段落并格式化它们等。输入非常简单,通常是格式良好的html(不是整个文档,只是一堆内容,通常没有anchor或图像)。我可以将几个正则表达式放在一起,让我达到80%,但我认为可能有一些现有的解决方案更智能。 最佳答案 首先,不要尝试为此使用正则表达式。很有可能你会想出一个脆弱/脆弱的解决方案,它会随着HTML的变化而崩溃,或者很难管理和维护。您可以使用Nokogiri快速解析HTML并提取文本:require'nokogiri'h

  10. ruby - 在 64 位 Snow Leopard 上使用 rvm、postgres 9.0、ruby 1.9.2-p136 安装 pg gem 时出现问题 - 2

    我想为Heroku构建一个Rails3应用程序。他们使用Postgres作为他们的数据库,所以我通过MacPorts安装了postgres9.0。现在我需要一个postgresgem并且共识是出于性能原因你想要pggem。但是我对我得到的错误感到非常困惑当我尝试在rvm下通过geminstall安装pg时。我已经非常明确地指定了所有postgres目录的位置可以找到但仍然无法完成安装:$envARCHFLAGS='-archx86_64'geminstallpg--\--with-pg-config=/opt/local/var/db/postgresql90/defaultdb/po

随机推荐