iOS下的闭包下篇-Closure

发表于 2年以前  | 总阅读数:2125 次

iOS下的闭包下篇-Closure

题记:用最通俗的语言,描述最难懂的技术

❝最近在学习和迁移Swift方面的代码,正好看到了闭包这部分,看完之后整个人都被着魔了一样,于是便有了这篇文章,如果有哪些结论模糊或者不准确,请联系weiniu@sohu-inc.com

目录表

  • Closure是什么

  • Closure有什么用

  • 使用场景

  • 原理

  • 生成SIL文件

  • 闭包捕获列表

  • 闭包捕获上下文

  • 如何存储捕获值

  • 注意事项

  • 参考文献

  • 结束语

Closure是什么

ClosureSwift语言下的闭包的实现,就像The Swift Programming Language 5.5 Edition(链接附文后)中提到的一样,「Closure是独立的功能块,可以在你的代码中传递和使用」。可以这么理解Closure(swift) ≈ Block(c,c++,objective-c)≈ lambdas (other languages)

Block一样,Closure可以捕获和存储代码上下文中声明的常量和变量。同样Swift会处理所有捕获的值的内存。

Closure三种表现形式

  • 有名字的全局闭包,不捕获任何值

  • 有名字的嵌套闭包,从嵌套的方法代码中捕获值

  • 无名字的闭包,作为一个轻量简洁的语法,从上下文中捕获值

Swift对闭包的优化

  • 自动从上下文推断参数和返回值类型

  • 返回值可以是省略关键字的单行表达式

  • 简短的参数名字

  • 尾随闭包语法

Closure有什么用

综上所述,它的作用已经很清楚了:可选的传递某些参数从而实现某些回调功能

语法

局部变量

// 通用格式
{ (parameters) -> return type in
 statements
}

var variableName: (parametersType) -> return type
eg:
var successClosure: ([String : Int]) -> (Void)

尾随闭包(作为方法的最后一个参数,优化过多的参数和返回值)

func someMethod(closureName: (parameters) -> return type) {...}
eg:
func urlRequest(successBlock: ([String : Int]) -> (Void)) { ... }

逃逸闭包(在方法完成之后进行调用)

func someFuncEscapingClousre(closure: @escaping (parametersType) -> return Type) { ... }

eg:
/// Use @escaping keyword to define
func loadImageCompletion(closure: @escaping () -> Void) { ... }

自动闭包(闭包不带参数,作为函数的参数,返回一个封装的数据作为结果)

func someFuncAutoclosure(closure: () -> return Type) { ... }
eg:
func haveBreakfast(for food: () -> String) { ... }

自动+逃逸

///  @autoclosure @escaping must define
func someFuncAutoEscapeclosure(closure:  @autoclosure @escaping () -> return Type) { ... }
eg:
func haveBreakfast(closure: @autoclosure @escaping () -> String) { ...  }

使用场景

  • 延迟执行的场景
  • 耗时任务的场景
  • 后台任务的场景
  • 延长某些实例对象的生命周期的场景

❝注释:在Swift中,枚举和结构体的初始化之后应该称为实例,而类初始化之后称之为对象,根据对象的特性,继承特性是区分的关键

原理

生成SIL文件

如果你对一个问题没有任何思路,那就从相似的问题中找一些突破口,比如Objective-C下是使用clang命令把OC代码转成相对底层的C++源码,所以我们可以推断,预测有一个xxx的指令也可以把swift语言转成相对底层的语言,然后你就搜索相关关键词swift底层源码等去找答案,现在我帮你找好了,这个命令就是swiftc,目标文件就是SIL(Swift Intermediate Language),这个SIL就等价于OC中的IR具体的SIL相关知识不做讲解,请自行查阅,接下来还是准备工作

创建项目Xcode->File->New->Project

选择macOS->Command Line Tool->Next

填入Product Name和选择Language修改为Swift

执行命令,swiftc -emit-sil main.swift | xcrun swift-demangle > ./main.sil,查看更多使用swiftc -h

❝注释:xcrun swift-demangle,是把变量或者方法名混淆还原成可读的代码

特别说明下,这个文件可比那个11w的舒服太多了,我们接下来开始分析

