如何写出更优雅的swift代码

背景

写此篇文章的背景是团队例行分享。
自从宝宝出生后,便很少花时间看技术上的东西,这点确实需要自我反省。
毕竟技术的提升,需要长期累积。

主要内容

开门见山,直接切入主题,今天主要介绍swift的一些关键词、高阶函数、属性封装器,
通过以上3个思路,可以帮助我们写出更优雅的swift代码。

1、defer

defer定义:一个函数在return前,会执行defer的block代码,
相当于@try {
} @catch (NSException *exception) {
} @finally {
}里面的finally的block一样的效果。

应用场景:

场景1:清理工作、回收资源

//关闭文件、数据库、关闭loading
func processFile(filename: String) throws {
    if exists(filename) {
        let file = open(filename)
        defer {
            close(file)
        }
        while let line = try file.readline() {
            // 处理文件。
        }
        // close(file) 会在这里被调用,即作用域的最后。
    }
}

场景2:减少冗余代码,以网络请求后的业务逻辑作为案例

//网络请求
func loadData(_ complete: ((Error?, [Any]?) -> ())?) {
    let request = NetRequest(path: "homepage")
    request.fetch { response in
        guard let dict = response as? [String: AnyObject] else {
            DispatchQueue.main.async {
                complete?(error, nil)
            }
            return
        }
        guard let success = dict["success"] as? bool, success == true else {
            DispatchQueue.main.async {
                complete?(error, nil)
            }
            return
        }
        guard let homelist = dict["homelist"] as? [String]? else {
            DispatchQueue.main.async {
                complete?(error, nil)
            }
            return
        }
        DispatchQueue.main.async {
            complete?(nil, homelist)
        }
    }
}

使用defer后,可以抽出冗余的代码,并简化成如此:

func loadData(_ complete: ((Error?, [Any]?) -> ())?) {
    let request = NetRequest(path: "homepage")
    request.fetch { response in
        var error: Error? = nil
        var data: [String]? = nil
        defer {
            //避免重复使用
            DispatchQueue.main.async {
                complete?(error, data)
            }
        }
        guard let dict = response as? [String: AnyObject] else {
            return
        }
        guard let success = dict["success"] as? bool, success == true else {
            return
        }
        guard let homelist = dict["homelist"] as? [String]? else {
            data = homelist
            return
        }
    }
}

2、高阶函数 Map、Filter、FlatMap、Reduce

一、 Map
定义:遍历集合,并对集合中的每个元素执行相同的操作。

//将[a, b, c]转换为[avalue, bvalue, cvalue]
let array = ["a","b","c"]
let array1 = array.map{ str in
    return str + "value1"
 }
let array2 = array.map {
    return $0 + "value2"
}
// print avalue,bvalue,cbalue,写法array1与array2是等价的。$0,$1表示闭包里的第1个,第2个参数

二、Filter
定义:遍历集合,返回包含满足条件的元素的数组

let array = ["a","b","c"]
let array1 = array.map{ str in
      return str + "value1"
}
let array2 = array.map {
       return $0 + "value2"
 }
 let array3 = array.filter{ str in
      return str != "b"
  }
  let array4 = array.filter{
       return $0 != "b"
  }
//print a,c

三、FlatMap
定义:数组集合降阶

var array = [[1,2,3],[6,7,8]]
var array1 = array.flatMap{$0}
//print: [1, 2, 3, 6, 7, 8]

四、Reduce
定义:将集合中的所有项组合起来,创建一个单一的值, 可以加减乘除,拼凑字符串等

//未使用 reduce,常规写法
var sum = 0  
let mArray = [1,2,3,4,5]  
for i in mArray {  
   sum = i+sum  
} 

//使用 reduce写法
var nums = [1, 2, 3, 4, 5]
var sum = nums.reduce(0) { $0 + $1 } // 15
//(0)表示初始值

3、属性包装器:@propertyWrapper

定义:用来修饰属性,抽取关于属性重复的逻辑来达到简化代码的目的

//未使用属性包装器的通常做法,如果有多个属性,就需要重复多个代码
class XLUtil {
    static var ccdguide: Bool {
        set {
            UserDefaults.standard.setValue(newValue, forKey: "abcdguide")
        }
        get {
            return UserDefaults.standard.bool(forKey: "abcdguide")
        }
    }
}
//使用属性包装器后,代码
class XLUtil {
    @XLUserConfigSecond("accdguide", false)
    static var accdguide
    
    @XLUserConfigSecond("accdguide2", false)
    static var accdguide2
    
    @XLUserConfigSecond("accdguide3", false)
    static var accdguide3
    
    @propertyWrapper
    struct XLUserConfigSecond<T> {
        var key: String
        var defaultValue: T
        
        init(_ key: String, _ defaultValue: T) {
            self.key = key
            self.defaultValue = defaultValue
        }
        
