iOS编码规范

概览

为了减少团队的大家代码风格的不一致,降低出错率,提供代码的可维护性,针对App自身特点制定以下规范。

规范按照执行力度分为

  • 强制遵守 【must】
  • 建议遵守 【suggest】
  • 最佳实践 【best practice】

强制遵守:为一些非常重要的原则和既定的约定,下面简称【M】
建议遵守:为一些建议遵守的原则和代码风格,下面简称【S】
最佳实践:为一些经过实践验证比较优的写法,下面简称【BP】

这篇文章内容主要包含以下三个部分

  • 核心原则:介绍了这篇代码规范所遵循的核心原则
  • 通用规范:不局限于iOS的通用性的代码规范(使用C语言和Swift语言)
  • iOS规范:仅适用于iOS的代码规范(使用Objective-C语言)

每条原则有一个默认执行力度,最终执行力度将由大家团队讨论,半数以上的得票为最终方案,在执行过程中更加反馈,后续可能会有所调整,直到最佳。

一. 核心原则【M】

原则一:简洁性原则--代码应该简洁易懂,逻辑清晰

因为软件是需要人来维护的。这个人在未来很可能是你也可算是你的同事,简洁的代码易于维护。

  • 不要过分追求技巧,降低程序的可读性。
  • 简洁的代码可以让bug无处藏身。要写出明显没有bug的代码,而不是没有明显bug的代码。

原则二:可拓展性--面向变化编程,而不是面向需求编程。

需求是暂时的,只有变化才是永恒的。
本次迭代不能仅仅为了当前的需求,写出扩展性强,易修改的程序才是负责任的做法,对自己负责,对公司负责。

原则三:效率优先--先保证程序的正确性,防止过度工程

过度工程(over-engineering):在正确可用的代码写出之前就过度地考虑扩展,重用的问题,使得工程过度复杂。
引用《王垠:编程的智慧》里的话:

  1. 先把眼前的问题解决掉,解决好,再考虑将来的扩展问题。
  2. 先写出可用的代码,反复推敲,再考虑是否需要重用的问题。
  3. 先写出可用,简单,明显没有bug的代码,再考虑测试的问题。

二. 通用规范

格式【M】


  • 使用4个空格(tab)来缩进
  • 方法- 和 + 和返回值之前为1个空格
  • 方法参数之间有一个空格,其他地方不出现多余的空格
  • 方法长度不超过80行,建议不超过50行。

大括号【S】


  • 控制语句(if,for,while,switch)中,大括号开始与行尾
  • 函数中,大括号要开始于行首

推荐这样写:

//控制语句
while(someCondition) {

}

if (someCondition) {

} else {

}

for (NSInteger i = 0; i < 5; i++) {

}

//函数
void function(param1,param2)
{

}

运算符


1. 运算符与变量之间的间隔

1.1 一元运算符与变量之间没有空格【M】:

!bValue
~iValue
++iCount
*strSource
&fSum

1.2 二元运算符与变量之间必须有空格【M】

fWidth = 5 + 5;
fLength = fWidth * 2;
fHeight = fWidth + fLength;
for (int i = 0; i < 10; i++)

2. 多个不同的运算符同时存在时应该使用括号来明确优先级【M】

在多个不同的运算符同时存在的时候应该合理使用括号,不要盲目依赖操作符优先级。
因为有的时候不能保证阅读你代码的人就一定能了解你写的算式里面所有操作符的优先级。

来看一下这个算式:2 << 2 + 1 * 3 - 4

这里的<<是移位操作直观上却很容易认为它的优先级很高,所以就把这个算式误认为:(2 << 2) + 1 *3 - 4
但事实上,它的优先级是比加减法还要低的,所以该算式应该等同于:2 << (2 + 1 *3 - 4)。
所以在以后写这种复杂一点的算式的时候,尽量多加一点括号,避免让其他人误解(甚至是自己)。

变量


1. 一个变量有且只有一个功能,尽量不要把一个变量用作多种用途【M】

2. 变量在使用前应初始化,防止未初始化的变量被引用【M】

3. 局部变量应该尽量接近使用它的地方【S】

推荐这样写:

func someFunction() {

 let index = ...;
 //Do something With index

 ...
 ...

 let count = ...;
 //Do something With count

}

不推荐这样写:

func someFunction() {

 let index = ...;
 let count = ...;
 //Do something With index

 ...
 ...

 //Do something With count
}

if语句


1. 必须列出所有分支(穷举所有的情况),而且每个分支都必须给出明确的结果。【S】

推荐这样写:

var hintStr;
if (count < 3) {
    hintStr = "Good";
} else {
    hintStr = "";
}

不推荐这样写:

var hintStr;
if (count < 3) {
    hintStr = "Good";
}

2. 不要使用过多的分支,要善于使用return来提前返回错误的情况【M】

推荐这样写:

- (void)someMethod {
    if (!goodCondition) {
        return;
    }
    //Do something
}

不推荐这样写:

- (instancetype)initWithDictionary:(NSDictionary *)dict error:(NSError)err
{
 //方法1\. 参数为nil
 if (!dict) {
 if (err) *err = [JSONModelError errorInputIsNil];
 return nil;
    }

    //方法2\. 参数不是nil,但也不是字典
    if (![dict isKindOfClass:[NSDictionary class]]) {
        if (err) *err = [JSONModelError errorInvalidDataWithMessage:@"Attempt to initialize JSONModel object using initWithDictionary:error: but the dictionary parameter was not an 'NSDictionary'."];
        return nil;
    }

    //方法3\. 初始化
    self = [self init];
    if (!self) {
        //初始化失败
        if (err) *err = [JSONModelError errorModelIsInvalid];
        return nil;
    }

    //方法4\. 检查用户定义的模型里的属性集合是否大于传入的字典里的key集合(如果大于,则返回NO)
    if (![self __doesDictionary:dict matchModelWithKeyMapper:self.__keyMapper error:err]) {
        return nil;
    }

    //方法5\. 核心方法:字典的key与模型的属性的映射
    if (![self __importDictionary:dict withKeyMapper:self.__keyMapper validation:YES error:err]) {
        return nil;
    }

    //方法6\. 可以重写[self validate:err]方法并返回NO,让用户自定义错误并阻拦model的返回
    if (![self validate:err]) {
        return nil;
    }

    //方法7\. 终于通过了!成功返回model
    return self;
}

可以看到,在这里,首先判断出各种错误的情况然后提前返回,把最正确的情况放到最后返回。

3. 条件表达式如果很长,则需要将他们提取出来赋给一个BOOL值【M】

推荐这样写:

let nameContainsSwift = sessionName.hasPrefix("Swift")
let isCurrentYear = sessionDateCompontents.year == 2014
let isSwiftSession = nameContainsSwift && isCurrentYear
if (isSwiftSession) { 
 // Do something
}

不推荐这样写:

if ( sessionName.hasPrefix("Swift") && (sessionDateCompontents.year == 2014) ) { 
 // Do something
}

4. 条件语句的判断应该是变量在左,常量在右【S】

推荐这样写:

if ( count == 6) {
}

或者

if ( object == nil) {
}

或者

if ( !object ) {
}

不推荐这样写:

if ( 6 == count) {
}

或者

if ( nil == object ) {
}

5. 每个分支的实现代码都必须被大括号包围【S】

推荐这样写:

if (!error) {
 return success;
}

不推荐这样写:

if (!error)
 return success;

或者

if (!error) return success;

6. 条件过多,过长的时候应该换行【S】

推荐这样写:

if (condition1() && 
    condition2() && 
    condition3() && 
    condition4()) {
  // Do something
}

不推荐这样写:

if (condition1() && condition2() && condition3() && condition4()) {
  // Do something
}

for语句


1. 不可在for循环内修改循环变量,防止for循环失去控制。【M】

for (int index = 0; index < 10; index++){
    ...
    logicToChange(index)
}

2. 避免使用continue和break。【S】

continue和break所描述的是“什么时候不做什么”,所以为了读懂二者所在的代码,我们需要在头脑里将他们取反。

其实最好不要让这两个东西出现,因为我们的代码只要体现出“什么时候做什么”就好了,而且通过适当的方法,是可以将这两个东西消灭掉的:

2.1 如果出现了continue,只需要把continue的条件取反即可

var filteredProducts = Array()
for level in products {
    if level.hasPrefix("bad") {
        continue
    }
    filteredProducts.append(level)
}

我们可以看到,通过判断字符串里是否含有“bad”这个prefix来过滤掉一些值。其实我们是可以通过取反,来避免使用continue的:

for level in products {
    if !level.hasPrefix("bad") {
        filteredProducts.append(level)
    }
}

2.2 消除while里的break:将break的条件取反,并合并到主循环里

在while里的block其实就相当于“不存在”,既然是不存在的东西就完全可以在最开始的条件语句中将其排除。

while里的break:

while (condition1) {
    ... 
    if (condition2) {
        break;
    }
}

取反并合并到主条件:

while (condition1 && !condition2) {
  ...
}

2.3 在有返回值的方法里消除break:将break转换为return立即返回

有些朋友喜欢这样做:在有返回值的方法里break之后,再返回某个值。其实完全可以在break的那一行直接返回。

func hasBadProductIn(products : Array) -> Bool {

    var result = false
    for level in products {
        if level.hasPrefix("bad") {
            result = true
        }
    }
    return result
}

遇到错误条件直接返回:

func hasBadProductIn(products: Array) -> Bool {
 for level in products {
 if level.hasPrefix("bad") {
 return true
 }
 }
 return false
}

这样写的话不用特意声明一个变量来特意保存需要返回的值,看起来非常简洁,可读性高。

Switch语句


1. 每个分支都必须用大括号括起来,每个case太长建议后面空一行再写下一个case【S】

推荐这样写:

switch (integer) {
    case 1:
        {
            // ...
        }
        break;
    case 2:
        {
            // ...
        }
        break;
    case 3:
        {
            // ...
        }
        break;
    default:
        {
            // ...
        }
        break;
}

或
switch (integer) {
    case 1:{
            // ...
        }
        break;
        
    case 2:{
            // ...
        }
        break;

    case 3:{
            // ...
        }
        break;
        
    default:{
            // ...
        }
        break;
}

2. 使用枚举类型时,不能有default分支, 除了使用枚举类型以外,都必须有default分支【M】

RWTLeftMenuTopItemType menuType = RWTLeftMenuTopItemMain;
switch (menuType) {
    case RWTLeftMenuTopItemMain:
        {
            // ...
            break;
        }
    case RWTLeftMenuTopItemShows:
        {
            // ...
            break;
        }
    case RWTLeftMenuTopItemSchedule:
        {
            // ...
            break;
        }
}

在Switch语句使用枚举类型的时候,如果使用了default分支,在将来就无法通过编译器来检查新增的枚举类型了。

函数


1. 一个函数的长度必须限制在80行以内,建议50行以内【M】

通常来说,在阅读一个函数的时候,如果视需要跨过很长的垂直距离会非常影响代码的阅读体验。如果需要来回滚动眼球或代码才能看全一个方法,就会很影响思维的连贯性,对阅读代码的速度造成比较大的影响。最好的情况是在不滚动眼球或代码的情况下一眼就能将该方法的全部代码映入眼帘。

2. 一个函数只做一件事(单一原则)【M】

每个函数的职责都应该划分的很明确(就像类一样)。

推荐这样写:

dataConfiguration()
viewConfiguration()

不推荐这样写:

void dataConfiguration()
{ 
    ...
    viewConfiguration()
}

3. 对于有返回值的函数(方法),每一个分支都必须有返回值【S】

推荐这样写:

int function() {
    if (condition1) {
        return count1
    } else if (condition2) {
        return count2
    } else {
        return defaultCount
    }
}

不推荐这样写:

int function() {
    if (condition1) {
        return count1
    } else if (condition2) {
        return count2
    }
}

4. 对输入参数的正确性和有效性进行检查,参数错误立即返回【S】

推荐这样写:

void function(param1, param2)
{
 if (param1 is unavailable){
    return;
 }

 if (param2 is unavailable){
    return;
 }

 //Do some right thing
}

5. 如果在不同的函数内部有相同的功能,应该把相同的功能抽取出来单独作为另一个函数【S】

原来的调用:

void logic() {
    a();
    b(); 
    if (logic1 condition) {
        c();
    } else {
        d();
    }
}

将a,b函数抽取出来作为单独的函数

void basicConfig() {
  a();
  b();
}

void logic1() {
  basicConfig();
  c();
}

void logic2() {
  basicConfig();
  d();
}

6. 将函数内部比较复杂的逻辑提取出来作为单独的函数【S】

一个函数内的不清晰(逻辑判断比较多,行数较多)的那片代码,往往可以被提取出去,构成一个新的函数,然后在原来的地方调用它这样你就可以使用有意义的函数名来代替注释,增加程序的可读性。

举一个发送邮件的例子:

openEmailSite();
login();

writeTitle(title);
writeContent(content);
writeReceiver(receiver);
addAttachment(attachment);

send();

中间的部分稍微长一些,我们可以将它们提取出来:

void writeEmail(title, content,receiver,attachment)
{
 writeTitle(title);
 writeContent(content);
 writeReceiver(receiver);
 addAttachment(attachment); 
}

然后再看一下原来的代码:

openEmailSite();
login();
writeEmail(title, content,receiver,attachment)
send();

8. 避免使用全局变量,类成员(class member)来传递信息,尽量使用局部变量和参数。【M】

在一个类里面,经常会有传递某些变量的情况。而如果需要传递的变量是某个全局变量或者属性的时候,有些朋友不喜欢将它们作为参数,而是在方法内部就直接访问了:

class A {
    var x;

    func updateX() {
        ...x = ...;
    }

    func printX() {
        updateX();
        print(x);
    }
}

我们可以看到,在printX方法里面,updateX和print方法之间并没有值的传递,乍一看我们可能不知道x从哪里来的,导致程序的可读性降低了。

而如果你使用局部变量而不是类成员来传递信息,那么这两个函数就不需要依赖于某一个类,而且更加容易理解,不易出错:

func updateX() - > String {
    x = ...;
    return x;
}

func printX() {
    String x = updateX();
    print(x);
}

注释规范


参考:Cocoa代码风格指南之注释规范(三)

优秀的代码大部分是可以自描述的,我们完全可以用程代码本身来表达它到底在干什么,而不需要注释的辅助。

但并不是说一定不能写注释,有以下几种情况比较适合写注释:

  1. 解释实现文件中复杂的逻辑
  2. 对代码进行标注
  3. 对接口进行说明
  4. 容易产生歧义的代码

除了上述这几种情况,如果别人只能依靠注释才能读懂你的代码的时候,就要反思代码出现了什么问题。

最后,对于注释的内容,相对于“做了什么”,更应该说明“为什么这么做”。

解释实现文件中复杂的逻辑

在实现文件中,有时候会有一些复杂的逻辑,这种情况通过看代码去理解代价往往很大。如果不写备注,可能以后连自己都很难维护。所以,就算现在你对这些代码的逻辑有很清晰的理解,也最好写上备注,因为以后你肯定会忘记。这种注释由于不需要文档化,也不需要联想,所以使用 C/C++ 的注释风格就可以。当然如果你有文档化的需求,那还是按照统一的注释风格。

// 这是注释
/* 这是也是注释 */

对代码进行标注

在程序中,我们见过最多的注释莫过于//TODO了。这种注释如果在后边加上“:”就能在方法列表中显示,效果类似于#pragma mark。Xcode支持类似功能的注释有如下几种。

//MARK:MARK
//TODO:TODO
//FIXME:FIXME
// !!!:!!!
//???:???