闭包捕获列表

main.swift中文件写下测试代码,并使用swiftc命令生成main.sil中间文件

let bdNum = 3
let printNum = {
    [bdNum] in
    let _ = bdNum
}
printNum()

查看main.sil文件

可以清楚的看到闭包在main函数中被转化为@closure #1,继续定位该方法的实现

在这个定位过程中,我们发现闭包的类型由() -> () 变为 (int) -> (),猜测应该是把外部的全局变量传入进来了,为了验证猜测我们可以增加几个参数

从这个文件中我们就可以看出,编译器把(Float) -> () 转化为(Float,Int,String) -> ()类型,保存捕获列表里的值,类似函数传参一样,进行了值拷贝

let bdNum = 3
let name = "Augus"
let printNum = {
  [bdNum,name] (height: Float) in
 _ = bdNum
  _ = name
}
printNum(1.74)

闭包捕获总结

  • 闭包捕获列表的参数类型与闭包外的参数类型一致
  • 编译器内部会把闭包捕获列表的参数添加到原来闭包参数列表的后面,然后以参数传递的形式传入到闭包内部,这里的传递是以值拷贝进行的

闭包捕获上下文

这个小节以The Swift Programming Language 5.5 Edition中的例子为例进行分析

func makeIncrementer() -> () -> Int {
    var runningTotal = 12
    func incrementer() -> Int {
        runningTotal += 1
        return runningTotal
    }
    return incrementer
}

main.sil文件注释

// makeIncrementer()
sil hidden @main.makeIncrementer() -> () -> Swift.Int : $@convention(thin) () -> @owned @callee_guaranteed () -> Int {
bb0:
  // 在堆上开辟一个空间,并取名为"runningTotal"
  %0 = alloc_box ${ var Int }, var, name "runningTotal" // users: %8, %7, %6, %1
  // 把该值和类型包装成project_box的类型
  %1 = project_box %0 : ${ var Int }, 0           // user: %4
  // 初始化该值为12
  %2 = integer_literal $Builtin.Int64, 12         // user: %3
  %3 = struct $Int (%2 : $Builtin.Int64)          // user: %4
  store %3 to %1 : $*Int                          // id: %4
  // function_ref incrementer #1 () in makeIncrementer()
  %5 = function_ref @incrementer #1 () -> Swift.Int in main.makeIncrementer() -> () -> Swift.Int : $@convention(thin) (@guaranteed { var Int }) -> Int // user: %7
  strong_retain %0 : ${ var Int }                 // id: %6
  // 把包装后的"runningTotal"传递给闭包
  %7 = partial_apply [callee_guaranteed] %5(%0) : $@convention(thin) (@guaranteed { var Int }) -> Int // user: %9
  strong_release %0 : ${ var Int }                // id: %8
  return %7 : $@callee_guaranteed () -> Int       // id: %9
} // end sil function 'main.makeIncrementer() -> () -> Swift.Int'

// incrementer #1 () in makeIncrementer()
sil private @incrementer #1 () -> Swift.Int in main.makeIncrementer() -> () -> Swift.Int : $@convention(thin) (@guaranteed { var Int }) -> Int {
// %0 "runningTotal"                              // user: %1
bb0(%0 : ${ var Int }):
  // 把传递过来包装后的"runningTotal"值赋值给%1
  %1 = project_box %0 : ${ var Int }, 0           // users: %16, %4, %2
  debug_value_addr %1 : $*Int, var, name "runningTotal", argno 1 // id: %2
  // 要累加的Int类型的值 1
  %3 = integer_literal $Builtin.Int64, 1          // user: %8
  %4 = begin_access [modify] [dynamic] %1 : $*Int // users: %13, %5, %15
  %5 = struct_element_addr %4 : $*Int, #Int._value // user: %6
  // 取出"runningTotal"目前的值
  %6 = load %5 : $*Builtin.Int64                  // user: %8
  %7 = integer_literal $Builtin.Int1, -1          // user: %8
  // 调用加法
  %8 = builtin "sadd_with_overflow_Int64"(%6 : $Builtin.Int64, %3 : $Builtin.Int64, %7 : $Builtin.Int1) : $(Builtin.Int64, Builtin.Int1) // users: %10, %9
  %9 = tuple_extract %8 : $(Builtin.Int64, Builtin.Int1), 0 // user: %12
  %10 = tuple_extract %8 : $(Builtin.Int64, Builtin.Int1), 1 // user: %11
  // 判断是否堆栈溢出
  cond_fail %10 : $Builtin.Int1, "arithmetic overflow" // id: %11
  // 将计算结果赋值给包装后的"runningTotal"
  %12 = struct $Int (%9 : $Builtin.Int64)         // user: %13
  store %12 to %4 : $*Int                         // id: %13
  %14 = tuple ()
  end_access %4 : $*Int                           // id: %15
  // 打开包装,进行最新值的读取
  %16 = begin_access [read] [dynamic] %1 : $*Int  // users: %17, %18
  %17 = load %16 : $*Int                          // user: %19
  end_access %16 : $*Int                          // id: %18
  // 返回最新值
  return %17 : $Int                               // id: %19
} // end sil function 'incrementer #1 () -> Swift.Int in main.makeIncrementer() -> () -> Swift.Int'

