24 篇博文 含有标签「iOS」
查看所有标签从一道网易面试题浅谈 Tagged Pointer
前言
这篇博客九月就想写了,因为赶项目拖了到现在,抓住17年尾巴写吧~
正文
上次看了一篇 《从一道网易面试题浅谈OC线程安全》 的博客,主要内容是:
作者去网易面试,面试官出了一道面试题:下面代码会发生什么问题?
@property (nonatomic, strong) NSString *target;
//....
dispatch_queue_t queue = dispatch_queue_create("parallel", DISPATCH_QUEUE_CONCURRENT);
for (int i = 0; i < 1000000 ; i++) {
dispatch_async(queue, ^{
self.target = [NSString stringWithFormat:@"ksddkjalkjd%d",i];
});
}
答案是:会 crash。
我们来看看对target
属性(strong
修饰)进行赋值,相当与 MRC 中的
- (void)setTarget:(NSString *)target {
if (target == _target) return;
id pre = _target;
[target retain];//1.先保留新值
_target = target;//2.再进行赋值
[pre release];//3.释放旧值
}
因为在 并行队列 DISPATCH_QUEUE_CONCURRENT
中异步 dispatch_async
对 target
属性进行赋值,就会导致 target 已经被 release
了,还会执行 release
。这就是向已释放内存对象发送消息而发生 crash 。
但是
我敲了这段代码,执行的时候发现并不会 crash~
@property (nonatomic, strong) NSString *target;
dispatch_queue_t queue = dispatch_queue_create("parallel", DISPATCH_QUEUE_CONCURRENT);
for (int i = 0; i < 1000000 ; i++) {
dispatch_async(queue, ^{
// ‘ksddkjalkjd’删除了
self.target = [NSString stringWithFormat:@"%d",i];
});
}
原因就出在对 self.target
赋值的字符串上。博客的最后也提到了 - ‘上述代码的字符串改短一些,就不会崩溃’,还有 Tagged Pointer
这个东西。
我们将上面的代码修改下:
NSString *str = [NSString stringWithFormat:@"%d", i];
NSLog(@"%d, %s, %p", i, object_getClassName(str), str);
self.target = str;
输出:
0, NSTaggedPointerString, 0x3015
发现这个字符串类型是 NSTaggedPointerString
,那我们来看看 Tagged Pointer 是什么?
Tagged Pointer
Tagged Pointer 详细的内容可以看这里 深入理解Tagged Pointer。
Tagged Pointer 是一个能够提升性能、节省内存的有趣的技术。
- Tagged Pointer 专门用来存储小的对象,例如 NSNumber 和 NSDate(后来可以存储小字符串)
- Tagged Pointer 指针的值不再是地址了,而是真正的值。所以,实际上它不再是一个对象了,它只是一个披着对象皮的普通变量而已。
- 它的内存并不存储在堆中,也不需要 malloc 和 free,所以拥有极快的读取和创建速度。
参考:
GCD 在 Swift 中的用法
DispatchQueue
Swift 中,对 GCD 语法进行了彻底改写。引入了 DispatchQueue
这个类。
先来看看在一个异步队列中读取数据, 然后再返回主线程更新 UI, 这种操作在新的 Swift 语法中是这样的:
DispatchQueue.global().async {
DispatchQueue.main.async {
// 更新UI操作
}
}
DispatchQueue.global().async
相当于使用全局队列进行异步操作。然后在调用 DispatchQueue.main.async
使用主线程更新相应的 UI 内容。
优先级
新的 GCD 引入了 QoS (Quality of Service) 的概念。
先看看下面的代码:
DispatchQueue.global(qos: .userInitiated).async {
}
QoS 对应的就是 Global Queue
中的优先级。 其优先级由最低的 background
到最高的 userInteractive
共五个,还有一个未定义的 unspecified
。
public static let background: DispatchQoS
public static let utility: DispatchQoS
public static let `default`: DispatchQoS
public static let userInitiated: DispatchQoS
public static let userInteractive: DispatchQoS
public static let unspecified: DispatchQoS
自定义 Queue
除了直接使用 DispatchQueue.global().async
这种封装好的代码外,还可以通过DispatchWorkItem
自定义队列的优先级,特性:
let queue = DispatchQueue(label: "swift_queue")
let dispatchworkItem = DispatchWorkItem(qos: .userInitiated, flags: .inheritQoS) {
}
queue.async(execute: dispatchworkItem)
GCD定时器
Swift 中 dispatch_time
的用法改成了:
let delay = DispatchTime.now() + .seconds(60)
DispatchQueue.main.asyncAfter(deadline: delay) {
}
相较与OC来说更易读了:
let dispatch_time = dispatch_time(DISPATCH_TIME_NOW, Int64(60 * NSEC_PER_SEC))
Swift 4 新特性
private 权限扩大
在 Swift 4 中,extension
可以读取 private
变量了。
Swift 3 中,如果将主体函数的变量定义为 private
,则其 extension
无法读取此变量,必须将其改为 filePrivate
才可以。
单向区间
单向区间是一个新的类型,主要分两种:确定上限和确定下限的区间。直接用字面量定义大概可以写成 …6
和 2…
例如
let intArr = [0, 1, 2, 3, 4]
let arr1 = intArr[...3] // [0, 1, 2, 3]
let arr2 = intArr[3...] // [3, 4]
字符串改动
String 操作简化了
String
许多要通过 .characters
进行的操作,可以直接用 String 进行操作了。
例如:
let greeting = "Hello, 😜!"
// No need to drill down to .characters
let n = greeting.count
let endOfSentence = greeting.index(of: "!")!
新增 Substring 类型
swift 4 为字符串片段新增了一个叫 Substring
的类型。
当你创建一个字符串的片段时,会产生一个 Substring
实例。Substring
与 String
用法相同, 因为子串和原字符串共享内存,所以对子串的操作快速而且高效。
let greeting = "Hi there! It's nice to meet you! 👋"
let endOfSentence = greeting.index(of: "!")!
// 产生 Substring 实例
let firstSentence = greeting[...endOfSentence]
// firstSentence == "Hi there!"
// `Substring` 与 `String` 用法相同
let shoutingSentence = firstSentence.uppercased()
// shoutingSentence == "HI THERE!"
但是要注意一个 Substring
保留从其生成的完整的 String
值。 当您传递一个看似很小的 Substring
时,这可能导致意外的高内存开销。所以使用 Substring
时,最好转化为 String
.
let newString = String(substring)
换行可以不用 \n
了!
Swift 3,字符串换行要插入 \n
。
例如:
在 Swift 4 可以这样操作:
用两个 “”“
包裹起来的字符串会自动添加 \n
换行,更加直观了。注意:换行与缩进参照的是第二个 “”“
号的位置。
嗯,我觉得OK!
支持 Unicode 9
Swift 4 支持 Unicode 9,为现代表情符号修正了一些问题。
let family1 = "👨👩👧👦"
let family2 = "👨\u{200D}👩\u{200D}👧\u{200D}👦"
family1 == family2 // → true
居然还有这种操作~
新增 KeyPath 数据类型
KeyPath 是 Swift 4 新增加的数据类型。
定义两个结构体 Person
与Book
struct Person {
var name: String
}
struct Book {
var title: String
var authors: [Person]
var primaryAuthor: Person {
return authors.first!
}
}
let abelson = Person(name: "Harold Abelson")
let sussman = Person(name: "Gerald Jay Sussman")
let book = Book(title: "Structure and Interpretation of Computer Programs", authors: [abelson, sussman])
book[keyPath: \Book.title]
book[keyPath: \Book.primaryAuthor.name]
// 相当与
book.title
book.primaryAuthor.name
这里 \Book.title
与 \Book.primaryAuthor.name
就是 KeyPath.
KeyPath 可以用 .appending
拼接
let authorKeyPath = \Book.primaryAuthor
let nameKeyPath = authorKeyPath.appending(path: \.name)
// nameKeyPath = \Book.primaryAuthor.name
新增 swapAt()
函数
Swift 4 引入了一种在集合中交换两个元素的新方法: swapAt()
Swift 3 交换集合中的元素的用 swap()
var numbers = [1,2,3,4,5]
swap(&numbers[0], &numbers[1])
// numbers = [2,1,3,4,5]
Swift 4 中可以直接用
var numbers = [1,2,3,4,5]
numbers.swapAt(0,1)
// numbers = [2,1,3,4,5]
其他改动
其他改动如:新的整数协议、泛型下标、NSNumber bridging等
可以参考:whats new in swift4
iTunes Connect 构建版本不显示
前言
今天新项目上架,在Xcode打包上传到App Store后,在iTunes Connect构建版本中居然找不到上传的App...
解决
从iOS10开始,苹果更加注重对用于隐私的保护,App 里边如果需要访问用户隐私,必须要做描述,所以要在 plist 文件中添加描述。
而这三个基础描述是必须添加的:
-
麦克风权限:
Privacy - Microphone Usage Description
是否允许此App使用你的麦克风? -
相机权限:
Privacy - Camera Usage Description
是否允许此App使用你的相机? -
相册权限:
Privacy - Photo Library Usage Description
是否允许此App访问你的媒体资料库?
其他的权限可以根据自己 APP 的情况来添加。
添加完权限之后然后继续提交 App 就可以了。
若还是找不到,返回 plist 文件中,删除之前的权限,重新添加一下,有可能你哪不小心添加的权限末尾有空格,或者字段不对。
End
在 Swift 中使用 IBInspectable
通过 IB 设置 控件 的属性非常的方便。
但是缺点也很明显,那就是有一些属性没有暴露在 IB 的设置面板中。这时候就要使用 @IBInspectable
在 IB 面板中添加这些没有的属性。
关于在 OC 中使用 IBInspectable
可以看一下我的 这篇文章
正文
在项目中最常遇到的情况是为 view 设置圆角、描边,以及为 文本控件 添加本地化字符串。
圆角、描边
先来看看设置圆角、描边
extension UIView {
@IBInspectable var cornerRadius: CGFloat {
get {
return layer.cornerRadius
}
set {
layer.cornerRadius = newValue
layer.masksToBounds = newValue > 0
}
}
@IBInspectable var borderWidth: CGFloat {
get {
return layer.borderWidth
}
set {
layer.borderWidth = newValue > 0 ? newValue : 0
}
}
@IBInspectable var borderColor: UIColor {
get {
return UIColor(cgColor: layer.borderColor!)
}
set {
layer.borderColor = newValue.cgColor
}
}
}
添加完成就可以在 IB 中设置 view 的这些属性了
运行效果
利用 @IBDesignable 在 IB 中实时显示 @IBInspectable 的样式
创建一个新的 class 继承 UIView
,并且使用 @IBDesignable
声明
import UIKit
@IBDesignable class IBDesignableView: UIView {
}
在 IB 中,选择 view 的 class 为 我们新建的 IBDesignableView
这样在 IB 调整属性时,这些属性的变化就会实时显示在 IB 中。
本地化字符串
本地化字符串的解决方法和上面的添加圆角一样
extension UILabel {
@IBInspectable var localizedKey: String? {
set {
guard let newValue = newValue else { return }
text = NSLocalizedString(newValue, comment: "")
}
get { return text }
}
}
extension UIButton {
@IBInspectable var localizedKey: String? {
set {
guard let newValue = newValue else { return }
setTitle(NSLocalizedString(newValue, comment: ""), for: .normal)
}
get { return titleLabel?.text }
}
}
extension UITextField {
@IBInspectable var localizedKey: String? {
set {
guard let newValue = newValue else { return }
placeholder = NSLocalizedString(newValue, comment: "")
}
get { return placeholder }
}
}
这样,在 IB 中我们就可以利用对应类型的 Localized Key 来直接设置本地化字符串了:
结语
IBInspectable
可以使用这些的类型
Int
CGFloat
Double
String
Bool
CGPoint
CGSize
CGRect
UIColor
UIImage
合理的使用@IBInspectable
能减少很多的模板代码,提高我们的开发效率。
参考
R.swift 的使用
介绍 R.swift 前,我们先看看 R.swift 能做什么
通常,我们是基于 字符串 来获取资源,例如:图片、xib、或者是 segue
let myImage = UIImage(named: "myImage")
let myViewController = R.storyboard.main.myViewController()
使用 R.swfit,我们可以这样写
let myImage = R.image.myImage()
let viewController = R.storyboard.main.myViewController()
R.swift 通过扫描你的各种基于字符串命名的资源,创建一个使用类型来获取资源。
在保证类型安全的同时,也在自动补全的帮助下节省了大量的时间。
导入 R.swift
R.swift 开源在 github 上。
这里是导入的视频教程
使用 CocoaPods 导入项目中
-
添加
pod 'R.swift'
到 Podfile 文件,然后运行pod install
-
添加一个
New Run Script Phase
-
将
Run Script
拖动到Check Pods Manifest.lock
的下面,并且添加脚本"$PODS_ROOT/R.swift/rswift" "$SRCROOT/项目名称"
-
Command+B
编译项目,在项目代码目录下,会生成一个R.generated.swift
的文件,将它拖如项目中注意:不要勾选
Copy items if needed
选项,因为每次编译都会生成新的R.generated.swift
文件,copy 的话,旧的R.generated.swift
将不会被覆盖。
tip: 可以在添加
.gitignore
添加一行*.generated.swift
忽略该文件,避免造成冲突
用法
导入完成后,就可以在使用 R.swift 了
关于 R.swift 的更多用法,可以 看这里。
Swift 的懒加载和计算型属性
懒加载
常规(简化)写法
懒加载的属性用 var
声明
lazy var name: String = {
return "BY"
}()
完整写法
lazy var name: String = { () -> String i
return "BY"
}()
本质是一个创建一个闭包 {}
并且在调用该属性时执行闭包 ()
。
如OC的懒加载不同的是 swift 懒加载闭包只调用一次,再次调用该属性时因为属性已经创建,不再执行闭包。
计算型属性
常规写法
var name: string {
return "BY"
}
完整写法
var name: string {
get {
return "BY"
}
}
计算型属性本质是重写了 get
方法,其类似一个无参有返回值函数,每次调用该属性都会执行 return
通常这样使用
struct Cuboid {
var width = 0.0, height = 0.0, depth = 0.0
var volume: Double {
return width * height * depth
}
}
let fourByFiveByTwo = Cuboid(width: 4.0, height: 5.0, depth: 2.0)
print("the volume of fourByFiveByTwo is \(fourByFiveByTwo.volume)")
// Prints "the volume of fourByFiveByTwo is 40.0"
两者对比
相同点
- 使用方法完全一致
- 都是用
var
声明
不同点
-
实现原理不同
懒加载是第一次调用属性时执行闭包进行赋值
计算型属性是重写
get
方法 -
调用
{}
的次数不同懒加载的闭包只在属性第一次调用时执行 计算型属性每次调用都要进入
{}
中,return
新的值
RVM 使用指南
RVM 常用的命令整理
RVM 是一个命令行工具,可以提供一个便捷的多版本 Ruby 环境的管理和切换。<https://rvm.io/>
我相信做为iOS开发者,对ruby的使用都是从安装 CocoaPods 开始的吧~
Note:这里所有的命令都是再用户权限下操作的,任何命令最好都不要用 sudo.
RVM 安装
$ gpg --keyserver hkp://keys.gnupg.net --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3
$ \curl -sSL https://get.rvm.io | bash -s stable
$ source ~/.bashrc
$ source ~/.bash_profile
修改 RVM 的 Ruby 安装源到 Ruby China 的 Ruby 镜像服务器,这样能提高安装速度
$ echo "ruby_url=https://cache.ruby-china.org/pub/ruby" > ~/.rvm/user/db
Ruby版本的安装与切换
列出已知的 Ruby 版本
rvm list known
安装一个 Ruby 版本
rvm install 2.2.0 --disable-binary
切换 Ruby 版本
rvm use 2.2.0
如果想设置为默认版本,这样一来以后新打开的控制台默认的 Ruby 就是这个版本
rvm use 2.2.0 --default
查询已经安装的ruby
rvm list
卸载一个已安装版本
rvm remove 1.8.7
参考:
<https://ruby-china.org/wiki/rvm-guide>
iOS自动打包
利用xcode的命令行工具
xcdeobulid
进行项目的编译打包,生成ipa包,并上传到fir
现在网上的自动打包教程几乎都还是xcodebuild + xcrun
的方式先生成.app
包 再生成.ipa
包,结果弄了一整天硬是没成功~
后来发现PackageApplication is deprecated
,悲剧。然后手动压缩的 .ipa
包因为签名问题无法装到手机上。
后来用了archive + -exportArchive
终于可以了~
正文
Xcodebuild
xcodebuild 的使用可以用 man xcodebuild
查看。
查看项目详情
# cd 项目主目录
xcodebuild -list
输出项目的信息
Information about project "StackGameSceneKit":
Targets:
StackGameSceneKit
StackGameSceneKitTests
Build Configurations:
Debug
Release
If no build configuration is specified and -scheme is not passed then "Release" is used.
Schemes:
StackGameSceneKit
要留意 Configurations
,Schemes
这两个属性。
自动打包流程
生成 archive
生成archive的命令是 xcodebuild archive
xcodebuild archive -workspace ${project_name}.xcworkspace \
-scheme ${scheme_name} \
-configuration ${build_configuration} \
-archivePath ${export_archive_path}
-
参数一:项目类型,,如果是混合项目 workspace 就用
-workspace
,如果是 project 就用-project
-
-scheme
:项目名,上面xcodebuild -list
中的Schemes
-
-configuration
:编译类型,在configuration
选择,Debug
或者Release
-
-archivePath
:生成 archive 包的路径,需要精确到xx/xx.archive
首先需要创建一个AdHocExportOptions.plist
文件
导出ipa包
导出.ipa
包经常会出现错误,在ruby2.4.0版本中会报错,所以请使用其他版本的ruby,最初的原因是使用了 ruby2.4.0 进行编译时出现的错误。
解决方法是低版本的 ruby 进行编译,如使用系统版本:rvm use system
。后面升级macOS系统(10.12.5)后发现 ruby2.4.0 能成功 导出ipa包了。
导出ipa包使用命令:xcodebuild -exportArchive
xcodebuild -exportArchive \
-archivePath ${export_archive_path} \
-exportPath ${export_ipa_path} \
-exportOptionsPlist ${ExportOptionsPlistPath}
archivePath
:上面生成 archive 的路径-exportPath
:导出 ipa包 的路径exportOptionsPlist
:导出 ipa包 类型,需要指定格式的plist
文件,分别是AppStroe
、AdHoc
、Enterprise
,如下图
选择这三个类别需要分别创建三个plist
文件:
-
AdHocExportOptionsPlist.plist
-
AppStoreExportOptionsPlist.plist
-
EnterpriseExportOptionsPlist.plist
上传到 Fir
将项目上传到 Fir
下载 fir 命令行工具
gem install fir-cli
获取 fir 的 API Token(右上角)
上传
fir publish "ipa_Path" -T "firApiToken"
自动打包脚本
再次提醒,请不要使用 ruby 2.4.0 运行该脚本!,若在 ruby 2.4.0 下编译失败,请切换低版本的ruby。
切换完毕记得重新安装 fir 命令行工具。
脚本我fork了 jkpang 的脚本进行修改,添加了自动上传到 fir 的功能。
使用方法在Github上有详细介绍。
GitHub:<https://github.com/qiubaiying/iOSAutoArchiveScript>
利用 自定义终端指令 简化打包过程
以zsh为例:
open ~/.zshrc
添加自定义命令 cd + sh
alias mybuild='cd 项目地址/iOSAutoArchiveScript/ && sh 项目地址/iOSAutoArchiveScript/iOSAutoArchiveScript.sh'
这样打开终端输入mybuild
,就可以轻松实现一键打包上传了