两种设计模式
每次在视图中读取数据时,都会在那个视图中创建一个依赖
每次数据发生变化时,视图也会发生变化
在视图层级中个,读取的每一条数据,都只有一个数据源
例如,在同级视图中,用同一个数据,你的实现是这样的:
03:40
这种实现会在不同的View中维护同一个数据来源,这一个过程很容易造成数据同步错误导致的BUG。不妨改成这样来实现:
4:10
@State
1
@State var isPlaying: Bool = false
@State告诉系统
isPlaying
是一个可以变化的值,视图也会随之改变9:08
当你声明一个
@State
变量时,SwiftUI框架会为之分配永久存储,SwiftUI可以在变量发生变化时,重新渲染其关联的视图和其所有的SubvIew(只会重新渲染发生改变的地方)11:02
在SwiftUI中,数据流向是单一向的
@Binding
保持不同视图的数据同步
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24struct PlayerView: View {
let episode: Episode
@State private var isPlaying: Bool = false
var body: some View {
VStack {
Text(episode.title).foregroundColor(isPlaying ?.white : .gray)
Text(episode.showTitle).font(.caption).foregroundColor(.gray)
PlayButton(isPlaying: $isPlaying)
}
}
}
struct PlayButton: View {
// 这里的isPlaying和👆的是同一个,所以无需同步数据
@Binding var isPlaying: Bool
var body: some View {
Button(action: {
self.isPlaying.toggle()
}) {
Image(systemName: isPlaying ? "pause.circle" : "play.circle")
}
}
}Publisher
19:45
在SwiftUI中,使用Publisher来表述这些例如Timer和通知的外部事件。
1
2
3
4// 接受来自currentTimePublisher的事件
.onReceive(PodcastPlayer.currentTimePublisher, perform: { newCurrentTime in
self.currentTime = newCurrentTime
})BindableObject
1
2
3
4
5
6
7
8
9
10
11
12
13class PodcastPlayerStore: BindableObject {
var didChange = PassthroughSubject<Void, Never>()
// ...
func advance() {
currentEpisode = nextEpisode
currentTime = 0.0
didChange.send()
}
}PassthroughSubject是一个Publisher,SwiftUI会订阅这个Publisher,来更新视图层级
@ObjectBinding
当使用
@ObjectBinding
声明模型时,SwiftUI会识别出这个属性,并给它设置依赖,一旦模型发生变化,框架就会自动明白什么时候刷新视图。1
2
3
4
5
6struct MyView: View {
@ObjectBinding var model: MyModelObject
// ...
}
MyView(model: modelInstance)26:08
所有声明@ObjectBindng模型的视图,都会自动订阅模型,并在模型发生改变时,刷新视图。(即:依赖自动追踪)
创建一个间接依赖
上面声明的依赖关系,属于视图直接依赖,接下来看看如何生成一个视图的间接依赖
27:01
用SwiftUI实现一个精美的App
SwiftUI中的一切都是视图。
SwiftUI布局基础
1 | struct ContentView: View { |
这段代码包含三个视图:
视图等级底部的文本(图中Hello World)
内容视图(和文本的布局一致,即图中Hello World四周白线内)
根视图(屏幕Size - 安全区域)
如果想将根视图扩展到安全区,可以使用
edgesIgnoringSafeArea(.all)
修饰器
当然,文本和文本的内容视图,我们通常当做同一个来操作
在SwiftUI中,不能给子视图强制规定一个尺寸,而是应该有父视图决定
布局步骤
- 父视图提供给子视图一个Size
- 子视图决定自身的Size(子视图也许不能完全使用父视图的Size)
- 父视图将子视图放在其坐标系中
- SwiftUI会让视图坐标像最接近的像素值取整
例一:查看一段代码的布局:
1 | var body: some View { |
在设置background
、padding
修饰器时,会在Text视图和根视图中间插入对应的背景视图和边距视图
例二:图片的原尺寸为20x20,我们希望1.5倍尺寸展示图片
1 | struct ContentView: View { |
做法:
1 | .frame(width: 30, height: 30) |
效果:图片尺寸不会发生变化,但是在图片周围会插入一个30x30尺寸的Frame视图
在SwiftUI中frame并不是一个重要的布局元素,它其实只是一个View。
例三:
1 | // 子视图必须平等竞争一个空间 |
设置文字底基线对齐
设置图片的底基线
例四:让不同容器中的视图对齐
自定义对齐方式
1
2
3
4
5
6
7
8
9extension VerticalAlignment {
private enum MidStarAndTitle : AlignmentID {
// 告诉SwiftUI如何计算默认值
static func defaultValue(in d: ViewDimensions) -> CGFloat {
return d[.bottom]
}
}
static let midStarAndTitle = VerticalAlignment(MidStarAndTitle.self)
}设置文字的基线
SwiftUI绘图
SwiftUI默认提供了多种样式的图形,比如圆形,胶囊和椭圆
实现渐变色
角渐变色
使用角渐变色填充圆
使用渐变色填充圆环
实现复杂图形绘制
完整代码可参见:官方Demo
总体步骤主要包括:
- 创建单个楔形的数据模型
1
2
3
4
5
6
7
8
9
10
11class Ring: ObservableObject {
/// A single wedge within a chart ring.
struct Wedge: Equatable {
/// 弧度值(所有楔形的弧度值之合最大为2π,即360°)
var width: Double
/// 横轴深度比例 [0,1]. (用来计算楔形的长度)
var depth: Double
/// 颜色值
var hue: Double
}
}- 绘制单个子图形
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18struct WedgeShape: Shape {
func path(in rect: CGRect) -> Path {
// WedgeGeometry是用来计算绘制信息的类,详细代码见Demo。
let points = WedgeGeometry(wedge, in: rect)
var path = Path()
path.addArc(center: points.center, radius: points.innerRadius,
startAngle: .radians(wedge.start), endAngle: .radians(wedge.end),
clockwise: false)
path.addLine(to: points[.bottomTrailing])
path.addArc(center: points.center, radius: points.outerRadius,
startAngle: .radians(wedge.end), endAngle: .radians(wedge.start),
clockwise: true)
path.closeSubpath()
return path
}
// ···
}- 用ZStack组装所有的楔形
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18let wedges = ZStack {
ForEach(ring.wedgeIDs, id: \.self) { wedgeID in
WedgeView(wedge: self.ring.wedges[wedgeID]!)
// use a custom transition for insertions and deletions.
.transition(.scaleAndFade)
// remove wedges when they're tapped.
.onTapGesture {
withAnimation(.spring()) {
self.ring.removeWedge(id: wedgeID)
}
}
}
// 如果不加这个Spacer(),会使Mac程序,在没添加任何楔形时,APP尺寸为0。
Spacer()
}为了更好的理解这个工程,你还需要一些知识:
如何使用
Animatable
自定义复杂的动画假如我们想实现一个简单的SwiftUI动画,比如点击Button按钮渐变消失,我们可以这样来实现:
1
2
3
4
5
6
7
8
9@State private var hidden = false
var body: some View {
Button("Tap Me") {
self.hidden = true
}
.opacity(hidden ? 0 : 1)
.animation(.easeInOut(duration: 2))
}在这个例子中,我们使用
@State
修饰变量hidden
,当hidden
的值发生变化时,SwiftUI会自动为我们处理渐变动画。而SwiftUI能够为我们自动执行动画的前提是:SwiftUI已经知道要如果展示该动画效果,那什么情况下,SwiftUI不知道要如何展示动画呢?比如:我们通过下面代码绘制出了多角形。
1
Shape(sides: 3)
该方法支持传入不同的值,生成不同的多边形。如果我们希望从三边形变成四边型,那么可以写如下代码:
1
2
3Shape(sides: isSquare ? 4 : 3)
.stroke(Color.blue, lineWidth: 3)
.animation(.easeInOut(duration: duration))但是运行代码后发现,这段代码并无动画过渡效果。这是因为在执行动画的过程中,SwiftUI会从起始状态到终止状态分成不同的阶段来绘制,像
opacity
从0-1,可能会分成0,0.1,0.2,0.3,···,0.9,1.0,SwiftUI依次进行绘制,从而展示过渡状态。同理,从三角形变到四边形,SwiftUI也需要绘制中间状态,但SwiftUI并不知道该如何绘制3.5边形。这时就需要我们自己来告诉SwiftUI该如何绘制了。
animatableData
是Animatable
协议中,唯一需要实现的方法,通过这个方法来告诉SwiftUI需要监听哪些属性的变化。1
2
3
4
5// 代表需要监听的值为Float类型
var animatableData: Float {
get { //··· }
set { //··· }
}然而并不是所有类型的属性都能够被SwiftUI所监听,只有遵循
VectorArithmetic
协议的对象AnimatablePair
,CGFloat
,Double
,EmptyAnimatableData
andFloat
才能被SwiftUI监听。要实现Demo中楔形图片变换的效果,需要监听的值有
start
,end
,depth
和hue
这四个值。animatableData
属性的返回值也应该包含这四个值。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15extension Ring.Wedge: Animatable {
typealias AnimatableData = AnimatablePair<AnimatablePair<Double, Double>, AnimatablePair<Double, Double>>
var animatableData: AnimatableData {
get {
.init(.init(start, end), .init(depth, hue))
}
set {
start = newValue.first.first
end = newValue.first.second
depth = newValue.second.first
hue = newValue.second.second
}
}
}接下来在这四个属性发生变化时,SwiftUI会通过函数
func path(in rect: CGRect) -> Path {}
重新绘制,这样就可以展示动画的过渡效果了
一些琐碎的知识点
使用
drawingGroup()
提高复杂UI的渲染效率以往每创建一个楔形,都是一个单独的View,当楔形数量非常多时,再加上每个View都在执行动画,非常耗费性能。在SwiftUI中,可以通过
drawingGroup()
将相同类型的View通过Metal绘制在一张画布上,从而减少渲染耗费的性能,避免卡顿。使用
Equatable
防止视图的新值和旧值相同时,更新子视图1
2
3struct Wedge: Equatable {
// ···
}使用
PassthroughSubject
通知SwiftUI值发生变化PassthroughSubject
- 使用
PassthroughSubject
通知绑定的属性的视图,属性发生变化,需要重新绘制。
1
2
3
4
5
6
7let objectWillChange = PassthroughSubject<Void, Never>()
private(set) var wedgeIDs = [Int]() {
willSet {
objectWillChange.send()
}
}- 在
contentView
中监听了Ring
模型
1
@EnvironmentObject var ring: Ring
因此在Ring模型的
wedgeIDs
发生变化时,会发出通知告知contentView
使用其绘制的地方,需要重新绘制- 使用
CurrentValueSubject
我们常用的
@Published
属性包装器,实际上就是一种CurrentValueSubject
简单来说:
PassthroughSubject
用于表示事件。CurrentValueSubject
用于表示状态。用现实世界的案例进行类比。PassthroughSubject = 门铃按钮,当有人按门时,只有在你在家时才会收到通知。CurrentValueSubject = 电灯开关,当你在外面时,有人打开了您家中的灯。你回到家,你知道有人打开了它们。
参考:
https://stackoverflow.com/questions/60482737/what-is-passthroughsubject-currentvaluesubject
汇编语言学习笔记(九)
4.1 一个源程序从写出到执行的过程
- 一个汇编语言程序从写出到最终执行的简要过程:
- 编写
- 编译连接
- 执行
4.2 源程序
汇编指令
有对应机器码的指令,可以被编译为机器指令,最终为CPU所执行
伪指令
没有对应的机器码的指令,最终不被CPU所执行。
伪指令是由编译器来执行的指令,编译器根据伪指令来进行相关的编译工作。
定义一个段
segment和ends是一对成对使用的伪指令,这是在写可被编译器编译的汇编程序时,必须要用到的一对伪指令
- segment和ends的功能是定义一个段,segment说明一个段开始,ends说明一个段结束
一个汇编程序是由多个段组成的,这些段被用来存放代码、数据或当做栈空间来使用。
- 一个有意义的程序至少有一个段
寄存器与段的关联假设
assume:含义为“假设”
- 它假设某一段寄存器和程序中的某一个用segment…ends定义的段相关联。
- 通过assume说明这种关联,在需要的情况下,编译程序可以将段寄存器和某一个具体的段相联系。
汇编源程序
- 伪指令(编译器处理)
- 汇编指令(编译为机器码)
程序:源程序中最终由计算机执行,处理的指令或数据。
手把手教你用SwiftUI写程序
学习SwifUI的最好方式是,用SwiftUI编写一个程序
文章来源:Introducing SwiftUI: Building Your First App
注:视频中使用的部分API,已经在后续版本中废弃,本文代码根据最新版API进行了调整,确保 Demo工程 能够运行
一:编写第一个SwiftUI程序
- 创建SwiftUI工程
左侧是代码区
右侧是Canvas
编写代码时,右侧的Canvas能够实时显示出代码的UI预览效果
编写UI布局代码
通过拖拽增加UI控件
- VStack:SwiftUI常用的一种布局元素,可以用来垂直地叠加视图
- HStack:水平叠加视图
cmd+点击VStack,插入HStack
1
2
3
4
5
6HStack {
VStack {
Text("Rooms")
Text("20 people")
}
}在文字左侧增加一个图片
1
2
3
4
5
6
7
8
9HStack {
// `photo`是系统自带资源库中的图片
Image(systemName: "photo")
VStack {
Text("Rooms")
Text("20 people")
}
}在Canvas中将VStack修改为左对齐
设置Text的字号
1
2
3
4
5
6
7
8
9HStack {
Image(systemName: "photo")
VStack(alignment: .leading) {
Text("Rooms")
Text("20 people")
.font(.subheadline)
}
}我们称
.font(.subheadline)
为修饰器(modifier),用来自定义视图的外观或行为设置Text的颜色为
secondary
1
2
3
4
5
6
7
8
9
10HStack {
Image(systemName: "photo")
VStack(alignment: .leading) {
Text("Rooms")
Text("20 people")
.font(.subheadline)
.foregroundColor(.secondary)
}
}将HStack替换为List
设置数据源
增加Room模型
在SwiftUI中,需要让模型遵循
Identifiable
协议,实现id
属性
在ContentView中使用Room数据来展示UI
当代码发生重大改变(比如增加属性),Xcode会暂停预览,直到我们做好重新更新的准备(点击Resum按钮)
丰富UI内容
设置图片圆角
通过拖拽Modifier库来实现
设置Navigation、NavigationTitle以及给每个单元格设置跳转
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17NavigationView {
List(rooms) { room in
NavigationLink(destination: Text(room.name)) {
Image(systemName: "photo")
.cornerRadius(8.0)
VStack(alignment: .leading) {
Text(room.name)
Text("\(room.capacity) people")
.font(.subheadline)
.foregroundColor(.secondary)
}
}
}
.navigationTitle(Text("Rooms"))
}
进入实时模式,查看效果
将子视图提成一个单独的视图
创建新的页面:RoomDetail
1
2
3
4
5
6
7
8
9
10
11
12
13
14struct RoomDetail: View {
let room: Room
var body: some View {
Image(room.imageName)
.resizable()
.aspectRatio(contentMode: .fit)
}
}
struct RoomDetail_Previews: PreviewProvider {
static var previews: some View {
RoomDetail(room: testData[0])
}
}设置
navigationBarTitle
1
2
3
4Image(room.imageName)
.resizable()
.aspectRatio(contentMode: .fit)
.navigationBarTitle(Text(room.name))此时页面不会更新,因为RoomDetail中缺乏
NavigationView
作为上下文在预览中添加
NavigationView
上下文1
2
3
4
5
6
7struct RoomDetail_Previews: PreviewProvider {
static var previews: some View {
NavigationView {
RoomDetail(room: testData[0])
}
}
}调整
navigationBarTitle
的展示模式
将RoomCell的跳转修改为前往RoomDetail页面
1
2
3
4
5
6
7
8
9struct RoomCell: View {
let room: Room
var body: some View {
NavigationLink(destination: RoomDetail(room: room)) {
// ···
}
}
}
二:Swift UI的工作方式
2.1 View
A View Defines a Piece of UI
在SwiftUI中,视图是一种遵守
View
协议的结构,而不是继承自基础类的类A View Defines its Dependencies
2.2 状态属性
@State
当SwiftUI看到一个带
@State
状态变量的视图时,它会以视图的名义为那个变量分配存储空间。- 绿色部分是APP的内存
- 紫色是SwiftUI所管理的内存
SwiftUI可以观察到
@State
变量合时被读写,同时SwiftUI知道zoom
是从body
中读取的,SwiftUI会在@State
变量发生更改时,使用新的状态值,刷新渲染。
例:实现在RoomDetail中,点击图片修改填充模式
1 | struct RoomDetail: View { |
2.3 事实来源
在SwiftUI中,UI可能因不同的数据,处于不同的状态,我们将这些用来绘制UI的数据称为“事实来源”,“事实来源“由状态变量
和模型
共同组成。
- 属性可以简单地分为:事实来源(Source of Truth)和衍生值(Derived Value)
zoomed
变量是一个事实来源,contentMode
衍生自它,当系统观察到zoomed
变量发生变化时,SwiftUI框架会请求新的body
,刷新渲染,重新生成一个新的宽高比视图,接下来覆盖contentMode
。
像RoomDetail中的,
room
属性,也是一个衍生值。
数据流原语(Data Flow Primitives)
关于数据流详细的介绍在:Data Flow Through SwiftUI WWDC 19
SwiftUI是数据驱动,而不是事件驱动
三:完善Rooms APP
增加动画
1
2
3
4
5.onTapGesture {
withAnimation {
self.zoomed.toggle()
}
}添加一个ZStack
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15ZStack(alignment: .topLeading) {
Image(room.imageName)
.resizable()
.aspectRatio(contentMode: zoomed ? .fit : .fill)
.navigationBarTitle(Text(room.name), displayMode: .inline)
.onTapGesture {
withAnimation {
self.zoomed.toggle()
}
}
Image(systemName: "video.fill")
.font(.title)
.padding(.all)
}固定图标的位置
1
2Image(room.imageName)
.frame(minWidth:0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)更多关于SwiftUI布局相关的内容 - > Buiding Custom Views with SwiftUI
同时预览多个View
1
2
3
4
5
6
7
8
9
10
11
12
13
14struct RoomDetail_Previews: PreviewProvider {
static var previews: some View {
Group {
NavigationView {
RoomDetail(room: testData[0])
}
NavigationView {
RoomDetail(room: testData[1])
}
}
}
}增加动效
- 给视频图标增加过渡动效
1
2
3
4
5
6if room.hasVideo && !zoomed {
Image(systemName: "video.fill")
.font(.title)
.padding(.all)
.transition(.move(edge: .leading))
}给图片的动效延长时间
1
2
3
4
5.onTapGesture {
withAnimation(.easeInOut(duration: 2)) {
self.zoomed.toggle()
}
}
支持动态增加
监测数据模型的改变,实时更新UI
创建RoomStore储存Room模型
1
2
3
4
5
6
7
8
9import SwiftUI
class RoomStore {
var rooms: [Room]
init(rooms: [Room] = []) {
self.rooms = rooms
}
}遵守
ObservableObject
协议1
2
3
4class RoomStore: ObservableObject {
@Published var rooms: [Room]
// ···
}声明
EnvironmentObject
类型变量1
@EnvironmentObject var store: RoomStore
传入
EnvironmentObject
类型变量1
2
3
4
5
6struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
.environmentObject(RoomStore(rooms: testData))
}
}列表中增加一个按钮
1
2
3
4
5
6
7
8
9
10
11List {
Button(action:{
}) {
Text("Add Room")
}
// ForEach为它的每个集合项都创建一个视图
ForEach(store.rooms) { room in
RoomCell(room: room)
}
}1
2
3
4
5
6
7Button(action:addRoom) {
Text("Add Room")
}
func addRoom() {
store.rooms.append(Room(name: "Hall 2", capacity: 2000))
}
修改List的样式
修改
listStyle
1
2
3
4
5
6
7NavigationView {
List {
// ···
}
.navigationBarTitle(Text("Rooms"))
.listStyle(GroupedListStyle())
}设置分组
1
2
3
4
5
6
7
8
9
10
11
12
13
14List {
Section {
Button(action:addRoom) {
Text("Add Room")
}
}
Section {
ForEach(store.rooms) { room in
RoomCell(room: room)
}
}
}
支持动态删除
1
2func delete(at offsets: IndexSet) {
store.rooms.remove(atOffsets: offsets)1
2
3
4ForEach(store.rooms) { room in
RoomCell(room: room)
}
.onDelete(perform: delete)设置NavigationBarItem
1
2
3
4
5
6
7NavigationView {
List {
}
.navigationBarItems(trailing: EditButton())
}支持列表重新排序
1
2
3func move(from source: IndexSet, to destination: Int) {
store.rooms.move(fromOffsets: source, toOffset: destination)
}1
2
3
4
5ForEach(store.rooms) { room in
RoomCell(room: room)
}
.onDelete(perform: delete)
.onMove(perform: move)
设置预览环境
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17Group {
ContentView()
.environmentObject(RoomStore(rooms: testData))
// 大字号环境
ContentView()
.environmentObject(RoomStore(rooms: testData))
.environment(\.sizeCategory, .extraExtraLarge)
// 深色模式
ContentView()
.environmentObject(RoomStore(rooms: testData))
.environment(\.colorScheme, .dark)
// 布局方向
ContentView()
.environmentObject(RoomStore(rooms: testData))
.environment(\.layoutDirection, .rightToLeft)
.environment(\.locale, Locale(identifier: "ar"))
}
总结
SwiftUI四个主要设计原则:
- Declarative
- Compositional
- Automatic
- Consistent
SwiftUI使用陈述性语法
在SwiftUI中,Xcode预览可以让我们浏览、编辑和调试APP,我们甚至不需要运行项目工程
汇编语言学习笔记(八)
3.10 栈段
我们可以将长度为N(N <= 64k)的一组地址连续、起始地址为16的倍数的内存单元,当作栈来用,从而定义了一个栈段。
问题:3.11
如果我们将10000H~1FFFFH这段空间当做栈段,初始状态是空的,此时,SS=1000H,SP=?
换位思考:
栈为空,相当于栈中唯一的元素出栈,出栈后,SP=SP+2。
- SP原来为FFFEH,加2后SP=0,所以,当栈为空的时候,SS=1000H,SP=0。
问题:3.12
一个栈段最大可以设为多少?
所以栈顶的变化范围是0~FFFFH,所以一个栈段的容量最大是64KB
(0
1H,容量为2, 0FFFF,容量为10000H,即64KB = 2^16)
- 数据段:段地址放在DS中
- 代码段:段地址放在CS中,第一条指令的偏移地址放在IP中。
- 栈段:段地址放在SS中,栈顶单元的偏移地址放在SP中
汇编语言学习笔记(七)
3.8 栈顶越界的问题
push越界,上溢出;pop越界,下溢出
栈空间之前的空间里很可能存放了具有其他用途的数据、代码
- 我们在编程时要自己操心栈顶越界的问题,要根据可能用到的最大栈空间,来安排栈的大小,防止入栈的数据太多而导致越界
- 执行出栈的操作时也要注意,以防止栈空的时候继续出栈而导致的越界
3.9 push、pop指令
push和pop指令是可以在寄存器和内存之间传送数据的。
栈空间当然也是内存空间的一部分,它只是一段可以以一种特殊方式进行访问的内存空间
push和pop指令的格式
一:
- push 寄存器:将一个寄存器中的数据入栈
- pop 寄存器:用一个寄存器接受出栈的数据
二:
- push 段寄存器:将一个段寄存器中的内容入栈
- pop 段寄存器:用一个段寄存器接受出栈的数据
三:
push 内存单元:将一个内存单元处的字入栈(栈操作都是以字为单位)
pop 内存单元:用一个内存单元来接收出栈的数据
push [0]
、pop [2]
指令执行时,CPU要知道内存单元的地址,可以在push、pop指令中给出内存单元的偏移地址,段地址在指令执行时,CPU从ds中取得。
问题:3.7
将10000H~1000FH这段空间当做栈,初始状态是空的,将AX、BX、DS中的数据入栈。
1
2
3
4
5
6
7 mov ax 1000H
mov ss, ax ; 设置栈的段地址,SS=1000H,不能直接向段寄存器SS送入数据
mov sp, 0010H ; 栈为空时,ss:sp在1000FH的下一位
push ax
push bx
push ds
问题:3.8
编程:
- 将10000H~1000FH 这段空间当作栈,初始状态是空的;
- 设置AX=001AH,BX=001BH;
- 将AX、BX中的数据入栈;
- 然后将AX、BX清零;
- 从栈中恢复AX、BX原来的内容。
类似于函数调用过程:main函数调用a函数,初始时先将main函数的寄存器入栈,进行完操作后恢复寄存器到初始状态,回到main函数执行下面的内容
1
2
3
4
5
6
7
8
9
10
11 mov ax, 1000H
mov ss, ax
mov sp, 1010H ;初始化栈顶
mov ax, 001AH
mov bx, 001BH
sub ax, ax ;将ax清零,也可以用赋值:mov ax, 0 异或: xor ax, ax
sub bx, bx ;sub ax,ax的机器码为2个字节
;mov ax,0 的机器码为3个字节
pop bx ;从栈中恢复ax,bx原来的数据,当前栈顶的内容是bx
pop ax ;中原来的内容
问题3.9
编程:
- 将10000H~1000FH这段空间当作栈,初始状态是空的
- 设置AX=002AH,BX=002BH
- 利用栈,交换AX和BX中的数据
1
2
3
4
5
6
7
8
9 mov ax, 1000H
mov ss, ax
mov sp, 1010H
mov ax, 002AH
mov bx, 002BH
push ax
push bx
pop ax
pop bx
问题3.10
我们如果要在10000H处写入字型数据2266H,可以用以下的代码完成:
1
2
3
4 mov ax, 1000H
mov ds, ax
mov ax, 2266H
mov [0], ax换一个写法:要求写入字型数据2266H
1
2
3
4
5 ______
______
______
mov ax, 2266H
push ax不能使用
mov 内存单元, 寄存器
这类指令答:
1
2
3 mov ax, 1000H
mov ss, ax
mov sp, 2 ; push 会让 sp = sp - 2
执行push、pop指令需要两步:
- 执行push时
- 先改变sp,后向SS:SP处传送
- SP=SP-2
- 向SS:SP指向的字单元中送入数据
- 先改变sp,后向SS:SP处传送
- 执行pop时
- 先读取SS:SP处的数据,后改变SP。
- 向SS:SP指向的字单元中读取数据
- SP=SP+2
- 先读取SS:SP处的数据,后改变SP。
push、pop等栈操作指令,修改的只是SP。也就是说,栈顶的变化范围最大为:0~FFFFH。
综述:
- 任意时刻,SS:SP指向栈顶元素
- 8086CPU只记录栈顶,栈空间的大小我们要自己管理
Untitled
一个浮点数引发的悲剧
不期而遇
某天我开开心心地写了一段代码:
1 | NSInteger a = 10; |
我预期c
值为-4,但是编译器却输出了一个:
![截屏2021-06-17 上午11.56.32](/Users/Joshsone/Library/Application Support/typora-user-images/截屏2021-06-17 上午11.56.32.png)
WTF?这是啥,因为这莫名的缘分,我展开了一段追根溯源的路程。
寻觅之旅
为了厘清这段神秘的数字到底从何而来,我将Xcode调整为Always Show Disassembly,来看看编译器到底做了些啥?
![截屏2021-06-17 下午12.05.44](/Users/Joshsone/Library/Application Support/typora-user-images/截屏2021-06-17 下午12.05.44.png)
这段汇编代码对应到执行完-4 - (a - b - 1) * 13
,但是还没有赋值给c
,寄存器rax
中存放的就是计算结果,我们打印来看下:
1 | (lldb) expression $rax |
可以看出rax
中存放的是无符号长整形,其二进制形式是:
1 | (lldb) p/t 18446744073709551612 |
可以看到,代码执行到这一步,已经出现了问题,我们期待结果是负数,那必然就是有符号整形,但是由于我们将b
声明为NSUInteger
,系统便将计算结果强转为了NSUInteger
。
另外提一句:有符号的-4,二进制格式也为0b1111111111111111111111111111111111111111111111111111111111111100
汇编语言学习笔记(六)
3.6栈
操作规则:LIFO(后进先出)
8086CPU提供的入栈和出栈的指令:
Push(入栈)
push ax:将寄存器ax中的数据送入栈中
Pop(出栈)
push ax:从栈顶取出数据送入ax。
8086CPU的入栈和出栈操作都是以字为单位进行的
CPU如何知道当前要执行的指令所在的位置?
寄存器CS和IP中存放着当前指令的段地址和偏移地址
执行push和pop的时候,如何知道哪个单元是栈顶单元?
8086CPU中,有两个寄存器:
- 段寄存器SS 存放栈顶的段地址
- 寄存器SP 存放栈顶的偏移地址
任何时刻,SS:SP指向栈顶元素
push ax时:
SP = SP - 2
将ax中的内容送入SS:SP指向的内存单元处,SS:SP此时指向新栈顶
将10000H~1000FH这段空间当做栈,初始状态栈是空的,此时,SS=1000H,SP=多少?
pop指令的执行过程
pop ax
- 将SS:SP指向的内存单元处的数据放入ax中;
- SP=SP+2,SS:SP指向当前栈顶下面的单元,以当前栈顶下面的单元为新的栈顶。
注意:
- 出栈后,SS:SP指向新的栈顶1000EH,pop操作前的栈顶元素,1000CH处的2266H依然存在,但是,它已不再栈中。
- 当再次执行push等入栈指令后,SS:SP移至1000CH,并在里面写入新的数据,它将被覆盖。
iOS中的Hang是什么?该如何治理?
什么是Hang?
试想这样的场景:当用户在你的APP中进行交互时,APP却没有及时的响应用户的操作。
这种体验可以被描述为延时、慢、卡顿,在Apple开发中,我们称这种无响应的表现为Hang。
(温馨提示:本片内容来源自WWDC21:Understand and eliminate hangs from your app)
想了解更多WWDC2021内容的小伙伴,可以阅读我以下文章,欢迎多多交流和指正
理解main runloop
用户的交互均在主线程的Runloop发生。当用户和APP交互,主线程会接受到这个事件,接下来处理这个事件,然后更新UI。
如果处理事件耗费的很长时间,就从用户交互到UI更新之间会发生延时(delay)。
Hang通常由什么引起?
主线程任务忙(Busy)
例如:过渡加载资源。当前页面中只需要展示前4个图片,但是却一次性加载了所有的图片。
例如:执行了与主线程不相关的任务。
再比如了使用次优的API。像绘制图片圆角有两种方法:
方法一:
方法二:
比较这两种方法,法一是CPU密集型操作,会消耗大量内存。法二使用GPU进行绘制,更快更及时。
主线程阻塞(Blocked)
可能导致主线程阻塞的操作,例如:
同步请求网络,并等待数据返回
文件IO等访问系统资源的行为
数据库操作
锁
如何诊断Hang
使用Instruments,排查线下的问题
Time Profile
使用MetricKit,检测线上的情况
如果治理Hang?
核心目标:减轻主线程的工作
优化必需在主线程中执行的任务
使用缓存
例如:由其他线程负责保存和更新缓存,主线程只负责读
使用Notification
在主线程中发送通知,其他线程接受通知并异步处理数据
移除主线程不必要的任务
通常来说,为UI提供关键信息的任务,应该在主线程执行。此外,所有的View和View Controller必须在主线程创建、修改和销毁。
而计算任务可以在其他线程执行,然后在主线程同步UI。一些不重要的维护、非时间敏感的任务应该在其他线程异步执行。
例如:网络请求
使用GCD异步处理任务
相关资料
Diagnose power and performance regressions in your app