这样的注释能够起到对代码进行标记的作用。比如项目中如果存在还没有完成的功能就可以加上//TODO:Response button event。如果某块代码需要修改,可以加上//FIXME:There is a problem on iOS7。如果你需要警告他人// !!!:DO NOT TOUCH MY CODE!。或者你对哪里的代码有不解(不满)//???:What is this shit。如果你想在方法内部实现像#pragma mark一样的标注,就可以使用//MARK:Initialization,是的,就是这么简单。如果你很另类,你还可以这样来注释代码。

对接口进行说明

当我们封装库或在开发较复杂的项目时,因为接口需要供多人使用,所以对接口的注释说明就十分重要。

先说行尾注释。生成文档的话,只有Docxygen是支持行尾注释的。但是所幸 Xcode Quick Help 的支持让我们有了使用的理由。

行尾注释有以下几种

/**< 行尾注释1. AppleDoc 不支持会变为下一项的注释, Quick Help 支持,Doxygen 支持, 根据英文句号自动切分简要描述与详细描述. */
/*!< 行尾注释2. AppleDoc 不支持会变为下一项的注释, Quick Help 支持,Doxygen 支持, 会全部当作详细描述, 而缺少简要描述. */
///< 行尾注释3. AppleDoc 不支持会变为下一项的注释, Quick Help 不支持, Doxygen 支持。
//!< 行尾注释4. AppleDoc 不支持会会忽略, Quick Help 支持, Doxygen 支持。

推荐

  • 行首单行注释///
  • 行尾单行注释//!<
  • 行首多行注释/** */

通用二级标签

Xcode 基本支持所有的二级标签颜色加深的功能。二级标签可以在生成文档、联想和使用 Quick Help 时显示。不加任何标签的情况的下生成文档,单行注释默认为简要描述。多行注释默认为详细描述。在使用多行注释的时候也可以在不加标签的情况下同时生成简要描述和详细描述

/** 简要描述
 * 详细描述
 */
///简要描述.详细描述

多行注释中的简要描述也可以用@brief来代替。除此之外还有很多支持的二级标签。这里列举一些 AppleDoc 和 Doxygen 均支持的常用二级标签。

/**
 * @brief : 简要注释. appledoc中仅对属性、方法有效,对类、协议 无效,会造成后续内容解析失败.
 * @param <name> <description>: 参数描述.
 * @return <description>: 返回值描述.
 * @exception <name> <description>: 异常描述.
 * @see <name>: 参见.
 * @sa <name>: 参见. 同@see.
 * @warning <text>: 警告.
 * @bug <text>: 警告.
 * @name <title>: 组名. 用于给成员们分组, 既文档中Tasks区的子类别.
 * 代码块 `int sum = 0;`
 * 多行代码块:
 *     int sum = 0;
 *     for(int i = 1; i <= 10; i++) {
 *         sum += i;
 *     }
 * 无序列表:
 * - first
 * - second
 * - third
 * 有序列表:
 * 1. first
 * 2. second
 * 3. third
 * 多级列表:
 * - abc
 *    - a
 *    - b
 *    - c
 * - rgb
 *    - red
 *        1. first.
 *            1. alpha.
 *            2. beta.
 *        2. second.
 *        3. third.
 *    - green
 *    - blue
 * 链接:
 * <http://www.xuyafei.cn>
 * [xuyafei](<http://www.xuyafei.cn>)
 */
</code></pre> 
 <p>下边是 Quick Help 支持的常用二级标签。</p> 
 <pre><code>/**
 * @brief It converts temperature degrees from Celsius to Fahrenheit scale.
 * @param  fromCelcius The celsius degrees value.
 * @return float The degrees in the Fahrenheit scale.
 * @code
 *     float f = [self toCelsius:80];
 * @endcode
 * @remark This is a super-easy method.
 */
</code></pre> 
 <h2>Code Review</h2> 
 <hr> 
 <p>换行、注释、方法长度、代码重复等这些是通过机器检查出来的问题,是无需通过人来做的。</p> 
 <p>而且除了审查需求的实现的程度,bug是否无处藏身以外,更应该关注代码的设计。比如类与类之间的耦合程度,设计的可扩展性,复用性,是否可以将某些方法抽出来作为接口等等。</p> 
 <h1>三. iOS规范</h1> 
 <h2>变量</h2> 
 <hr> 
 <h3>1. 变量名必须使用驼峰格式【M】</h3> 
 <p>类,协议使用大驼峰:</p> 
 <pre><code>HomePageViewController<HeaderViewDelegate>
</code></pre> 
 <p>对象等局部变量使用小驼峰:</p> 
 <pre><code>NSString *personName = @"";
NSUInteger totalCount = 0;
</code></pre> 
 <h3>2. 变量的名称必须同时包含功能与类型【S】</h3> 
 <pre><code>UIButton *addBtn //添加按钮
UILabel *nameLbl //名字标签
NSString *addressStr//地址字符串
</code></pre> 
 <h3>3. 系统常用类作实例变量声明时加入后缀【S】</h3> 
 <table> 
  <thead> 
   <tr> 
    <th>类型</th> 
    <th>后缀</th> 
   </tr> 
  </thead> 
  <tbody> 
   <tr> 
    <td>UIViewController</td> 
    <td>VC</td> 
   </tr> 
   <tr> 
    <td>UIView</td> 
    <td>View</td> 
   </tr> 
   <tr> 
    <td>UILabel</td> 
    <td>Lbl</td> 
   </tr> 
   <tr> 
    <td>UIButton</td> 
    <td>Btn</td> 
   </tr> 
   <tr> 
    <td>UIImage</td> 
    <td>Img</td> 
   </tr> 
   <tr> 
    <td>UIImageView</td> 
    <td>ImagView</td> 
   </tr> 
   <tr> 
    <td>NSArray</td> 
    <td>Array</td> 
   </tr> 
   <tr> 
    <td>NSMutableArray</td> 
    <td>Marray</td> 
   </tr> 
   <tr> 
    <td>NSDictionary</td> 
    <td>Dict</td> 
   </tr> 
   <tr> 
    <td>NSMutableDictionary</td> 
    <td>Mdict</td> 
   </tr> 
   <tr> 
    <td>NSString</td> 
    <td>Str</td> 
   </tr> 
   <tr> 
    <td>NSMutableString</td> 
    <td>MStr</td> 
   </tr> 
   <tr> 
    <td>NSSet</td> 
    <td>Set</td> 
   </tr> 
   <tr> 
    <td>NSMutableSet</td> 
    <td>Mset</td> 
   </tr> 
  </tbody> 
 </table> 
 <h2>常量</h2> 
 <hr> 
 <h3>1. 常量以相关类名作为前缀【S】</h3> 
 <p>推荐这样写:</p> 
 <pre><code>static const NSTimeInterval ZOCSignInViewControllerFadeOutAnimationDuration = 0.4;
</code></pre> 
 <p>不推荐这样写:</p> 
 <pre><code>static const NSTimeInterval fadeOutTime = 0.4;
</code></pre> 
 <h3>2. 建议使用类型常量,不建议使用#define预处理命令【S】</h3> 
 <p>首先比较一下这两种声明常量的区别:</p> 
 <ul> 
  <li>预处理命令:简单的文本替换,不包括类型信息,并且可被任意修改。</li> 
  <li>类型常量:包括类型信息,并且可以设置其使用范围,而且不可被修改。</li> 
 </ul> 
 <p>使用预处理虽然能达到替换文本的目的,但是本身还是有局限性的:</p> 
 <ul> 
  <li>不具备类型信息。</li> 
  <li>可以被任意修改。</li> 
 </ul> 
 <h3>3. 对外公开某个常量:【M】</h3> 
 <p>如果我们需要发送通知,那么就需要在不同的地方拿到通知的“频道”字符串(通知的名称),那么显然这个字符串是不能被轻易更改,而且可以在不同的地方获取。这个时候就需要定义一个外界可见的字符串常量。</p> 
 <p>推荐这样写:</p> 
 <pre><code>//头文件
extern NSString *const ZOCCacheControllerDidClearCacheNotification;
</code></pre> 
 <pre><code>//实现文件
static NSString * const ZOCCacheControllerDidClearCacheNotification = @"ZOCCacheControllerDidClearCacheNotification";
static const CGFloat ZOCImageThumbnailHeight = 50.0f;
</code></pre> 
 <p>不推荐这样写:</p> 
 <pre><code>#define CompanyName @"Apple Inc." 
#define magicNumber 42
</code></pre> 
 <h2>宏</h2> 
 <hr> 
 <h3>1. 宏、常量名都要使用大写字母,用下划线‘_’分割单词。【S】</h3> 
 <p>// TODO</p> 
 <pre><code>#define URL_GAIN_QUOTE_LIST @"/v1/quote/list"
#define URL_UPDATE_QUOTE_LIST @"/v1/quote/update"
#define URL_LOGIN  @"/v1/user/login”
</code></pre> 
 <h3>2. 宏定义中如果包含表达式或变量,表达式和变量必须用小括号括起来。</h3> 
 <pre><code>#define MY_MIN(A, B)  ((A)>(B)?(B):(A))
</code></pre> 
 <h2>CGRect函数</h2> 
 <hr> 
 <p>其实iOS内部已经提供了相应的获取CGRect各个部分的函数了,它们的可读性比较高,而且简短,推荐使用:</p> 
 <p>推荐这样写:</p> 
 <pre><code>CGRect frame = self.view.frame; 
CGFloat x = CGRectGetMinX(frame); 
CGFloat y = CGRectGetMinY(frame); 
CGFloat width = CGRectGetWidth(frame); 
CGFloat height = CGRectGetHeight(frame); 
CGRect frame = CGRectMake(0.0, 0.0, width, height);
</code></pre> 
 <p>而不是</p> 
 <pre><code>CGRect frame = self.view.frame;  
CGFloat x = frame.origin.x;  
CGFloat y = frame.origin.y;  
CGFloat width = frame.size.width;  
CGFloat height = frame.size.height;  
CGRect frame = (CGRect){ .origin = CGPointZero, .size = frame.size };
</code></pre> 
 <h2>范型</h2> 
 <hr> 
 <p>建议在定义NSArray和NSDictionary时使用泛型,可以保证程序的安全性:</p> 
 <pre><code>NSArray<NSString *> *testArr = [NSArray arrayWithObjects:@"Hello", @"world", nil];
NSDictionary<NSString *, NSNumber *> *dic = @{@"key":@(1), @"age":@(10)};
</code></pre> 
 <h2>Block</h2> 
 <hr> 
 <p>为常用的Block类型创建typedef 【S】</p> 
 <p>如果我们需要重复创建某种block(相同参数,返回值)的变量,我们就可以通过typedef来给某一种块定义属于它自己的新类型</p> 
 <p>例如:</p> 
 <pre><code>int (^variableName)(BOOL flag, int value) =^(BOOL flag, int value){
     // Implementation
     return someInt;
}
</code></pre> 
 <p>这个Block有一个bool参数和一个int参数,并返回int类型。我们可以给它定义类型:</p> 
 <pre><code>int(^EOCSomeBlock)(BOOL flag, int value);
</code></pre> 
 <p>再次定义的时候,就可以通过简单的赋值来实现:</p> 
 <pre><code>EOCSomeBlock block = ^(BOOL flag, int value){
// Implementation
};
</code></pre> 
 <p>定义作为参数的Block:</p> 
 <pre><code>- (void)startWithCompletionHandler: (void(^)(NSData *data, NSError *error))completion;
</code></pre> 
 <p>这里的Block有一个NSData参数,一个NSError参数并没有返回值</p> 
 <pre><code>typedef void(^EOCCompletionHandler)(NSData *data, NSError *error);

- (void)startWithCompletionHandler:(EOCCompletionHandler)
</code></pre> 
 <p>通过typedef定义Block签名的好处是:如果要某种块增加参数,那么只修改定义签名的那行代码</p> 
 <h2>字面量</h2> 
 <p>尽量使用字面量值来创建 NSString , NSDictionary , NSArray , NSNumber 这些不可变【S】</p> 
 <p>推荐这样写:</p> 
 <pre><code>    NSArray *names = @[@"Brian", @"Matt", @"Chris", @"Alex", @"Steve", @"Paul"];
    NSDictionary *productManagers = @{@"iPhone" : @"Kate", @"iPad" : @"Kamal", @"Mobile Web" : @"Bill"}; 
    NSNumber *shouldUseLiterals = @YES;NSNumber *buildingZIPCode = @10018;
</code></pre> 
 <p>不推荐这样写:</p> 
 <pre><code>NSArray *names = [NSArray arrayWithObjects:@"Brian", @"Matt", @"Chris", @"Alex", @"Steve", @"Paul", nil];
NSDictionary *productManagers = [NSDictionary dictionaryWithObjectsAndKeys: @"Kate", @"iPhone", @"Kamal", @"iPad", @"Bill" ];
NSNumber *shouldUseLiterals = [NSNumber numberWithBool:YES];NSNumber *buildingZIPCode = [NSNumber numberWithInteger:10018];
</code></pre> 
 <h2>属性</h2> 
 <hr> 
 <h3>1. 属性的命名使用小驼峰【M】</h3> 
 <p>推荐这样写:</p> 
 <pre><code>@property (nonatomic, readwrite, strong) UIButton *confirmButton;
</code></pre> 
 <h3>2. 属性的关键字推荐按照 原子性,读写,内存管理的顺序排列【M】</h3> 
 <p>推荐这样写:</p> 
 <pre><code>@property (nonatomic, readwrite,   copy) NSString *name;
@property (nonatomic, readonly,    copy) NSString *gender;
@property (nonatomic, readwrite, strong) UIView *headerView;
</code></pre> 
 <h3>3. Block属性应该使用copy关键字 【M】</h3> 
 <p>推荐这样写:</p> 
 <pre><code>typedef void (^ErrorCodeBlock) (id errorCode, NSString *message);
@property (nonatomic, readwrite, copy) ErrorCodeBlock errorBlock;//将block拷贝到堆中
</code></pre> 
 <h3>4. 形容词性的BOOL属性的getter应该加上is前缀【S】</h3> 
 <p>推荐这样写:</p> 
 <pre><code>@property (assign, getter=isEditable) BOOL editable;
