Swift小知识

1. 关于Swift中Protocol

1. 在 Swift 中,Delegate 就是基于 Protocol 实现的。
2. 定义委托时,我们让 protocol 继承自 AnyObject。这是由于,在 Swift 中,这表示这一个协议只能被应用于 class(而不是 struct 和 enum)。
3. 那么为什么不使用 class 和 NSObjectProtocol,而要使用 AnyObject 呢?NSObjectProtocol 来自 Objective-C,在 pure Swift 的项目中并不推荐使用。class 和 AnyObject 并没有什么区别,在 Xcode 中也能达到相同的功能,但是官方还是推荐使用 AnyObject。

2. 关于SnapKit适配 #available(iOS 11.0, *) 刘海适配
make.bottom.equalTo(self.view.safeAreaLayoutGuide.snp.bottom)
3. 是否全面屏(刘海屏) 适配 iOS 13.0+
    var cc_isFullScreen: Bool {
    if #available(iOS 13, *) {
        let scene = UIApplication.shared.connectedScenes.first?.delegate as? SceneDelegate
        guard let unwrappedWindow = scene?.window else {
        return false
    }
    
    if unwrappedWindow.safeAreaInsets.left > 0 ||
        unwrappedWindow.safeAreaInsets.bottom > 0 {
        return true
    }
    
    } else if #available(iOS 11, *) {
        guard let window = UIApplication.shared.delegate?.window, let unwrapped = window else {
          return false
       }
    
    if unwrapped.safeAreaInsets.left > 0 || unwrapped.safeAreaInsets.bottom > 0 {
        return true
    }
  }
  return false
}
4. 关于CGSize转String; String转CGSize
let size = CGSize(width: 200, height: 80)

 // CGSize转String
🌰:  let sizeString = "\(size)"  →  (200.0, 80.0)

 // String转CGSize
 字符串的size格式: {width, height}
 🌰:  let rsSize = NSCoder.cgSize(for: sizeString)  →  (200.0, 80.0)

注: NSCoder类, 需注意字符串格式

public class func string(for offset: UIOffset) -> String
public class func cgPoint(for string: String) -> CGPoint
public class func cgVector(for string: String) -> CGVector
public class func cgSize(for string: String) -> CGSize
public class func cgRect(for string: String) -> CGRect
public class func cgAffineTransform(for string: String) -> CGAffineTransform
public class func uiEdgeInsets(for string: String) -> UIEdgeInsets
5. 关于将字面量转换为特定的类型

Swift枚举中支持以下四种关联值类型:

  • 整型(Integer)
  • 浮点数(Float Point)
  • 字符串(String)
  • 布尔类型(Boolean)
    使用自定义类型作为枚举的值,想要支持别的类型,可以通过实现 StringLiteralConvertible 协议来完成

Swift 3.0之前,字面量协议的名称:

  • ArrayLiteralConvertible 数组字面量协议
  • BooleanLiteralConvertible 布尔值字面量协议
  • DictionaryLiteralConvertible 字典字面量协议
  • FloatLiteralConvertible 浮点数字面量协议
  • NilLiteralConvertible nil字面量协议
  • IntegerLiteralConvertible 整数字面量协议
  • StringLiteralConvertible 字符串字面量协议
- ExpressibleByArrayLiteral  数组字面量协议
- ExpressibleByBooleanLiteral  布尔值字面量协议
- ExpressibleByDictionaryLiteral  字典字面量协议
- ExpressibleByFloatLiteral 浮点数字面量协议
- ExpressibleByNilLiteral  nil字面量协议
- ExpressibleByIntegerLiteral 整数字面量协议
- ExpressibleByStringLiteral 字符串字面量协议

其中,ExpressibleByStringLiteral 字符串字面量协议还依赖于以下 2 个协议:
ExpressibleByUnicodeScalarLiteral
ExpressibleByExtendedGraphemeClusterLiteral
也就是说,实现 ExpressibleByStringLiteral 协议时,还需要实现其依赖的另外 2 个协议

