股票场内基金交易,没时间盯盘?

   
使用优财助手电脑客户端记录下您的股票买入卖出数据,能帮您时刻盯盘,会根据您记录的未售出买入价计算上涨或下跌幅度,及时弹框通知您。想知道如何使用?快点击左方视频观看了解吧~~下载地址:http://youcaizhushou.com

内存相关的基本概念

Objective-C(以下简称为 OC)使用引用计数来管理内存。简单的来说,引用计数的核心思想就是:当一个对象生成或者被使用时,它的引用计数(retainCount)就会加 1,而当这个对象被解除一次引用或者是使用 release 释放之后,其引用计数就会减 1。当计数降为 0 时,就会释放内存空间来销毁这个对象。为了掌握 iOS 的内存管理机制,我们需要首先了解内存相关的基本概念。

首先介绍一下操作系统中对于内存使用的分类,iOS 系统对于内存的使用分为栈区、堆区、全局区、常量区、代码区五个区域。其中全局区、常量区、代码区存储着全局变量、静态变量、字符串常量以及函数体的二进制代码。因此这三个区域占用的内存空间很小,而且其存储操作都是系统自动分配的。所以这三个区域不需要我们深入理解,了解即可。

而我们主要使用和需要深入了解的就是堆区和栈区。值得注意的是,在数据结构中也有栈和堆这两种结构,但是这里的栈、堆和我们在数据结构中学习的栈和堆是不同的,但是它们之间又具有着一定的联系。

  • 数据结构中的栈是一种操作受限的线形表,它限定着仅在表尾才能进行插入或者删除操作,满足后进先出的特点。如图中所示,就像一个单行线上的火车一样:

数据结构 栈1340013975_1616

  • 而内存中的栈区是指内存中的一片区域,它在进行数据存储中也同样具有着后进先出的相似性。其中存储的内容也是的连续且有序的排列。
  • 数据结构中的堆是一棵特殊的完全二叉树,它分为大顶堆和小顶堆,大顶堆(小顶堆)是一棵每一个节点的键值(Key)都不小于(不大于)其孩子(如果存在)的键值的树。它的定义是利用递归进行的,堆中任一子树亦是堆,而且都满足同样的性质。如下图所示分别为大顶堆和小顶堆。它的根节点的取值就是整个堆中的最大(或最小)值,因此这种结构常用于排序或者取得最值的操作。

数据结构 堆Snip20160131_23

  • 而内存中的堆区指的是内存中的一片区域,它的操作与数据结构的堆差异就很大了。如下图所示

堆区的操作示意图

它的操作方法是『操作系统中有一个存放堆内空闲存储块地址和大小的链表,当程序员申请空间的时候,系统就会遍历整个链表,找到第一个比申请空间大的空闲块节点,系统会将该空闲块从空闲链表中删除,分配给程序,由于申请的空间不一定与找到的空闲块大小相同,多出来剩余的空闲区会被系统重新添加到空闲链表中。当我需要删除对象时,便会根据指针纪录的地址,将这一块区域重新加入到链表中』。

下面进行详细介绍:

栈(stack)

栈区(stack)— 由编译器自动分配释放,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。

当程序执行某个方法(或者函数)时,会从内存中栈(stack)的区域分配出一块内存空间,这个内存空间被称之为帧(frame)用来储存程序在这个方法内声明的变量的值。当应用启动并运行 main 函数时,它的帧会被存在栈的底部。如图所示:

Snip20160130_8

当 main 继续调用另外一个方法(或函数)时,这个方法(或函数)的帧又会继续被压入栈的顶部(上一个帧的上面)。被调用的方法还可以再调用其他方法,以此类推,会有帧继续被压入栈顶,最后在栈中形成一个塔状的帧序列。如图所示:

Snip20160130_9

在被调用的方法(或函数)结束后,程序会将其帧从栈顶释放,遵循从栈顶到栈底的顺序。

堆(heap)

堆区(heap)-一般由程序员分配释放,若程序员不释放,程序结束时可能由 OS 回收。它包含了大量无序的活动对象,需要通过指针(这些指针储存在栈中)来保存这些对象在堆中的地址。

当应用向某个类发送 alloc 消息时,系统便会从堆中分配出一块内存来存放对象的相应实例变量。iOS 应用在启动和运行时会持续创建需要的对象,但是可供应用支配的堆空间是有限的,而且 iOS 设备的内存也有限。因此当不再需要某个对象时,就要将其使用 dealloc 方法释放掉,使其所占用的内存还给堆。由此可见堆区的内存分配是动态的。使用十分灵活,但也可能因为错误地分配或释放内存而出错。

