跳到主要内容

24 篇博文 含有标签「iOS」

查看所有标签

Xcode命令行工具管理

· 阅读需 1 分钟
BY

安装

xcode-select --install

Xcode版本切换

显示当前使用的xocde版本

$ xcode-select --print-path

选择Xcode中的默认版本

$ sudo xcode-select -switch /Applications/Xcode.app

从一道网易面试题浅谈 Tagged Pointer

· 阅读需 3 分钟
BY

前言

这篇博客九月就想写了,因为赶项目拖了到现在,抓住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_asynctarget属性进行赋值,就会导致 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 专门用来存储小的对象,例如 NSNumberNSDate(后来可以存储小字符串)
  • Tagged Pointer 指针的值不再是地址了,而是真正的值。所以,实际上它不再是一个对象了,它只是一个披着对象皮的普通变量而已。
  • 它的内存并不存储在堆中,也不需要 malloc 和 free,所以拥有极快的读取和创建速度。

参考:

GCD 在 Swift 中的用法

· 阅读需 2 分钟
BY

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 新特性

· 阅读需 3 分钟
BY

private 权限扩大

在 Swift 4 中,extension 可以读取 private 变量了。

Swift 3 中,如果将主体函数的变量定义为 private,则其 extension 无法读取此变量,必须将其改为 filePrivate 才可以。

单向区间

单向区间是一个新的类型,主要分两种:确定上限和确定下限的区间。直接用字面量定义大概可以写成 …62…

例如

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 实例。SubstringString 用法相同, 因为子串和原字符串共享内存,所以对子串的操作快速而且高效。

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 新增加的数据类型。

定义两个结构体 PersonBook

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 构建版本不显示

· 阅读需 1 分钟
BY

前言

今天新项目上架,在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

· 阅读需 3 分钟
BY

本文首次发布于 BY Blog, 作者 @柏荧(BY) ,转载请保留原文链接.

通过 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 的使用

· 阅读需 2 分钟
BY

本文首次发布于 BY Blog, 作者 @柏荧(BY) ,转载请保留原文链接.

介绍 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 导入项目中

  1. 添加 pod 'R.swift'到 Podfile 文件,然后运行 pod install

  2. 添加一个 New Run Script Phase

  3. Run Script 拖动到 Check Pods Manifest.lock 的下面,并且添加脚本 "$PODS_ROOT/R.swift/rswift" "$SRCROOT/项目名称"

  4. 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 的懒加载和计算型属性

· 阅读需 2 分钟
BY

本文首次发布于 BY Blog, 作者 @柏荧(BY) ,转载请保留原文链接.

懒加载

常规(简化)写法

懒加载的属性用 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 使用指南

· 阅读需 1 分钟
BY

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自动打包

· 阅读需 3 分钟
BY

利用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

要留意 ConfigurationsSchemes这两个属性。

自动打包流程

生成 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文件,分别是AppStroeAdHocEnterprise,如下图

选择这三个类别需要分别创建三个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,就可以轻松实现一键打包上传了

本文首次发布于 BY Blog, 作者 @柏荧(BY) ,转载请保留原文链接.