🌰例子:

// 实现 CGSize 的字符串字面量协议
// NSCoder.cgSize(for: String), 针对CGSize时String的格式是: {width, height}

extension CGSize: ExpressibleByStringLiteral {
    public init(stringLiteral value: String) {
       let size = NSCoder.cgSize(for: value)
       self.init(width: size.width, height: size.height)
    }

    public init(extendedGraphemeClusterLiteral value: String) {
       let size = NSCoder.cgSize(for: value)
       self.init(width: size.width, height: size.height)
    }

   public init(unicodeScalarLiteral value: String) {
       let size = NSCoder.cgSize(for: value)
       self.init(width: size.width, height: size.height)
   }
}

enum Devices: CGSize {
  case i5 = "{320, 568}" // 设置值需要字符串类型
}

// 获取Devices.i5真实值, 需要访问枚举的是 rawValue 属性;  rawValue类型为CGSize
print(Devices.i5.rawValue) → (320.0, 568.0)

针对官方文档连接:
https://developer.apple.com/documentation/foundation/nscoder/1624484-cgsize

4. 关于iOS 15.0+ 列表滚动到导航栏后,导航栏颜色发生变化。禁止导航栏颜色发生变化
全局设置
if #available(iOS 15.0, *) { //UINavigationBarAppearance属性从iOS13开始
   let navBarAppearance = UINavigationBarAppearance()
   navBarAppearance.configureWithOpaqueBackground()
   // 背景色
   navBarAppearance.backgroundColor = UIColor.white

   self.navigationController?.navigationBar.standardAppearance = navBarAppearance
   self.navigationController?.navigationBar.scrollEdgeAppearance = navBarAppearance
 }
5. 关于iOS 11.0+ 禁止有导航栏 scrollView会默认把 scrollview 向下平移一个导航栏高度
if #available(iOS 11.0, *) {
     self.scrollView.contentInsetAdjustmentBehavior = .never
} else {
     self.automaticallyAdjustsScrollViewInsets = false
 }
6. 关于 百度地图SDK组件化 和 BMKLocationKit,Podfile 以及 xxx.podspec的配置
1、在 Podfile 里配置:
    #百度地图SDK现在使用的7个framework,为了支持ssl所以还添加了两个.a的静态库,这个时候需要使用如下命令来让cocoapods对静态库支持
    pre_install do |installer|
      # workaround for https://github.com/CocoaPods/CocoaPods/issues/3289
      Pod::Installer::Xcode::TargetValidator.send(:define_method, :verify_no_static_framework_transitive_dependencies) {}
    end

2、依赖 BaiduMapKit、BMKLocationKit时,需要配置的 podspec:
    # 依赖百度地图 SDK; 由于 BMKLocationKit 库中没有组件化文件,
    # 第一种方式 :自行添加 Modules文件夹 -> 修改 module.modulemap。问题:直接移进去,这样会导致 `pod install` 时可能丢失该文件
    # 第二种方式:使用 prepare_command 下载库文件完成时,执行一个脚本

  s.dependency 'BaiduMapKit'
  s.dependency 'BMKLocationKit'
  s.frameworks   = "BMKLocationKit", "BaiduMapAPI_Base", "BaiduMapAPI_Map", "BaiduMapAPI_Search", "BaiduMapAPI_Utils", "Accelerate"
  s.libraries    = "z", "crypto", "ssl", "c++", "sqlite3.0"
  # -undefined dynamic_lookup 这个表明了当主工程和framework都包含同一个库时,会优先使用主工程的库。
  s.pod_target_xcconfig = {
      # 'FRAMEWORK_SEARCH_PATHS'   => '$(inherited) $(PODS_ROOT)/BaiduMapKit/BaiduMapKit ${PODS_ROOT}/BMKLocationKit/framework',
      'LIBRARY_SEARCH_PATHS'     => '$(inherited) $(PODS_ROOT)/BaiduMapKit/BaiduMapKit/thirdlibs',
      'OTHER_LDFLAGS'            => '$(inherited) -undefined dynamic_lookup -ObjC',
      'ENABLE_BITCODE'           => 'NO'
  }
  # mkdir:创建目录(文件夹)touch:创建文件
  s.prepare_command     = <<-EOF
  mkdir ../../Pods/BMKLocationKit/framework/BMKLocationKit.framework/Modules
  touch ../../Pods/BMKLocationKit/framework/BMKLocationKit.framework/Modules/module.modulemap
  cat <<-EOF > ../../Pods/BMKLocationKit/framework/BMKLocationKit.framework/Modules/module.modulemap
  framework module BMKLocationKit {
    umbrella header "BMKLocationComponent.h"

    export *
    module * { export * }
  }
  \EOF
  EOF