</code></pre> 
 <h3>5. 使用getter方法做懒加载 【S】</h3> 
 <p>实例化一个对象是需要耗费资源的,如果这个对象里的某个属性的实例化要调用很多配置和计算,就需要懒加载它,在使用它的前一刻对它进行实例化:</p> 
 <pre><code>- (NSDateFormatter *)dateFormatter {
    if (!_dateFormatter) {
        _dateFormatter = [[NSDateFormatter alloc] init];
        NSLocale *enUSPOSIXLocale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"];
        [_dateFormatter setLocale:enUSPOSIXLocale];
        [_dateFormatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ss.SSS"];
    } 
    return _dateFormatter;
}
</code></pre> 
 <p>但是也有对这种做法的争议:getter方法可能会产生某些副作用,例如如果它修改了全局变量,可能会产生难以排查的错误。</p> 
 <h3>6. 除了init和dealloc方法,建议都使用点语法访问属性【M】</h3> 
 <p>使用点语法的好处:</p> 
 <h4>setter:</h4> 
 <ol> 
  <li>setter会遵守内存管理语义(strong, copy, weak)。</li> 
  <li>通过在内部设置断点,有助于调试bug。</li> 
  <li>可以过滤一些外部传入的值。</li> 
  <li>捕捉KVO通知。</li> 
 </ol> 
 <h4>getter:</h4> 
 <ol> 
  <li>允许子类化。</li> 
  <li>通过在内部设置断点,有助于调试bug。</li> 
  <li>实现懒加载(lazy initialization)。</li> 
 </ol> 
 <blockquote> 
  <p>注意:</p> 
  <ol> 
   <li>懒加载的属性,必须通过点语法来读取数据。因为懒加载是通过重写getter方法来初始化实例变量的,如果不通过属性来读取该实例变量,那么这个实例变量就永远不会被初始化。</li> 
   <li>在init和dealloc方法里面使用点语法的后果是:因为没有绕过setter和getter,在setter和getter里面可能会有很多其他的操作。而且如果它的子类重载了它的setter和getter方法,那么就可能导致该子类调用其他的方法。</li> 
  </ol> 
 </blockquote> 
 <h3>7. 不要滥用点语法,要区分好方法调用和属性访问【S】</h3> 
 <p>推荐这样写:</p> 
 <pre><code>view.backgroundColor = [UIColor orangeColor]; 
[UIApplication sharedApplication].delegate;
</code></pre> 
 <p>不推荐这样写:</p> 
 <pre><code>[view setBackgroundColor:[UIColor orangeColor]]; 
UIApplication.sharedApplication.delegate;
</code></pre> 
 <h3>8. 尽量使用不可变对象【S】</h3> 
 <p>建议尽量把对外公布出来的属性设置为只读,在实现文件内部设为读写。具体做法是:</p> 
 <ul> 
  <li>在头文件中,设置对象属性为</li> 
 </ul> 
 <ul> 
  <li> <p>在实现文件中设置为<code>readwrite</code>。</p> <p>这样一来,在外部就只能读取该数据,而不能修改它,使得这个类的实例所持有的数据更加安全。而且,对于集合类的对象,更应该仔细考虑是否可以将其设为可变的。</p> <p>如果在公开部分只能设置其为只读属性,那么就在非公开部分存储一个可变型。所以当在外部获取这个属性时,获取的只是内部可变型的一个不可变版本,例如:</p> <p>在公共API中:</p> <pre><code>@interface EOCPerson : NSObject

@property (nonatomic, copy, readonly) NSString *firstName;
@property (nonatomic, copy, readonly) NSString *lastName;
@property (nonatomic, strong, readonly) NSSet *friends //向外公开的不可变集合

*   (id)initWithFirstName:(NSString*)firstName andLastName:(NSString*)lastName;
*   (void)addFriend:(EOCPerson*)person;
*   (void)removeFriend:(EOCPerson*)person;

@end
</code></pre> 
   <blockquote> 
    <p>在这里,我们将friends属性设置为不可变的set。然后,提供了来增加和删除这个set里的元素的公共接口。</p> 
   </blockquote> <p>在实现文件里:</p> <pre><code>@interface EOCPerson ()

@property (nonatomic, copy, readwrite) NSString *firstName;
@property (nonatomic, copy, readwrite) NSString *lastName;

@end

@implementation EOCPerson {
NSMutableSet *_internalFriends; //实现文件里的可变集合
}

- (NSSet*)friends {
    return [_internalFriends copy]; //get方法返回的永远是可变set的不可变型
    }

- (void)addFriend:(EOCPerson*)person {
    [_internalFriends addObject:person]; //在外部增加集合元素的操作
    //do something when add element
    }

- (void)removeFriend:(EOCPerson*)person {
    [_internalFriends removeObject:person]; //在外部移除元素的操作
    //do something when remove element
    }

- (id)initWithFirstName:(NSString*)firstName andLastName:(NSString*)lastName {

    if ((self = [super init])) {

    _firstName = firstName;
    _lastName = lastName;
    _internalFriends = [NSMutableSet new];

    }
    return self;
}
</code></pre> 
   <blockquote> 
    <p>我们可以看到,在实现文件里,保存一个可变set来记录外部的增删操作。</p> 
   </blockquote> <p>这里最重要的代码是:</p> <pre><code>- (NSSet*)friends {
    return [_internalFriends copy];
    }
</code></pre> <p>这个是friends属性的获取方法:它将当前保存的可变set复制了一不可变的set并返回。因此,外部读取到的set都将是不可变的</p> </li> 
 </ul> 
 <h2>方法</h2> 
 <hr> 
 <h3>1. 方法名中不应使用and,而且签名要与对应的参数名保持高度一致【M】</h3> 
 <p>推荐这样</p> 
 <pre><code class="objc">- (instancetype)initWithWidth:(CGFloat)width height:(CGFloat)height;
</code></pre> 
 <p>不推荐这样写:</p> 
 <pre><code>- (instancetype)initWithWidth:(CGFloat)width andHeight:(CGFloat)height;
- (instancetype)initWith:(int)width and:(int)height;
</code></pre> 
 <h3>2. 方法实现时,如果参数过长,则令每个参数占用一行,以冒号对齐。【S】</h3> 
 <pre><code>- (void)doSomethingWith:(NSString *)theFoo
                   rect:(CGRect)theRect
               interval:(CGFloat)theInterval
{
 //Implementation
}
</code></pre> 
 <h3>3. 私有方法应该在实现文件中申明。【S】</h3> 
 <pre><code>@interface ViewController ()
- (void)basicConfiguration;
@end

@implementation ViewController
- (void)basicConfiguration
{
 //Do some basic configuration
}
@end
</code></pre> 
 <h3>4. 方法名用小写字母开头的单词组合而成【M】</h3> 
 <pre><code>- (NSString *)descriptionWithLocale:(id)locale;
</code></pre> 
 <h3>5. 方法名前缀【S】</h3> 
 <ul> 
  <li>刷新视图的方法名要以<code>refresh</code>为首。</li> 
  <li>更新数据的方法名要以<code>update</code>或者<code>configure</code>为首。</li> 
 </ul> 
 <p>推荐这样写:</p> 
 <pre><code>- (void)refreshHeaderViewWithCount:(NSUInteger)count;
- (void)updateDataSourceWithViewModel:(ViewModel *)viewModel;
</code></pre> 
 <h2>面向协议编程</h2> 
 <hr> 
 <p>如果某些功能(方法)具备可复用性,我们就需要将它们抽取出来放入一个抽象接口文件中(在iOS中,抽象接口即协议),让不同类型的对象遵循这个协议,从而拥有相同的功能。</p> 
 <p>因为协议是不依赖于某个对象的,所以通过协议,我们可以解开两个对象之间的耦合。如何理解呢?我们来看一下下面这个例子:</p> 
 <p>现在有一个需求:在一个<code>UITableViewController</code>里面拉取feed并展示出来。</p> 
 <h3>方案一:</h3> 
 <p>定义一个拉取feed的类<code>ZOCFeedParser</code>,这个类有一些代理方法实现feed相关功能:</p> 
 <pre><code>@protocol ZOCFeedParserDelegate <NSObject>

@optional
- (void)feedParserDidStart:(ZOCFeedParser *)parser;
- (void)feedParser:(ZOCFeedParser *)parser didParseFeedInfo:(ZOCFeedInfoDTO *)info; 
- (void)feedParser:(ZOCFeedParser *)parser didParseFeedItem:(ZOCFeedItemDTO *)item; 
- (void)feedParserDidFinish:(ZOCFeedParser *)parser;
- (void)feedParser:(ZOCFeedParser *)parser didFailWithError:(NSError *)error;@end 

@interface ZOCFeedParser : NSObject

@property (nonatomic, weak  ) id <ZOCFeedParserDelegate> delegate; 
@property (nonatomic, strong) NSURL *url; 

- (id)initWithURL:(NSURL *)url; 
- (BOOL)start; 
- (void)stop; 

@end
</code></pre> 
 <p>然后在<code>ZOCTableViewController</code>里面传入<code>ZOCFeedParser</code>,并遵循其代理方法,实现feed的拉取功能。</p> 
 <pre><code>@interface ZOCTableViewController : UITableViewController<ZOCFeedParserDelegate>
- (instancetype)initWithFeedParser:(ZOCFeedParser *)feedParser; 
@end
</code></pre> 
 <p>具体应用:</p> 
 <pre><code>NSURL *feedURL = [NSURL URLWithString:@"http://bbc.co.uk/feed.rss"]; 
ZOCFeedParser *feedParser = [[ZOCFeedParser alloc] initWithURL:feedURL]; 
ZOCTableViewController *tableViewController = [[ZOCTableViewController alloc] initWithFeedParser:feedParser]; 
feedParser.delegate = tableViewController;
</code></pre> 
 <p>OK,现在我们实现了需求:在<code>ZOCTableViewController</code>里面存放了一个<code>ZOCFeedParser</code>对象来处理feed的拉取功能。</p> 
 <p>但这里有一个严重的耦合问题:<code>ZOCTableViewController</code>只能通过<code>ZOCFeedParser</code>对象来处理feed的拉取功能。<br> 于是我们重新审视一下这个需求:其实我们实际上只需要<code>ZOCTableViewController</code>拉取feed就可以了,而具体是由哪个对象来拉取,<code>ZOCTableViewController</code>并不需要关心。</p> 
 <p>也就是说,我们需要提供给<code>ZOCTableViewController</code>的是一个更范型的对象,这个对象具备了拉取feed的功能就好了,而不应该仅仅局限于某个具体的对象(<code>ZOCFeedParser</code>)。所以,刚才的设计需要重新做一次修改:</p> 
 <h3>方案二:</h3> 
 <p>首先需要在一个接口文件<code>ZOCFeedParserProtocol.h</code>里面定义抽象的,具有拉取feed功能的协议:</p> 
 <pre><code>@protocol ZOCFeedParserDelegate <NSObject>

@optional
- (void)feedParserDidStart:(id<ZOCFeedParserProtocol>)parser;
- (void)feedParser:(id<ZOCFeedParserProtocol>)parser didParseFeedInfo:(ZOCFeedInfoDTO *)info; 
- (void)feedParser:(id<ZOCFeedParserProtocol>)parser didParseFeedItem:(ZOCFeedItemDTO *)item; 
- (void)feedParserDidFinish:(id<ZOCFeedParserProtocol>)parser;
- (void)feedParser:(id<ZOCFeedParserProtocol>)parser didFailWithError:(NSError *)error;

@end 

@protocol ZOCFeedParserProtocol <NSObject>

@property (nonatomic, weak) id <ZOCFeedParserDelegate> delegate; 
@property (nonatomic, strong) NSURL *url;

- (BOOL)start;
- (void)stop;

@end
</code></pre> 
 <p>而原来的<code>ZOCFeedParser</code>仅仅是需要遵循上面这个协议就具备了拉取feed的功能:</p> 
 <pre><code>@interface ZOCFeedParser : NSObject <ZOCFeedParserProtocol> 
- (id)initWithURL:(NSURL *)url;//仅仅需要通过传入url即可,其他事情都交给ZOCFeedParserProtocol@end
</code></pre> 
 <p>而且,<code>ZOCTableViewController</code>也不直接依赖于<code>ZOCFeedParser</code>对象,我们只需要传给它一个遵循<code><ZOCFeedParserProtocol></code>的对象即可。</p> 
 <pre><code>@interface ZOCTableViewController : UITableViewController <ZOCFeedParserDelegate>
- (instancetype)initWithFeedParser:(id<ZOCFeedParserProtocol>)feedParser;
@end
</code></pre> 
 <p>这样一来,<code>ZOCTableViewController</code>和<code>ZOCFeedParser</code>之间就没有直接的关系了。以后,如果我们想:</p> 
 <ul> 
  <li>给这个feed拉取器增加新的功能:仅需要修改<code>ZOCFeedParserProtocol.h</code>文件。</li> 
  <li>更换一个feed拉取器实例:创建一个新类型来遵循<code>ZOCFeedParserProtocol.h</code>即可。</li> 
 </ul> 
 <h2>iOS 中委托的设计</h2> 
 <hr> 
 <h3>1. 要区分好代理和数据源的区别【S】</h3> 
 <p>在iOS开发中的委托模式包含了delegate(代理)和datasource(数据源)。虽然二者同属于委托模式,但是这两者是有区别的。这个区别就是二者的信息流方向是不同的:</p> 
 <ul> 
  <li>delegate :事件发生的时候,委托者需要通知代理。(信息流从委托者到代理)</li> 
  <li>datasource:委托者需要从数据源拉取数据。(信息流从数据源到委托者)</li> 
 </ul> 
 <p>然而包括苹果也没有做好榜样,将它们彻底的区分开。就拿UITableView来说,在它的delegate方法中有一个方法:</p> 
 <pre><code>- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath;
</code></pre> 
 <p>这个方法正确地体现了代理的作用:委托者(tableview)告诉代理(控制器)“我的某个cell被点击了”。但是,UITableViewDelegate的方法列表里还有这个方法:</p> 
 <pre><code>- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath;
</code></pre> 
 <p>该方法的作用是 由控制器来告诉tabievlew的行高,也就是说,它的信息流是从控制器(数据源)到委托者(tableview)的。准确来讲,它应该是一个数据源方法,而不是代理方法。</p> 
 <p>在UITableViewDataSource中,就有标准的数据源方法:</p> 
 <pre><code>- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView;
</code></pre> 
 <p>这个方法的作用就是让tableview向控制器拉取一个section数量的数据。</p> 
 <p>所以,在我们设计一个视图控件的代理和数据源时,一定要区分好二者的区别,合理地划分哪些方法属于代理方法,哪些方法属于数据源方法。</p> 
 <h3>2. 代理方法的第一个参数必须为委托者【S】</h3> 
 <p>代理方法必须以委托者作为第一个参数(参考UITableViewDelegate)的方法。其目的是为了区分不同委托着的实例。因为同一个控制器是可以作为多个tableview的代理的。若要区分到底是哪个tableview的cell被点击了,就需要在方法中做个区分</p> 
 <pre><code>- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath。
</code></pre> 
 <h3>向代理发送消息时需要判断其是否实现该方法</h3> 
 <p>最后,在委托着向代理发送消息的时候,需要判断委托着是否实现了这个代理方法:</p> 
 <pre><code>if ([self.delegate respondsToSelector:@selector(signUpViewControllerDidPressSignUpButton:)]) { 
    [self.delegate signUpViewControllerDidPressSignUpButton:self]; 
}
</code></pre> 
 <h3>3. 遵循代理过多的时候,换行对齐显示【S】</h3> 
 <pre><code>@interface ShopViewController () <UIGestureRecognizerDelegate,
                                  HXSClickEventDelegate,
                                  UITableViewDelegate,
                                  UITableViewDataSource>
</code></pre> 
 <h3>4. 代理的方法需要明确必须执行和可不执行</h3> 
 <p>代理方法在默认情况下都是必须执行的,然而在设计一组代理方法的时候,有些方法可以不是必须执行(是因为存在默认配置),这些方法就需要使用<code>@optional</code>关键字来修饰:</p> 
 <pre><code>@protocol ZOCServiceDelegate <NSObject>
@optional
- (void)generalService:(ZOCGeneralService *)service didRetrieveEntries:(NSArray *)entries; 
@end
</code></pre> 
 <h2>类</h2> 
 <hr> 
 <h3>1. 类的名称应该以三个大或者两个写字母为前缀;创建子类的时候,应该把代表子类特点的部分放在前缀和父类名的中间【S】</h3> 
 <p>推荐这样写:</p> 
 <pre><code>//父类
ZOCSalesListViewController

//子类
ZOCDaySalesListViewController
ZOCMonthSalesListViewController
</code></pre> 
 <h3>2. initializer && dealloc</h3> 
 <p>推荐:</p> 
 <ul> 
  <li>将 dealloc 方法放在实现文件的最前面</li> 
  <li>将init方法放在dealloc方法后面。如果有多个初始化方法,应该将指定初始化方法放在最前面,其他初始化方法放在其后。</li> 
 </ul> 
 <h4>2.1 dealloc方法里面应该直接访问实例变量,不应该用点语法访问 【M】</h4> 
 <h4>2.2 init方法的写法:【M】</h4> 
 <ul> 
  <li>init方法返回类型必须是instancetype,不能是id。</li> 
  <li>必须先实现[super init]。</li> 
 </ul> 
 <pre><code>- (instancetype)init { 
 self = [super init]; // call the designated initializer 
 if (self) {
 // Custom initialization
 }
 return self;
}
</code></pre> 
 <h4>2.3 指定初始化方法【S】</h4> 
 <p>指定初始化方法(designated initializer)是提供所有的(最多的)参数的初始化方法,间接初始化方法(secondary initializer)有一个或部分参数的初始化方法。</p> 
 <p>注意事项1:间接初始化方法必须调用指定初始化方法。</p> 
 <pre><code>@implementation ZOCEvent 

//指定初始化方法
- (instancetype)initWithTitle:(NSString *)title date:(NSDate *)date 
location:(CLLocation *)location
{ 
 self = [super init];
 if (self) {
    _title = title;
    _date = date;
    _location = location;
 }
 return self;
} 

//间接初始化方法
- (instancetype)initWithTitle:(NSString *)title date:(NSDate *)date
{ 
    return [self initWithTitle:title date:date location:nil];
}

//间接初始化方法
- (instancetype)initWithTitle:(NSString *)title 
{ 
    return [self initWithTitle:title date:[NSDate date] location:nil];
}

@end
</code></pre> 
 <p>注意事项2:如果直接父类有指定初始化方法,则必须调用其指定初始化方法</p> 
 <pre><code>- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; 
    if (self) {
    }
    return self; 
}
</code></pre> 
 <p>注意事项3:如果想在当前类自定义一个新的全能初始化方法,则需要如下几个步骤</p> 
 <ol> 
  <li>定义新的指定初始化方法,并确保调用了直接父类的初始化方法。</li> 
  <li>重载直接父类的初始化方法,在内部调用新定义的指定初始化方法。</li> 
  <li>为新的指定初始化方法写文档。</li> 
 </ol> 
 <p>看一个标准的例子:</p> 
 <pre><code>@implementation ZOCNewsViewController