捕获的总结

  • 捕获的上下文存储在堆空间,也就是引用类型的基础
  • 闭包的类型由() -> Int 转化为(@guaranteed { var Int }) -> Int,引用类型的runningTotal被当作参数传递进来,从而实现了闭包捕获上下文中变量的过程

如何存储捕获值

如果想了解存储的原理,SIL文件是不够的,这个时候就需要更底层的编译器的指令,还是main.swift文件,添加以下代码

// 声明一个结构体,添加一个名字为biBao,类型为closure的变量属性
struct BDTest {
    var biBao: (() -> ())
}

执行编译器的相关指令swiftc -emit-ir main.swift | xcrun swift-demangle > ./main.ll

%swift.vwtable = type { i8*, i8*, i8*, i8*, i8*, i8*, i8*, i8*, i64, i64, i32, i32 }
%swift.type_metadata_record = type { i32 }
// 1.%swift.type就是 UInt64的封装,所以%swift.type*就是一个指向UInt64整型的指针
%swift.type = type { i64 }
// 2.%swift.refcounted的构成部分,%swift.type*是%swift.type类型的指针,i64为UInt64
%swift.refcounted = type { %swift.type*, i64 }
// 3.BDTest结构体的声明,在llvm下该结构体为<{ %swift.function }>,属性biBao类型为%swift.function
%T4main6BDTestV = type <{ %swift.function }>
// 4.%swift.function的构成部分,i代表Int,后面的数字代表位数,i8=UInt8,i64=UInt64等,%swift.refcounted* 是swift.refcounted类型的指针
%swift.function = type { i8*, %swift.refcounted* }
%"main.BDTest.biBao.modify : () -> () with unmangled suffix ".Frame"" = type {}
%swift.opaque = type opaque
%swift.metadata_response = type { %swift.type*, i64 }

现在最大的疑问就是%swift.refcounted,所以我们去Swift开源代码(链接附文后)中寻找答案,其余的%swift.function%swift.type均在这个源码文件中找到答案

  RefCountedStructTy = llvm::StructType::create(getLLVMContext(), "swift.refcounted");
  RefCountedPtrTy = RefCountedStructTy->getPointerTo(/*addrspace*/ 0);
  RefCountedNull = llvm::ConstantPointerNull::get(RefCountedPtrTy);

  // A type metadata record is the structure pointed to by the canonical
  // address point of a type metadata.  This is at least one word, and
  // potentially more than that, past the start of the actual global
  // structure.
  TypeMetadataStructTy = createStructType(*this, "swift.type", {
    MetadataKindTy          // MetadataKind Kind;
  });

   FunctionPairTy = createStructType(*this, "swift.function", {
    FunctionPtrTy,
    RefCountedPtrTy,
  });

对以上的源码进行分析

我们如果分析RefCountedPtrTy会比较模糊,但是我们可以根据它的向下一层的结构swift.type的进行猜测,因为TypeMetadataStructTy其实就是MetadataKindTy的封装,而MetadataKindTy的底层就是HeapObject,一个基于Objc的结构,所以目前的闭包用底层结构表达就会类似这样