7、记录 Podfile 相关指令
# as mentioned here https://github.com/react-native-maps/react-native-maps/issues/3597#issuecomment-745026120
#禁用 bitcode
post_install do |installer|
  installer.pods_project.targets.each do |target|
#    设置全部target ENABLE_BITCODE = NO
#    target.build_configurations.each do |config|
#      config.build_settings['ENABLE_BITCODE'] = 'NO'
#    end


#    target为 TUIChat || TUICore时,设置以下属性
    if target.name == 'TUIChat' || target.name == 'TUICore' || target.name == 'TUIConversation'
      target.build_configurations.each do |config|
        config.build_settings['ENABLE_BITCODE'] = 'NO'
        
#        config.build_settings['MACH_O_TYPE'] = 'Static Library'
#        config.build_settings['CLANG_ALLOW_NON_MODULAR_INCLUDES_IN_FRAMEWORK_MODULES'] = 'YES'
#        'LIBRARY_SEARCH_PATHS'     => '$(inherited) $(PODS_ROOT)/TUIChat/TUIChat/VoiceConvert',
#        'OTHER_LDFLAGS'            => '$(inherited) -undefined dynamic_lookup -ObjC',
      end
    end
  end
end

8、关于swift async - await,将闭包包装成 async + await

例子:

// 请求定位权限权限
 func checkLocationPermission() async -> Result<Bool, CNMError> {
     // 将闭包包装成 async + await
     await withCheckedContinuation { continuation in
         // 开启定位权限提示
         cnm_checkLocationAuthority { granted in
             var result: Result<Bool, CNMError>
             
             if granted {
                 result = .success(granted)
             } else {
                 result = .failure(.none)
             }
             continuation.resume(returning: result)
         }
     }
 }

// You can spawn another Task dispatched explicitly to the MainActor
Task { @MainActor in
   // 切回主线程
}

例子:
Task {  //  async + await 利用 Task闭包执行
   // 请求服务器信息
   await CommonRequest.requestServerInfo()
        
   // You can spawn another Task dispatched explicitly to the MainActor
   Task { @MainActor in
      // 切回主线程
   }
}
9、渐变设置根控制过度
// 设置TabBarController为根控制器
static func setTabBarControllerRoot() {
   let fadeOutAnimation = CATransition()
   fadeOutAnimation.duration = 0.25
   fadeOutAnimation.type = .fade
   fadeOutAnimation.fillMode = .forwards
   fadeOutAnimation.isRemovedOnCompletion = true
   // 渐变显示根控制器
   window?.layer.add(fadeOutAnimation, forKey: "fadeOutAnimation")
   window?.rootViewController = txhTabBarController
 }
10、Alamofire中使用 async + await

例子:

struct TXHHttps {
    /// Session实例
    var sessionManager: Session?