//新的指定初始化方法
- (id)initWithNews:(ZOCNews *)news {
    self = [super initWithNibName:nil bundle:nil]; 
    if (self) {
    _news = news;
    }
    return self;
} 

// 重载父类的初始化方法
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil{
    return [self initWithNews:nil]; 
}
@end
</code></pre> 
 <p>在这里,重载父类的初始化方法并在内部调用新定义的指定初始化方法的原因是你不能确定调用者调用的就一定是你定义的这个新的指定初始化方法,而不是原来从父类继承来的指定初始化方法。</p> 
 <p>假设你没有重载父类的指定初始化方法,而调用者却恰恰调用了父类的初始化方法。那么调用者可能永远都调用不到你自己定义的新指定初始化方法了。</p> 
 <p>而如果你成功定义了一个新的指定初始化方法并能保证调用者一定能调用它,你最好要在文档中明确写出哪一个才是你定义的新初始化方法。或者你也可以使用编译器指令<code>__attribute__((objc_designated_initializer))</code>来标记它。</p> 
 <h3>3. 所有返回类对象和实例对象的方法都应该使用instancetype【S】</h3> 
 <p>将instancetype关键字作为返回值的时候,可以让编译器进行类型检查,同时适用于子类的检查,这样就保证了返回类型的正确性(一定为当前的类对象或实例对象)</p> 
 <p>推荐这样写:</p> 
 <pre><code>@interface ZOCPerson
+ (instancetype)personWithName:(NSString *)name; 
@end
</code></pre> 
 <p>不推荐这样写:</p> 
 <pre><code>@interface ZOCPerson
+ (id)personWithName:(NSString *)name; 
@end
</code></pre> 
 <h3>4. 在类的头文件中尽量少引用其他头文件【S】</h3> 
 <p>有时,类A需要将类B的实例变量作为它公共API的属性。这个时候,我们不应该引入类B的头文件,而应该使用向前声明(forward declaring)使用class关键字,并且在A的实现文件引用B的头文件。</p> 
 <pre><code>// EOCPerson.h
#import <Foundation/Foundation.h>

@class EOCEmployer;

@interface EOCPerson : NSObject

@property (nonatomic, copy  ) NSString *firstName;
@property (nonatomic, copy  ) NSString *lastName;
@property (nonatomic, strong) EOCEmployer *employer;//将EOCEmployer作为属性

@end

// EOCPerson.m
#import "EOCEmployer.h"
</code></pre> 
 <p>这样做有什么优点呢:</p> 
 <blockquote> 
  <ul> 
   <li>不在A的头文件中引入B的头文件,就不会一并引入B的全部内容,这样就减少了编译时间。</li> 
   <li>可以避免循环引用:因为如果两个类在自己的头文件中都引入了对方的头文件,那么就会导致其中一个类无法被正确编译。</li> 
  </ul> 
 </blockquote> 
 <p>但是个别的时候,必须在头文件中引入其他类的头文件:</p> 
 <blockquote> 
  <p>主要有两种情况:</p> 
  <ol> 
   <li>该类继承于某个类,则应该引入父类的头文件。</li> 
   <li>该类遵从某个协议,则应该引入该协议的头文件。而且最好将协议单独放在一个头文件中。</li> 
  </ol> 
 </blockquote> 
 <h3>5. 类的布局【S】</h3> 
 <pre><code>#pragma mark - Life Cycle Methods
- (instancetype)init
- (void)dealloc

- (void)viewWillAppear:(BOOL)animated
- (void)viewDidAppear:(BOOL)animated
- (void)viewWillDisappear:(BOOL)animated
- (void)viewDidDisappear:(BOOL)animated

#pragma mark - Override Methods

#pragma mark - Intial Methods

#pragma mark - Network Methods

#pragma mark - Target Methods

#pragma mark - Public Methods

#pragma mark - Private Methods

#pragma mark - UITableViewDataSource  
#pragma mark - UITableViewDelegate  

#pragma mark - Lazy Loads

#pragma mark - NSCopying  

#pragma mark - NSObject  Methods
</code></pre> 
 <h2>分类</h2> 
 <hr> 
 <h3>1. 分类添加的方法需要添加前缀和下划线【S】</h3> 
 <p>推荐这样写:</p> 
 <pre><code>@interface NSDate (ZOCTimeExtensions)
 - (NSString *)zoc_timeAgoShort;
@end
</code></pre> 
 <p>不推荐这样写:</p> 
 <pre><code>@interface NSDate (ZOCTimeExtensions) 
- (NSString *)timeAgoShort;
@end
</code></pre> 
 <h3>2. 把类的实现代码分散到便于管理的多个分类中【S】</h3> 
 <p>一个类可能会有很多公共方法,而且这些方法往往可以用某种特有的逻辑来分组。我们可以利用Objecctive-C的分类机制,将类的这些方法按一定的逻辑划入几个分区中。</p> 
 <p>举个:</p> 
 <p>先看一个没有使用无分类的类:</p> 
 <pre><code>#import <Foundation/Foundation.h>

@interface EOCPerson : NSObject

@property (nonatomic, copy,   readonly) NSString *firstName;
@property (nonatomic, copy,   readonly) NSString *lastName;
@property (nonatomic, strong, readonly) NSArray *friends;

- (id)initWithFirstName:(NSString *)firstName andLastName:(NSString *)lastName;

/* Friendship methods */
- (void)addFriend:(EOCPerson *)person;
- (void)removeFriend:(EOCPerson *)person;
- (BOOL)isFriendsWith:(EOCPerson *)person;

/* Work methods */
- (void)performDaysWork;
- (void)takeVacationFromWork;

/* Play methods */
- (void)goToTheCinema;
- (void)goToSportsGame;

@end
</code></pre> 
 <p>分类之后:</p> 
 <pre><code>#import <Foundation/Foundation.h>

@interface EOCPerson : NSObject

@property (nonatomic, copy,   readonly) NSString *firstName;
@property (nonatomic, copy,   readonly) NSString *lastName;
@property (nonatomic, strong, readonly) NSArray *friends;

- (id)initWithFirstName:(NSString *)firstName andLastName:(NSString *)lastName;

@end

@interface EOCPerson (Friendship)

- (void)addFriend:(EOCPerson *)person;
- (void)removeFriend:(EOCPerson *)person;
- (BOOL)isFriendsWith:(EOCPerson *)person;

@end

@interface EOCPerson (Work)

- (void)performDaysWork;
- (void)takeVacationFromWork;

@end

@interface EOCPerson (Play)

- (void)goToTheCinema;
- (void)goToSportsGame;

@end
</code></pre> 
 <p>其中,FriendShip分类的实现代码可以这么写:</p> 
 <pre><code>// EOCPerson+Friendship.h
#import "EOCPerson.h"

@interface EOCPerson (Friendship)

- (void)addFriend:(EOCPerson *)person;
- (void)removeFriend:(EOCPerson *)person;
- (BOOL)isFriendsWith:(EOCPerson *)person;

@end

// EOCPerson+Friendship.m
#import "EOCPerson+Friendship.h"

@implementation EOCPerson (Friendship)

- (void)addFriend:(EOCPerson *)person {
 /* ... */
}

- (void)removeFriend:(EOCPerson *)person {
 /* ... */
}

- (BOOL)isFriendsWith:(EOCPerson *)person {
 /* ... */
}

@end
</code></pre> 
 <blockquote> 
  <p>注意:在新建分类文件时,一定要引入被分类的类文件。</p> 
 </blockquote> 
 <p>通过分类机制,可以把类代码分成很多个易于管理的功能区,同时也便于调试。因为分类的方法名称会包含分类的名称,可以马上看到该方法属于哪个分类中。</p> 
 <p>利用这一点,我们可以创建名为Private的分类,将所有私有方法都放在该类里。这样一来,我们就可以根据private一词的出现位置来判断调用的合理性,这也是一种编写“自我描述式代码(self-documenting)”的办法。</p> 
 <h2>单例</h2> 
 <hr> 
 <h3>1. 单例不能作为容器对象来使用【M】</h3> 
 <p>单例对象不应该暴露出任何属性,也就是说它不能作为让外部存放对象的容器。它应该是一个处理某些特定任务的工具,比如在iOS中的GPS和加速度传感器。我们只能从他们那里得到一些特定的数据。</p> 
 <h3>2. 使用dispatch_once来生成单例【M】</h3> 
 <p>推荐这样写:</p> 
 <pre><code>+ (instancetype)sharedInstance { 
     static id sharedInstance = nil; 
     static dispatch_once_t onceToken = 0;
     dispatch_once(&onceToken, ^{ 
        sharedInstance = [[self alloc] init];
     }); 
     return sharedInstance; 
}
</code></pre> 
 <p>不推荐这样写:</p> 
 <pre><code>+ (instancetype)sharedInstance { 
    static id sharedInstance; 
    @synchronized(self) { 
        if (sharedInstance == nil) {  
            sharedInstance = [[MyClass alloc] init]; 
        } 
    } 
    return sharedInstance; 
}
</code></pre> 
 <h2>相等性的判断【S】</h2> 
 <hr> 
 <p>判断两个person类是否相等的合理做法:</p> 
 <pre><code>-  (BOOL)isEqual:(id)object {

    if (self == object) {  
        return YES; //判断内存地址
    } 
    if (![object isKindOfClass:[ZOCPerson class]]) { 
        return NO; //是否为当前类或派生类 
    } 
    
    return [self isEqualToPerson:(ZOCPerson *)object]; 
}

//自定义的判断相等性的方法
-  (BOOL)isEqualToPerson:(Person *)person { 
    if (!person) {  
        return NO;
    } 

    BOOL namesMatch = (!self.name && !person.name) || [self.name isEqualToString:person.name]; 
    BOOL birthdaysMatch = (!self.birthday && !person.birthday) || [self.birthday isEqualToDate:person.birthday];
    
    return haveEqualNames && haveEqualBirthdays; 
}
</code></pre> 
 <h2>方法文档</h2> 
 <hr> 
 <p>一个函数(方法)必须有一个字符串文档来解释,除非它:</p> 
 <ul> 
  <li>非公开,私有函数。</li> 
  <li>很短。</li> 
  <li>显而易见。</li> 
 </ul> 
 <p>而其余的,包括公开接口,重要的方法,分类,以及协议,都应该伴随文档(注释):</p> 
 <ul> 
  <li>以/开始</li> 
  <li>第二行识总结性的语句</li> 
  <li>第三行永远是空行</li> 
  <li>在与第二行开头对齐的位置写剩下的注释。</li> 
 </ul> 
 <p>建议这样写:</p> 
 <pre><code>/* This comment serves to demonstrate the format of a doc string.

Note that the summary line is always at most one line long, and after the opening block comment,
and each line of text is preceded by a single space.
*/
</code></pre> 
 <p>看一个指定初始化方法的注释:</p> 
 <pre><code>/ *
  *  Designated initializer. *
  *  @param store The store for CRUD operations.
  *  @param searchService The search service used to query the store. 
  *  @return A ZOCCRUDOperationsStore object.
  */ 
- (instancetype)initWithOperationsStore:(id<ZOCGenericStoreProtocol>)store searchService:(id<ZOCGenericSearchServiceProtocol>)searchService;
</code></pre> 
 <h3>多用队列,少用同步锁来避免资源抢夺【S】</h3> 
 <hr> 
 <p>多个线程执行同一份代码时,很可能会造成数据不同步。建议使用GCD来为代码加锁的方式解决这个问题。</p> 
 <h4>方案一:使用串行同步队列来将读写操作都安排到同一个队列里:</h4> 
 <pre><code>_syncQueue = dispatch_queue_create("com.effectiveobjectivec.syncQueue", NULL);

//读取字符串
- (NSString*)someString {

    __block NSString *localSomeString;
    dispatch_sync(_syncQueue, ^{
        localSomeString = _someString;
    });

    return localSomeString;

}

//设置字符串
- (void)setSomeString:(NSString *)someString {

    dispatch_sync(_syncQueue, ^{
        _someString = someString;
    });
}
</code></pre> 
 <p>这样一来,读写操作都在串行队列进行,就不容易出错。</p> 
 <p>但是,还有一种方法可以让性能更高:</p> 
 <h4>方案二:将写操作放入栅栏快中,让他们单独执行;将读取操作并发执行。</h4> 
 <pre><code>_syncQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

//读取字符串
- (NSString*)someString {

     __block NSString *localSomeString;
     dispatch_sync(_syncQueue, ^{
        localSomeString = _someString;
    });
     return localSomeString;
}
</code></pre> 
 <pre><code>//设置字符串