struct HeapObject {
 var Kind: UInt64
 var refcount: UInt64
}

struct FunctionPairTy {
 // UnsafeMutableRawPointer swift下表示指针的结构体,以后会单独开一篇文章介绍它,在这里你需要知道他是swift下操作内存地址的结构即可
 // 闭包代码的实现的内存地址
 var FunctionPairTy: UnsafeMutableRawPointer
 // 捕获上下文变量的指针,在堆空间,如果没有捕获,为null
 var RefCountedStructTy: UnsafeMutablePointer<HeapObject>
}

闭包捕获变量的流程,用llvm文件进行编译

/// 仍然是官方的例子
func makeIncrementer() -> (() -> Int) {
    var runningTotal = 12
    func incrementer() -> Int {
        runningTotal += 1
        return runningTotal
    }
    return incrementer
}

代码解释

define hidden swiftcc { i8*, %swift.refcounted* } @"main.makeIncrementer() -> () -> Swift.Int"() #0 {
entry:
  %runningTotal.debug = alloca %TSi*, align 8
  %0 = bitcast %TSi** %runningTotal.debug to i8*
  call void @llvm.memset.p0i8.i64(i8* align 8 %0, i8 0, i64 8, i1 false)
  // 1. %1调用了swift_allocObject向堆申请了空间,类型是%swift.refcounted* 的指针类型
  %1 = call noalias %swift.refcounted* @swift_allocObject(%swift.type* getelementptr inbounds (%swift.full_boxmetadata, %swift.full_boxmetadata* @metadata, i32 0, i32 2), i64 24, i64 7) #1
  // 2. 把%1类型强转为%2,也就是%2的类型是<{ %swift.refcounted, [8 x i8] }>*的指针类型
  %2 = bitcast %swift.refcounted* %1 to <{ %swift.refcounted, [8 x i8] }>*
  // 3. 重点是%3的结构,%3取的是结构体{ %swift.refcounted, [8 x i8] }类型 %2的第二个元素,也就是 %3是结构体{ %swift.refcounted, [8 x i8] }中 [8 x i8]的指针,分析开始的12放到了该位置
  %3 = getelementptr inbounds <{ %swift.refcounted, [8 x i8] }>, <{ %swift.refcounted, [8 x i8] }>* %2, i32 0, i32 1
  // 4. %3 类型强转为 %4
  %4 = bitcast [8 x i8]* %3 to %TSi*
  store %TSi* %4, %TSi** %runningTotal.debug, align 8
  // 5.  %._value是取的是%4结构体第一元素的指针
  %._value = getelementptr inbounds %TSi, %TSi* %4, i32 0, i32 0
  // 6. 存储UInt64类型的变量12到 %._value,
  store i64 12, i64* %._value, align 8
  // 7. 引用计数的+1操作
  %5 = call %swift.refcounted* @swift_retain(%swift.refcounted* returned %1) #1
  // 8. 引用计数的-1操作
  call void @swift_release(%swift.refcounted* %1) #1
  // 9. 包装box返回结果
  // i8*被插入了 { i8* bitcast (i64 (%swift.refcounted*)* @"partial apply forwarder for incrementer #1 () -> Swift.Int in main.makeIncrementer() -> () -> Swift.Int" to i8*), %swift.refcounted* undef }, 这么长的一段其实就是闭包的实现的内存地址
  // %swift.refcounted*则被插入了%1的地址,也就是存放12值的{ %swift.refcounted, [8 x i8] }类型的指针
  %6 = insertvalue { i8*, %swift.refcounted* } { i8* bitcast (i64 (%swift.refcounted*)* @"partial apply forwarder for incrementer #1 () -> Swift.Int in main.makeIncrementer() -> () -> Swift.Int" to i8*), %swift.refcounted* undef }, %swift.refcounted* %1, 1
  // 10. 返回包装box结果
  ret { i8*, %swift.refcounted* } %6
}

