Skip to content

FPS Optimizasyonu ve Akıcı Kullanıcı Deneyimi ​

Mobil uygulamalarda akıcı kullanıcı deneyimi sağlamak için hedeflenen frame rate (genellikle 60 FPS) sürdürülmelidir. Bu bölümde FPS optimizasyonu teknikleri ve akıcı kullanıcı deneyimi sağlama yöntemleri ele alınacaktır.

Hızlı Karar ​

SemptomÖnce BakDikkat
Scroll jankMain thread workGörünür item maliyeti azalt
Animasyon takılıyorFrame timeDebug build yanıltır
120Hz cihazda düşüş8.33ms budgetAnimasyon basitleştirilebilir
İlk etkileşim yavaşStartup/render blockingLazy yükleme gerekebilir

Üretim Kontrol Listesi ​

  • Problem: Hangi ekranda hangi frame budget aşılıyor?
  • Çözüm: Profiling cihazı, baseline, threshold, animation scope ve fallback net mi?
  • Trade-off: Zengin animasyon ve dinamik layout daha fazla CPU/GPU/batarya ister.
  • Hata durumu: Main-thread blocking, GC pause, overdraw ve image decode spike ele alınmalı.
  • Ölçüm: FPS, p95 frame time, jank rate, dropped frames ve battery impact izlenmeli.
  • Güvenlik/maliyet: Performans için erişilebilirlik kapatılmamalı; düşük cihaz testi maliyetli ama gereklidir.

FPS ve Frame Rate Temelleri ​

60 FPS Hedefi ​

javascript
// React Native - FPS İzleme
import { PerformanceMonitor } from 'react-native-performance';

const FPSMonitor = () => {
  const [fps, setFps] = useState(60);
  
  useEffect(() => {
    const monitor = new PerformanceMonitor({
      onFPSChange: (currentFPS) => {
        setFps(currentFPS);
        if (currentFPS < 30) {
          console.warn('Düşük FPS tespit edildi:', currentFPS);
        }
      }
    });
    
    return () => monitor.stop();
  }, []);
  
  return (
    <View style={styles.monitor}>
      <Text>FPS: {fps.toFixed(1)}</Text>
    </View>
  );
};

Android FPS Optimizasyonu ​

kotlin
// GPU Profiler ile FPS izleme
class FPSMonitor {
    private var frameStartTime = 0L
    private val frameMetrics = mutableListOf<Long>()
    
    fun startFrameMonitoring(activity: Activity) {
        activity.window.addOnFrameMetricsAvailableListener(
            { _, frameMetrics, _ ->
                val totalDuration = frameMetrics.getMetric(
                    FrameMetrics.TOTAL_DURATION
                )
                
                // 16.67ms = 60 FPS threshold
                if (totalDuration > 16_670_000) { // nanoseconds
                    Log.w("FPS", "Dropped frame: ${totalDuration / 1_000_000}ms")
                }
                
                this.frameMetrics.add(totalDuration)
                if (this.frameMetrics.size > 100) {
                    analyzePerformance()
                    this.frameMetrics.clear()
                }
            },
            Handler(Looper.getMainLooper())
        )
    }
    
    private fun analyzePerformance() {
        val avgFrameTime = frameMetrics.average()
        val fps = 1_000_000_000 / avgFrameTime
        
        Log.i("FPS", "Ortalama FPS: ${fps.toInt()}")
    }
}

// Choreographer ile frame callback optimizasyonu
class SmoothAnimationManager : Choreographer.FrameCallback {
    private val choreographer = Choreographer.getInstance()
    private var lastFrameTime = 0L
    
    fun startSmoothing() {
        choreographer.postFrameCallback(this)
    }
    
    override fun doFrame(frameTimeNanos: Long) {
        if (lastFrameTime != 0L) {
            val frameDuration = (frameTimeNanos - lastFrameTime) / 1_000_000
            
            if (frameDuration > 16.67) {
                // Frame drop tespit edildi
                optimizeNextFrame()
            }
        }
        
        lastFrameTime = frameTimeNanos
        choreographer.postFrameCallback(this)
    }
    
    private fun optimizeNextFrame() {
        // Gereksiz işlemleri sonraki frame'e ertele
        choreographer.postFrameCallback {
            // Kritik olmayan güncellemeleri yap
        }
    }
}