- (void)setSomeString:(NSString*)someString {

     dispatch_barrier_async(_syncQueue, ^{
        _someString = someString;
    });
}
</code></pre> 
 <p>显然,数据的正确性主要取决于写入操作,那么只要保证写入时,线程是安全的,那么即便读取操作是并发的,也可以保证数据是同步的。<br> 这里的<code>dispatch_barrier_async</code>方法使得操作放在了同步队列里“有序进行”,保证了写入操作的任务是在串行队列里。</p> 
 <h2>实现description方法打印自定义对象信息【S】</h2> 
 <hr> 
 <p>在打印我们自己定义的类的实例对象时,在控制台输出的结果往往是这样的:<code>object = <EOCPerson: 0x7fd9a1600600></code></p> 
 <p>这里只包含了类名和内存地址,它的信息显然是不具体的,远达不到调试的要求。</p> 
 <p>但是!如果在我们自己定义的类覆写description方法,我们就可以在打印这个类的实例时输出我们想要的信息。</p> 
 <p>例如:</p> 
 <pre><code>- (NSString*)description {
     return [NSString stringWithFormat:@"<%@: %p, %@ %@>", [self class], self, firstName, lastName];
}
</code></pre> 
 <p>在这里,显示了内存地址,还有该类的所有属性。</p> 
 <p>而且,如果我们将这些属性值放在字典里打印,则更具有可读性:</p> 
 <pre><code>- (NSString*)description {

     return [NSString stringWithFormat:@"<%@: %p, {\n%@\n}",[self class],self,
    @{ @"title":_title,
       @"latitude":@(_latitude),
       @"longitude":@(_longitude)}
    ];
}
</code></pre> 
 <p>输出结果:</p> 
 <pre><code>location = <EOCLocation: 0x7f98f2e01d20, {
   latitude = "51.506";
   longitude = 0;
   title = London;
}
</code></pre> 
 <p>我们可以看到,通过重写<code>description</code>方法可以让我们更加了解对象的情况,便于后期的调试,节省开发时间。</p> 
 <p>Tips:在lldb处于断点状态时,可以重写<code>debugDescription</code>方法改变debug时输出样式</p> 
 <h2>NSArray& NSMutableArray</h2> 
 <hr> 
 <h3>1. addObject之前要非空判断。</h3> 
 <h3>2. 取下标的时候要判断是否越界。</h3> 
 <h3>3. 取第一个元素或最后一个元素的时候使用firtstObject和lastObject</h3> 
 <h2>NSCache</h2> 
 <hr> 
 <h3>1. 构建缓存时选用NSCache 而非NSDictionary【S】</h3> 
 <p>如果我们缓存使用得当,那么应用程序的响应速度就会提高。只有那种“重新计算起来很费事的数据,才值得放入缓存”,比如那些需要从网络获取或从磁盘读取的数据。</p> 
 <p>在构建缓存的时候很多人习惯用NSDictionary或者NSMutableDictionary,但是作者建议大家使用NSCache,它作为管理缓存的类,有很多特点要优于字典,因为它本来就是为了管理缓存而设计的。</p> 
 <h3>2. NSCache优于NSDictionary的几点:</h3> 
 <ul> 
  <li>当系统资源将要耗尽时,NSCache具备自动删减缓冲的功能。并且还会先删减“最久未使用”的对象。</li> 
  <li>NSCache不拷贝键,而是保留键。因为并不是所有的键都遵从拷贝协议(字典的键是必须要支持拷贝协议的,有局限性)。</li> 
  <li>NSCache是线程安全的:不编写加锁代码的前提下,多个线程可以同时访问NSCache。</li> 
 </ul> 
 <h2>iOS 图片命名规范【S】</h2> 
 <hr> 
 <p>参考: iOS 图片命名规范</p> 
 <h2>git提交</h2> 
 <hr> 
 <ul> 
  <li>提交的commit必须能通过编译不能有error和多余的警告【M】</li> 
  <li>commit message 应该清晰明了,说明本次提交的目的【M】</li> 
 </ul> 
 <h2>NSNotification</h2> 
 <hr> 
 <h3>1. 通知的名称【M】</h3> 
 <p>建议将通知的名字作为常量,保存在一个专门的类中:</p> 
 <pre><code>// Const.h
extern NSString * const ZOCFooDidBecomeBarNotification

// Const.m
NSString * const ZOCFooDidBecomeBarNotification = @"ZOCFooDidBecomeBarNotification";
</code></pre> 
 <h3>2. 通知的注册【M】</h3> 
 <p>注册通知建议是在 <code>- (void)viewDidLoaded</code>进行,特殊情况可以在<code>- (void)viewWillAppear:</code>,此时必须在<code>- (void)viewWillDisappear:</code>成对调用</p> 
 <h3>3. 通知的移除【M】</h3> 
 <p>通知必须要在对象销毁之前移除掉。</p> 
 <h2>其他</h2> 
 <hr> 
 <h3>1. 尽量采用MVC架构,使用fram或者Masonry布局。【S】</h3> 
 <p>文件按照<code>Model</code> <code>View</code> <code>Controller</code> <code>Cell</code> <code>Other</code>来组织</p> 
 <h3>2. Xcode工程文件的物理路径要和逻辑路径保持一致。【S】</h3> 
 <h3>3. 忽略没有使用变量的编译警告【S】</h3> 
 <p>对于某些暂时不用,以后可能用到的临时变量,为了避免警告,我们可以使用如下方法将这个警告消除:</p> 
 <pre><code>- (NSInteger)giveMeFive { 
 NSString *foo; 
 #pragma unused (foo) 
 return 5; 
}
</code></pre> 
 <h3>4. 手动标明警告和错误【S】</h3> 
 <h4>手动明确一个错误:</h4> 
 <pre><code>- (NSInteger)divide:(NSInteger)dividend by:(NSInteger)divisor { 
 #error Whoa, buddy, you need to check for zero here! 
 return (dividend / divisor); 
}
</code></pre> 
 <h4>手动明确一个警告:</h4> 
 <pre><code>- (float)divide:(float)dividend by:(float)divisor { 
 //#warning Dude, don't compare floating point numbers like this! 
    if (divisor != 0.0) { 
        return (dividend / divisor); 
    } else {  
        return NAN; 
    } 
}
</code></pre> 
 <hr> 
 <h1>参考文献:</h1> 
 <ol> 
  <li>王垠:编程的智慧</li> 
  <li>美团点评技术团队:聊聊clean code</li> 
  <li>禅与 Objective-C 编程艺术</li> 
  <li>J_Knight 的文集:iOS - 《Effective Objective-C 2.0》</li> 
  <li>蝴蝶之梦天使:iOS代码编程规范-根据项目经验汇总</li> 
  <li>高家二少爷:Objective-C高质量代码参考规范</li> 
  <li>J_Knight:iOS 代码规范</li> 
 </ol> 
</article>
                            </div>
                        </div>
                    </div>
                    <!--PC和WAP自适应版-->
                    <div id="SOHUCS" sid="1402491991777923072"></div>
                    <script type="text/javascript" src="/views/front/js/chanyan.js"></script>
                    <!-- 文章页-底部 动态广告位 -->
                    <div class="youdao-fixed-ad" id="detail_ad_bottom"></div>
                </div>
                <div class="col-md-3">
                    <div class="row" id="ad">
                        <!-- 文章页-右侧1 动态广告位 -->
                        <div id="right-1" class="col-lg-12 col-md-12 col-sm-4 col-xs-4 ad">
                            <div class="youdao-fixed-ad" id="detail_ad_1"> </div>
                        </div>
                        <!-- 文章页-右侧2 动态广告位 -->
                        <div id="right-2" class="col-lg-12 col-md-12 col-sm-4 col-xs-4 ad">
                            <div class="youdao-fixed-ad" id="detail_ad_2"></div>
                        </div>
                        <!-- 文章页-右侧3 动态广告位 -->
                        <div id="right-3" class="col-lg-12 col-md-12 col-sm-4 col-xs-4 ad">
                            <div class="youdao-fixed-ad" id="detail_ad_3"></div>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div>
    <div class="container">
        <h4 class="pt20 mb15 mt0 border-top">你可能感兴趣的:(iOS编码规范)</h4>
        <div id="paradigm-article-related">
            <div class="recommend-post mb30">
                <ul class="widget-links">
                    <li><a href="/article/1834874286859448320.htm"
                           title="vue axios跨域访问相关问题 | axios默认发送‘application/x-www-form-urlencoded‘格式数据 | Content-Type is not allowed b" target="_blank">vue axios跨域访问相关问题 | axios默认发送‘application/x-www-form-urlencoded‘格式数据 | Content-Type is not allowed b</a>
                        <span class="text-muted">就是爱吃肉ro</span>
<a class="tag" taget="_blank" href="/search/%23/1.htm">#</a><a class="tag" taget="_blank" href="/search/Vue/1.htm">Vue</a><a class="tag" taget="_blank" href="/search/%26amp%3B/1.htm">&</a><a class="tag" taget="_blank" href="/search/uni-app/1.htm">uni-app</a><a class="tag" taget="_blank" href="/search/axios/1.htm">axios</a><a class="tag" taget="_blank" href="/search/ajax/1.htm">ajax</a><a class="tag" taget="_blank" href="/search/cors%E8%B7%A8%E5%9F%9F/1.htm">cors跨域</a><a class="tag" taget="_blank" href="/search/vue/1.htm">vue</a><a class="tag" taget="_blank" href="/search/x-www-form-url/1.htm">x-www-form-url</a>
                        <div>文章目录概述报错1Content-TypeisnotallowedbyAccess-Control-Allow-Headersinpreflightrespon报错2返回状态码500好久没更博客了,最近一直搞框架搞项目,好多问题也都没有记录下来…好吧,那从今天起来,继续开始保持记录的好习惯,先写一下在axios上踩下了这么多坑.概述通过以下两个报错,来介绍解决使用axios来进行网络请求中的遇到的</div>
                    </li>
                    <li><a href="/article/1834872140986413056.htm"
                           title="html ios 滚动条隐藏,CSS溢出滚动和隐藏滚动条(iOS)" target="_blank">html ios 滚动条隐藏,CSS溢出滚动和隐藏滚动条(iOS)</a>
                        <span class="text-muted">社长从来不假装</span>