根据以上的代码注释swiftclosure的底层原理结构可以更具体一些


 struct HeapObject {
  var Kind: UInt64
  var refcount: UInt64
 }

 // 负责包装的结构体,也就是用来包装捕获需要更新的值
 struct Box {
     var refCounted: HeapObject
     // 这个捕获的值的类型根据捕获的值进行分配,此处规范操作是写泛型
     // var value: Int
      var value: <T>
 }

 struct FunctionPairTy {
  var FunctionPairTy: UnsafeMutableRawPointer
  var RefCountedStructTy: UnsafeMutablePointer<Box>
 }

验证猜测

struct FunctionPairTy {
    var FunctionPtrTy: UnsafeMutableRawPointer
    var RefCountedPtrTy: UnsafeMutablePointer<Box>
}

struct HeapObject {
    var Kind: UInt64
    var refcount: UInt64
}

struct Box {
    var refCounted: HeapObject
    var value: Int
}

func makeIncrementer() -> () -> Int {
    var runningTotal = 12
    func incrementer() -> Int {
        runningTotal += 1
        return runningTotal
    }
    return incrementer
}

// 这里需要用结构体把闭包包一层,否则会被底层的逻辑所包装
struct FuncShell {
    var fun: () -> Int
}

var fun = FuncShell(fun: makeIncrementer())

var closure = withUnsafeMutablePointer(to: &fun) {
    return UnsafeMutableRawPointer($0).assumingMemoryBound(to: FunctionPairTy.self).pointee
}

print(closure)

print("end")

print("end")处进行断点,然后进行截图中的一些验证

x/8g 内存地址:查看内存里的值

dis -s 内存地址:查看汇编

捕获值扩展,在main.swift文件中输入以下代码

func makeIncrementer() -> () -> Int {
    var runningTotal = 12
    var bd1 = 1
    let bd2 = 2
    var bd3 = "a"
    let bd4 = "b"
    func incrementer() -> Int {
        runningTotal += 1
        bd1 += bd2
        bd3 += bd4
        return runningTotal
    }
    return incrementer
}

直接运行swiftc -emit-ir main.swift | xcrun swift-demangle > ./main.ll命令,直接查看Box结构体存放的值类型

我们等价替换一下截图中的类型,%swift.refcounted可以看作是一个包装类型Box,那么从左到右依次是Box *,Box*,Int,Box*,String

编译器验证,把这段代码替换刚才的那个程序中的同名函数,然后依然是进行断点

通过上述论述不难发现,这和我们的推断是一样的,但是此处还有一个问题,就是所有的值都会被包装Box么?我们依然是通过源码进行定位,把以上的程序生成SIL文件

可以清楚的看出,凡是变量在闭包内进行更新的就会被包装,反之则不会

注意事项

引用循环,不管是Block还是Closure我们都可以把它当作对象来对待,然后它捕获的变量自然是强引用,如果外部有对该闭包也有一个强引用,那么就会造成引用循环。这也考验我们在实际开发中需要及时对引用关系进行准确的梳理,然后对一方的引用进行弱引用修饰,打破循环即可,原理都是一样的,表现方式不同

Closure下的引用循环,原理和Block下的解决思路一致,让我们看看实现方式

class Cat {

    let name: String?
    lazy var nickName: () -> String = {
        // [weak self] in
        [unowned self] in
        if let name = self.name {
            return "nick of \(name)"
        } else {
            return "none of nick"
        }

    }

    init(name: String?) {
        self.name = name
    }

    deinit {
        print("cat is deinitialized")
    }
}
// aCat strong to instance of Cat
// instance of Cat strong to () -> String
// () -> String strong to self
var aCat: Cat? = Cat(name: "Tom")
print(aCat!.nickName())

关于选关键词的说明

  • 使用weak那么,在以后的代码中使用self的时候需要加上self?书写,一方面可读性,另一方面美观都会降低
  • 效率问题,[weak self]会添加self的弱引用计数,而弱引用计数需要开辟一个新的空间存SideTableSideTable中会存放弱引用计数及其它引用计数,而开辟空间操作相对于常规操作来说,性能消耗相比unowned是多的
  • Apple官方的选择说明,如果修饰的实例声明周期短那就选择weak,反之选择unowned,其实也就是效率的高级体现