举例说明

我们来通过一个例子来演示内存的储存过程吧。首先我们在 Xcode 中新建一个 Command Line Tool 工程,然后手动添加一个类,类名为 Person,并在 Person.h 中加入:

然后我们回到 main.m 中,输入:

在上述的代码中,我们做了这么几件事:

  1. 使用 alloc 方法建立了三个 Person 类对象,并分别用 person1、person2、person3 这三个指针来指向这三个对象。
  2. 分别输出这三个对象在内存中储存的地址。
  3. 分别输出指向这三个对象的指针变量在内存中的地址,注意“&”表示的是取址符号。
  4. 输出这三个指针的占内存空间的大小,sizeof() 用来返回一个类型所占的内存字节数。这里我们返回的是三个指针所占的内存字节数,在 32 位操作系统中,指针占用的内存字节数恒为 4,而在 64 位操作系统中指针占用的内存字节数恒为 8。我现在使用的就是 64 位操作系统,因此应该都会输出 8。
    接着,让我们运行三次来看一看输出的结果:

运行结果1
运行结果2
运行结果3

我们可以发现一些规律:

  • 指针指向的内存空间的地址的长度与指针变量自身在内存空间的地址的长度是不同的,比如 0x100205870 和 0x7fff5fbff798。
  • 指针指向的内存空间的地址随着程序每一次的运行是不断变化着的。
  • 而指针变量自身在内存的地址恒为 0x7fff5fbff798、0x7fff5fbff790、0x7fff5fbff788。
  • 而三个指针变量自身在内存的地址相差为 8,并且呈现出递减的规律。

这是因为:

  • 当我们使用 alloc 方法创建一个 Person 对象时,内存中会在堆区中开辟一片空间来存储 Person 对象,同时在栈区建立一个指针 person1 来指向这个对象在堆区的空间,如图所示:

栈堆的举例Snip20160201_24

于是 此前 输出的不同长度的地址,实际上是一个位于堆区,一个位于栈区。

  • 堆区的内存分配是动态的,程序的每一次运行都会有一个动态过程:

    • 从系统链表检索空闲块节点,将空闲块节点从链表中删除。
    • 被删除空闲块节点分为两个部分,一部分用于放置 Person 对象,另一部分重新添加到链表中。
    • 在释放对象是会把对象占用的堆区内存重新加入链表中。

    于是我们看到输出的指针指向的内存空间的地址是随着每一次程序运行而不断变化的。

  • 栈区的内存是系统自动申请的而且是有序的。我们在申请栈空间时就只能在栈的顶部进行申请。因此当我们运行三次程序时,系统会在同一个栈区同一个地址进行申请 / 销毁的操作。输出的地址也就都相同了。
  • person1、person2、person3 这三个指针变量的地址相差为 8,而且是递减的,这是因为在 64 位操作系统中指针占有内存恒为 8 个字节(注意:由于我使用的是 %p 来输出,因此输出的结果为 16 进制,0x7fff5fbff790 与 0x7fff5fbff788 相隔是 8 个字节而不是 2)。另外由于栈是向低地址扩展,所以新分配的地址会越来越小。

OC 的内存管理机制

具体实现介绍

其实在OC中内存的管理是依赖对象引用计数器来进行的:在 OC 中每个对象内部都有一个与之对应的整数(retainCount),叫『引用计数器』。它属于 NSUInteger 类型,我们也可以通过输出 “对象名.retainCount”来查看这个对象当时的引用计数。

我们来打个比方,引用计数就相当于表示现在有几个『人』在使用(拥有)这个对象,第一个人生成这个对象时这个对象的 retainCount 就会初始化为 1,之后再有每个人使用那么计数就都会加 1,一个人不使用(拥有)这个对象了便会使对象的计数减 1,最好当计数为 0 时,就代表没有人使用(拥有)这个对象,那么这个对象就可以在内存中释放掉了。

Snip20160130_11

如上图,这个对象正在被 A、B、C 三者使用(拥有),那么它的引用计数就是 3。