iOS FPS Optimizasyonu ​

swift
// CADisplayLink ile FPS izleme
class FPSCounter {
    private var displayLink: CADisplayLink?
    private var lastTimestamp: CFTimeInterval = 0
    private var frameCount: Int = 0
    
    func startMonitoring() {
        displayLink = CADisplayLink(target: self, selector: #selector(displayLinkTick))
        displayLink?.add(to: .main, forMode: .common)
    }
    
    @objc private func displayLinkTick(displayLink: CADisplayLink) {
        if lastTimestamp == 0 {
            lastTimestamp = displayLink.timestamp
            return
        }
        
        frameCount += 1
        let elapsed = displayLink.timestamp - lastTimestamp
        
        if elapsed >= 1.0 {
            let fps = Double(frameCount) / elapsed
            print("FPS: \(Int(fps))")
            
            if fps < 45 {
                // Düşük FPS tespit edildi
                optimizeRendering()
            }
            
            frameCount = 0
            lastTimestamp = displayLink.timestamp
        }
    }
    
    private func optimizeRendering() {
        // Render optimizasyonları
        DispatchQueue.main.async {
            // UI güncellemelerini optimize et
        }
    }
}

// Metal ile GPU optimizasyonu
class MetalRenderer {
    private var device: MTLDevice!
    private var commandQueue: MTLCommandQueue!
    
    func optimizeGPURendering() {
        // Triple buffering kullan
        let bufferCount = 3
        
        // Command buffer pool
        let commandBuffer = commandQueue.makeCommandBuffer()
        
        // Parallel encoding
        let parallelEncoder = commandBuffer?.makeParallelRenderCommandEncoder(
            descriptor: renderPassDescriptor
        )
        
        // GPU iş yükünü dağıt
        for i in 0..<bufferCount {
            if let encoder = parallelEncoder?.makeRenderCommandEncoder() {
                encoder.setRenderPipelineState(pipelineState)
                encoder.drawPrimitives(type: .triangle, vertexStart: 0, vertexCount: 3)
                encoder.endEncoding()
            }
        }
        
        parallelEncoder?.endEncoding()
        commandBuffer?.commit()
    }
}

Frame Drop'ları Önleme ​

Main Thread Optimizasyonu ​

dart
// Flutter - Isolate kullanarak ağır işlemleri main thread'den ayırma
class HeavyComputationManager {
  static Future<List<ProcessedData>> processDataAsync(
    List<RawData> rawData
  ) async {
    final receivePort = ReceivePort();
    
    await Isolate.spawn(_computeInIsolate, {
      'data': rawData,
      'sendPort': receivePort.sendPort,
    });
    
    return await receivePort.first as List<ProcessedData>;
  }
  
  static void _computeInIsolate(Map<String, dynamic> params) {
    final data = params['data'] as List<RawData>;
    final sendPort = params['sendPort'] as SendPort;
    
    // Ağır hesaplama
    final processed = data.map((item) {
      // Karmaşık işlemler
      return ProcessedData(
        result: heavyCalculation(item),
        timestamp: DateTime.now(),
      );
    }).toList();
    
    sendPort.send(processed);
  }
}

// Widget rebuild optimizasyonu
class OptimizedListWidget extends StatefulWidget {
  @override
  _OptimizedListWidgetState createState() => _OptimizedListWidgetState();
}

class _OptimizedListWidgetState extends State<OptimizedListWidget> {
  @override
  Widget build(BuildContext context) {
    return ListView.builder(
      // addRepaintBoundaries: true (varsayılan)
      itemBuilder: (context, index) {
        return RepaintBoundary(
          child: OptimizedListItem(
            key: ValueKey(items[index].id),
            item: items[index],
          ),
        );
      },
    );
  }
}

class OptimizedListItem extends StatelessWidget {
  final Item item;
  
  const OptimizedListItem({Key? key, required this.item}) : super(key: key);
  
  @override
  Widget build(BuildContext context) {
    // Gereksiz rebuilds'i önlemek için const constructors kullan
    return const Card(
      child: ListTile(
        title: Text(item.title),
        subtitle: Text(item.description),
      ),
    );
  }
}

Async Operations Optimizasyonu ​

javascript
// React Native - İşlem kuyruğu ile frame blocking'i önleme
class OperationQueue {
  constructor() {
    this.queue = [];
    this.isProcessing = false;
    this.frameDeadline = 16; // ms
  }
  