    /// 单例
    static let share = TXHHttps()
    private init() {
        let config = URLSessionConfiguration.af.default
        config.timeoutIntervalForRequest = 10 // request 请求超时时间,默认60s
        config.timeoutIntervalForResource = 15 // 任务等待整个资源加载的超时时间(单位是秒)默认值是7天
        config.waitsForConnectivity = true // 是否应等待连接变为可用或者立即失败

        sessionManager = Session(configuration: config)
    }

 /// post 请求
    /// - Parameters:
    ///   - url: 请求地址
    ///   - parameters: 请求参数
    ///   - headers: 请求头
    ///   - success: 请求成功回调
    ///   - fail:请求失败回调
    static func post(url: String,
                     parameters: Parameters? = nil,
                     headers: [String: String]? = nil) async -> Result<Dictionary<String, Any?>, CNMError> {
        // 添加请求头
        var http_headers: HTTPHeaders = ["Accept": "application/json"]
        if let _headers = headers, !_headers.isEmpty {
            for item in _headers {
                http_headers.add(name: item.key, value: item.value)
            }
        }
        
        // 请求
        let dataResp = await TXHHttps.share.sessionManager?.request(url, method: .post, parameters: parameters, encoding: JSONEncoding.default, headers: http_headers).serializingData().response
        
        return jsonResponse(dataResp: dataResp)
    }
}


extension TXHHttps {
    /// 请求响应回调函数
    private static func jsonResponse(dataResp: DataResponse<Data, AFError>?) -> Result<Dictionary<String, Any?>, CNMError> {
        
        guard let dataResponse = dataResp, let data = dataResponse.data else { return .failure(CNMError.none) }
        
        let statusCode = dataResponse.response?.statusCode
        if statusCode != 200 {
            return .failure(CNMError(statusCode))
        }
        
        // 解析响应data
        // try? 将整个表达式转换为 nil。它已经自行处理了错误,因此 catch block 不再需要捕获错误
        // 如果你想让错误被catch block 捕获,你应该使用try
        do {
            guard let json = try JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? Dictionary<String, Any?>
            else {
                return .failure(CNMError.none)
            }
            return .success(json)
        } catch {
            return .failure(CNMError.none)
        }
    }
}

// 错误
enum CNMError: Error {
    case none
    case timeout
    case cancelled
    case cannotConnectToHost
    case notConnectedToInternet
    case badServerResponse
    
    
    init(_ code: Int?) {
        switch code {
        case NSURLErrorTimedOut:
            self = .timeout
        case NSURLErrorCancelled:
            self = .cancelled
        case NSURLErrorCannotConnectToHost:
            self = .cannotConnectToHost
        case NSURLErrorNotConnectedToInternet:
            self = .notConnectedToInternet
        case NSURLErrorBadServerResponse:
            self = .badServerResponse
        default:
            self = .none
        }
    }
}

extension CNMError: LocalizedError {
    var errorDescription: String? {
        switch self {
            case .timeout: return "抱歉,请求超时"
            case .cancelled: return "抱歉,请求取消"
            case .cannotConnectToHost: return "抱歉,连接服务器失败"
            case .notConnectedToInternet: return "抱歉,网络连接失败, 请检查网络"
            case .badServerResponse: return "抱歉,服务器未响应"
            default: return "抱歉,请求失败"
        }
    }
    
    var failureReason: String? {
        return errorDescription
    }
}

// 请求例子:
// 请求服务器信息
static func requestServerInfo() async {
        let rs = await TXHHttps.post(url: "\(base_url)\(getServerPath)", parameters: GlobalApi.getServiceInfo.getServiceParams())

        switch rs {
        case let .success(json):
            Observable<TXHMap>
                .just(json)
                .mapToModel(ServerModel.self)
                .bind(to: AppConfig.serverSuccess)
                .dispose()
        case let .failure(error):
            Observable<CNMError>
                .just(error)
                .bind(to: AppConfig.serverFailed)
                .dispose()
        }
    }    
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。