<a class="tag" taget="_blank" href="/search/html/1.htm">html</a><a class="tag" taget="_blank" href="/search/ios/1.htm">ios</a><a class="tag" taget="_blank" href="/search/%E6%BB%9A%E5%8A%A8%E6%9D%A1%E9%9A%90%E8%97%8F/1.htm">滚动条隐藏</a>
                        <div>6个答案:答案0:(得分:5)我只是玩了这个codepen(https://codepen.io/devstreak/pen/dMYgeO),看来如果你为下面的所有三个属性设置background-color到transparent,滚动(在这个例子中)同时删除box-shadows),根本看不到滚动条:#style-1::-webkit-scrollbar-track{//-webkit-box</div>
                    </li>
                    <li><a href="/article/1834862560977973248.htm"
                           title="vue axios 如何读取项目下的json文件" target="_blank">vue axios 如何读取项目下的json文件</a>
                        <span class="text-muted">战族狼魂</span>
<a class="tag" taget="_blank" href="/search/%E5%89%8D%E7%AB%AF/1.htm">前端</a><a class="tag" taget="_blank" href="/search/vue.js/1.htm">vue.js</a><a class="tag" taget="_blank" href="/search/json/1.htm">json</a><a class="tag" taget="_blank" href="/search/javascript/1.htm">javascript</a>
                        <div>在Vue项目中,使用axios读取本地的JSON文件可以通过将JSON文件放置在public目录中,然后通过axios发起请求读取。步骤:将JSON文件放置在public目录下:Vue项目中的public目录是静态资源目录,项目编译后这些文件可以通过URL直接访问。将你的JSON文件,比如data.json,放在public目录中。使用axios读取JSON文件:在你的Vue组件中,通过axios</div>
                    </li>
                    <li><a href="/article/1834834201300529152.htm"
                           title="Java-后端程序员个人知识总结" target="_blank">Java-后端程序员个人知识总结</a>
                        <span class="text-muted">金肴羽</span>
<a class="tag" taget="_blank" href="/search/java/1.htm">java</a><a class="tag" taget="_blank" href="/search/%E5%BC%80%E5%8F%91%E8%AF%AD%E8%A8%80/1.htm">开发语言</a>
                        <div>文章目录概要1.编程语言2.数据结构与算法3.数据库知识4.框架和库5.服务器管理6.网络知识7.版本控制8.测试9.安全知识10.系统设计11.编码规范与最佳实践12.持续学习和适应能力概要后端程序员,主要负责应用程序的逻辑、数据库交互、服务器配置以及应用的性能优化等。成为一名优秀的后台程序员,需要掌握以下技能:1.编程语言掌握至少一种后台编程语言JavaPythonHtmlJavaScript</div>
                    </li>
                    <li><a href="/article/1834832688285052928.htm"
                           title="WebRTC之LiveKit的基础入门使用(入门必看)" target="_blank">WebRTC之LiveKit的基础入门使用(入门必看)</a>
                        <span class="text-muted">tabzzz</span>
<a class="tag" taget="_blank" href="/search/%E5%89%8D%E7%AB%AF/1.htm">前端</a><a class="tag" taget="_blank" href="/search/webrtc/1.htm">webrtc</a><a class="tag" taget="_blank" href="/search/web3/1.htm">web3</a><a class="tag" taget="_blank" href="/search/typescript/1.htm">typescript</a>
                        <div>LiveKit本文主要是讲解在Next13+中如何使用LiveKit来实现简单的音视频通话,想了解更多的还是要去官方文档去掌握更复杂、高级的使用方法。什么是LiveKitLiveKit是一个开源的实时通信平台,基于WebRTC,主要用于构建高质量的音视频通话、实时数据传输和互动应用。LiveKit除了方便以外的大优势就是它提供了丰富的API和SDK,支持多种平台,包括Web、iOS、Android</div>
                    </li>
                    <li><a href="/article/1834819377241878528.htm"
                           title="2K价位的手机还有这些神仙功能,绿厂ColorOS系统yyds" target="_blank">2K价位的手机还有这些神仙功能,绿厂ColorOS系统yyds</a>
                        <span class="text-muted">机测女孩</span>

                        <div>有一个有趣的现象,即使到了2021年,苹果手机在硬件技术上已经被安卓阵营大幅领先,但依然有大批的粉丝为了体验iOS趋之若鹜,可见用户对于手机系统的看重并不亚于参数配置。近年来,不少国产品牌的手机明显意识到了这一点,在堆砌参数的同时也非常重视系统的构建与优化,诸如华为、OPPO等手机品牌在整体系统体验大幅上升。就拿我手上的2k价位的OPPOA96来说,作为一款价格相当亲民的中端机型,其系统体验上却完</div>
                    </li>
                    <li><a href="/article/1834814025607770112.htm"
                           title="Cocos2d、Cocos2dx、Cocos Creator、Cocos Studio的区别" target="_blank">Cocos2d、Cocos2dx、Cocos Creator、Cocos Studio的区别</a>
                        <span class="text-muted">Thomas游戏圈</span>

                        <div>一、Cocos2d和Cocos2dx的区别【开发语言】:Cocos2d是Object-C写的,Cocos2dx是C++写的,支持使用C++、Lua或Java进行开发。【运行平台】:Cocos2d只能在IOS下运行,Cocos2dx是跨平台的,适配iOS、Android、HTML5、Windows和Mac系统,功能侧重于原生移动平台。点击链接加入群聊【Unity/Cocos交流群】【国籍】:Coco</div>
                    </li>
                    <li><a href="/article/1834793792289468416.htm"
                           title="iOS GCD底层分析(2)--同步异步函数、死锁、GCD单例" target="_blank">iOS GCD底层分析(2)--同步异步函数、死锁、GCD单例</a>
                        <span class="text-muted">冼同学</span>

                        <div>前言上一篇文章iOSGCD底层分析(1)留下了四个问题,分别是:死锁底层是怎么样子产生的?如果是异步函数,线程是怎样子创建的?底层通过_dispatch_worker_thread2方法完成任务的回调执行,那么触发调用的位置在哪?单例的底层原理是什么?准备工作libdispatch.dylibiOSGCD底层分析(1)1.同步函数上一篇文章中分系同步函数时进入了_dispatch_sync_f_i</div>
                    </li>
                    <li><a href="/article/1834792851813265408.htm"
                           title="Qt框架在跨平台应用开发中的优势" target="_blank">Qt框架在跨平台应用开发中的优势</a>
                        <span class="text-muted">NewmanEdwarda2</span>
<a class="tag" taget="_blank" href="/search/qt/1.htm">qt</a><a class="tag" taget="_blank" href="/search/%E5%BC%80%E5%8F%91%E8%AF%AD%E8%A8%80/1.htm">开发语言</a>
                        <div>随着软件技术的不断发展,跨平台应用开发已成为软件开发领域的一个重要趋势。在众多跨平台开发框架中,Qt因其强大的功能和灵活性而备受开发者的青睐。本文将从多个方面详细探讨Qt框架在跨平台应用开发中的优势。一、跨平台兼容性Qt框架最显著的优势之一就是其卓越的跨平台兼容性。Qt支持多种操作系统,包括Windows、Linux、macOS、Android、iOS等,使得开发者能够使用同一套代码库为不同平台开</div>
                    </li>
                    <li><a href="/article/1834768522685804544.htm"
                           title="uni-app开发微信小程序" target="_blank">uni-app开发微信小程序</a>
                        <span class="text-muted">hong161688</span>
<a class="tag" taget="_blank" href="/search/uni-app/1.htm">uni-app</a><a class="tag" taget="_blank" href="/search/%E5%BE%AE%E4%BF%A1%E5%B0%8F%E7%A8%8B%E5%BA%8F/1.htm">微信小程序</a><a class="tag" taget="_blank" href="/search/%E5%B0%8F%E7%A8%8B%E5%BA%8F/1.htm">小程序</a>
                        <div>uni-app是一个使用Vue.js开发所有前端应用的框架,它支持编译到iOS、Android、H5、以及各种小程序(微信/支付宝/百度/字节跳动/QQ/京东等)平台。使用uni-app开发微信小程序,可以充分利用Vue.js的开发效率和小程序平台的原生能力,实现跨平台的高效开发。以下将详细介绍使用uni-app开发微信小程序的全过程,包括项目搭建、开发、调试、测试及发布等环节,内容将尽量达到或超</div>
                    </li>
                    <li><a href="/article/1834756931286298624.htm"
                           title="Swift 基本语法" target="_blank">Swift 基本语法</a>
                        <span class="text-muted">lly202406</span>
<a class="tag" taget="_blank" href="/search/%E5%BC%80%E5%8F%91%E8%AF%AD%E8%A8%80/1.htm">开发语言</a>
                        <div>Swift基本语法Swift是一种由苹果公司开发的编程语言,用于在iOS、macOS、watchOS和tvOS上开发应用程序。它是一种强类型语言,具有清晰的语法和现代特性,使得开发过程更加高效和易于维护。本文将介绍Swift的一些基本语法,帮助初学者快速上手。变量和常量在Swift中,使用let关键字来声明一个常量,使用var关键字来声明一个变量。常量的值在初始化后不能被改变,而变量的值可以随时更</div>
                    </li>
                    <li><a href="/article/1834756301549301760.htm"
                           title="使用 uni-app 开发微信小程序:深入教程与技巧" target="_blank">使用 uni-app 开发微信小程序:深入教程与技巧</a>
                        <span class="text-muted">代码伐木匠</span>
<a class="tag" taget="_blank" href="/search/%E7%AC%94%E8%AE%B0%E6%9C%AC/1.htm">笔记本</a><a class="tag" taget="_blank" href="/search/uni-app/1.htm">uni-app</a><a class="tag" taget="_blank" href="/search/%E5%BE%AE%E4%BF%A1%E5%B0%8F%E7%A8%8B%E5%BA%8F/1.htm">微信小程序</a><a class="tag" taget="_blank" href="/search/notepad%2B%2B/1.htm">notepad++</a>
                        <div>使用uni-app开发微信小程序:深入教程与技巧uni-app是一个使用Vue.js语法开发跨平台应用的框架,能够编译到iOS、Android、H5、微信小程序等多个平台。通过uni-app,你可以一套代码同时生成多个端的应用,极大提升开发效率。本文将带你深入了解如何使用uni-app开发微信小程序,并提供大量实践经验与技巧。1.环境配置与项目创建要开始使用uni-app开发微信小程序,首先需要搭</div>
                    </li>
                    <li><a href="/article/1834718751627505664.htm"
                           title="谈谈你对多线程开发的理解?ios中有几种实现多线程的方法?" target="_blank">谈谈你对多线程开发的理解?ios中有几种实现多线程的方法?</a>
                        <span class="text-muted">充满活力的早晨</span>

                        <div>好处:1.使用线程可以把占据时间长的程序中的任务放到后台去处理2.用户界面可以更加吸引人,这样比如用户点击了一个按钮去触发某些事件的处理,可以弹出一个进度条来显示处理的进度3.程序的运行速度可能加快4·在一些等待的任务实现上如用户输入、文件读写和网络收发数据等,线程就比较有用了。缺点:1.如果有大量的线程,会影响性能,因为操作系统需要在它们之间切换。2.更多的线程需要更多的内存空间。3.线程的中止</div>
                    </li>
                    <li><a href="/article/1834680032790933504.htm"
                           title="swift和python语法区别_Swift 基本语法" target="_blank">swift和python语法区别_Swift 基本语法</a>
                        <span class="text-muted">碧色将逝</span>

                        <div>Swift基本语法在上一章节中我们已经讲到如何创建Swift语言的"Hello,World!"程序。现在我们来复习下。如果创建的是OSXplayground需要引入Cocoa:importCocoa/*我的第一个Swift程序*/varmyString="Hello,World!"print(myString)如果我们想创建iOSplayground则需要引入UIKit:importUIKitva</div>
                    </li>
                    <li><a href="/article/1834622479771987968.htm"
                           title="如何在Mac、 iPhone、iPad上将 HEIF 照片转换为 JPG?" target="_blank">如何在Mac、 iPhone、iPad上将 HEIF 照片转换为 JPG?</a>
                        <span class="text-muted">Mac123123</span>

                        <div>默认情况下,您的iPhone和iPad以HEIF格式保存您拍摄的照片和视频。这只是不能被每个设备查看。在本文中,我们将解释如何在iPhone、iPad或Mac上调整此类照片的大小。转换HEIF照片在iOS11中,Apple引入了新的HEIF和HEVC格式。使用这些文件格式,您可以节省大约40%到50%的存储空间,而不会降低图像质量。因此,您可以在iPhone或iCloud中保存更多照片。这一切听起</div>
                    </li>
                    <li><a href="/article/1834577959051161600.htm"
                           title="ios runtime 简单路由" target="_blank">ios runtime 简单路由</a>
                        <span class="text-muted">Mjs</span>

                        <div>1.协议本路由通过协议方式实现首先定义一个必须实现的方法typedefvoid(^JCompletionHandler)(id_Nullableresult,NSError*_Nullableerror);@protocolJRouteHandler@required+(void)handleRequestWithParameters:(nullableJParameters)parameters</div>
                    </li>
                    <li><a href="/article/1834557014483759104.htm"
                           title="VMware Fusion 13.6 OEM BIOS 2.7 - 在 macOS 中运行 Windows 虚拟机的最佳方式" target="_blank">VMware Fusion 13.6 OEM BIOS 2.7 - 在 macOS 中运行 Windows 虚拟机的最佳方式</a>
                        <span class="text-muted">sysin.org</span>
<a class="tag" taget="_blank" href="/search/VMware/1.htm">VMware</a><a class="tag" taget="_blank" href="/search/macos/1.htm">macos</a><a class="tag" taget="_blank" href="/search/windows/1.htm">windows</a><a class="tag" taget="_blank" href="/search/fusion/1.htm">fusion</a><a class="tag" taget="_blank" href="/search/oem/1.htm">oem</a><a class="tag" taget="_blank" href="/search/bios/1.htm">bios</a><a class="tag" taget="_blank" href="/search/%E8%99%9A%E6%8B%9F%E5%8C%96/1.htm">虚拟化</a><a class="tag" taget="_blank" href="/search/2025/1.htm">2025</a>
                        <div>VMwareFusion13.6OEMBIOS2.7-在macOS中运行Windows虚拟机的最佳方式VMwareFusion13原版App中集成OEMBIOS请访问原文链接:https://sysin.cn/blog/vmware-fusion-13-oem/,查看最新版。原创作品,转载请保留出处。作者主页:sysin.org2024-09-03,版本13.6更新,支持macOSSequoia作</div>
                    </li>
                    <li><a href="/article/1834523737697906688.htm"
                           title="HP电脑如何启动硬件检测" target="_blank">HP电脑如何启动硬件检测</a>
                        <span class="text-muted">哲伦贼稳妥</span>
<a class="tag" taget="_blank" href="/search/%E7%94%B5%E8%84%91%E7%A1%AC%E4%BB%B6/1.htm">电脑硬件</a><a class="tag" taget="_blank" href="/search/%E7%94%B5%E8%84%91/1.htm">电脑</a><a class="tag" taget="_blank" href="/search/%E7%BB%8F%E9%AA%8C%E5%88%86%E4%BA%AB/1.htm">经验分享</a><a class="tag" taget="_blank" href="/search/windows/1.htm">windows</a><a class="tag" taget="_blank" href="/search/%E5%85%B6%E4%BB%96/1.htm">其他</a>
                        <div>许多人都在使用HP电脑,但是当出现问题时候,不知道该如何测试,本文来分享一下,如何在电脑能开机但是有问题时进行检测。使用F2键进行组件测试步骤:开机后不停敲击键盘上【F2】的按键,进入BIOS设置界面,点击【组件测试】,然后选择想要检测的硬件组件(如鼠标/触摸板、键盘、网络、风扇等)。注意事项:不同型号的HP电脑进入BIOS的按键可能不同,通常为F2、F10、F12或Delete键。使用UEFI诊</div>
                    </li>
                    <li><a href="/article/1834504832900231168.htm"
                           title="Flutter 应用程序开发的优缺点" target="_blank">Flutter 应用程序开发的优缺点</a>
                        <span class="text-muted">pxr007</span>
<a class="tag" taget="_blank" href="/search/flutter/1.htm">flutter</a><a class="tag" taget="_blank" href="/search/android/1.htm">android</a><a class="tag" taget="_blank" href="/search/ui/1.htm">ui</a>
                        <div>编者注:这篇文章于2022年9月19日更新,包含有关Flutter3.0的信息,以及通过有组织的文档和更快的应用程序开发来增加可访问性。如果您是一位经验丰富的移动应用程序开发人员,那么您已经决定为Android或iOS设备构建应用程序。如果您是初学者,您可能仍在考虑哪个是您的首选平台。这些决定决定了您在成为移动或跨平台应用程序开发人员的道路上应该学习哪些技术。无论您的应用程序开发之旅将您带到哪里,</div>
                    </li>
                    <li><a href="/article/1834499409954566144.htm"
                           title="Flutter PDFView:在Flutter应用中嵌入PDF阅读器" target="_blank">Flutter PDFView:在Flutter应用中嵌入PDF阅读器</a>
                        <span class="text-muted">明似水</span>
<a class="tag" taget="_blank" href="/search/flutter/1.htm">flutter</a><a class="tag" taget="_blank" href="/search/flutter/1.htm">flutter</a><a class="tag" taget="_blank" href="/search/pdf/1.htm">pdf</a>
                        <div>FlutterPDFView:在Flutter应用中嵌入PDF阅读器在移动应用开发中,经常会遇到需要在应用内展示PDF文件的需求。Flutter作为一个跨平台的UI工具包,提供了丰富的包来满足开发者的各种需求。flutter_pdfview就是这样一个包,它允许开发者在iOS和Android平台上无缝地嵌入PDF阅读器。简介flutter_pdfview是一个原生的PDF查看器,支持iOS和And</div>
                    </li>
                    <li><a href="/article/1834480753681985536.htm"
                           title="cross-plateform 跨平台应用程序-06-uni-app 介绍" target="_blank">cross-plateform 跨平台应用程序-06-uni-app 介绍</a>
                        <span class="text-muted">知识分享官</span>
<a class="tag" taget="_blank" href="/search/uni-app/1.htm">uni-app</a>
                        <div>详细介绍一下uni-app?whatuni-app是一个使用Vue.js开发所有前端应用的框架,开发者编写一次代码,可发布到iOS、Android、Web(响应式)、以及各种小程序(微信/支付宝/百度/头条/飞书/QQ/快手/京东/美团/钉钉/淘宝)、快应用等多个平台。以下是uni-app的一些核心特性和优势:使用Vue.js开发:uni-app基于Vue.js,这意味着如果你已经熟悉Vue.js</div>
                    </li>
                    <li><a href="/article/1834477981238652928.htm"
                           title="flutter开发多端平台应用的探索 上(基本操作)" target="_blank">flutter开发多端平台应用的探索 上(基本操作)</a>
                        <span class="text-muted">夏目艾拉</span>
<a class="tag" taget="_blank" href="/search/flutter/1.htm">flutter</a><a class="tag" taget="_blank" href="/search/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/1.htm">设计模式</a><a class="tag" taget="_blank" href="/search/java/1.htm">java</a><a class="tag" taget="_blank" href="/search/android/1.htm">android</a>
                        <div>前言Flutter是一个跨平台的开发框架,它允许开发者使用相同的代码库来构建iOS、Android、Web和桌面应用程序。最近想了想,自己一直在用flutter开发特定端的应用,但是对于其他端的端特性case,如桌面端的菜单、多窗口、鼠标事件,需要怎么处理还没什么概念,本文旨在简单探索这方面的内容。正文首先就是判断平台了,可以通过如下方式import'dart:io'showPlatform;vo</div>
                    </li>
                    <li><a href="/article/1834471505933725696.htm"
                           title="苹果海淘软件哪个好?ios海淘app推荐-苹果海淘app下载" target="_blank">苹果海淘软件哪个好?ios海淘app推荐-苹果海淘app下载</a>
                        <span class="text-muted">氧惠帮朋友一起省</span>

                        <div>今天我为大家整理了多款正品优惠的购物app,其中包裹氧惠、高省、唯品会、亚马逊购物等,横扫全球精品好货,覆盖了美妆护肤、服饰鞋包、食品居家、母婴保健品、家具家电等众多海淘购物类别,全球超in超nice好物正品低价,无需代购,快过海淘,还提供了全球最新购物攻略及最热爆拼,给你购物新指南,每日不定期会推出优惠活动,优惠券、秒杀、限时抢购等,足不出门即可揽收全球精品,还可以晒出自己的各种扫货战利品,接下</div>
                    </li>
                    <li><a href="/article/1834453822940999680.htm"
                           title="Flutter学习之九 ListView" target="_blank">Flutter学习之九 ListView</a>
                        <span class="text-muted">MQ_Twist</span>

                        <div>极端很容易,平衡才是最难的。Flutter学习之八Container前言Flutter中的ListView的地位,就好比于iOS中的UITableView,算是最常用的可滚动组件之一,它可以沿一个方向线性排布所有子组件,并且它也支持列表项懒加载(在需要时才会创建)。默认构造函数我们看看ListView的默认构造函数定义:ListView({...//可滚动widget公共参数AxisscrollD</div>
                    </li>
                    <li><a href="/article/1834450647114674176.htm"
                           title="YOHO!小插曲" target="_blank">YOHO!小插曲</a>
                        <span class="text-muted">哈迪斯先生</span>

                        <div>Readme:这是体验的有货小插曲部分,时间估计在五月中旬,6.6.1还未出现的情况下,留个纪念,(这个不是产品体验报告)手机系统:Ios有货版本:6.6.0体验有货发生了两件事情。第一件事情:一天晚上我连着WIFI在信号频段非常不好的情况下,进入了APP,显示的东西与我之前所查阅产品体验报告的不同,只有男生和女生。但是我因为信号问题进不去,所以不了了之。在我没有正式进入,且用户提示网络异常的情况</div>
                    </li>
                    <li><a href="/article/1834440292812550144.htm"
                           title="【App】React Native" target="_blank">【App】React Native</a>
                        <span class="text-muted">卿卿qing</span>
<a class="tag" taget="_blank" href="/search/react/1.htm">react</a><a class="tag" taget="_blank" href="/search/native/1.htm">native</a><a class="tag" taget="_blank" href="/search/react.js/1.htm">react.js</a><a class="tag" taget="_blank" href="/search/javascript/1.htm">javascript</a>
                        <div>ReactNative的优势:开发体验好用统一的代码规范开发移动端程序,不用关注移动端的差异.开发成本低开发一次,可以生成Android和IOS俩个系统上的App学习成本低只要掌握JavaScript和React就可以进行移动端开发ReactNative的不足:不成熟性能差整体性能仍不如原生兼容性差设计底层的功能,需要针对Android和IOS双端单独开发ReactNative的核心组件核心组件R</div>
                    </li>
                    <li><a href="/article/1834438653460443136.htm"
                           title="native react 集成微信登录_微信SDK集成示例 – react-native-wechat-ios" target="_blank">native react 集成微信登录_微信SDK集成示例 – react-native-wechat-ios</a>
                        <span class="text-muted">weixin_39682477</span>
<a class="tag" taget="_blank" href="/search/native/1.htm">native</a><a class="tag" taget="_blank" href="/search/react/1.htm">react</a><a class="tag" taget="_blank" href="/search/%E9%9B%86%E6%88%90%E5%BE%AE%E4%BF%A1%E7%99%BB%E5%BD%95/1.htm">集成微信登录</a>
                        <div>react-native-wechat-ios微信SDK集成示例,现已完成微信认证登录,分享链接,支付功能。(本模块仅支持iOS,点我获取Android支持)如何安装1.下载包文件$npmireact-native-wechat-ios2.链接库文件到你的项目中a.给RCTWeChat添加头文件搜索路径:$(SRCROOT)/../../react-native/React,并选择recursiv</div>
                    </li>
                    <li><a href="/article/1834414839448039424.htm"
                           title="ReactNative SDK 库之 react-native-dev-sdk" target="_blank">ReactNative SDK 库之 react-native-dev-sdk</a>
                        <span class="text-muted">执念1012</span>
<a class="tag" taget="_blank" href="/search/react/1.htm">react</a><a class="tag" taget="_blank" href="/search/native/1.htm">native</a><a class="tag" taget="_blank" href="/search/react.js/1.htm">react.js</a><a class="tag" taget="_blank" href="/search/javascript/1.htm">javascript</a>
                        <div>react-native-dev-sdk封装大部业务中常用组件,方法、方便项目中适用安装依赖使用,安装需要将dev嵌入项目.npmi@react-native-async-storage/async-storage@react-navigation/nativeaxiosreact-native-root-toast@react-navigation/native-stack@react-navi</div>
                    </li>
                    <li><a href="/article/1834379166389202944.htm"
                           title="ios7 手势滑动切换ViewController 问题总结" target="_blank">ios7 手势滑动切换ViewController 问题总结</a>
                        <span class="text-muted">wxcswd</span>
<a class="tag" taget="_blank" href="/search/ios/1.htm">ios</a>
                        <div>在handleGesture函数中注意ViewController的dismiss应该放在caseUIGestureRecognizerStateBegan滑动切换dismiss掉之后,必须在present出该ViewController的响应函数中添加判断if(!self.presentedViewController)//presentedViewController在其头文件中说明为read</div>
                    </li>
                    <li><a href="/article/1834372359595847680.htm"
                           title="【iOS】MVC入门" target="_blank">【iOS】MVC入门</a>
                        <span class="text-muted">安和昴</span>
<a class="tag" taget="_blank" href="/search/ios/1.htm">ios</a><a class="tag" taget="_blank" href="/search/mvc/1.htm">mvc</a>
                        <div>【iOS】MVC模式的学习文章目录【iOS】MVC模式的学习前言MVC模式概念MVC的交流模式MVC的一个简单实践Model层View层Controller层MVC的优点与缺点总结前言笔者在暑假的学习中完成了一些小项目,这些小项目中间有时候出现了一个小bug都要寻找很久,而且会导致所有整个项目无法运行,这时候就更体现了我们一个优秀的项目需要满足的几个要求:高内聚,低耦合。代码均摊,易于扩展,具有易</div>
                    </li>
                                <li><a href="/article/97.htm"
                                       title="TOMCAT在POST方法提交参数丢失问题" target="_blank">TOMCAT在POST方法提交参数丢失问题</a>
                                    <span class="text-muted">357029540</span>
<a class="tag" taget="_blank" href="/search/java/1.htm">java</a><a class="tag" taget="_blank" href="/search/tomcat/1.htm">tomcat</a><a class="tag" taget="_blank" href="/search/jsp/1.htm">jsp</a>
                                    <div>摘自http://my.oschina.net/luckyi/blog/213209 
昨天在解决一个BUG时发现一个奇怪的问题,一个AJAX提交数据在之前都是木有问题的,突然提交出错影响其他处理流程。 
 
检查时发现页面处理数据较多,起初以为是提交顺序不正确修改后发现不是由此问题引起。于是删除掉一部分数据进行提交,较少数据能够提交成功。 
 
恢复较多数据后跟踪提交FORM DATA ,发现数</div>
                                </li>
                                <li><a href="/article/224.htm"
                                       title="在MyEclipse中增加JSP模板 删除-2008-08-18" target="_blank">在MyEclipse中增加JSP模板 删除-2008-08-18</a>
                                    <span class="text-muted">ljy325</span>
<a class="tag" taget="_blank" href="/search/jsp/1.htm">jsp</a><a class="tag" taget="_blank" href="/search/xml/1.htm">xml</a><a class="tag" taget="_blank" href="/search/MyEclipse/1.htm">MyEclipse</a>
                                    <div>在D:\Program Files\MyEclipse 6.0\myeclipse\eclipse\plugins\com.genuitec.eclipse.wizards_6.0.1.zmyeclipse601200710\templates\jsp  目录下找到Jsp.vtl,复制一份,重命名为jsp2.vtl,然后把里面的内容修改为自己想要的格式,保存。 
然后在 D:\Progr</div>
                                </li>
                                <li><a href="/article/351.htm"
                                       title="JavaScript常用验证脚本总结" target="_blank">JavaScript常用验证脚本总结</a>
                                    <span class="text-muted">eksliang</span>
<a class="tag" taget="_blank" href="/search/JavaScript/1.htm">JavaScript</a><a class="tag" taget="_blank" href="/search/javaScript%E8%A1%A8%E5%8D%95%E9%AA%8C%E8%AF%81/1.htm">javaScript表单验证</a>
                                    <div>     转载请出自出处:http://eksliang.iteye.com/blog/2098985   
     下面这些验证脚本,是我在这几年开发中的总结,今天把他放出来,也算是一种分享吧,现在在我的项目中也在用!包括日期验证、比较,非空验证、身份证验证、数值验证、Email验证、电话验证等等...! 
&nb</div>
                                </li>
                                <li><a href="/article/478.htm"
                                       title="微软BI(4)" target="_blank">微软BI(4)</a>
                                    <span class="text-muted">18289753290</span>
<a class="tag" taget="_blank" href="/search/%E5%BE%AE%E8%BD%AFBI+SSIS/1.htm">微软BI SSIS</a>
                                    <div>1) 
Q:查看ssis里面某个控件输出的结果:  
A MessageBox.Show(Dts.Variables["v_lastTimestamp"].Value.ToString()); 
这是我们在包里面定义的变量 
2):在关联目的端表的时候如果是一对多的关系,一定要选择唯一的那个键作为关联字段。 
3) 
Q:ssis里面如果将多个数据源的数据插入目的端一</div>
                                </li>
                                <li><a href="/article/605.htm"
                                       title="定时对大数据量的表进行分表对数据备份" target="_blank">定时对大数据量的表进行分表对数据备份</a>
                                    <span class="text-muted">酷的飞上天空</span>
