Dev

技术相关

shell 脚本零食铺

隐藏指令输出 如果想让终端不打印某条指令的运行结果,可以使用: command > /dev/null 2>&1 解决引入另一个脚本后,在其他路径执行脚本报错的问题 脚本 A 引入了脚本 B 和 C。如果不在脚本 A 所在的路径执行脚本 A,那么终端会因为找不到 B 和 C 而报错:CommonEcho.sh: No such file or directory 比较好的解决方案是在引入脚本 B 和 C的时候指明它们的路径(但不是写死路径): source $(dirname "$0")/lib_example source $(dirname "

By Gray

Swift 零碎知识

关于继承 SwiftUI 的解决方案 struct没有继承的功能,所以SwiftUI里面也不能像UIKit那样做写一个BaseView,然后子类继承里面的所有功能。网上有大神给出了这一点的解决方案 Creating BaseView class in SwiftUI 重写 1. 子类重写父类的属性后,也能通过super.someProperty获取父类的属性 关于类初始化 1. 通过给存储属性赋默认值,或者在初始化函数(包括便利构造器)中给存储属性赋值,不会触发该变量的didSet 2. 初始化构造器的三条法则: * A designated initializer must call a designated initializer from its immediate superclass. * A convenience initializer must call another initializer from the same class. * A

By Gray

Obj-C 零食铺

预处理 总览 宏指令 含义 #define 给一串代码指定一个常量,本质是「替换」 #include 一般用于导入头文件 #undef 将某个已经定义的宏移除 #ifdef 如果定义了某个宏,返回YES,等价于#if defined (...) #ifndef 如果没有定义某个宏,返回YES,等价于#if !defined (...) #if, #else, #elif, #endif 条件处理 #pragma 使用标准化方法向编译器发送某些命令,比如忽略某些警告 注意: define结尾不用带分号。因为define的本质是「替换」,如果带上分号的话,分号也会被替换到程序中。比如: #define MAXVAL 1000; // 报错,因为 MAXVAL 的值其实为「1;」,并不是整型 int y

By Gray

objc 关键字之 NS_UNAVAILABLE 和 NS_DESIGNATED_INITIALIZER

NS_UNAVAILABLE 简介 NS_UNAVAILABLE将方法标记为不可用,在类外调用该方法时会直接报错,提示'someMethod' is unavailable。但其实这个方法还能通过一些其他方式调用,比如performSelector 用处 * 禁用NSObject的init或者new方法,让开发者只能使用指定的初始化函数。 + (instancetype)new NS_UNAVAILABLE; - (instancetype)init NS_UNAVAILABLE; - (instancetype)initWithFrame:(CGRect)frame NS_UNAVAILABLE; - (instancetype)initWithParam:(NSString *)param; // 只能用这个方法初始化 * 整理代码的时候将某个方法标记为不可用,最后再把它删掉。 - (instancetype)init NS_UNAVAILABLE; - (instancetype)init __attribute_

By Gray

Swift Package 设置 file header

今天给自己的 Swift Package 仓库新增文件的时候,发现文件头的作者、时间、copy right 等信息不太全,一时兴起研究了一下。 一般来说,给 Xcode Project 新增文件时会带上 organization name。但是单独打开 Swift Package 时,是没有办法设置 organization name 的。这样新建的文件头部就会光秃秃的,不甚美观 一番研究之后发现,可以这样设置 Swift Package 的 file header: 1. 首先新建一个 IDETemplateMacros.plist 文件,并写入下面的内容: <?xml version="1.0" encoding="UTF-8&

By Gray

Dispatch Semaphore VS Dispatch Group

Dispatch Group Dispatch Group 在项目中比较常见,用于多任务多线程之间的协作。比如,使用两个队列分别请求不同的接口,等请求全部完成后,刷新页面。下面这段代码使用两个队列分别执行不同任务,当两个任务都完成后,通知主队列执行完成代码 import Foundation import PlaygroundSupport PlaygroundPage.current.needsIndefiniteExecution = true let group = DispatchGroup() let q1 = DispatchQueue(label: "q1") group.notify(queue: .main) { print("Task All Done!") } q1.async(group: group) { for i in 1 ... 5

By Gray

Swift 单例中的线程安全问题

单例是常见的一种设计模式。最近在编写单例代码的时候,发现公司很多同事的 Swift 单例写法都是这样的, extension NSObject { @discardableResult static func kep_synchronized<T>(_ lock: AnyObject, closure: () -> T) -> T { objc_sync_enter(lock) defer { objc_sync_exit(lock) } return closure() } } class DefaultDict: NSObject { private static var manager: DefaultDict? static var sharedManager: DefaultDict { get { var newShared

By Gray

记一次错误使用 UIView Animation 引起的崩溃

上周做新版本的新手引导需求的时候,写了下面一段代码 @objc func nextStep() { // 点击事件 if currentStepIndex < viewChain.count { viewChain[currentStepIndex].clickBlock?() } // 隐藏当前步骤,展示下一个步骤 if currentStepIndex + 1 < viewChain.count { // 过渡动画,问题就出现在这里 UIView.animate(withDuration: 0.5) { self.viewChain[self.currentStepIndex].isHidden = true } completion: { finished in if finished { self.viewChain[self.currentStepIndex + 1].isHidden = false self.currentStepIndex += 1

By Gray

UIButton 设置图片在右的几种方法

UIButton的默认布局是图片在左,标题在右。想要得到一个图片在右,标题在做的UIButton,可以使用以下几种方法 方法一: 改变 Button 的语义属性 不同国家的文字书写方向不一样。在中国,文字书写方式是从左往右,因此UIButton的默认布局是图片在左;而在一些文字书写方向是从右往左的国家里,UIButton的默认布局是图片在右。UIView 有一个 semanticContentAttribute 属性,用来控制 View 的布局。我们可以将其强制更改为从右往左,这样 UIButton 的图片就在右边了: btn.semanticContentAttribute = .forceRightToLeft 方法二: 写一个 UIButton 的子类,重写绘制方法 final class MyCustomTitleImageButton: UIButton { /// 是否把图片放在左边,默认关闭 var isHeadBtn = false /// 是否显示箭头 var needArrow = true var it

By Gray

iOS 开发中的 inline 函数

前两天在开发的时候遇到这样一个问题:想在 OC 代码中实现一个全局变量,这个变量会根据设备类型取不同的值。代码如下: static CGFloat cardLeftMargin = isIPAD ? 30 : 16; // 这里的 isIPAD 是个宏 在 swift 中很容易就能实现这个全局变量。但是在 OC 里面,上面的代码编译时报错了, Initializer element is not a compile-time constant.在 OC 里面,全局变量在编译的时候得有个确定的值。解决这个问题有两个方法,一是用宏定义 #define,二是用inline内联函数。 先贴出来使用 inline 函数解决这个问题的方案: // .m file inline CGFloat cardLeftMargin() { return isIPAD ? 30 : 16; } // .h

By Gray

Swift GCD

基础知识 iOS 多线程实现方式 * Grand Central Dispatch (GCD): 自动管理线程生命周期 * NSOperation: 自动管理线程生命周期,基于 GCD 实现 * NSThread: 手动管理线程生命周期 并发和并行 * 并发: 并发不是真正的多线程,而是通过CPU(单核)在多个线程间快速切换调度,实现接近同时执行的效果 * 并行: 并行是真正的多线程,多个线程能同时执行 同步和异步 * 同步:在当前线程里按顺序执行多项任务,且任务结束的顺序和任务开始执行的顺序是一致的。执行同步任务不会开启新线程,所有的任务都在当前线程执行。把一个同步任务分配出去后,当前线程会一直等到这个任务执行完才能接着运行后面的代码(因为同步任务本身就是分配给当前线程干的) * 异步:异步任务也是按顺序分配给线程的,但是可能会放在多个线程里执行的,所以每个任务结束的顺序有可能是随机的。执行异步任务可能会开启新线程,要具体情况具体分析。把一个异步任务分配出去后,当前线程不用等这个任务执行完就可以执行后面的代码,因为这个异步任务有可能交给别的线程去做了

By Gray

SwiftUI 自适应布局

SwiftUI Adaptive Layout 使用 UIKit 写一套能够根据屏幕尺寸和方向自动更换布局的代码是比较麻烦的(至少从我目前的经验来看~),但是用 SwiftUI 来实现就会轻松一点。 在 SwiftUI 中,Apple 引入了一个概念 Size Classes (尺寸类型),且在横向和竖向两个维度上分别有 Horizontal Size Class 和 Vertical Size Class. Size Classes 可进一步分为两种:regular (标准) 和 compact (紧凑). 而设备的 Size Classes 由它的屏幕尺寸和屏幕方向决定,目前常见的设备 Size Classes 如下表所示, Horizontal Size Class (Width) Vertical Size Class (Height)

By Gray