  addOperation(operation) {
    this.queue.push(operation);
    if (!this.isProcessing) {
      this.processQueue();
    }
  }
  
  async processQueue() {
    this.isProcessing = true;
    
    while (this.queue.length > 0) {
      const startTime = performance.now();
      
      // Frame deadline'ını aşmamak için zaman kontrolü
      while (
        this.queue.length > 0 && 
        (performance.now() - startTime) < this.frameDeadline
      ) {
        const operation = this.queue.shift();
        try {
          await operation();
        } catch (error) {
          console.error('Operation error:', error);
        }
      }
      
      // Bir sonraki frame'e bekle
      if (this.queue.length > 0) {
        await new Promise(resolve => requestAnimationFrame(resolve));
      }
    }
    
    this.isProcessing = false;
  }
}

// Kullanım
const operationQueue = new OperationQueue();

// Ağır işlemleri kuyruğa ekle
operationQueue.addOperation(() => processLargeDataSet(data1));
operationQueue.addOperation(() => updateComplexUI(uiData));
operationQueue.addOperation(() => performCalculations(calcData));

GPU ve Render Optimizasyonu ​

Texture ve Shader Optimizasyonu ​

swift
// iOS Metal Shader optimizasyonu
class OptimizedShaderManager {
    private var pipelineStates: [String: MTLRenderPipelineState] = [:]
    
    func createOptimizedPipeline(for shaderName: String) -> MTLRenderPipelineState? {
        if let cachedState = pipelineStates[shaderName] {
            return cachedState
        }
        
        let pipelineDescriptor = MTLRenderPipelineDescriptor()
        
        // Vertex shader
        pipelineDescriptor.vertexFunction = library.makeFunction(name: "\(shaderName)_vertex")
        
        // Fragment shader - optimized precision
        pipelineDescriptor.fragmentFunction = library.makeFunction(name: "\(shaderName)_fragment")
        
        // Color attachment optimization
        pipelineDescriptor.colorAttachments[0].pixelFormat = .bgra8Unorm
        
        // Alpha blending optimization
        let colorAttachment = pipelineDescriptor.colorAttachments[0]!
        colorAttachment.isBlendingEnabled = true
        colorAttachment.rgbBlendOperation = .add
        colorAttachment.alphaBlendOperation = .add
        colorAttachment.sourceRGBBlendFactor = .sourceAlpha
        colorAttachment.destinationRGBBlendFactor = .oneMinusSourceAlpha
        
        do {
            let pipelineState = try device.makeRenderPipelineState(descriptor: pipelineDescriptor)
            pipelineStates[shaderName] = pipelineState
            return pipelineState
        } catch {
            print("Pipeline state creation failed: \(error)")
            return nil
        }
    }
}

// Texture atlasing için optimizasyon
class TextureAtlasManager {
    private var atlases: [MTLTexture] = []
    private var textureCoordinates: [String: CGRect] = [:]
    
    func createAtlas(from images: [String: UIImage]) -> MTLTexture? {
        let atlasSize = calculateOptimalAtlasSize(imageCount: images.count)
        
        let textureDescriptor = MTLTextureDescriptor.texture2DDescriptor(
            pixelFormat: .rgba8Unorm,
            width: atlasSize,
            height: atlasSize,
            mipmapped: true
        )
        
        guard let atlasTexture = device.makeTexture(descriptor: textureDescriptor) else {
            return nil
        }
        
        // Pack images into atlas
        var currentX = 0, currentY = 0, rowHeight = 0
        
        for (name, image) in images {
            let imageSize = image.size
            
            if currentX + Int(imageSize.width) > atlasSize {
                currentX = 0
                currentY += rowHeight
                rowHeight = 0
            }
            
            // Copy image data to atlas
            copyImageToAtlas(image: image, atlas: atlasTexture, x: currentX, y: currentY)
            
            // Store texture coordinates
            textureCoordinates[name] = CGRect(
                x: Double(currentX) / Double(atlasSize),
                y: Double(currentY) / Double(atlasSize),
                width: imageSize.width / Double(atlasSize),
                height: imageSize.height / Double(atlasSize)
            )
            
            currentX += Int(imageSize.width)
            rowHeight = max(rowHeight, Int(imageSize.height))
        }
        
        return atlasTexture
    }
}

Draw Call Optimizasyonu ​

kotlin
// Android - Draw call batching
class DrawCallOptimizer {
    private val batchedDrawCommands = mutableListOf<DrawCommand>()
    private var currentTexture: Texture? = null
    