实质上来说呢,一个对象被使用(拥有)的实际含义是指『有一个指针指向这个对象的堆区的空间』,即指针变量暗含了对其所指对象的所有权(ownership)

  • 当某个方法(或函数)有一个指向某个对象的局部变量时,可以称该变量拥有(own)变量所指的对象.
  • 当某个对象有一个指向其他对象的实例变量时,可以称该对象拥有该实例变量所指向的对象。

对象所有权的概念可以帮助我们最终决定是否应该释放某个对象。应当注意以下几点:

  • 如果某个对象失去了拥有者(变成没有拥有者)那么应该将其释放掉,否则没有拥有者的对象会被孤立而程序找不到,并且始终占用着一块内存,导致内存泄漏
  • 如果某个对象有至少一个拥有者,那么就必须保留不能释放,否则的话其他对象或者方法仍然有指向这个对象的指针沦为野指针(空指针)。这称之为过早释放,这是十分危险的,因为当野指针指向的内存区域再次被某个新的对象使用时,野指针上的操作便会破坏这个新对象造成文件丢失或者崩溃。野指针一般报错形式如下:

Snip20160131_19

一个对象的 retainCount 变化随着其被使用具有以下几个阶段:

  • 当一个对象在创建之后它的引用计数器为 1,当调用这个对象的 alloc、retain、new、copy 方法之后引用计数器自动在原来的基础上加 1(OC 中调用一个对象的方法就是给这个对象发送一个消息)。其中,alloc 用来表示第一次创建使用对象,只能进行一次。retain 表示对现有对象进行使用,可以调用多次。

  • 当调用这个对象的 release 或者 autorelease 方法之后它的引用计数器减1。其中这样的情况包括:

    • 指向该对象的指针变量指向别的对象或者设置为 nil
    • 当程序释放对象的某个拥有者
    • 当从 collection(比如数组)类中删除对象
  • 如果一个对象的引用计数器为 0,则系统会自动调用这个对象的 dealloc 方法来从内存中销毁这个对象。

值得注意的是,简单的赋值,比如:

并不会表示『拥有这个对象』,想要『拥有这个对象』我们还需要发送『创建』或者 retain 消息给该对象,我们会在后面的 MRC 介绍中进行系统的阐述。

内存管理举例

我们新建一个 OS X 中的 Command Line Tool 工程,然后新建一个类,类名为 Person。为了显示 Person 类对象的销毁过程,我们需要在 Person.m 中覆写 dealloc 方法,如下:

main 函数的代码如下所示

运行结果为:

Snip20160130_13

总结

  • 适用对象:OC 中的内存管理只针对所有 OC 中的对象,而基本数据类型(int,long,double,float 等等)不需要我们管理内存。

  • 目的:

    • 不要释放或者覆盖还在使用的内存,这会引起程序崩溃;
    • 释放不再使用的内存,防止内存泄露。iOS 程序的内存资源是宝贵的。
  • 核心思想:当一个对象生成或者被引用时,它的引用计数(retainCount)就会+1,而当这个对象被解除一次引用或者是实用 release 释放之后,其引用计数就会-1。当计数降为 0 时,就会自动释放内存。

  • 使用的方法:

    • 手动引用计数(MRC,即 mannual reference counting)
    • 自动引用计数(ARC,即 automatic reference counting)
  • 优点:OC 的内存释放及时、平滑,时机可控使得iOS对内存的利用率很高

  • 缺点:

    • 很容易在循环引用时造成内存泄漏,因此要时刻保持清晰的对象间联系。
    • 有时候在浏览网页等操作时由于内存释放过快返回之前的页面需要重载。
    • 手动管理内存(MRC)时十分复杂并且容易出错。

MRC 手动引用计数

意义

在 WWDC2011 和 iOS5 出现之前,iOS 内存管理的方式还只有手动引用计数这一种。那段时间堪称 iOS 开发者的黑暗时期 Σ(゚д゚lll) 因为复杂的手动管理很容易出错而且使开发的上手难度大增。此后,我们有了先进的 ARC 系统来尽职尽责的替我们来完成这一切,但是学会 MRC 依然是非常有必要的:

  • 只有能够掌握 MRC 才能真正的理解 iOS 的内存机制。
  • 面试也很可能考呀!
  • 极少数的轮子(Github 开源项目)还在使用 MRC。
  • 一旦内存方面出现 bug ,需要手动来修复。

总而言之,如果你只是想速成 iOS 开发作为娱乐,那么 ARC 足矣,但是如果你想以 iOS 开发为职业或者成为一名优秀的全栈程序员那么 MRC 是必须的。建议入门时使用 MRC 打好基础吧。

