美文网首页
GeekBand Objective-C编程语言学习笔记(第一周

GeekBand Objective-C编程语言学习笔记(第一周

作者: Hysoka | 来源:发表于2016-03-05 21:47 被阅读51次

    第一天视频课程:

    Objective-C 语言简介

    Objective-C 语言是一门在C语言基础上做了面向对象扩展的编程语言,1983年由Brad Cox 和Tom Love发明,是目前苹果开发平台的主力语言,与Cocoa 和Cocoa Touch框架高度集成,支持开发Mac OS X、iOS应用。在苹果开发平台上通过LLVM(LowLevelVirtualMachine)编译器架构支持与Swift语言双向互操作。

    iOS开发平台

    Cocoa框架部分包括了系统内核(Core OS)、内核服务(Core Service)、媒体(Media)、触摸(Cocoa Touch)这几个内库。通过LLVM编译框架和Objective-C运行时编译和运行。目前主要有Objective-C、Swift和C/C++这几种语言来进行编码,常用的开发工具为Xcode。

    掌握高级编程语言的思维方式

    底层思维:从微观、机器的层面理解语言的构造、编译转换、内存模型和运行时机制。

    抽象思维:将我们周围世界抽象为程序代码,即面向对象的思维方式,组件封装、设计模式、架构模式。

    “时空人”三位一体分析方法

    时间分析,发生在什么时候?编译时还是运行时。

    空间分析,变量放在那里?堆空间还是栈空间。

    人物分析,代码哪里来的?程序员还是编译器、运行时、框架。

    Objective-C 语言的两种开发方式

    Clang 或 GCC命令行:适合调试、研究、微观探查。

    Xcode项目:适合构建正规工程项目,使用大型框架,追求设计质量与代码组织。

    Objective-C 语言代码学习

    #import 导入头文件(类似C语言的#include),可以避免相同头文件的重复导入,推荐使用#import代替#include。

    @autoreleasepool 支持ARC(Automatic Reference Counting)的一个池,用来表明启用了内存自动回收机制。

    NSlog(@"Hello,world!"); NSlog类似C语言里的printf用来打印字符串,OC语言里的字符串前需要加@符号(@"Hello,world!")。

     头文件的扩展名是.h,主程序文件的扩展名是.m

    编译命名行:clang -fobjc-arc HelloWorld.m -o HelloWorld其中-fobjc-arc为ARC内存管理的开关命名,其中-o HelloWorld,-o 为output即输出的意思,一起为将输出的文件命名为HelloWorld。clang也可以换成gcc即用gcc编译器编译。(推荐用clang)

    命名行:clang -help 用来显示clang的设置帮助文档,可以用来了解clang的设置。

    命名行:./HelloWorld 执行生成的HelloWorld文件。

    ObjC编译过程

    目前主流为LLVM-Clang的编译过程,由OC、C\C++代码通过Clang前端再通过LLVM优化和LLVM代码生成器生成出X86-64机器码。

    学习资源

    苹果官方文档:https://developer.apple.com/library/

    programming with Objective-C: https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/

    iOS专区:https://developer.apple.com/library/ios/

    苹果开发者大会WWDC:https://developer.apple.com/videos/wwdc/2014/

                          WWDC:https://developer.apple.com/videos/wwdc/2015/

    第二天视频课程:

    类型系统

    引用类型 reference type:包括class、pointer、block。

    值类型 value type:包括基础数值类型、结构struct、枚举enum。

    类型装饰:包括协议protocol、类别category、扩展extension

    类 class VS. 结构 struct

    类型与实例:类的实例是对象,结构的实例是值。

    类是一个引用类型,是位于栈上的指针指向了一个位于堆上的实体对象。

    结构是一个值类型,它直接位于栈中。

    例子1:声明一个类(建立一个对外接口)

    @interface RPoint:NSObject  //声明一个类,类名RPoint,继承自类NSObject。:表示继承。

    @property int x; //声明一个属性 int X

    @property int y;

    -(void)print; //声明一个实例方法 print

    @end

    在OC中创建类时要先在头文件中(.h)创建一个对外借口,@interface来表示对外接口的起始,用@end来对应结束。

    @property 在类中定义属性的关键字,用来表示这个类的状态。

    -(void)print 是在类中定义方法的语句,其中-表示是一个实例方法,方法的返回值是void,方法的名字是print.类中的方法来表示这个类的行为。

    例子2:实现一个类

    #import<Foundation/Foundation.h> //导入Foundation.h头文件

    #import"rpoint.h" //导入rpoint.h头文件,即上面我们创建的类声明文件。

    @implementation RPoint  //定义一个类名为RPoint 的类

    -(void)print{

    NSLog(@"[%d,%d]", self.x, self.y);  //print方法的实现

    }

    @end

    OC的类需要在主文件中(.m)实现,@implementation 来表示实现的起始,用@end来对应结束。

    NSLog(@"[%d,%d]", self.x, self.y);  NSLog表示打印一个字符串,self表示当前的事例,整句语句表示将当前事例的属性x和y打印出来。

    例子3:

    #import<Foundation/Foundation.h>

    #import"rpoint.h" //导入类所在的文件

    #import"spoint.h"  //导入结构体所在的文件

    void process(RPoint* rp3, SPoint sp3); //函数process的声明

    int main(int argc, const char * argv[]){ //入口函数

        @autoreleasepool{

          RPoint* rp1=[[RPoint alloc]init]; //生成一个RPoint类的对象rp1

          rp1.x=10;

          rp1.y=20;

          [rp1 print]; //显示结果为10, 20

          SPoint sp1; //生成一个SPoint结构体实例。

          sp1.x=10;

          sp2.y=20;

          NSLog(@"拷贝----------");

          RPoint* rp2=rp1;

          rp2.x++;

          rp2.y++;

          [rp1 print]; //显示结果为11, 21

          [rp2 print]; //显示结果为11, 21

          SPoint sp2=sp1;

          sp2.x++;

          sp2.y++;

          NSLog(@"[%d,%d]",sp1.x, sp1.y); //显示结果为10, 20

          NSLog(@"[%d,%d]",sp2.x, sp2.y); //显示结果为11, 21

          NSLog(@"传参----------")

          process(rp1, sp1);

          [rp1 print]; //显示结果为12, 22

          NSLog(@"[%d,%d]",sp1.x, sp1.y); //显示结果为10, 20

        }

        return0;

    }

    void process(RPoint* rp3, SPoint sp3){

      rp3.x++;

      rp3.y++;

      sp3.x++;

      sp3.y++;

      [rp3 print];

      NSLog(@"[%d,%d]",sp3.x, sp3.y);

    }

    RPoint* rp1=[[RPoint alloc] init]; //在内存栈空间创建了一个名字为rp1的RPpoint类实例对象。[]为调用符号,调用也可以称为发送消息。alloc的用处是手动在堆空间申请内存空间,init为初始化所分配的空间。

    拷贝行为内存视图 传参行为内存视图

    堆(heap):堆空间用于存储引用类型对象,由程序员手动申请内存空间,释放由运行时ARC机制自动释放,函数之间通过拷贝引用(指针)传递。堆空间具有全局性,总体大小受制于系统内存整体大小。

    由于rp1是一个类的实例对象,所以无论是复制还是传参,都是由一个栈中的指针指向堆中的实体对象,复制副本和传参参数的改变都能直接导致rp1指向的实际对象发生改变。

    SPoint sp1; //在内存的堆空间创建了一个名字为sp1的结构体。

    栈(stack):栈空间用于存储值类型,无ARC负担,由系统自动管理,以执行函数为单位。栈的空间大小在编译时确定(根据参数+局部变量来计算),在函数执行时由系统自动分配一个栈,函数执行结束系统立即自动收回该栈空间,函数之间通过拷贝值传递。栈空间具有局部性,大小有限额(编译工具可以设定栈的大小,一般为1M),超出会栈溢出(stack overflow)。

    由于sp1是一个结构值,它存储于栈中,无论复制还是传参,都将在栈中复制一个sp1的副本,sp1的原始值保持不变。

    第三天视频课程:

    OC类的类型成员

    OC类所包含的类型成员(Type Member)主要分为两大类,一类是数据成员(data member)用来描述对象的状态,还有一类是函数成员(function member)用来描述对象的行为。其中数据成员又有实例变量(instance variable)和属性(property)两种。函数成员分为方法(method)、初始化器(init)和析构器(dealloc)三种。

    认识属性

    属性表达实例状态,描述类型对外接口。相比直接访问实例变量,属性可以做更多控制。

    默认情况下,编译器会为属性定义propertyName自动合成一个getter访问器方法:propertyName、一个setter访问器方法:setPropertyName还有一个实例变量_propertyName。

    举例:

    在OC类中声明一个属性:@property NSString* firstName; //声明一个类所包含的属性。

    此时编译器会自动生成类似如下代码,但是并不显示出来:

    -(NSString*)firstName{  //生成一个getter函数

       return _firstName;

    }

    -(void)setFirstName:(NSString *)newValue{  //生成一个setter函数

      _firstName=newValue;

    }

    NSString* _firstName; //创建一个以 _(下划线)+属性名的实例变量

    由于编译器自动生成了类似如上代码,我们可以用编译器生成的getter方法和setter方法来访问或修改该属性的内容。

    例如:

    Employee* employee=[[Employee alloc] init];

    [employee setFirstName: @"Tom"]; //将employee类的FirstName属性设置为“Tom”,setFirstName这个setter方法是编译器自动生成的。

    NSLog(@"First Name: %@", [employee firstName]); //打印出employee类的FirstName属性,此处[employee firstName]是调用了编译器自动生成的getter方法firstName。

    除了以上的方法来访问getter和setter方法外,可以用类名+.(点表达式)+属性名来访问属性的getter和setter方法。

    例如:

    employee.lastName=@"Chen"; //访问了lastName属性的setter表达式,等价于[employee setFirstName: @"Chen"];

    NSLog(@"First Name: %@", employee.lastName); //访问了lastName属性的getter表达式,等价于NSLog(@"First Name: %@", [employee firstName]);

    两种方法在本质上没有什么区别,推荐用点表达式,使用比较方便。

    可自定义访问器方法,也可更改访问器方法名或实例变量名。

    例如:

    @property (readonly) NSString* fullName; //在头文件中定义了一个只读属性fullName

    -(NSString *)fullName{

      return [NSString stringWithFormat:@"%@ %@", self.firstName, self.lastName];

    } //在主文件中自定义了fullName属性的getter访问器方法。stringWithFormat函数是用来链接两个字符串类实例。

    @property (getter=GivenName, setter=GivenName:) NSString* firstName; //声明firstName属性时将该属性的getter、setter访问器的名字都设置为GivenName。

    @synthesize firstName=givenName; //在主文件中将firstName属性由系统自动生成的_firstName实例变量改名为givenName。

    可以使用静态全局变量(C语言)+类方法,模拟类型属性。

    static int _max=100; //在主文件中定义一个静态变量_max

    +(int)max; //在头文件中声明一个类方法(getter访问器) +(int)max

    +(void)setMax:(int)newValue; //在头文件中声明一个类方法(setter访问器)  +(void)setMax

    +(int)max{

    return _max;  //在主文件中实现类方法+(int)max

    }

    +(void)setMax:(int)newValue{

          _max=newValue;  //在主文件中实现类方法+(void)setMax

    }

    完成以上步骤后,可以实现访问类型属性。

    [Employee setMax:400]; 等同于Employee.max=400; 模拟出来的类属性。

    实例变量

    可以定义实例变量,而不定义属性(这样外部不能访问到实例变量),只有实例变量,没有类变量(使用静态全局变量+类方法可以模拟出类变量的效果)。

    如果同时自定义了getter和setter访问器方法,或者针对只读属性定义了getter访问器方法,编译器将不再合成实例变量。

    引用类型的实例变量在类外一律使用属性来访问,类内大多也通过self使用属性访问。只有以下情就卡了况使用实例变量来访问:1、初始化器 init  2、析构器 dealloc 3、自定义访问器方法。 

    实例变量的生存周期

    实例变量的储存:跟随对象实例存储在堆上。

    值类型实例变量直接“内嵌”在对象实例中。跟随对象实例内存释放而被释放。

    引用类型实例变量通过指针“引用”堆上的引用类型实例,ARC针对引用进行计数管理,自动释放引用计数为0的对象。

    属性的描述特性(Attribute)

    属性的描述特性可以指定属性不同环境下的不同功能。

    读写特性,默认情况属性都是可读写(readwrite)的,也可以设置为只读(readonly)属性。

    例如:

    @property (readonly) NSString* fullName; //在头文件中定义了一个只读属性fullName

    多线程特性,默认情况下是属性是原子性(atomic)的,表示多线程时原子性属性不能被线程抢走,要么没有运行,开始运行了必须运行到结束。也可以设置为非原子性(nonatomic).

    例如:

    @property (nonatomic) NSString* fullName; //在头文件中定义了一个非原子性属性fullName

    内存管理特性

    引用属性默认为强(strong)引用属性,弱(weak)引用阻止循环引用。当两个实例对象属性相互强引用时会形成循环引用,这时ARC内存管理将视这两个互引用的属性都占用,无法将这两个实例的引用属性释放。

    两个类型相互强引用导致内存泄露。

    为了避免上述情况发生把其中一个对象属性设置为弱引用,这样就不会出现不能释放的问题了。

    一个弱引用一个强引用就可以互相引用,并可以正常释放内存

    拷贝(copy)属性创建独立拷贝,当引用属性不想被外界直接引用时,可以用拷贝属性,让外界引用一个拷贝副本,来确保原始数据不会被外界修改。

    不使用拷贝特性时,被引用将多一条指针指向实例属性 使用拷贝特性后,被引用时多一条指针指向实例属性的副本

    第四天视频课程:

    认识方法 Method

    代码段上的可执行指令序列就是函数,函数有全局函数(C语言函数),和成员函数(OC中也叫方法)。

    方法是类的成员函数,表达实例行为或类型行为。

    举例:

    以下声明了4个方法

    -(void) print; //-表示是实例方法,返回值是void, 方法名是print, 无参数

    -(BOOL) isEqualToPoint: (BLNPoint*) point; //返回值是BOOL, 参数名是point, 参数类型是BLNPoint*

    -(void) moveToX: (int)x toY: (int)y;  //有两个(int)类型的参数x和y

    +(BLNPoint*) getOriginPoint; //+表示getOriginPoint是一个类方法

    所有方法默认为公有方法。没有private或protected方法(可以在接口文件中不要声明方法,而在实现文件中实现方法来做到类似private方法)。

    动态消息发布:方法调用通过运行时动态消息分发实现,在对象上调用方法又称“向对象发送消息”。

    例子:

    [p1 print]; //实例p1调用了print方法,也可以说向对象p1发送了一个print消息

    [p1 moveToX:100 toY:200]; //向对象p1发送了一个moveToX消息,其中还包含了两个参数,100和200

    BLNPoint* origin=[BLNPoint getOriginPoint]; //向BLNPoint类发送了一个getOriginPoint消息,返回值赋值给BLNPoint的类型实例origin

    实例方法或类型方法

    实例方法用来表达实例的行为所以只能通过实例对象来调用,实例方法在内部实现时可以访问实例成员包括实例属性、实例变量和实例方法。也可以访问类型方法和静态变量。

    类方法用来表达类的行为只能通过类来调用,类型方法在实现时可以访问类型方法和静态变量,不能访问实例成员包括实例属性、实例变量和实例方法。

    编译器背后对实例方法和类方法的不同处理:self指针

    例子:

    实例方法实现

    -(void) print{

          NSLog(@"[%d, %d]", self.x, self.y);

    }

    上面的实例方法实现中self实际上是一个隐藏的指针参数,用来传递当前实例地址,编译器编译后用C语言的表达方式可以写成:

    void print(BLNPoint* self){

          NSLog(@"[%d, %d]", self.x, self.y);

    }

    调用该方法时:[p1 print]; //print(p1);

    类方法实现

    +(BLNPoint*) getOriginPoint{

          BLNPoint* origin=[[BLNPoint alloc] init];

          origin.x=0;

          origin.y=0;

          return origin;

    }

    上述的类方法实现编译后用C语言表示为

    BLNPoint* getOriginPoint(){

          BLNPoint* origin=[[BLNPoint alloc] init];

          origin.x=0;

          origin.y=0;

          return origin;

    }

    类方法里面不能用self关键字来访问实例变量,但是依然可以使用self关键字,这时self关键字用来表示当前类。在类方法实现里面可以用[self process]来调用当前类中的process方法,也等同于[BLNPoint process]。

    方法参数

    如果参数类型是值类型,为传值方式,如果参数类型为引用类型,则为传指针方式。

    方法可以没有参数,也可以没有返回值。

    如果方法有参数,方法名约定包含第一个参数,从第二个参数开始需要显示提供外部参数名。

    -(void) moveToX: (int)x toY: (int)y; //toY是第二个参数的外部参数名,x是第一个内部参数名,y是第二个内部参数名。

    调用时,第一个参数名忽略,但后面的参数名必须显示标明。如: [p1 moveToX:100 toY:200];

    动态方法调用机制--消息分发表

    在OC里所有的对象类型都可以声明为id类型

    例子:

    id obj=[[BLNPoint alloc] init];

    [obj moveToX:50 toY:60];

    [obj print];

    这里类型为id的对象obj可以调用类型为BLNPoint的对象方法,是由于OC的动态调用机制造成的。

    消息分发机制

    OC中调用所有方法都会通过消息分发表,上图灰色的部分其中有一个指针,指向class再指向method list。这样做可以增加灵活性,支持在运行时向方法表添加新的方法。缺点是每次调用方法都要多次寻址有性能损失。

    第五天视频课程:

    初始化器与析构器

    初始化器和析构器是类型的特殊函数成员,初始化器用于初始化对象实例或者类型。

    对象初始化器:-(id)init 可以重载多个。

    例子:

    在头文件中可以声明多个有不同参数列表的-(id)init函数。

    -(id)init;

    -(id)initWithName:(NSString *)name;

    -(id)initWithName:(NSString *)name WithPages:(int)pages;

    -(id)initWithName:(NSString *)name WithPages:(int)pages WithCategory:(NSString *)category;

    在主文件中实现声明的init方法

    -(id)init{

          self = [super init];

          if(self){  //如果父类初始化失败self指针将等于Null

            NSLog(@"Book Object init“); //在调用了父类的init方法后可以添加自己所需的内容。

          }

          return self;

    }

    -(id)initWithName:(NSString *)name WithPages:(int)pages{

          return [self initWithName:name WithPages:pages WithCategory:@"General"];

    }

    -(id)initWithName:(NSString *)name WithPages:(int)pages WithCategory:(NSString *)category{ //由于该初始化器的参数是最多的,作为主初始化器,其他参数少的初始化器可以直接调用主初始化器。

          self = [super init];

          if(self){

          _name = [name copy]; //在初始化器中使用实例变量而不要使用实例属性。

          _pages = pages;

          _category = [category copy];

          }

          return self:

    }

    初始化对象实例时,init通常和alloc搭配使用。

    Book *b1 = [[Book alloc]init]; //alloc是一个从NSObject继承过来的类方法。Tips: 按住commond健再点击关键词可以查看其类库。

    Book *b1 = [[Book alloc]init]; 也可以拆分为:

    Book *b1 = [Book alloc];

    b1 =[b1 init]; //这里等号左边的b1是不可以省略的,因为[b1 init]有一个返回值,是返回一个地址,OC在这里返回的地址有可能和上一行Book *b1 = [Book alloc];其中的b1地址不一样。

    alloc所做的事情--NSObject已实现:1、在对上分配合适大小的内存。 2、将属性或者实例变量的内存置0。

    init所做的事情--可以自定义:1、调用父类初始化器[super init](前置调用)。 2、初始化当前对象实例变量。

    Book *b1= [Book new]; //new相当于调用 alloc/init的无参数版本,不能传递参数。

    类初始化器

    类型初始化器:+(void)initialize 只能有一个,负责类型级别初始化,初始化类里面的静态变量。

    initialize在每个类使用之前被系统自动调用,且每个进程周期中只被调用一次。

    子类的initalize会自动调用父类的initialize(前置调用)。

    +(void)initialize{

          if(self ==[Book class]){ //判断该类是否是Book类

                NSLog(@"Book Class initialize");

          }

    }

    对象析构器

    对象析构器 -(void)dealloc 只能有一个,用于释放对象(没有类型析构器)拥有的动态资源,无返回值。

    -(void)dealloc{

    自动调用:1.ARC将对象属性引用计数减持(-1)

    手动实现:2.释放不受ARC管理的动态内存,如malloc分配的内存,关闭非内存资源,如文件句柄、网络端口

    自动调用:3.父类dealloc

    }

    dealloc由ARC根据对象引用计数规则,在释放对象内存前自动调用,无法手工调用。

    子类的dealloc会自动调用父类的dealloc(后置调用)。

    第六天视频课程:

    认识面向对象

    封装 encapsulation:隐藏对象内部实现细节,对外部仅提供公共接口访问。

    继承 inheritance:一个类型在另外类型基础上进行的扩展实现。每一个类只能有一个基类,子类自动继承基类的:实例变量、属性、实例方法、类方法。NSObject类是所有类的根类,所有类向上追溯最上面的类都是NSObject类。继承有两层含义:1、成员复用,子类复用基类的成员。所有的成员都会被继承,就算是私有成员也被继承,只是成员访问不到。2、类型抽象,将子类当作父类来使用。

    例子:

    建立一个类Shape

    @interface Shape : NSObject{ //Shape类继承于NSObject Tips:按住option键点击关键字可以看到相关的参考文档。

          @public int _data; //定义了一个公开的实例变量

    }

          @property int no;

          -(void)draw;

          +(void)process;

    @end

    建立一个Shape的子类Circle

    @interface Circle:Shape //Circle类继承了Shape类

    @property int radius; //Circle类自己的实例属性

    @end

    由于Circle继承了Shape类,所以Shape类的实例变量、属性、实例方法、类方法Circle类都可以使用。

    Circle* circle=[[Circle alloc]init];

    circle.no=200;

    circle->_data++; //在访问父类实例变量时用->来访问

    [circle draw];

    [Circle process]; //Circle类也可以调用父类的类方法。

    Circle类继承Shape类的内存模型

    第七天视频课程:

    多态 polymorphism:不同类型针对同一行为接口的不同实现方式。

    对比重写(override)与重载:子类重写父类同名同参数方法,子类只可以重写父类方法。方法名相同、参数不同,OC不支持方法的重载(重载指的是方法名相同,参数不同。用不同的参数传递给相同名字的方法,达到不同的运行效果)。

    在子类的代码中,可以使用super类调用基类的实现,self具有多态性,可以指向不同子类,super没有多态性,仅指向当前父类。

    例子:

    建立一个Shape的子类Rectangle

    @interface Rectangle:Shape

    @property int width;

    @property int length;

    @end

    基类Shape中有一个实例方法move:

    -(void)move{

          NSLog(@"Shape object move");

          [self draw];

    }

    针对Rectangle类增加了两个属性(width和length)重写(override)继承过来的方法

    @implementation Rectangle

    -(id)init{

          self = [super init];  //重写时子类初始化器中必须首先调用基类的初始化器

          if(self){

                _length = 10;

                _width = 20;

          }

          return self;

    }

    -(void)draw{

          NSLog(@"Rectangle object draw: length=%d, width=%d", self.length, self.width);

    }

    -(void)print{

          NSLog(@"Rectangle Instance variable %d", _data);

    }

    +(void)process{

          NSLog(@"Rectangle class process");

    }

    -(void)dealloc{

           NSLog(@"Rectangle dealloc");

    }

    当实例对象调用时,Rectangle实例将调用重写的方法。Rectangle实例调用-(void)move时将调用父类Shape类的实例方法-(void)move,此方法中调用的draw方法[self draw],此时self有多态性,将调用Rectangle实例中的draw方法。

    对于父类中的属性也可以在子类中重写。

    父类Shape中的属性:@property int no;

    在子类Rectangle中重写:

    -(int)no{

          return super.no;  //super表示调用父类的方法

    }

    -(int)setNo:(int)no{

          super.no = no;

    }

    属性的重写本质上就是getter和setter两个访问器的重写。

    继承中的init和dealloc

    初始化器 init:子类自动继承基类的初始化器,子类也可以重写初始化器,重写时子类初始化器中必须首先调用基类的初始化器(手工调用)再添加其他内容。

    析构器 dealloc:子类可以选择继续继承基类析构器,或者重写基类析构器,子类析构器执行完毕后,会自动调用基类析构器(后置调用,且不支持手工调用)。子类析构器自动具有多态性。

    Tips:尽量避免在父类init和dealloc中调用子类重写的方法。

    相关文章

      网友评论

          本文标题:GeekBand Objective-C编程语言学习笔记(第一周

          本文链接:https://www.haomeiwen.com/subject/wtrbkttx.html