    fun addDrawCommand(command: DrawCommand) {
        // Texture değişimi kontrolü
        if (currentTexture != command.texture) {
            flushBatch()
            currentTexture = command.texture
        }
        
        batchedDrawCommands.add(command)
        
        // Batch size limiti
        if (batchedDrawCommands.size >= MAX_BATCH_SIZE) {
            flushBatch()
        }
    }
    
    private fun flushBatch() {
        if (batchedDrawCommands.isEmpty()) return
        
        // Vertex buffer'ı hazırla
        val vertexBuffer = createBatchedVertexBuffer(batchedDrawCommands)
        
        // Tek draw call ile tüm batch'i çiz
        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, currentTexture?.id ?: 0)
        GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, vertexBuffer.id)
        
        GLES20.glVertexAttribPointer(
            positionHandle, 3, GLES20.GL_FLOAT, false,
            VERTEX_STRIDE, 0
        )
        GLES20.glVertexAttribPointer(
            textureCoordHandle, 2, GLES20.GL_FLOAT, false,
            VERTEX_STRIDE, 12
        )
        
        GLES20.glDrawArrays(
            GLES20.GL_TRIANGLES, 0,
            batchedDrawCommands.size * 6
        )
        
        batchedDrawCommands.clear()
    }
    
    private fun createBatchedVertexBuffer(commands: List<DrawCommand>): VertexBuffer {
        val vertices = FloatArray(commands.size * 30) // 6 vertices * 5 floats per vertex
        var index = 0
        
        for (command in commands) {
            val quad = command.generateQuadVertices()
            System.arraycopy(quad, 0, vertices, index, quad.size)
            index += quad.size
        }
        
        return VertexBuffer(vertices)
    }
    
    companion object {
        private const val MAX_BATCH_SIZE = 1000
        private const val VERTEX_STRIDE = 20 // 5 floats * 4 bytes
    }
}

Animation Performance ​

Hardware Accelerated Animations ​

dart
// Flutter - Transform ve Opacity animasyonları (GPU accelerated)
class OptimizedAnimationWidget extends StatefulWidget {
  @override
  _OptimizedAnimationWidgetState createState() => _OptimizedAnimationWidgetState();
}

class _OptimizedAnimationWidgetState extends State<OptimizedAnimationWidget>
    with TickerProviderStateMixin {
  late AnimationController _controller;
  late Animation<double> _scaleAnimation;
  late Animation<double> _opacityAnimation;
  
  @override
  void initState() {
    super.initState();
    
    _controller = AnimationController(
      duration: Duration(milliseconds: 300),
      vsync: this,
    );
    
    // GPU ile hızlandırılmış animasyonlar
    _scaleAnimation = Tween<double>(
      begin: 1.0,
      end: 1.2,
    ).animate(CurvedAnimation(
      parent: _controller,
      curve: Curves.easeInOut,
    ));
    
    _opacityAnimation = Tween<double>(
      begin: 1.0,
      end: 0.8,
    ).animate(_controller);
  }
  
  @override
  Widget build(BuildContext context) {
    return AnimatedBuilder(
      animation: _controller,
      builder: (context, child) {
        return Transform.scale(
          scale: _scaleAnimation.value,
          child: Opacity(
            opacity: _opacityAnimation.value,
            child: Container(
              width: 100,
              height: 100,
              decoration: BoxDecoration(
                color: Colors.blue,
                borderRadius: BorderRadius.circular(8),
              ),
            ),
          ),
        );
      },
    );
  }
  
  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }
}

// Custom painter ile optimize edilmiş çizim
class OptimizedCustomPainter extends CustomPainter {
  final Animation<double> animation;
  
  OptimizedCustomPainter(this.animation) : super(repaint: animation);
  