黄金法则

如果对一个对象使用了 alloc、[Mutable]copy,retain,那么你必须使用相应的 realease 或者 autorelease。

具体方法实现

set 方法研究

我们通过一个工程包括 Person 类与 Dog 类的使用关系来研究自动引用计数的过程,首先新建一个 Command Line Tool 工程然后新建两个类:Person 和 Dog。Person 类的实例变量为 _dog,指向 Dog 类对象表示人拥有狗,以及 _name表示人的名字。Dog 类的实例变量为 _name 表示狗的名字。

首先使用最初始的 set 方法和 get 方法,在 Person.m 中实现为

然后在 main.m 中运行

理论上来说给 Person 类 setDog(即添加一个使用对象)会使 Dog 对象的引用计数+1,让我们运行看一看结果:

Snip20160131_14

结果确实 dog1 在创建后 retainCount = 1,然后就没有增加了,这是不合理的。我们需要修改 set 方法从而在调用 setDog 方法时手动的增加引用计数。我们试试将 Person.m 中的 setDog 方法修改为:

然后运行,结果如图:

Snip20160131_15

但是这样是有缺陷的,我们试着再建立一只狗名叫来福,然后把张三使用的狗修改为来福,在 main.m 的最下面中添加:

运行,结果显示:

Snip20160131_15

最终 dog1.retainCount 还是 3,这是因为我们需要在 setDog 方法中将原来被替换掉的对象 release 一遍,将 setDog 修改为:

注意在第一次使用 setDog 时会执行[nil release]这句代码是可行的,并不会报错。根据黄金法则如果对一个对象使用了 alloc、[Mutable]copy,retain,那么你必须使用相应的 realease 或者 autorelease 我们需要对创建的对象 release,这样最后输出的 retainCount 数就等于被这个对象拥有者的个数了。由于 dog1 和 dog2 现在的拥有者 > 0,那么它们可以在代码的中间 release,而 p1,p2 现在没有拥有者,一次它们需要在代码的末尾 release ,将main.m 修改为:

这样输入结果如图:
Snip20160131_16

此时的模型就像这样:

Snip20160131_18

retainCount 就代表了 Dog 被拥有的个数了。但是还会出现一个问题,如果在 [dog1 release] 之后再次运行[p2 setDog:dog1] 那么 dog1 就会先被release(此时 dog1 的引用计数变为 0,dog1 对象被销毁 )然后将 dog1 赋与 p2(既然 dog1 被销毁了,当然这一步无法进行,程序会报错)。所以我们应该继续将 setDog 修改为:

这就是 set 方法的正确思路。它的核心思想是给全局对象赋值的时候,需要将此对象retain。同样 setName 方法也要按照 setDog 的方法来修改啦。只不过要把 retain 替换成 copy ,至于为什么呢,大家可以查看 http://www.cnblogs.com/celestial/archive/2012/10/12/2721244.html来进行了解。接下来我们来看看 release 方法。

release 方法研究

为了观察 release 方法在最后调用 dealloc 的过程,我们先继续之前的工程,覆写 Person 与 Dog 的 dealloc 方法 ,在 .m 文件中加入

将 main.m 的内容修改为:

运行完显示为

最终运行完之后只有 p1 被销毁,dog1 的引用计数当然还是 1,但是我们希望当 p1 被释放时,dog1 就会失去其拥有者,那么 dog1 应该也要被 release,最终使得 dog1 也被销毁,于是我们要继续改写 Person 类的 dealloc 方法:

运行之后输出结果为:

这样就好啦。我们要注意,在 dealloc 中对全局对象都 release,Person 与 Dog 具有的 _name 也需要在 delloc 中 release ,在 dealloc 中添加如下就可以啦

自定义初始化方案

自定义初始化实质上还是 init 方法与 set 方法合二为一的结合,掌握了 set 方法的原理,那么也就很显然了,比如:

属性(Property)

如果我们用传统的方式,需要为类的每一个实例变量声明并实现一对存取方法(set 方和 get 方法),这是十分麻烦的我们可以通过声明属性来大大简化代码量,比如为 Person 类设置 _name 属性:

传统的方法需要在 Person.h 声明:

然后在 Person.m 中实现:

但是使用 @property 的方法我们只需要在 Person.h 中加入:

