这儿每天共享一个 iOS 的新知识,快来重视我吧

前言

Swift 5.9 出了一个新功能,Swift Parameter Packs(Swift 参数包),在之前的文章中也有提到过,不过其时没讲参数包应该怎么运用,今日就来学习一下什么是参数包,以及在日常开发中有哪些运用场景。

晋级 Xcode 15 之后,你能用到哪些 swift 新特性

什么是 Swift Parameter Packs

咱们用一个例子来说明,当咱们在函数中运用泛型时,可能是这样的:

funceachFirst<T>(_item:T)->T?

当咱们运用两个泛型时:

funceachFirst<T1,T2>(_item1:T1,_item2:T2)->(T1?,T2?)

当咱们运用三个泛型时:

funceachFirst<T1,T2,T3>(_item1:T1,_item2:T2,_item3:T3)->(T1?,T2?,T3?)

大家发现问题了吗?当运用的泛型越来越多,就会使得函数越来越长,越来越丑。

在 SwiftUI 结构中 buildBlock 函数竟然多达 10 个泛型:

staticfuncbuildBlock<C0,C1,C2,C3,C4,C5,C6,C7,C8,C9>(_c0:C0,_c1:C1,_c2:C2,_c3:C3,_c4:C4,_c5:C5,_c6:C6,_c7:C7,_c8:C8,_c9:C9)->TupleView<(C0,C1,C2,C3,C4,C5,C6,C7,C8,C9)>whereC0:View,C1:View,C2:View,C3:View,C4:View,C5:View,C6:View,C7:View,C8:View,C9:View

这看起来是不是十分令人头疼,swift 为了处理这个问题,提出了 Swift Parameter Packs 的概念,简单来讲便是,能够用 each 关键字来替代那些重复的代码,运用 each 之后的 buildBlock 函数变为:

staticfuncbuildBlock<eachContent>(_content:repeateachContent)->TupleView<(repeateachContent)>whererepeateachContent:View

对比一下是不是简洁多了。

接下来讲几个实用的例子。

KeyPath 取值

比方我有个用户类,然后我想要取其中的特点,并以元组的方式回来:

classUser{
letid:String
letname:String
letage:Int
init(id:String,name:String,age:Int){
self.id=id
self.name=name
self.age=age
}
}
funcvalueAt(_object:User,keyPath1:KeyPath<User,String>,keyPath2:KeyPath<User,String>,keyPath3:KeyPath<User,Int>)->(String,String,Int){
return(object[keyPath:keyPath1],object[keyPath:keyPath2],object[keyPath:keyPath3])
}
letuser=User(id:"0",name:"iOS新知",age:1)
print(valueAt(user,keyPath1:.id,keyPath2:.name,keyPath3:.age))

实际上,这种写法既不高雅,也很费事,还不通用。

运用参数包的方式改一下:

funcvaluesAt<T,eachU>(_subject:T,keyPathskeyPath:repeatKeyPath<T,eachU>)->(repeateachU){
(repeat(subject[keyPath:eachkeyPath]))
}

修正完调用只需求:

valuesAt(user,keyPaths:.id,.name,.age)

后面的参数能够传恣意多个,而且运用泛型之后不仅 User 能够调用,其他的类也能运用。

缓存性能开支大的函数

有一些函数可能需求时间比较久,或者性能开支比较大,这时候的优化方案一般需求把操作过的缓存起来,不要每次都重复执行,比方加载图片比较耗时,那么就需求把加载过的图片缓存起来,那么你可能有这样的函数:

funcfetchImage(url:String)->UIImage{
ifletresult=storage[url]{
returnresult
}else{
//TODO:加载图片
returnxxx
}
}

这个办法欠好的地方在于,它只适用于你当下图片的场景,能够把这一类需求运用参数包给封装起来:

funcmemoize<eachArgument:Hashable,Return>(
_function:@escaping(repeateachArgument)->Return
)->(repeateachArgument)->Return{
varstorage=[AnyHashable:Return]()

return{(argument:repeateachArgument)in
varkey=[AnyHashable]()
repeatkey.append(AnyHashable(eachargument))

ifletresult=storage[key]{
returnresult
}else{
letresult=function(repeateachargument)
storage[key]=result
returnresult
}
}
}

这个函数要求传入一个函数,并回来一个新函数,那么上边缓存图片的例子就能够用这个函数实现:

//loadImage是加载图片的函数
letmemoizedLoadImage=memoize(loadImage)
memoizedLoadImage(URL(filePath:"some-url"))
memoizedLoadImage(URL(filePath:"some-url"))
memoizedLoadImage(URL(filePath:"other-url"))

当调用 memoizedLoadImage 函数时,函数体内会先从 storage 查看,假如存在直接回来,假如不存在,则调用传入的 loadImage 办法来获取。

装修高阶函数

能够运用参数包把高阶函数封装起来,以便通用,举个例子:

funcdecorateAround<eachArgument,Return>(
_function:@escaping(repeateachArgument)->Return,
around:@escaping((repeateachArgument)->Return,repeateachArgument)->Return
)->(repeateachArgument)->Return{
{(argument:repeateachArgument)in
around(function,repeateachargument)
}
}

它承受两个参数:functionaround,而且回来一个新的函数,这个新函数也承受与原函数相同的参数,并将它们传递给 around 函数。适当所以把 function 装修了一下。

听起来比较绕对吧?来看个例子:

funcaddTwoNumbers(_a:Int,_b:Int)->Int{
returna+b
}
//创建一个装修函数,用于在调用原始函数之前和之后输出信息
letdecoratedAdd=decorateAround(addTwoNumbers){(originalFunction,a,b)in
print("调用addTwoNumbers之前")
letresult=originalFunction(a,b)
print("调addTwoNumbers之后")
returnresult
}
decoratedAdd(1,2)//打印3

decorateAround 函数用于创建一个装修函数 decoratedAdd,我在该函数在调用 addTwoNumbers 的前后输出两段信息。最后,我调用了装修后的函数,观察输出的信息。

这儿每天共享一个 iOS 的新知识,快来重视我吧

本文同步自微信公众号 “iOS新知”,每天准时共享一个新知识,这儿只是同步,想要及时学到就来重视我吧!