  @override
  void paint(Canvas canvas, Size size) {
    final paint = Paint()
      ..color = Colors.blue
      ..style = PaintingStyle.fill;
    
    // Animasyonlu çizim
    final progress = animation.value;
    final radius = size.width * 0.5 * progress;
    
    canvas.drawCircle(
      Offset(size.width * 0.5, size.height * 0.5),
      radius,
      paint,
    );
  }
  
  @override
  bool shouldRepaint(OptimizedCustomPainter oldDelegate) {
    return animation.value != oldDelegate.animation.value;
  }
}

Interpolation Optimizasyonu ​

javascript
// React Native - Native driver ile optimize edilmiş animasyonlar
import { Animated, Easing } from 'react-native';

class OptimizedAnimationManager {
  constructor() {
    this.animatedValues = new Map();
    this.runningAnimations = new Set();
  }
  
  createOptimizedAnimation(key, config) {
    const animatedValue = new Animated.Value(config.initialValue || 0);
    this.animatedValues.set(key, animatedValue);
    
    return {
      start: (toValue, duration = 300, callback) => {
        const animation = Animated.timing(animatedValue, {
          toValue,
          duration,
          easing: Easing.bezier(0.25, 0.1, 0.25, 1),
          useNativeDriver: true, // GPU acceleration
        });
        
        this.runningAnimations.add(key);
        
        animation.start(() => {
          this.runningAnimations.delete(key);
          callback && callback();
        });
        
        return animation;
      },
      
      stop: () => {
        animatedValue.stopAnimation();
        this.runningAnimations.delete(key);
      },
      
      getValue: () => animatedValue._value,
      getAnimatedValue: () => animatedValue,
    };
  }
  
  // Spring animation ile doğal hareket
  createSpringAnimation(key, config) {
    const animatedValue = new Animated.Value(config.initialValue || 0);
    this.animatedValues.set(key, animatedValue);
    
    return {
      spring: (toValue, springConfig = {}) => {
        const animation = Animated.spring(animatedValue, {
          toValue,
          friction: springConfig.friction || 8,
          tension: springConfig.tension || 100,
          useNativeDriver: true,
        });
        
        this.runningAnimations.add(key);
        animation.start(() => this.runningAnimations.delete(key));
        
        return animation;
      }
    };
  }
  
  // Paralel animasyonlar
  createParallelAnimation(animations) {
    const parallelAnimations = animations.map(anim => anim.animation);
    
    return Animated.parallel(parallelAnimations, {
      stopTogether: false
    });
  }
  
  // Sequence animasyonlar
  createSequenceAnimation(animations) {
    const sequenceAnimations = animations.map(anim => anim.animation);
    
    return Animated.sequence(sequenceAnimations);
  }
  
  stopAllAnimations() {
    this.runningAnimations.forEach(key => {
      const animatedValue = this.animatedValues.get(key);
      if (animatedValue) {
        animatedValue.stopAnimation();
      }
    });
    this.runningAnimations.clear();
  }
}

// Kullanım örneği
const animationManager = new OptimizedAnimationManager();

const fadeAnimation = animationManager.createOptimizedAnimation('fade', {
  initialValue: 0
});

const scaleAnimation = animationManager.createSpringAnimation('scale', {
  initialValue: 1
});

// Paralel animasyon
const showModal = () => {
  const parallelAnim = animationManager.createParallelAnimation([
    fadeAnimation.start(1, 250),
    scaleAnimation.spring(1.1)
  ]);
  
  parallelAnim.start();
};

Performans İzleme ve Profiling ​

Real-time FPS Monitoring ​

kotlin
// Android - Gerçek zamanlı performans izleme
class PerformanceProfiler {
    private val frameMetrics = FrameMetricsCollector()
    private val memoryMonitor = MemoryMonitor()
    private val listeners = mutableListOf<PerformanceListener>()
    