关于为什么是self?

  • 如果执行环境是多线程,那么不确定哪个线程会进行调用,调用几次,代码块内完成之后self就会被释放,这个时候需要进行安全判断,所以在Objc下是强化或者非法提前退出进行处理,而在Swift下则是可选值的使用

参考文献

  • The Swift Programming Language 5.5 Edition:https://docs.swift.org/swift-book/
  • SIL Doc:https://github.com/apple/swift/blob/main/docs/SIL.rst#abstract
  • Swift开源代码:https://github.com/apple/swift/blob/d5e5253cee0599a5362363d6ba0fe640493ea0e6/lib/IRGen/IRGenModule.cpp#L255

结束语

好了,关于iOS下的闭包说了很多,也有一些底层的东西需要去理解和动手,困难肯定是有的,希望一起乘风破浪,所向披靡

本文由哈喽比特于2年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/97Ij2N545ydx6WBNAwncOA

 相关推荐

刘强东夫妇:“移民美国”传言被驳斥

京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。

发布于:1年以前  |  808次阅读  |  详细内容 »

博主曝三大运营商,将集体采购百万台华为Mate60系列

日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。

发布于:1年以前  |  770次阅读  |  详细内容 »

ASML CEO警告:出口管制不是可行做法,不要“逼迫中国大陆创新”

据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。

发布于:1年以前  |  756次阅读  |  详细内容 »

抖音中长视频App青桃更名抖音精选,字节再发力对抗B站

今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。

发布于:1年以前  |  648次阅读  |  详细内容 »

威马CDO:中国每百户家庭仅17户有车

日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。

发布于:1年以前  |  589次阅读  |  详细内容 »

研究发现维生素 C 等抗氧化剂会刺激癌症生长和转移

近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。

发布于:1年以前  |  449次阅读  |  详细内容 »

苹果据称正引入3D打印技术,用以生产智能手表的钢质底盘

据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。

发布于:1年以前  |  446次阅读  |  详细内容 »

千万级抖音网红秀才账号被封禁

9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...

发布于:1年以前  |  445次阅读  |  详细内容 »

亚马逊股东起诉公司和贝索斯,称其在购买卫星发射服务时忽视了 SpaceX

9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。

发布于:1年以前  |  444次阅读  |  详细内容 »

苹果上线AppsbyApple网站,以推广自家应用程序

据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。

发布于:1年以前  |  442次阅读  |  详细内容 »

特斯拉美国降价引发投资者不满:“这是短期麻醉剂”

特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。

发布于:1年以前  |  441次阅读  |  详细内容 »

光刻机巨头阿斯麦:拿到许可,继续对华出口

据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。

发布于:1年以前  |  437次阅读  |  详细内容 »

马斯克与库克首次隔空合作:为苹果提供卫星服务

近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。

发布于:1年以前  |  430次阅读  |  详细内容 »

𝕏(推特)调整隐私政策,可拿用户发布的信息训练 AI 模型

据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。

发布于:1年以前  |  428次阅读  |  详细内容 »

荣耀CEO谈华为手机回归:替老同事们高兴,对行业也是好事

9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。

发布于:1年以前  |  423次阅读  |  详细内容 »

AI操控无人机能力超越人类冠军

《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。

发布于:1年以前  |  423次阅读  |  详细内容 »

AI生成的蘑菇科普书存在可致命错误

近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。

发布于:1年以前  |  420次阅读  |  详细内容 »

社交媒体平台𝕏计划收集用户生物识别数据与工作教育经历

社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”

发布于:1年以前  |  411次阅读  |  详细内容 »

国产扫地机器人热销欧洲,国产割草机器人抢占欧洲草坪

2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。

发布于:1年以前  |  406次阅读  |  详细内容 »

罗永浩吐槽iPhone15和14不会有区别,除了序列号变了

罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。

发布于:1年以前  |  398次阅读  |  详细内容 »
 相关文章
快速配置 Sign In with Apple 5年以前  |  7675次阅读
使用 GPUImage 实现一个简单相机 5年以前  |  5971次阅读
APP适配iOS11 5年以前  |  5749次阅读
 目录