<a class="tag" taget="_blank" href="/search/%E5%A4%A7%E6%95%B0%E6%8D%AE%E9%87%8F/1.htm">大数据量</a>
                                    <div>工作中遇到数据库中一个表的数据量比较大,属于日志表。正常情况下是不会有查询操作的,但如果不进行分表数据太多,执行一条简单sql语句要等好几分钟。。 
  
分表工具:linux的shell + mysql自身提供的管理命令 
原理:使用一个和原表数据结构一样的表,替换原表。 
  
linux shell内容如下: 
=======================开始 </div>
                                </li>
                                <li><a href="/article/732.htm"
                                       title="本质的描述与因材施教" target="_blank">本质的描述与因材施教</a>
                                    <span class="text-muted">永夜-极光</span>
<a class="tag" taget="_blank" href="/search/%E6%84%9F%E6%83%B3/1.htm">感想</a><a class="tag" taget="_blank" href="/search/%E9%9A%8F%E7%AC%94/1.htm">随笔</a>
                                    <div>  
       不管碰到什么事,我都下意识的想去探索本质,找寻一个最形象的描述方式。 
       我坚信,世界上对一件事物的描述和解释,肯定有一种最形象,最贴近本质,最容易让人理解 
  
     &</div>
                                </li>
                                <li><a href="/article/859.htm"
                                       title="很迷茫。。。" target="_blank">很迷茫。。。</a>
                                    <span class="text-muted">随便小屋</span>
<a class="tag" taget="_blank" href="/search/%E9%9A%8F%E7%AC%94/1.htm">随笔</a>
                                    <div>小弟我今年研一,也是从事的咱们现在最流行的专业(计算机)。本科三流学校,为了能有个更好的跳板,进入了考研大军,非常有幸能进入研究生的行业(具体学校就不说了,怕把学校的名誉给损了)。 
  
先说一下自身的条件,本科专业软件工程。主要学习就是软件开发,几乎和计算机没有什么区别。因为学校本身三流,也就是让老师带着学生学点东西,然后让学生毕业就行了。对专业性的东西了解的非常浅。就那学的语言来说</div>
                                </li>
                                <li><a href="/article/986.htm"
                                       title="23种设计模式的意图和适用范围" target="_blank">23种设计模式的意图和适用范围</a>
                                    <span class="text-muted">aijuans</span>
<a class="tag" taget="_blank" href="/search/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/1.htm">设计模式</a>
                                    <div>Factory Method  意图 定义一个用于创建对象的接口,让子类决定实例化哪一个类。Factory Method 使一个类的实例化延迟到其子类。    适用性 当一个类不知道它所必须创建的对象的类的时候。    当一个类希望由它的子类来指定它所创建的对象的时候。    当类将创建对象的职责委托给多个帮助子类中的某一个,并且你希望将哪一个帮助子类是代理者这一信息局部化的时候。  
Abstr</div>
                                </li>
                                <li><a href="/article/1113.htm"
                                       title="Java中的synchronized和volatile" target="_blank">Java中的synchronized和volatile</a>
                                    <span class="text-muted">aoyouzi</span>
<a class="tag" taget="_blank" href="/search/java/1.htm">java</a><a class="tag" taget="_blank" href="/search/volatile/1.htm">volatile</a><a class="tag" taget="_blank" href="/search/synchronized/1.htm">synchronized</a>
                                    <div>说到Java的线程同步问题肯定要说到两个关键字synchronized和volatile。说到这两个关键字,又要说道JVM的内存模型。JVM里内存分为main memory和working memory。 Main memory是所有线程共享的,working memory则是线程的工作内存,它保存有部分main memory变量的拷贝,对这些变量的更新直接发生在working memo</div>
                                </li>
                                <li><a href="/article/1240.htm"
                                       title="js数组的操作和this关键字" target="_blank">js数组的操作和this关键字</a>
                                    <span class="text-muted">百合不是茶</span>
<a class="tag" taget="_blank" href="/search/js/1.htm">js</a><a class="tag" taget="_blank" href="/search/%E6%95%B0%E7%BB%84%E6%93%8D%E4%BD%9C/1.htm">数组操作</a><a class="tag" taget="_blank" href="/search/this%E5%85%B3%E9%94%AE%E5%AD%97/1.htm">this关键字</a>
                                    <div>js数组的操作; 
  
一:数组的创建: 
1、数组的创建

var array = new Array(); //创建一个数组

var array = new Array([size]); //创建一个数组并指定长度,注意不是上限,是长度