    fun startProfiling(activity: Activity) {
        // Frame metrics
        frameMetrics.start(activity) { metrics ->
            val fps = calculateFPS(metrics)
            val frameTime = metrics.totalDuration / 1_000_000.0 // ms
            
            notifyListeners(PerformanceData(
                fps = fps,
                frameTime = frameTime,
                droppedFrames = metrics.droppedFrames,
                memoryUsage = memoryMonitor.getCurrentUsage()
            ))
            
            // Performans uyarıları
            if (fps < 30) {
                Log.w("Performance", "Düşük FPS: $fps")
                suggestOptimizations(metrics)
            }
        }
        
        // Memory monitoring
        memoryMonitor.startMonitoring { usage ->
            if (usage.percentUsed > 80) {
                Log.w("Performance", "Yüksek bellek kullanımı: ${usage.percentUsed}%")
                triggerGarbageCollection()
            }
        }
    }
    
    private fun calculateFPS(metrics: FrameMetrics): Double {
        val frameDuration = metrics.totalDuration / 1_000_000.0 // ms
        return 1000.0 / frameDuration
    }
    
    private fun suggestOptimizations(metrics: FrameMetrics) {
        when {
            metrics.layoutDuration > 2_000_000 -> { // 2ms
                Log.i("Optimization", "Layout optimizasyonu gerekli")
            }
            metrics.drawDuration > 8_000_000 -> { // 8ms
                Log.i("Optimization", "Draw call optimizasyonu gerekli")
            }
            metrics.syncDuration > 1_000_000 -> { // 1ms
                Log.i("Optimization", "UI thread senkronizasyonu gerekli")
            }
        }
    }
    
    interface PerformanceListener {
        fun onPerformanceUpdate(data: PerformanceData)
        fun onPerformanceWarning(warning: PerformanceWarning)
    }
}

data class PerformanceData(
    val fps: Double,
    val frameTime: Double,
    val droppedFrames: Int,
    val memoryUsage: MemoryUsage
)

Automated Performance Testing ​

swift
// iOS - Otomatik performans testleri
class PerformanceTestSuite {
    private let testMetrics = XCTMetric.default
    
    func runFPSPerformanceTest() {
        measure(metrics: [XCTCPUMetric(), XCTMemoryMetric()]) {
            // Test senaryosu
            performHeavyUIOperations()
        }
    }
    
    private func performHeavyUIOperations() {
        let expectation = XCTestExpectation(description: "UI Operations")
        
        DispatchQueue.main.async {
            // Ağır UI işlemleri simüle et
            for i in 0..<1000 {
                let view = UIView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
                view.backgroundColor = UIColor.random()
                view.layer.cornerRadius = 10
                view.layer.shadowOffset = CGSize(width: 2, height: 2)
                view.layer.shadowOpacity = 0.5
                
                // View hierarchy'ye ekle ve kaldır
                self.testView.addSubview(view)
                view.removeFromSuperview()
            }
            
            expectation.fulfill()
        }
        
        wait(for: [expectation], timeout: 10.0)
    }
    
    func measureScrollPerformance() {
        let tableView = UITableView(frame: CGRect(x: 0, y: 0, width: 375, height: 667))
        
        measure(metrics: [XCTOSSignpostMetric.scrollDecelerationMetric]) {
            // Scroll işlemi simüle et
            tableView.setContentOffset(CGPoint(x: 0, y: 1000), animated: true)
        }
    }
    
    func measureAnimationPerformance() {
        let animationView = UIView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
        
        measure(metrics: [XCTCPUMetric()]) {
            UIView.animate(
                withDuration: 1.0,
                delay: 0,
                options: [.curveEaseInOut],
                animations: {
                    animationView.transform = CGAffineTransform(scaleX: 2.0, y: 2.0)
                    animationView.alpha = 0.5
                }
            )
        }
    }
}

// Custom performance metrics
extension XCTMetric {
    static let frameDropMetric = XCTMetric(
        identifier: "com.app.framedrops",
        displayName: "Frame Drops",
        unitOfMeasurement: "frames"
    )
    
    static let renderTimeMetric = XCTMetric(
        identifier: "com.app.rendertime",
        displayName: "Render Time",
        unitOfMeasurement: "ms"
    )
}

Bu FPS optimizasyonu dokümantasyonu, mobil uygulamalarda akıcı kullanıcı deneyimi sağlamak için gerekli teknikleri kapsamlı bir şekilde ele almaktadır. Frame rate optimizasyonu, GPU kullanımı, animasyon performansı ve sürekli izleme konularında platform-specific çözümler sunmaktadır.

Eren Demir tarafından oluşturulmuştur.