        var wrappedValue: T {
            get {
                guard let value = UserDefaults.standard.object(forKey: self.key) else { return defaultValue }
                return value as! T
            }
            
            set {
                UserDefaults.standard.setValue(newValue, forKey: self.key)
            }
        }
    }

//首字母大写
    @propertyWrapper struct Capitalized {
        var wrappedValue: String {
            didSet { wrappedValue = wrappedValue.capitalized }
        }

        init(wrappedValue: String) {
            self.wrappedValue = wrappedValue.capitalized
        }
    }
}
//泛型可以传入block函数
class XLAbcDataSource: NSObject {
    
    var _ccdModels: [XLACDMaterialModel]?
    var ccdModels: [XLACDMaterialModel] {
        get {
            if _ccdModels == nil {
                _ccdModels = reloadData()
            }
            return _ccdModels ?? []
        }
    }
    
    var _selectedACDModel: XLACDMaterialModel?
    var selectedACDModel: XLACDMaterialModel? {
        get {
            if _selectedACDModel == nil {
                _selectedACDModel = XLAbcDataSource.defaultMaterial()
            }
            return _selectedACDModel
        }
        set {
            _selectedACDModel = newValue
        }
    }
    
    static func reloadData() -> [XLACDMaterialModel]? {
        let allModels = [XLACDMaterialModel(),XLACDMaterialModel()]
        return allModels
    }
    
    ///默认素材
    static func defaultMaterial() -> XLACDMaterialModel? {
        return XLACDMaterialModel()
    }
    
    static let block = {
        return XLACDMaterialModel.reloadData()
    }
    @XLLazy(defaultValue: block()) var acdModels2
}

@propertyWrapper struct XLLazy<T> {
    var defaultValue: T?
    var value: T?
    
    init(defaultValue: T?) {
        self.defaultValue = defaultValue
    }
    
    var wrappedValue: T? {
        set {
            value = newValue
        }
        get {
            guard let value = self.value else {
                return defaultValue
            }
            return value
        }
    }
}

4、异步代码,同步执行:async,wait

第1个例子,下面的函数通过一个 回调 返回数值:

//常规异步,通过block回调
func fetchData(completion: @escaping ([String]) -> Void) {
    DispatchQueue.main.async {
        completion(["apple", "pear"])
    }
}
//使用async后可以用同步代码继续异步任务
func fetchData() async -> [String] {
    Sleep(3)
    Return ["apple", "pear"]
}
func fetch() async {
    let items = await fetchData()
    for item in items {
        print(item)
    }
}

第2个例子:通过加载一张图片来理解,加载图片共分为 4 个步骤:

  1. 生成网络请求;
  2. 向服务器发送请求,并等待服务器返回结果;
  3. 根据下发的 Data 构建 UIImage;
  4. 最后准备缩略图,并在完成时执行回调。

常规异步做法,如下所示:

func fetchThumbnail(for id: String, completion: @escaping (UIImage?, Error?) -> Void) {
    let request = thumbnailURLRequest(for: id) 
    let task = URLSession.shared.dataTask(with: request) { data, response, error in
        if let error = error {
            completionHandler(nil, error)
        } else if (response as? HTTPURLResponse)?.statusCode != 200 {
            completion(nil, FetchError.badID)
        } else {
            guard let image = UIImage(data: data!) else {
                completion(nil, FetchError.badImage)
                return
            }
            image.prepareThumbnail(of: CGSize(width: 40, height: 40)) { thumbnail in
                guard let thumbnail = thumbnail else {
                    completion(nil, FetchError.badImage)
                    return
                }
                completion(thumbnail, nil)
            }
        }
    }
    task.resume()
}

使用async、wait后新流程,将异步的代码变成同步
1、创建缩略图URLRequest;
2、发送网络请求并接收服务端返回;
3、根据返回的 data 创建 UIImage;
4、返回获取到的缩略图。

func fetchThumbnail(for id: String) async throws -> UIImage {
    let request = thumbnailURLRequest(for: id)
    let (data, response) = try await URLSession.shared.data(for: request)
    guard (response as? HTTPURLResponse)?.statusCode == 200 else { throw FetchError.badID }
    let maybeImage = UIImage(data: data)
    guard let thumbnail = await maybeImage?.thumbnail else { throw FetchError.badImage }
    return thumbnail
}

5、@globalActor actor MainActor

后续介绍

6、associatedtype 协议泛型

后续介绍
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,646评论 4 366
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,979评论 1 301
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 110,391评论 0 250
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,356评论 0 215
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,740评论 3 293
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,836评论 1 224
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,022评论 2 315
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,764评论 0 204
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,487评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,728评论 2 252
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,200评论 1 263
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,548评论 3 260
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,217评论 3 241
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,134评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,921评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,919评论 2 283
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,766评论 2 274

推荐阅读更多精彩内容