var arrayObj = new Array([element0[, element1[, ...[, elementN]]]</div>
                                </li>
                                <li><a href="/article/1367.htm"
                                       title="别人的阿里面试感悟" target="_blank">别人的阿里面试感悟</a>
                                    <span class="text-muted">bijian1013</span>
<a class="tag" taget="_blank" href="/search/%E9%9D%A2%E8%AF%95%E5%88%86%E4%BA%AB/1.htm">面试分享</a><a class="tag" taget="_blank" href="/search/%E5%B7%A5%E4%BD%9C%E6%84%9F%E6%82%9F/1.htm">工作感悟</a><a class="tag" taget="_blank" href="/search/%E9%98%BF%E9%87%8C%E9%9D%A2%E8%AF%95/1.htm">阿里面试</a>
                                    <div>原文如下:http://greemranqq.iteye.com/blog/2007170 
        一直做企业系统,虽然也自己一直学习技术,但是感觉还是有所欠缺,准备花几个月的时间,把互联网的东西,以及一些基础更加的深入透析,结果这次比较意外,有点突然,下面分享一下感受吧! 
   &nb</div>
                                </li>
                                <li><a href="/article/1494.htm"
                                       title="淘宝的测试框架Itest" target="_blank">淘宝的测试框架Itest</a>
                                    <span class="text-muted">Bill_chen</span>
<a class="tag" taget="_blank" href="/search/spring/1.htm">spring</a><a class="tag" taget="_blank" href="/search/maven/1.htm">maven</a><a class="tag" taget="_blank" href="/search/%E6%A1%86%E6%9E%B6/1.htm">框架</a><a class="tag" taget="_blank" href="/search/%E5%8D%95%E5%85%83%E6%B5%8B%E8%AF%95/1.htm">单元测试</a><a class="tag" taget="_blank" href="/search/JUnit/1.htm">JUnit</a>
                                    <div>Itest测试框架是TaoBao测试部门开发的一套单元测试框架,以Junit4为核心, 
集合DbUnit、Unitils等主流测试框架,应该算是比较好用的了。 
近期项目中用了下,有关itest的具体使用如下: 
1.在Maven中引入itest框架: 
<dependency> 
  <groupId>com.taobao.test</groupId&g</div>
                                </li>
                                <li><a href="/article/1621.htm"
                                       title="【Java多线程二】多路条件解决生产者消费者问题" target="_blank">【Java多线程二】多路条件解决生产者消费者问题</a>
                                    <span class="text-muted">bit1129</span>
<a class="tag" taget="_blank" href="/search/java%E5%A4%9A%E7%BA%BF%E7%A8%8B/1.htm">java多线程</a>
                                    <div>package com.tom;

import java.util.LinkedList;
import java.util.Queue;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.loc</div>
                                </li>
                                <li><a href="/article/1748.htm"
                                       title="汉字转拼音pinyin4j" target="_blank">汉字转拼音pinyin4j</a>
                                    <span class="text-muted">白糖_</span>
<a class="tag" taget="_blank" href="/search/pinyin4j/1.htm">pinyin4j</a>
                                    <div> 以前在项目中遇到汉字转拼音的情况,于是在网上找到了pinyin4j这个工具包,非常有用,别的不说了,直接下代码: 
  
  

import java.util.HashSet;
import java.util.Set;

import net.sourceforge.pinyin4j.PinyinHelper;
import net.sourceforge.pinyin</div>
                                </li>
                                <li><a href="/article/1875.htm"
                                       title="org.hibernate.TransactionException: JDBC begin failed解决方案" target="_blank">org.hibernate.TransactionException: JDBC begin failed解决方案</a>
                                    <span class="text-muted">bozch</span>
<a class="tag" taget="_blank" href="/search/ssh/1.htm">ssh</a><a class="tag" taget="_blank" href="/search/%E6%95%B0%E6%8D%AE%E5%BA%93%E5%BC%82%E5%B8%B8/1.htm">数据库异常</a><a class="tag" taget="_blank" href="/search/DBCP/1.htm">DBCP</a>
                                    <div>org.hibernate.TransactionException: JDBC begin failed:     at org.hibernate.transaction.JDBCTransaction.begin(JDBCTransaction.java:68)    at org.hibernate.impl.SessionImp</div>
                                </li>
                                <li><a href="/article/2002.htm"
                                       title="java-并查集(Disjoint-set)-将多个集合合并成没有交集的集合" target="_blank">java-并查集(Disjoint-set)-将多个集合合并成没有交集的集合</a>
                                    <span class="text-muted">bylijinnan</span>
<a class="tag" taget="_blank" href="/search/java/1.htm">java</a>
                                    <div>
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.ut</div>
                                </li>
                                <li><a href="/article/2129.htm"
                                       title="Java PrintWriter打印乱码" target="_blank">Java PrintWriter打印乱码</a>
                                    <span class="text-muted">chenbowen00</span>
<a class="tag" taget="_blank" href="/search/java/1.htm">java</a>
                                    <div>一个小程序读写文件,发现PrintWriter输出后文件存在乱码,解决办法主要统一输入输出流编码格式。 
读文件: 
BufferedReader 
从字符输入流中读取文本,缓冲各个字符,从而提供字符、数组和行的高效读取。 
 
可以指定缓冲区的大小,或者可使用默认的大小。大多数情况下,默认值就足够大了。 
 
通常,Reader 所作的每个读取请求都会导致对基础字符或字节流进行相应的读取请求。因</div>
                                </li>
                                <li><a href="/article/2256.htm"
                                       title="[天气与气候]极端气候环境" target="_blank">[天气与气候]极端气候环境</a>
                                    <span class="text-muted">comsci</span>
<a class="tag" taget="_blank" href="/search/%E7%8E%AF%E5%A2%83/1.htm">环境</a>
                                    <div> 
 
      如果空间环境出现异变...外星文明并未出现,而只是用某种气象武器对地球的气候系统进行攻击,并挑唆地球国家间的战争,经过一段时间的准备...最大限度的削弱地球文明的整体力量,然后再进行入侵...... 
 
 
     那么地球上的国家应该做什么样的防备工作呢? 
 
 
 &n</div>
                                </li>
                                <li><a href="/article/2383.htm"
                                       title="oracle order by与union一起使用的用法" target="_blank">oracle order by与union一起使用的用法</a>
                                    <span class="text-muted">daizj</span>
<a class="tag" taget="_blank" href="/search/UNION/1.htm">UNION</a><a class="tag" taget="_blank" href="/search/oracle/1.htm">oracle</a><a class="tag" taget="_blank" href="/search/order+by/1.htm">order by</a>
                                    <div>当使用union操作时,排序语句必须放在最后面才正确,如下: 
 
 
只能在union的最后一个子查询中使用order by,而这个order by是针对整个unioning后的结果集的。So: 
如果unoin的几个子查询列名不同,如 
Sql代码  
select supplier_id, supplier_name  
from suppliers  
UNI</div>
                                </li>
                                <li><a href="/article/2510.htm"
                                       title="zeus持久层读写分离单元测试" target="_blank">zeus持久层读写分离单元测试</a>
                                    <span class="text-muted">deng520159</span>
<a class="tag" taget="_blank" href="/search/%E5%8D%95%E5%85%83%E6%B5%8B%E8%AF%95/1.htm">单元测试</a>
                                    <div>本文是zeus读写分离单元测试,距离分库分表,只有一步了.上代码: 
1.ZeusMasterSlaveTest.java 
package com.dengliang.zeus.webdemo.test;

import java.util.ArrayList;
import java.util.List;

import org.junit.Assert;
import org.j</div>
                                </li>
                                <li><a href="/article/2637.htm"
                                       title="Yii 截取字符串(UTF-8) 使用组件" target="_blank">Yii 截取字符串(UTF-8) 使用组件</a>
                                    <span class="text-muted">dcj3sjt126com</span>
<a class="tag" taget="_blank" href="/search/yii/1.htm">yii</a>
                                    <div>1.将Helper.php放进protected\components文件夹下。 
2.调用方法: 
  Helper::truncate_utf8_string($content,20,false);   //不显示省略号  Helper::truncate_utf8_string($content,20);  //显示省略号  
&n</div>
                                </li>
                                <li><a href="/article/2764.htm"
                                       title="安装memcache及php扩展" target="_blank">安装memcache及php扩展</a>
                                    <span class="text-muted">dcj3sjt126com</span>
<a class="tag" taget="_blank" href="/search/PHP/1.htm">PHP</a>
                                    <div>安装memcache    tar zxvf memcache-2.2.5.tgz     cd memcache-2.2.5/     /usr/local/php/bin/phpize (?)    ./configure --with-php-confi</div>
                                </li>
                                <li><a href="/article/2891.htm"
                                       title="JsonObject 处理日期" target="_blank">JsonObject 处理日期</a>
                                    <span class="text-muted">feifeilinlin521</span>
<a class="tag" taget="_blank" href="/search/java/1.htm">java</a><a class="tag" taget="_blank" href="/search/json/1.htm">json</a><a class="tag" taget="_blank" href="/search/JsonOjbect/1.htm">JsonOjbect</a><a class="tag" taget="_blank" href="/search/JsonArray/1.htm">JsonArray</a><a class="tag" taget="_blank" href="/search/JSONException/1.htm">JSONException</a>
                                    <div>    写这边文章的初衷就是遇到了json在转换日期格式出现了异常 net.sf.json.JSONException: java.lang.reflect.InvocationTargetException  原因是当你用Map接收数据库返回了java.sql.Date 日期的数据进行json转换出的问题话不多说  直接上代码 
 &n</div>
                                </li>
                                <li><a href="/article/3018.htm"
                                       title="Ehcache(06)——监听器" target="_blank">Ehcache(06)——监听器</a>
                                    <span class="text-muted">234390216</span>
<a class="tag" taget="_blank" href="/search/%E7%9B%91%E5%90%AC%E5%99%A8/1.htm">监听器</a><a class="tag" taget="_blank" href="/search/listener/1.htm">listener</a><a class="tag" taget="_blank" href="/search/ehcache/1.htm">ehcache</a>
                                    <div>监听器 
  
       Ehcache中监听器有两种,监听CacheManager的CacheManagerEventListener和监听Cache的CacheEventListener。在Ehcache中,Listener是通过对应的监听器工厂来生产和发生作用的。下面我们将来介绍一下这两种类型的监听器。 
 </div>
                                </li>
                                <li><a href="/article/3145.htm"
                                       title="activiti 自带设计器中chrome 34版本不能打开bug的解决" target="_blank">activiti 自带设计器中chrome 34版本不能打开bug的解决</a>
                                    <span class="text-muted">jackyrong</span>
<a class="tag" taget="_blank" href="/search/Activiti/1.htm">Activiti</a>
                                    <div>  在acitivti modeler中,如果是chrome 34,则不能打开该设计器,其他浏览器可以, 
经证实为bug,参考 
http://forums.activiti.org/content/activiti-modeler-doesnt-work-chrome-v34 
 
修改为,找到 
oryx.debug.js 
 
在最头部增加 
 

if (!Document.</div>
                                </li>
                                <li><a href="/article/3272.htm"
                                       title="微信收货地址共享接口-终极解决" target="_blank">微信收货地址共享接口-终极解决</a>
                                    <span class="text-muted">laotu5i0</span>
<a class="tag" taget="_blank" href="/search/%E5%BE%AE%E4%BF%A1%E5%BC%80%E5%8F%91/1.htm">微信开发</a>
                                    <div>   最近要接入微信的收货地址共享接口,总是不成功,折腾了好几天,实在没办法网上搜到的帖子也是骂声一片。我把我碰到并解决问题的过程分享出来,希望能给微信的接口文档起到一个辅助作用,让后面进来的开发者能快速的接入,而不需要像我们一样苦逼的浪费好几天,甚至一周的青春。各种羞辱、谩骂的话就不说了,本人还算文明。 
   如果你能搜到本贴,说明你已经碰到了各种 ed</div>
                                </li>
                                <li><a href="/article/3399.htm"
                                       title="关于人才" target="_blank">关于人才</a>
                                    <span class="text-muted">netkiller.github.com</span>
<a class="tag" taget="_blank" href="/search/%E5%B7%A5%E4%BD%9C/1.htm">工作</a><a class="tag" taget="_blank" href="/search/%E9%9D%A2%E8%AF%95/1.htm">面试</a><a class="tag" taget="_blank" href="/search/%E6%8B%9B%E8%81%98/1.htm">招聘</a><a class="tag" taget="_blank" href="/search/netkiller/1.htm">netkiller</a><a class="tag" taget="_blank" href="/search/%E4%BA%BA%E6%89%8D/1.htm">人才</a>
                                    <div>关于人才 
每个月我都会接到许多猎头的电话,有些猎头比较专业,但绝大多数在我看来与猎头二字还是有很大差距的。 与猎头接触多了,自然也了解了他们的工作,包括操作手法,总体上国内的猎头行业还处在初级阶段。 
总结就是“盲目推荐,以量取胜”。  
目前现状 
许多从事人力资源工作的人,根本不懂得怎么找人才。处在人才找不到企业,企业找不到人才的尴尬处境。 
企业招聘,通常是需要用人的部门提出招聘条件,由人</div>
                                </li>
                                <li><a href="/article/3526.htm"
                                       title="搭建 CentOS 6 服务器 - 目录" target="_blank">搭建 CentOS 6 服务器 - 目录</a>
                                    <span class="text-muted">rensanning</span>
<a class="tag" taget="_blank" href="/search/centos/1.htm">centos</a>
                                    <div>(1) 安装CentOS 
ISO(desktop/minimal)、Cloud(AWS/阿里云)、Virtualization(VMWare、VirtualBox) 
详细内容 
 
 
(2) Linux常用命令 
cd、ls、rm、chmod...... 
详细内容 
 
 
(3) 初始环境设置 
用户管理、网络设置、安全设置...... 
详细内容 
 
 
(4) 常驻服务Daemon</div>
                                </li>
                                <li><a href="/article/3653.htm"
                                       title="【求助】mongoDB无法更新主键" target="_blank">【求助】mongoDB无法更新主键</a>
                                    <span class="text-muted">toknowme</span>
<a class="tag" taget="_blank" href="/search/mongodb/1.htm">mongodb</a>
                                    <div>       Query query = new Query();            query.addCriteria(new Criteria("_id").is(o.getId()));                    &n</div>
                                </li>
                                <li><a href="/article/3780.htm"
                                       title="jquery 页面滚动到底部自动加载插件集合" target="_blank">jquery 页面滚动到底部自动加载插件集合</a>
                                    <span class="text-muted">xp9802</span>
<a class="tag" taget="_blank" href="/search/jquery/1.htm">jquery</a>
                                    <div>很多社交网站都使用无限滚动的翻页技术来提高用户体验,当你页面滑到列表底部时候无需点击就自动加载更多的内容。下面为你推荐 10 个 jQuery 的无限滚动的插件: 
1. jQuery ScrollPagination 
jQuery ScrollPagination plugin 是一个 jQuery 实现的支持无限滚动加载数据的插件。 
2. jQuery Screw 
S</div>
                                </li>
                </ul>
            </div>
        </div>
    </div>

<div>
    <div class="container">
        <div class="indexes">
            <strong>按字母分类:</strong>
            <a href="/tags/A/1.htm" target="_blank">A</a><a href="/tags/B/1.htm" target="_blank">B</a><a href="/tags/C/1.htm" target="_blank">C</a><a
                href="/tags/D/1.htm" target="_blank">D</a><a href="/tags/E/1.htm" target="_blank">E</a><a href="/tags/F/1.htm" target="_blank">F</a><a
                href="/tags/G/1.htm" target="_blank">G</a><a href="/tags/H/1.htm" target="_blank">H</a><a href="/tags/I/1.htm" target="_blank">I</a><a
                href="/tags/J/1.htm" target="_blank">J</a><a href="/tags/K/1.htm" target="_blank">K</a><a href="/tags/L/1.htm" target="_blank">L</a><a
                href="/tags/M/1.htm" target="_blank">M</a><a href="/tags/N/1.htm" target="_blank">N</a><a href="/tags/O/1.htm" target="_blank">O</a><a
                href="/tags/P/1.htm" target="_blank">P</a><a href="/tags/Q/1.htm" target="_blank">Q</a><a href="/tags/R/1.htm" target="_blank">R</a><a
                href="/tags/S/1.htm" target="_blank">S</a><a href="/tags/T/1.htm" target="_blank">T</a><a href="/tags/U/1.htm" target="_blank">U</a><a
                href="/tags/V/1.htm" target="_blank">V</a><a href="/tags/W/1.htm" target="_blank">W</a><a href="/tags/X/1.htm" target="_blank">X</a><a
                href="/tags/Y/1.htm" target="_blank">Y</a><a href="/tags/Z/1.htm" target="_blank">Z</a><a href="/tags/0/1.htm" target="_blank">其他</a>
        </div>
    </div>
</div>
<footer id="footer" class="mb30 mt30">
    <div class="container">
        <div class="footBglm">
            <a target="_blank" href="/">首页</a> -
            <a target="_blank" href="/custom/about.htm">关于我们</a> -
            <a target="_blank" href="/search/Java/1.htm">站内搜索</a> -
            <a target="_blank" href="/sitemap.txt">Sitemap</a> -
            <a target="_blank" href="/custom/delete.htm">侵权投诉</a>
        </div>
        <div class="copyright">版权所有 IT知识库 CopyRight © 2000-2050 E-COM-NET.COM , All Rights Reserved.
<!--            <a href="https://beian.miit.gov.cn/" rel="nofollow" target="_blank">京ICP备09083238号</a><br>-->
        </div>
    </div>
</footer>
<!-- 代码高亮 -->
<script type="text/javascript" src="/static/syntaxhighlighter/scripts/shCore.js"></script>
<script type="text/javascript" src="/static/syntaxhighlighter/scripts/shLegacy.js"></script>
<script type="text/javascript" src="/static/syntaxhighlighter/scripts/shAutoloader.js"></script>
<link type="text/css" rel="stylesheet" href="/static/syntaxhighlighter/styles/shCoreDefault.css"/>
<script type="text/javascript" src="/static/syntaxhighlighter/src/my_start_1.js"></script>





</body>

</html>