就可以啦,嚯嚯嚯。它包括了两个步骤:

  • 生成 _name 属性
  • 为 _name 属性自动生成 set 和 get 方法
属性的特性(attribute)

那么对于包含了 retain 或者 copy 的 set 方法呢?我么来使用:

Snip20160131_20

其中三个关键字分别可以使用:

Snip20160131_21

  1. 原子性涉及到多线程问题,我们目前统一用 nonatmic 就可以啦。
  2. 这三者根据我们的需要和类的种类来进行选择。
    其中使用 assign 就相当于这个形式:

    使用 retain 相当于:

    而使用 copy 则相当于:

  3. 一般情况下我们使用 readwrite,这样的话才能使用 set 方法。
数组的内存管理

涉及到数组的内存管理时,其引用计数是会自动的相应变化的,所以比较方便,但是我们要知道它是怎样变化的:

  • 当一个对象被添加进数组时 ,对象的引用计数也会相应的增加。
  • 当数组销毁时,所有对象均会 release。
  • 数组移除指定的对象或者时所有对象,其被移除的对象会 release。
自动释放池

自动释放池的 OC 中一种相对比较智能的内存自动管理机制,它的核心用法是:当自动释放池销毁时,它会对池内的每一个对象都调用一次 release 方法,这样我们就避免了为每一个对象都要手动进行一次 release 的繁琐,相当于是遵循黄金法则的一个批量释放自动解决方案。NSObject 类提供了一个 autorelease 消息,当我们想一个对象发送 autorelease 消息的时候,这个对象就会随着释放池的销毁而释放。如果要向使用使用自动释放池释放对象,我们首先要有一个入池操作,创建自动释放池有两种方法:

  • 第一种方法(新语法)

    代码应该写在{ }括号内,系统就会在运行后将{ }内的对象进行自动释放了。

  • 第二种方法(旧语法)

    我们需要将某个对象加入自动释放池时,只需要调用 autorelease 方法就好啦,这个对象就会相应的加入此前离它最近的创建的自动释放池。当对释放池调用 release 方法时,这个池内的所有对象就都会 release 了。

因此,采用 autorelease 手动内存管理要方便了许多,我们还需要注意以下几点:

  • 方法1中的变量只能在{ }内使用,出去就被销毁了。
  • 自动释放池是以栈的形式实现的,当某个对象调用了 autorelease 方法时,该对象会被加入自动释放池的栈顶。对于发送了 autorelease 消息的对象,当自动释放池销毁时,自动释放池会对这些对象发送一条 release 消息,来释放他们。因此自动释放池可以嵌套使用,而 autorelease 会将对象添加到离它最近的自动释放池例如,我们在 main.m 中加入:

    运行结果显示为:

  • 我们还可以向自动释放池发送 drain 消息。区别是:当我们向自动释放池pool发送 release 消息时,它会向池中的每一个发送了 autorelease 消息的对象发送一条 release 消息,并且自身也会销毁。当向它发送 drain 消息时,只会释放里面的对象,而不会销毁自己。

类方法及新语法创建对象的内存管理

我们知道,Foundation 框架中的类可以通过 alloc 创建或者是用类方法创建。那么这其中的区别就在于:类方法创建的对象会自动加入自动释放池比如我们来自定义一个类方法来建立对象,新建一个 Person 类,加入类方法:

就可以啦。同样使用新语法来建立也会加入自动释放池,比如

互相交叉引用问题

这个我还没学好,这周写。

ARC 自动引用计数

这个我还没学,这周我写这个部分吧 o(^_^)o

   

想获得去掉 5 元限制的证券账户吗?

证券交易股票基金的佣金,不足 5 元会按照 5 元收取。比如交易 1000 元的股票,按照普遍的证券佣金手续费率万 2.5,其交易佣金为 0.25 元,小于 5 元,实际会收取佣金 5 元,买卖两次需要支付 10 元佣金成本,1% 的利润就这样没了。

如果您想去掉最低交易佣金 5 元限制,使用微信扫描左边小程序二维码,访问微信小程序「优财助手」,点击底部菜单「福利」,阅读文章「通过优财开证券账户无最低交易佣金 5 元限制」,按照文章步骤操作即可获得免 5 元证券账户,股票基金交易手续费率万 2.5。

请注意,一定要按照文章描述严格操作,如错误开户是无法获得免 5 元证券账户的。

发表评论

电子邮件地址不会被公开。 必填项已用*标注